The alarm index was keyed on ns=N strings resolved once at subscribe time, while an
in-place SDK reconnect transfers the subscription without re-running it - a remote
namespace reorder silently orphaned every alarm tag. Keys are now nsu= on both sides.
Events match the authored nodeId against the event's ConditionId first, then its
SourceNode, so several conditions on one source can be authored 1:1. Acknowledge
passes the EventId of the event that named the remembered condition (paired, so node
and EventId can never come from different conditions) instead of an empty one.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
The host subscribes alarms with RawPaths and routes transitions on
(driver, RawPath), but the driver filtered upstream SourceNode ids against
the RawPaths and reported the upstream condition NodeId as ConditionId, so
no opc-plc (or any upstream) alarm could ever reach a condition node.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
S7.Net's own STRING/DT encoders throw ArgumentException; that fell into the
generic write catch (BadCommunicationError + Degraded health). Now surfaced as
S7ValueRangeException like the WSTRING/DATE codecs.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
- Unavailable keeps an ACTIVE condition active: quality-only BadNoCommunication; on data
return same statement => quality-only Good, Normal => Clear, different => Retrigger.
- A changed alarm reference set (alarm-bearing delta = full rebuild) clears the whole
last-told record so every active condition is re-asserted; the same set keeps it.
- Quality-only events go through the host's AlarmQualityUpdate path, never a projected
snapshot (which could ack a retained unacked condition or rewrite message/severity).
- Deterministic tie-break between equal-level conditions (ordinal nativeCode, then type).
- Dedup key is level+nativeCode+nativeSeverity+qualifier; a text-only change updates nothing.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
The parser keeps a condition's text, nativeCode, nativeSeverity, qualifier and type. A new
MTConnectConditionTracker applies MTConnect's multi-condition semantics (per-nativeCode
activation/clearing across /sample deltas, /current baseline reset, Unavailable), and
MTConnectConditionAlarmMapper turns tracker state into Part 9 transitions: Warning=500,
Fault=800, Normal=clear, Unavailable=inactive + BadNoCommunication; escalation or a new
code retriggers. The driver implements IAlarmSource keyed by the alarm tag's RawPath,
diffs against what the server was actually told (only when a handler is attached), so a
reconnect/re-baseline never re-raises an unchanged condition and a missed transition is
delivered on the next subscribe. Ack is a no-op success. Existing CONDITION String tags
are unchanged; a tag becomes an alarm only via the standard TagConfig 'alarm' object.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
New S7DataType members WString/Char/WChar/Dtl/Date/Tod/Time/S5Time mapped onto existing
DriverDataType (String / DateTime / Int32 ms); writes refuse out-of-range with BadOutOfRange.
Optional localTsap/remoteTsap/connectionType through options, factory JSON, probe and AdminUI form.
NegotiatedPduSize snapshotted at connect and exposed via GetDiagnostics (DriverHealth has no bag).
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
FOCAS, AbLegacy, AbCip, TwinCAT, S7 and OpcUaClient orphaned the previous
probe/recycle/fixed-tree loops and clients when re-initialised on a live driver.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
MAJOR-1: R4's exact element-type match refused ScadaBridge's CLR-list
writes (its element types are only Boolean/Int32/Float/Double/String/
DateTime) to every Int16/UInt16/Byte/UInt32/Int64/UInt64 array node and a
Double list to a Float array. EvaluateEquipmentWriteStructure now converts
a numeric array of another numeric element type element-wise, CHECKED, into
the node's element type and hands the converted array back through
OnEquipmentTagWrite's ref value, so the node stores and the driver receives
the node's own type. Any out-of-range, fractional-to-integer, NaN/Inf-to-
integer or Float-overflowing element refuses the whole write BadTypeMismatch
(nothing dispatched) - so the UInt32 -> Int32 bit reinterpretation stays
closed. Non-numeric mismatches stay refused. The wrong 'no client relies on
a looser rule' comment is corrected.
MINOR-1: the array-value-to-scalar-node refusal broke writes back to
browse-committed array tags (committed scalar, isArray never set) on
pass-through drivers. It now applies only when the owning driver's scalar
encoder cannot take an array (DriverArrayWriteSupport.
RefusesArrayValueOnScalarTag: Modbus, S7), carried RawTagPlan.
ScalarRefusesArrayValue / UnsReferenceVariable.BackingScalarRefusesArrayValue
-> EnsureVariable(refuseArrayOnScalar) -> a per-node set in the node
manager. OpcUaClient/Galaxy/others keep pre-#610 dispatch. Not on the
classifier whitelist (driver-type edit rebuilds); a mirror whose flag
changes is recreated, not updated in place.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
MAJOR-1: the SDK skips TypeInfo.IsInstanceOfDataType on any node with an
OnWriteValue handler (every writable equipment node), and the node manager's
fail-fast treated numeric<->numeric as compatible, so a UInt32[] to a Galaxy
Int32 array matched MxValueEncoder's int[] arm by CLR covariance and was
written bit-reinterpreted with a Good result. EvaluateEquipmentWriteStructure
now matches an ARRAY write's element type exactly (Byte[]/ByteString the only
equivalence; Variant[]/object[] refused) -> BadTypeMismatch, nothing
dispatched. MxValueEncoder's int[]/long[] arms are exact-type guarded too, and
the Galaxy parity test's false "the SDK refuses" premise is corrected (the
UInt32/UInt64 exclusion is now the encoder's own answer). Scalar writes keep
numeric widening by decision: consumers write CLR-typed values (Double to a
Float node) and every driver's scalar encoder range-checks.
MINOR: Modbus row honours maxRegistersPerWrite (smallest of DriverConfig /
the tag's DeviceConfig, default 123) and FC15's 1968-coil ceiling, and
requires region + numeric address; Modbus/S7/Galaxy require isArray + integer
arrayLength >= 1 (Galaxy: an unbounded node would let a short write resize the
attribute); an array value to a scalar node is refused inline. Docs: Uns.md
array-write rules + upgrade note (ReadWrite array tags become client-writable
on first boot), Modbus.md, Galaxy.md.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
- Galaxy: deadline bounds the wait, not the subscribe RPC; BadTimeout for all tags and late handles unsubscribed; doc comment relocated
- S7: probe connect runs on the connect deadline so a timeout opens the backoff window
- S7: a successful write only retires a Faulted the write side owns
- S7: null array elements rejected (BadTypeMismatch); multi-PDU array writes documented non-atomic
- S7 integration: array write test gated on S7_SIM_ALLOW_WRITES=1
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
A first-touch native write to a T/C/R status-bit sub-element (T4:0.DN) created
and initialised the tag's runtime before the runtime's write refusal could fire;
the init is one READ, so against a device lacking the file (the ab_server
fixture has no T/C/R) it surfaced as BadCommunicationError + Degraded health
instead of BadNotSupported. The bit-RMW path was not involved: AbLegacyAddress
rejects sub-element + /N, so a status bit's BitIndex is always null.
NativeAbLegacyTagRuntime.WriteRefusal is now the single predicate (status bit
by address whatever the authored type; any PLC-5 write), asked by the driver via
IAbLegacyTagFactory.WriteRefusal (default null, so libplctag is unchanged)
before EnsureTagRuntimeAsync and ahead of the bit-RMW branch.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe