83a314e734
Port of the ignitionoee TwinCAT string + type-reconciliation fixes onto the Beckhoff-library client: - STRING/WSTRING subscriptions never worked: AddDeviceNotificationExAsync was called with typeof(string) and no length, and the Beckhoff any-type marshaller throws ArgumentNullException for a string without one, so one string tag tore down its whole subscribe call. A string now registers the PLC's own byte width as raw bytes (0.19.2: STRING(n) of any n). - Strings are read as raw bytes and decoded by TwinCATStringCodec. The Beckhoff marshaller already cut a read at the first NUL (0.19.3 / TwinCAT-007 were not defects on the read path); the codec keeps that rule for both STRING and WSTRING and extends it to the new notification path. A STRING now decodes as Windows-1252 (TwinCAT-010) - or UTF-8 when the target reports UTF-8 strings - instead of the host culture's ANSI code page the Beckhoff marshaller falls back to (1250 on a Polish host, 1251 Cyrillic, ...). - TwinCATTypeGuard (TwinCAT-002/-008): a read refuses a numeric tag over a PLC string (and vice versa / STRING vs WSTRING / string over a container) with BadTypeMismatch instead of handing back a string object at Good; a notification - raw bytes decoded as the DECLARED type - additionally refuses width and float/integer mismatches (DINT over REAL reinterpreted the bits). - A refused notification is isolated: that tag publishes BadTypeMismatch with one WARN, keeps a dead intent for the probe-tick retry, and the rest of the subscription registers.
70 lines
3.1 KiB
C#
70 lines
3.1 KiB
C#
using System.Text;
|
||
|
||
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||
|
||
/// <summary>
|
||
/// Decodes an IEC <c>STRING(n)</c> / <c>WSTRING(n)</c> 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 (<c>CultureInfo.CurrentCulture.TextInfo.ANSICodePage</c>) for a non-UTF-8 target — so a
|
||
/// TwinCAT 2 string would decode differently on a Polish (1250) or Cyrillic (1251) server.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Two rules, both ported from the ignitionoee TwinCAT driver and both found on a real PLC:
|
||
/// <list type="bullet">
|
||
/// <item>A value ENDS at its first NUL — <c>0x00</c> for a STRING, an aligned <c>0x00 0x00</c>
|
||
/// 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 <c>67504440</c> read back with a stale
|
||
/// <c>"\0" + "1"</c> tail).</item>
|
||
/// <item>A STRING is single-byte <b>Windows-1252</b> (umlauts, <c>°</c> 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.</item>
|
||
/// </list>
|
||
/// </remarks>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>Decodes an IEC <c>STRING(n)</c> buffer: ends at the first NUL, Windows-1252 unless <paramref name="utf8"/>.</summary>
|
||
/// <param name="bytes">The raw symbol bytes (n+1 for a <c>STRING(n)</c>).</param>
|
||
/// <param name="utf8">True when the target reports UTF-8 string data.</param>
|
||
/// <returns>The decoded value.</returns>
|
||
public static string DecodeString(ReadOnlySpan<byte> bytes, bool utf8)
|
||
{
|
||
var end = bytes.IndexOf((byte)0);
|
||
if (end >= 0) bytes = bytes[..end];
|
||
return (utf8 ? Encoding.UTF8 : Windows1252).GetString(bytes);
|
||
}
|
||
|
||
/// <summary>Decodes an IEC <c>WSTRING(n)</c> buffer (UTF-16LE): ends at the first aligned <c>U+0000</c>.</summary>
|
||
/// <param name="bytes">The raw symbol bytes (2(n+1) for a <c>WSTRING(n)</c>).</param>
|
||
/// <returns>The decoded value.</returns>
|
||
public static string DecodeWString(ReadOnlySpan<byte> 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]);
|
||
}
|
||
}
|