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:
@@ -234,6 +234,59 @@ public sealed class ModbusTagConfigModelTests
|
||||
}
|
||||
}
|
||||
|
||||
private const string PreLaneRow = """{"region":"InputRegisters","address":5,"dataType":"Int16","addressString":"40011:F"}""";
|
||||
|
||||
/// <summary>Fix round 3 (review r2 N2): a pre-#741 row (keys + string, no marker) keeps resolving from its
|
||||
/// keys through a model save and a CSV export → import, under every family.</summary>
|
||||
[Fact]
|
||||
public void A_pre_lane_keys_plus_string_row_keeps_its_keys_through_save_and_csv_round_trip()
|
||||
{
|
||||
var m = ModbusTagConfigModel.FromJson(PreLaneRow);
|
||||
m.IsStringOnly.ShouldBeFalse();
|
||||
m.HasLegacyAddressString.ShouldBeTrue();
|
||||
m.Writable = false;
|
||||
var saved = m.ToJson();
|
||||
|
||||
var map = CsvColumnMap.Resolve(ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverTypeNames.Modbus).ShouldNotBeNull();
|
||||
var cells = map.ReadTypedColumns(PreLaneRow);
|
||||
cells["Region"].ShouldBe("InputRegisters");
|
||||
cells["Address"].ShouldBe("5");
|
||||
var reimported = map.ApplyTypedColumns(PreLaneRow, cells);
|
||||
|
||||
foreach (var json in new[] { saved, reimported })
|
||||
foreach (var family in new[] { ModbusFamily.Generic, ModbusFamily.DL205, ModbusFamily.MELSEC })
|
||||
{
|
||||
ModbusTagDefinitionFactory.FromTagConfig(json, "T", family, MelsecFamily.Q_L_iQR, out var def).ShouldBeTrue();
|
||||
def.Region.ShouldBe(ModbusRegion.InputRegisters, family.ToString());
|
||||
def.Address.ShouldBe((ushort)5, family.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Review r2 n5: a typed CSV address cell on a string-only row is an authored address — it is applied
|
||||
/// and drops the string, mirroring a hand edit in the editor (it used to be silently discarded).</summary>
|
||||
[Fact]
|
||||
public void A_typed_csv_address_cell_on_a_string_only_row_is_applied_and_drops_the_string()
|
||||
{
|
||||
var map = CsvColumnMap.Resolve(ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverTypeNames.Modbus).ShouldNotBeNull();
|
||||
var merged = map.ApplyTypedColumns("""{"addressString":"40011:F","addressResolved":true}""",
|
||||
new Dictionary<string, string>(StringComparer.Ordinal) { ["Region"] = "InputRegisters", ["Address"] = "7" });
|
||||
|
||||
merged.ShouldNotContain("addressString");
|
||||
merged.ShouldNotContain("addressResolved");
|
||||
ModbusTagDefinitionFactory.FromTagConfig(merged, "T", out var def).ShouldBeTrue();
|
||||
def.Region.ShouldBe(ModbusRegion.InputRegisters);
|
||||
def.Address.ShouldBe((ushort)7);
|
||||
}
|
||||
|
||||
/// <summary>Review r2 n6: authoring is never stricter than the driver — a string-only tag's ignored dataType /
|
||||
/// byteOrder keys are not judged (positive control: the same junk on a classic tag is refused).</summary>
|
||||
[Fact]
|
||||
public void Validate_does_not_judge_keys_the_driver_ignores_on_a_string_only_tag()
|
||||
{
|
||||
ModbusTagConfigModel.FromJson("""{"addressString":"40011:F","dataType":"Intt16","byteOrder":"Nope"}""").Validate().ShouldBeNull();
|
||||
ModbusTagConfigModel.FromJson("""{"region":"HoldingRegisters","address":1,"dataType":"Intt16"}""").Validate().ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_hand_edit_after_resolving_drops_the_address_string()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user