feat(modbus): wire Family / MelsecSubFamily / DisableFC23, and the form's timing keys
U1: a tag's addressString (Modicon / mnemonic / DL205 V-memory / MELSEC D-X-Y grammar) now resolves through ModbusAddressParser with the DRIVER's Family and MelsecSubFamily at every initialize and is authoritative; the family-unaware gates (deploy Inspect, AdminUI parse consult) accept what some family resolves. The tag editor gets an address-text field that resolves with the owning driver's family and persists both the string and the resolved region/offset/type fields; a hand edit drops the string. DisableFC23 now gates FC23: by default a 2..121-register holding write is one FC23 write + read-back (a unit answering exception 01, or failing FC23 at the transport, moves to FC16 for the rest of the generation); RTU framing deframes the FC23 reply. Live-checked: the pymodbus fixture implements FC23. Also: the AdminUI driver form serialises ModbusDriverOptions (TimeSpan strings), which the factory DTO never read - every timing field on the form was dead. The DTO now accepts both shapes (*Ms keys win). Refs #741 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -141,4 +141,43 @@ public sealed class ModbusTagConfigModelTests
|
||||
[Fact]
|
||||
public void Validate_refuses_an_incompatible_region_and_type()
|
||||
=> ModbusTagConfigModel.FromJson("""{"region":"Coils","address":1,"dataType":"Int16"}""").Validate().ShouldNotBeNull();
|
||||
|
||||
/// <summary>Review 07 U1: the editor resolves an address string with the DRIVER's family and persists
|
||||
/// both the string and the resolved fields; the driver re-resolves the string with its family.</summary>
|
||||
[Fact]
|
||||
public void ResolveAddress_uses_the_driver_family_and_persists_string_and_resolved_fields()
|
||||
{
|
||||
var (family, sub) = ModbusTagConfigModel.ReadFamily("""{"Family":"MELSEC","melsecSubFamily":"Q_L_iQR"}""");
|
||||
family.ShouldBe(ModbusFamily.MELSEC);
|
||||
|
||||
var m = ModbusTagConfigModel.FromJson("{}");
|
||||
m.ResolveAddress("D100", family, sub).ShouldBeNull();
|
||||
m.Region.ShouldBe(ModbusRegion.HoldingRegisters);
|
||||
m.Address.ShouldBe(100);
|
||||
|
||||
var json = m.ToJson();
|
||||
json.ShouldContain("\"addressString\":\"D100\"");
|
||||
json.ShouldContain("\"address\":100");
|
||||
ModbusTagDefinitionFactory.FromTagConfig(json, "T", ModbusFamily.MELSEC, MelsecFamily.Q_L_iQR, out var def).ShouldBeTrue();
|
||||
def.Address.ShouldBe((ushort)100);
|
||||
ModbusTagConfigModel.FromJson(json).Validate().ShouldBeNull("the family-unaware authoring gate accepts what the MELSEC driver serves");
|
||||
|
||||
// Positive control: under Generic the same text is an error, and nothing is persisted.
|
||||
var g = ModbusTagConfigModel.FromJson("{}");
|
||||
g.ResolveAddress("D100", ModbusFamily.Generic, MelsecFamily.Q_L_iQR).ShouldNotBeNull();
|
||||
g.AddressString.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_hand_edit_after_resolving_drops_the_address_string()
|
||||
{
|
||||
var m = ModbusTagConfigModel.FromJson("{}");
|
||||
m.ResolveAddress("40011:F", ModbusFamily.Generic, MelsecFamily.Q_L_iQR).ShouldBeNull();
|
||||
m.ClearAddressString();
|
||||
m.Address = 50;
|
||||
var json = m.ToJson();
|
||||
json.ShouldNotContain("addressString");
|
||||
ModbusTagDefinitionFactory.FromTagConfig(json, "T", out var def).ShouldBeTrue();
|
||||
def.Address.ShouldBe((ushort)50);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user