fix(modbus): review r2 - region+address keys always win; string-only means no keys
N2: pre-#741 rows carry region/address beside an addressString with no marker (the old driver never read the string, the old model preserved it as an unknown key). The r1 rule resolved those from the string - a silent remap or BadNodeIdUnknown on upgrade. The driver now loads from region+address whenever both are present (classic, editor-pinned, pre-lane); only a blob with NO keys is string-only and resolved through the parser. This is safe because the model no longer stamps keys onto a string-only tag. The model distinguishes string-only / legacy-string-beside-keys / pinned, and the editor hint says so. Test matrix over every persisted shape (keys only, pre-lane keys+string, pre-lane stamped HR/0+string, string only, marker+keys, marker no keys) under every family; the pre-lane row also through a model save and a CSV export->import. n5: a typed CSV address cell is an authored address - it drops addressString and the marker (new CsvTypedColumn.ClearsKeys) instead of being discarded. n6: Validate skips the region/dataType/byteOrder enum checks on a string-only tag (the driver ignores them). n7: docs/v2/modbus-addressing.md restates the key-presence rule. Refs #741 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -74,14 +74,39 @@ public sealed class ModbusFamilyWiringTests
|
||||
/// what a model round-trip of a string-only tag used to produce) never win: the string is resolved with
|
||||
/// the driver's family, so the tag cannot collapse onto HoldingRegisters[0].
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Defaulted_region_and_address_beside_an_unmarked_string_do_not_win()
|
||||
/// <summary>
|
||||
/// Fix round 3 (review r2 N2): the persisted-shape matrix. Every shape that carries region + address loads
|
||||
/// from them under every family — including the pre-#741 "keys + addressString, no marker" row, which the
|
||||
/// old driver served from its keys and must keep serving so. Only a string-only blob (no keys) resolves
|
||||
/// the string.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("keys only", """{"region":"InputRegisters","address":5}""", ModbusRegion.InputRegisters, 5)]
|
||||
[InlineData("pre-lane keys + string", """{"region":"InputRegisters","address":5,"dataType":"Int16","addressString":"40011:F"}""", ModbusRegion.InputRegisters, 5)]
|
||||
[InlineData("pre-lane stamped HR/0 + string", """{"region":"HoldingRegisters","address":0,"dataType":"Int16","addressString":"D100"}""", ModbusRegion.HoldingRegisters, 0)]
|
||||
[InlineData("string only", """{"addressString":"40011:F"}""", ModbusRegion.HoldingRegisters, 10)]
|
||||
[InlineData("string + marker + keys (editor-pinned)", """{"addressString":"D100","addressResolved":true,"region":"HoldingRegisters","address":100}""", ModbusRegion.HoldingRegisters, 100)]
|
||||
[InlineData("string + marker, no keys", """{"addressString":"40011:F","addressResolved":true}""", ModbusRegion.HoldingRegisters, 10)]
|
||||
public void Every_persisted_shape_resolves_to_its_address_under_every_family(
|
||||
string shape, string json, ModbusRegion region, int address)
|
||||
{
|
||||
const string json = """{"addressString":"D100","region":"HoldingRegisters","address":0,"dataType":"Int16"}""";
|
||||
ModbusTagDefinitionFactory.FromTagConfig(json, "T", ModbusFamily.MELSEC, MelsecFamily.Q_L_iQR, out var def).ShouldBeTrue();
|
||||
def.Address.ShouldBe((ushort)100);
|
||||
// Under a family that cannot parse it the tag is refused (BadNodeIdUnknown), never served as HR[0].
|
||||
ModbusTagDefinitionFactory.FromTagConfig(json, "T", ModbusFamily.Generic, MelsecFamily.Q_L_iQR, out _).ShouldBeFalse();
|
||||
foreach (var (family, sub) in new[]
|
||||
{
|
||||
(ModbusFamily.Generic, MelsecFamily.Q_L_iQR), (ModbusFamily.DL205, MelsecFamily.Q_L_iQR),
|
||||
(ModbusFamily.MELSEC, MelsecFamily.Q_L_iQR), (ModbusFamily.MELSEC, MelsecFamily.F_iQF),
|
||||
})
|
||||
{
|
||||
ModbusTagDefinitionFactory.FromTagConfig(json, "T", family, sub, out var def).ShouldBeTrue($"{shape} / {family}");
|
||||
def.Region.ShouldBe(region, $"{shape} / {family}");
|
||||
def.Address.ShouldBe((ushort)address, $"{shape} / {family}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_string_only_blob_no_family_parses_is_refused_not_served_as_HR0()
|
||||
{
|
||||
ModbusTagDefinitionFactory.FromTagConfig("""{"addressString":"D100"}""", "T", ModbusFamily.Generic, MelsecFamily.Q_L_iQR, out _)
|
||||
.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user