fix(modbus): a failed read or write drops the WriteOnChangeOnly suppression for the tag
After a comms fault the device value is unknown, so re-asserting the same setpoint must reach the wire - the write-side twin of the value-only recovery dedup trap. Refs #741 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -746,6 +746,7 @@ public sealed class ModbusDriver
|
||||
// Fail fast: this unit (or the whole endpoint) already failed at the transport this batch;
|
||||
// paying the same timeout once per remaining tag would stall the poll N-fold.
|
||||
results[i] = new DataValueSnapshot(null, StatusBadCommunicationError, null, now);
|
||||
InvalidateWriteCache(fullReferences[i]);
|
||||
continue;
|
||||
}
|
||||
try
|
||||
@@ -767,6 +768,7 @@ public sealed class ModbusDriver
|
||||
catch (Exception ex)
|
||||
{
|
||||
results[i] = new DataValueSnapshot(null, ClassifyFailure(ex, unit, fullReferences[i], batch, isWrite: false), null, now);
|
||||
InvalidateWriteCache(fullReferences[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1253,6 +1255,7 @@ public sealed class ModbusDriver
|
||||
foreach (var (idx, _) in block.Members)
|
||||
{
|
||||
results[idx] = new DataValueSnapshot(null, StatusBadCommunicationError, null, timestamp);
|
||||
InvalidateWriteCache(fullReferences[idx]);
|
||||
handled.Add(idx);
|
||||
}
|
||||
continue;
|
||||
@@ -1285,6 +1288,7 @@ public sealed class ModbusDriver
|
||||
foreach (var (idx, _) in block.Members)
|
||||
{
|
||||
results[idx] = new DataValueSnapshot(null, status, null, timestamp);
|
||||
InvalidateWriteCache(fullReferences[idx]);
|
||||
handled.Add(idx);
|
||||
}
|
||||
continue;
|
||||
@@ -1341,6 +1345,17 @@ public sealed class ModbusDriver
|
||||
? ReadBitBlockAsync(transport, unit, ReadFunctionCode(region), start, qty, ct)
|
||||
: ReadRegisterBlockAsync(transport, unit, ReadFunctionCode(region), start, qty, ct);
|
||||
|
||||
/// <summary>
|
||||
/// A failed read leaves the device's value UNKNOWN (it may have rebooted holding anything), so the
|
||||
/// WriteOnChangeOnly suppression for that tag is dropped — otherwise re-asserting the same setpoint
|
||||
/// after an outage would be swallowed, the write-side twin of the value-only recovery dedup trap.
|
||||
/// </summary>
|
||||
private void InvalidateWriteCache(string fullRef)
|
||||
{
|
||||
if (!_options.WriteOnChangeOnly) return;
|
||||
lock (_lastWrittenLock) _lastWrittenByRef.Remove(fullRef);
|
||||
}
|
||||
|
||||
private void InvalidateWriteCacheIfDiverged(string fullRef, object value)
|
||||
{
|
||||
if (!_options.WriteOnChangeOnly) return;
|
||||
@@ -1483,6 +1498,8 @@ public sealed class ModbusDriver
|
||||
catch (Exception ex)
|
||||
{
|
||||
results[i] = new WriteResult(MapWriteFailure(ex, unit, w.FullReference, batch, cancellationToken));
|
||||
// A failed write may or may not have landed — the device value is unknown now.
|
||||
InvalidateWriteCache(w.FullReference);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user