using System.Text; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; /// /// Decodes an IEC STRING(n) / WSTRING(n) buffer read at the PLC's own declared width. /// The driver reads string symbols as raw bytes and decodes them here instead of letting the /// Beckhoff marshaller pick an encoding, because that marshaller falls back to the HOST's ANSI code /// page (CultureInfo.CurrentCulture.TextInfo.ANSICodePage) for a non-UTF-8 target — so a /// TwinCAT 2 string would decode differently on a Polish (1250) or Cyrillic (1251) server. /// /// /// Two rules, both ported from the ignitionoee TwinCAT driver and both found on a real PLC: /// /// A value ENDS at its first NUL — 0x00 for a STRING, an aligned 0x00 0x00 /// code unit for a WSTRING. Whatever follows is stale content a longer earlier value left in the /// buffer (live on the Elma cleaner Z33856: work order 67504440 read back with a stale /// "\0" + "1" tail). /// A STRING is single-byte Windows-1252 (umlauts, ° in alarm texts), unless /// the target reports UTF-8 string data (TwinCAT 3.1 4026+ "UTF8 Encoding for STRING"). /// ISO-8859-1 is the fallback on a runtime without code-page support — identical outside /// 0x80–0x9F. /// /// internal static class TwinCATStringCodec { private static readonly Encoding Windows1252 = CreateWindows1252(); private static Encoding CreateWindows1252() { try { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); return Encoding.GetEncoding(1252); } catch (Exception) { return Encoding.Latin1; } } /// Decodes an IEC STRING(n) buffer: ends at the first NUL, Windows-1252 unless . /// The raw symbol bytes (n+1 for a STRING(n)). /// True when the target reports UTF-8 string data. /// The decoded value. public static string DecodeString(ReadOnlySpan bytes, bool utf8) { var end = bytes.IndexOf((byte)0); if (end >= 0) bytes = bytes[..end]; return (utf8 ? Encoding.UTF8 : Windows1252).GetString(bytes); } /// Decodes an IEC WSTRING(n) buffer (UTF-16LE): ends at the first aligned U+0000. /// The raw symbol bytes (2(n+1) for a WSTRING(n)). /// The decoded value. public static string DecodeWString(ReadOnlySpan bytes) { var end = bytes.Length & ~1; // whole UTF-16 code units only — a trailing odd byte is ignored for (var i = 0; i + 1 < end; i += 2) { if (bytes[i] == 0 && bytes[i + 1] == 0) { end = i; break; } } return Encoding.Unicode.GetString(bytes[..end]); } }