feat(modbus): block-read coalescing on by default; coils/DI/BitInRegister/String fold

U2: MaxReadGap defaults to 8 and 0 now means adjacent-only rather than
off; a new DisableReadCoalescing switch (factory DTO + AdminUI form) turns it
off entirely. The planner also merges Coils/DiscreteInputs (FC01/FC02, gap in
bits), String scalars and BitInRegister tags - sixteen bit tags on one status
word are one FC03 instead of sixteen. Auto-prohibition and its re-probe /
bisection use the region's own function code (a coil range was re-probed
with FC04).

Refs #741
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 15:24:38 -04:00
parent f827b3a134
commit 2ef3cd0df2
7 changed files with 269 additions and 56 deletions
@@ -80,7 +80,13 @@
<div class="col-md-3">
<label class="form-label">Max read gap (coalescing)</label>
<InputNumber @bind-Value="_form.MaxReadGap" @bind-Value:after="EmitAsync" class="form-control form-control-sm" />
<div class="form-text">0 = no coalescing. Typical 5–32.</div>
<div class="form-text">Default 8. 0 = adjacent tags only. Refused ranges fall back per tag.</div>
</div>
<div class="col-md-3">
<div class="form-check form-switch mt-4">
<InputCheckbox @bind-Value="_form.DisableReadCoalescing" @bind-Value:after="EmitAsync" class="form-check-input" id="disableCoalescing" />
<label class="form-check-label" for="disableCoalescing">Disable read coalescing</label>
</div>
</div>
</div>
</div>
@@ -311,7 +317,8 @@
public int MaxRegistersPerRead { get; set; } = 125;
public int MaxRegistersPerWrite { get; set; } = 123;
public int MaxCoilsPerRead { get; set; } = 2000;
public int MaxReadGap { get; set; } = 0;
public int MaxReadGap { get; set; } = ModbusDriverOptions.DefaultMaxReadGap;
public bool DisableReadCoalescing { get; set; } = false;
// Write options
public bool UseFC15ForSingleCoilWrites { get; set; } = false;
@@ -357,6 +364,7 @@
MaxRegistersPerWrite = o.MaxRegistersPerWrite,
MaxCoilsPerRead = o.MaxCoilsPerRead,
MaxReadGap = o.MaxReadGap,
DisableReadCoalescing = o.DisableReadCoalescing,
UseFC15ForSingleCoilWrites = o.UseFC15ForSingleCoilWrites,
UseFC16ForSingleRegisterWrites = o.UseFC16ForSingleRegisterWrites,
DisableFC23 = o.DisableFC23,
@@ -399,6 +407,7 @@
AutoProhibitReprobeInterval = AutoProhibitReprobeIntervalSeconds > 0
? TimeSpan.FromSeconds(AutoProhibitReprobeIntervalSeconds) : null,
MaxReadGap = (ushort)Math.Clamp(MaxReadGap, 0, 65535),
DisableReadCoalescing = DisableReadCoalescing,
Family = Family,
MelsecSubFamily = MelsecSubFamily,
Transport = Transport,