fix(twincat): refuse a scalar tag over a PLC array on the read path too
Live on Z33856 a UInt tag over .ARRFAULTCODE (ARRAY [0..12] OF UINT) made the Beckhoff read throw - BadCommunicationError and a Degraded driver - while the native path reported BadTypeMismatch. CheckRead now refuses a scalar declared over an array (by category, so an alias of an array type is caught), pointing at the array-tag / element-path alternatives.
This commit is contained in:
@@ -24,7 +24,9 @@ internal enum TwinCATPlcKind
|
||||
/// <param name="ByteSize">The symbol's byte size as the PLC reports it.</param>
|
||||
/// <param name="IsFloat">True for a REAL / LREAL scalar.</param>
|
||||
/// <param name="TypeName">The (alias-resolved) PLC type name, for log messages.</param>
|
||||
internal readonly record struct TwinCATPlcSymbolShape(TwinCATPlcKind Kind, int ByteSize, bool IsFloat, string TypeName)
|
||||
/// <param name="IsArray">True for an ARRAY (a <see cref="TwinCATPlcKind.NonScalar"/> with array category or name).</param>
|
||||
internal readonly record struct TwinCATPlcSymbolShape(
|
||||
TwinCATPlcKind Kind, int ByteSize, bool IsFloat, string TypeName, bool IsArray = false)
|
||||
{
|
||||
private static readonly Regex StringTypeName =
|
||||
new(@"^(W?)STRING(\s*\(\s*\d+\s*\))?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
@@ -71,7 +73,8 @@ internal readonly record struct TwinCATPlcSymbolShape(TwinCATPlcKind Kind, int B
|
||||
|| name.StartsWith("POINTER TO", StringComparison.OrdinalIgnoreCase)
|
||||
|| name.StartsWith("REFERENCE TO", StringComparison.OrdinalIgnoreCase)
|
||||
|| name.Contains("VAR_IN_OUT", StringComparison.OrdinalIgnoreCase))
|
||||
return new(TwinCATPlcKind.NonScalar, byteSize, false, name);
|
||||
return new(TwinCATPlcKind.NonScalar, byteSize, false, name,
|
||||
IsArray: category == DataTypeCategory.Array || name.StartsWith("ARRAY", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var mapped = AdsTwinCATClient.MapSymbolTypeName(name);
|
||||
return new(TwinCATPlcKind.Scalar, byteSize, mapped is TwinCATDataType.Real or TwinCATDataType.LReal, name);
|
||||
@@ -115,11 +118,14 @@ internal readonly record struct TwinCATArrayLayout(int ElementCount, int Stride)
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Read</b> (<see cref="CheckRead"/>) goes through the Beckhoff symbol cache, which
|
||||
/// decodes by the PLC's OWN type and converts the value to the declared CLR type — a numeric
|
||||
/// pair is value-converted, never reinterpreted. Only string-ness can go wrong there: a
|
||||
/// numeric tag over a PLC string got a <c>string</c> object back at Good.</item>
|
||||
/// pair is value-converted, never reinterpreted. Only string-ness can go wrong there (a
|
||||
/// numeric tag over a PLC string got a <c>string</c> object back at Good), plus a scalar over
|
||||
/// an ARRAY, which the read cannot convert at all.</item>
|
||||
/// <item><b>Notification</b> (<see cref="CheckNotification"/>) registers raw bytes decoded as the
|
||||
/// DECLARED type, so a width mismatch truncates/over-reads and a same-width float/integer clash
|
||||
/// reinterprets the bit pattern — both at Good.</item>
|
||||
/// <item><b>Array tags</b> (<see cref="CheckArray"/>) are read AND notified as raw bytes, so the
|
||||
/// element gets the notification rules on both paths.</item>
|
||||
/// </list>
|
||||
/// String-ness is a KIND (TwinCAT-008): a STRING/WSTRING tag matches only a PLC string of the SAME
|
||||
/// kind, at any declared length (0.19.2). A same-width, same-kind numeric divergence
|
||||
@@ -142,6 +148,9 @@ internal static class TwinCATTypeGuard
|
||||
: $"declared {declared} but the PLC symbol is {plc.TypeName} ({plc.ByteSize} B) — a string tag " +
|
||||
$"matches only a PLC {declaredKind.Value.ToString().ToUpperInvariant()} of any length";
|
||||
}
|
||||
if (plc.IsArray)
|
||||
return $"declared scalar {declared} but the PLC symbol is the array {plc.TypeName} ({plc.ByteSize} B) — " +
|
||||
"author it as an array tag (isArray + arrayLength) or address one element (Arr[i])";
|
||||
return plc.Kind is TwinCATPlcKind.String or TwinCATPlcKind.WString
|
||||
? $"declared {declared} but the PLC symbol is the string {plc.TypeName} — declare it String/WString"
|
||||
: null;
|
||||
@@ -157,10 +166,7 @@ internal static class TwinCATTypeGuard
|
||||
if (reason is not null || StringKindOf(declared) is not null) return reason;
|
||||
|
||||
if (plc.Kind == TwinCATPlcKind.NonScalar)
|
||||
return $"declared scalar {declared} but the PLC symbol is {plc.TypeName} ({plc.ByteSize} B) — not a scalar" +
|
||||
(plc.TypeName.StartsWith("ARRAY", StringComparison.OrdinalIgnoreCase)
|
||||
? " (author it as an array tag: isArray + arrayLength)"
|
||||
: string.Empty);
|
||||
return $"declared scalar {declared} but the PLC symbol is {plc.TypeName} ({plc.ByteSize} B) — not a scalar";
|
||||
|
||||
var size = DeclaredByteSize(declared);
|
||||
if (size != plc.ByteSize)
|
||||
|
||||
@@ -105,6 +105,26 @@ public sealed class TwinCATTypeGuardTests
|
||||
TwinCATTypeGuard.CheckRead(TwinCATDataType.String, Plc("ST_LOGDATA", 81, DataTypeCategory.Struct)).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A SCALAR tag over a PLC array has no value the symbol-aware read can convert: live on Z33856
|
||||
/// (2026-10-07) <c>.ARRFAULTCODE</c> (<c>ARRAY [0..12] OF UINT</c>) read as <c>UInt</c> made the Beckhoff
|
||||
/// read throw, surfacing as <c>BadCommunicationError</c> and a Degraded driver, while the native path
|
||||
/// said <c>BadTypeMismatch</c>. Both paths now refuse it with <c>BadTypeMismatch</c> — by category, so an
|
||||
/// alias of an array type is caught too. Other non-scalars (a pointer read as its address) are left
|
||||
/// to the read, as before.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Read_refuses_a_scalar_declared_over_a_plc_array()
|
||||
{
|
||||
TwinCATTypeGuard.CheckRead(TwinCATDataType.UInt, Plc("ARRAY [0..12] OF UINT", 26, DataTypeCategory.Array))
|
||||
.ShouldNotBeNull();
|
||||
TwinCATTypeGuard.CheckRead(TwinCATDataType.DInt, Plc("T_FAULTS", 52, DataTypeCategory.Array)).ShouldNotBeNull();
|
||||
TwinCATTypeGuard.CheckRead(TwinCATDataType.UDInt, Plc("POINTER TO BYTE", 4, DataTypeCategory.Pointer)).ShouldBeNull();
|
||||
Plc("ARRAY [0..12] OF UINT", 26, DataTypeCategory.Array).IsArray.ShouldBeTrue();
|
||||
Plc("ARRAY [0..12] OF UINT", 26).IsArray.ShouldBeTrue(); // by name when no category resolved
|
||||
Plc("UINT", 2, DataTypeCategory.Primitive).IsArray.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 0.19.2: a STRING / WSTRING tag matches a PLC <c>STRING(n)</c> / <c>WSTRING(n)</c> of ANY length.
|
||||
/// A numeric-over-numeric pair is left to the symbol-aware read, which converts the value.
|
||||
|
||||
Reference in New Issue
Block a user