fix(s7): Counter raw-word note + reject Writable Timer/Counter + Timer time-base tests (bundle review)

This commit is contained in:
Joseph Doherty
2026-06-17 06:18:48 -04:00
parent 8cfb8e920e
commit 11e8e4302d
3 changed files with 94 additions and 4 deletions
@@ -369,6 +369,15 @@ public sealed class S7Driver
$"S7 tag '{t.Name}' is a Counter address ('{t.Address}') but is typed {t.DataType}. " +
"Counter tags must be DataType=Int32 (decoded to count, read-only).");
}
// (d) Timer/Counter declared Writable — writes are read-only this phase; without this
// a node is discovered as Operate-writable but every write returns BadNotSupported.
if (parsed.Area is S7Area.Timer or S7Area.Counter && t.Writable)
{
throw new NotSupportedException(
$"S7 tag '{t.Name}' is a Timer/Counter ('{t.Address}') declared Writable — " +
"Timer/Counter are read-only this phase; set Writable=false.");
}
}
}
@@ -647,7 +656,14 @@ public sealed class S7Driver
if (addr.Area is S7Area.Timer)
return (object)global::S7.Net.Types.Timer.FromByteArray(block);
if (addr.Area is S7Area.Counter)
{
// NOTE: S7.Net Counter.FromByteArray returns the RAW big-endian word ((bytes[0]<<8)|bytes[1]),
// NOT a BCD decode. On classic S7-300/400 the C-area word is BCD (0-999), so on that hardware
// this raw value can differ from the displayed count; S7-1200/1500 use IEC/DB counters (plain
// ints), where it is correct. Surfacing S7.Net's value verbatim is the faithful choice; BCD
// reinterpretation for legacy C-area counters is a live-hardware-gated follow-up.
return (object)(int)global::S7.Net.Types.Counter.FromByteArray(block);
}
// Each numeric arm is boxed to object explicitly: a bare switch expression would unify
// long/ulong/double to their common type (double) and box THAT, mis-typing Int64/UInt64.