Pre-#148 behaviour: a coalesced FC03/FC04 read that crossed a write-only or PLC-fault register marked every member tag Bad until the operator manually flagged the offending tag with CoalesceProhibited. Healthy tags around the hole stayed broken indefinitely. Post-#148: two-stage recovery, no operator intervention needed. 1. Same-scan fallback: when a coalesced read fails with a Modbus exception (IllegalDataAddress, SlaveDeviceFailure, etc.), the planner does NOT mark members handled. The per-tag fallback in the same scan reads each member individually — non-protected members surface Good values immediately, and only the actual protected register stays Bad. 2. Cross-scan prohibition: the failed range (Unit, Region, Start, End) is recorded in a per-driver `_autoProhibited` set. On subsequent scans the planner checks each candidate merge against the set and refuses to re-form any block that overlaps a known-bad range. Net effect: after one scan with a failure, the protected range goes "per-tag mode" indefinitely while ranges around it keep coalescing normally. Communication failures (timeouts, socket drops) are NOT auto-prohibited — they're transport-level, not structural. The same coalesced read can succeed once the transport recovers; recording it as "permanently bad" would defeat coalescing for the whole driver instance. Auto-prohibition state lives for the driver lifetime and clears on ReinitializeAsync (operator restart). A periodic re-probe is a follow-up if deployments need it without a restart. Implementation: - Added `_autoProhibited` HashSet<(byte, ModbusRegion, ushort, ushort)> + `_autoProhibitedLock` on ModbusDriver. - `RangeIsAutoProhibited(unit, region, start, end)` overlap check called from the planner when forming blocks. - `RecordAutoProhibition(...)` called from the catch (ModbusException) branch. - The catch (Exception) branch (non-Modbus failures) keeps the pre-#148 "mark all Bad in this scan, don't auto-prohibit" behaviour. - Internal `AutoProhibitedRangeCount` accessor for tests. Tests (3 new ModbusCoalescingAutoRecoveryTests): - First_Failure_Falls_Back_To_PerTag_Same_Scan — three tags around a protected register at 102: T100 + T104 surface Good values via the per-tag fallback in the SAME scan; T102 surfaces the exception. - Second_Scan_Skips_Coalesced_Read_Of_Prohibited_Range — confirms scan 2 doesn't re-attempt the failed merge (no FC03 with quantity > 1 at the prohibited start). - Tags_Outside_Prohibited_Range_Still_Coalesce — separate cluster at HR 200..202 keeps coalescing normally even after the 100..104 cluster is prohibited. 234/234 unit tests green. Follow-ups intentionally NOT shipped (smaller, independent changes): - Bisection-style range narrowing — currently the prohibition range is the full failed block; the planner doesn't try to find the exact protected register. Operator-visible diagnostic + prohibition stays correct. - Periodic re-probe to clear stale prohibitions. - Surface auto-prohibited ranges through GetHostStatuses or a new diagnostic so the Admin UI can show what's been auto-isolated.
7.4 KiB
7.4 KiB