Auto: ablegacy-5 — PD/MG/PLS/BT structure files
Adds PD (PID), MG (Message), PLS (Programmable Limit Switch) and BT
(Block Transfer) file types to the PCCC parser. New AbLegacyDataType
enum members (PidElement / MessageElement / PlsElement /
BlockTransferElement) plus per-type sub-element catalogue:
- PD: SP/PV/CV/KP/KI/KD/MAXS/MINS/DB/OUT as Float32; EN/DN/MO/PE/
AUTO/MAN/SP_VAL/SP_LL/SP_HL as Boolean (word-0 status bits).
- MG: RBE/MS/SIZE/LEN as Int32; EN/EW/ER/DN/ST/CO/NR/TO as Boolean.
- PLS: LEN as Int32 (bit table varies per PLC).
- BT: RLEN/DLEN as Int32; EN/ST/DN/ER/CO/EW/TO/NR as Boolean.
Per-family flags on AbLegacyPlcFamilyProfile gate availability:
- PD/MG: SLC500 + PLC-5 (operator + status bits both present).
- PLS/BT: PLC-5 only (chassis-IO block transfer is PLC-5-specific).
- MicroLogix + LogixPccc: rejected — no legacy file-letter form.
Status-bit indices match Rockwell DTAM / 1747-RM001 / 1785-6.5.12:
PD word 0 bits 0-8, MG/BT word 0 bits 8-15. PLC-set status bits
(PE/DN/SP_*; ST/DN/ER/CO/EW/NR/TO) are surfaced as ViewOnly via
IsPlcSetStatusBit, matching the Timer/Counter/Control pattern from
ablegacy-3.
LibplctagLegacyTagRuntime decodes PD non-bit members as Float32 and
MG/BT/PLS non-bit members as Int32; status bits route through GetBit
with the bit-position encoded by the driver via StatusBitIndex.
Tests: parser positive cases per family + negative cases per family,
catalogue + bit-index + read-only-bit assertions.
Closes #248
This commit is contained in:
@@ -333,4 +333,170 @@ public sealed class AbLegacyAddressTests
|
||||
{
|
||||
AbLegacyFunctionFile.SubElementType(letter, sub).ShouldBe(expected);
|
||||
}
|
||||
|
||||
// ---- Structure files PD/MG/PLS/BT (Issue #248) ----
|
||||
//
|
||||
// PD (PID), MG (Message), PLS (Programmable Limit Switch), BT (Block Transfer) — accepted on
|
||||
// SLC500 + PLC-5 for PD/MG, PLC-5 only for PLS/BT. MicroLogix and LogixPccc reject all four.
|
||||
|
||||
[Theory]
|
||||
[InlineData("PD10:0.SP")]
|
||||
[InlineData("PD10:0.PV")]
|
||||
[InlineData("PD10:0.KP")]
|
||||
[InlineData("PD10:0.EN")]
|
||||
[InlineData("MG11:0.LEN")]
|
||||
[InlineData("MG11:0.DN")]
|
||||
public void TryParse_Slc500_accepts_pd_and_mg(string input)
|
||||
{
|
||||
AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.Slc500).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PLS12:0.LEN")]
|
||||
[InlineData("BT13:0.RLEN")]
|
||||
[InlineData("BT13:0.EN")]
|
||||
public void TryParse_Slc500_rejects_pls_and_bt(string input)
|
||||
{
|
||||
// PLS/BT are PLC-5 only; SLC500 must reject.
|
||||
AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.Slc500).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PD10:0.KP", "PD", "KP")]
|
||||
[InlineData("MG11:0.EN", "MG", "EN")]
|
||||
[InlineData("PLS12:0.LEN", "PLS", "LEN")]
|
||||
[InlineData("BT13:0.RLEN", "BT", "RLEN")]
|
||||
[InlineData("BT13:0.DN", "BT", "DN")]
|
||||
public void TryParse_Plc5_accepts_all_structure_files(string input, string letter, string sub)
|
||||
{
|
||||
var a = AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.Plc5);
|
||||
a.ShouldNotBeNull();
|
||||
a.FileLetter.ShouldBe(letter);
|
||||
a.SubElement.ShouldBe(sub);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PD10:0.SP")]
|
||||
[InlineData("MG11:0.LEN")]
|
||||
[InlineData("PLS12:0.LEN")]
|
||||
[InlineData("BT13:0.RLEN")]
|
||||
public void TryParse_MicroLogix_rejects_all_structure_files(string input)
|
||||
{
|
||||
AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.MicroLogix).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PD10:0.SP")]
|
||||
[InlineData("MG11:0.LEN")]
|
||||
[InlineData("PLS12:0.LEN")]
|
||||
[InlineData("BT13:0.RLEN")]
|
||||
public void TryParse_LogixPccc_rejects_all_structure_files(string input)
|
||||
{
|
||||
AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.LogixPccc).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_Default_overload_rejects_structure_files()
|
||||
{
|
||||
// Without a family the parser cannot allow structure-file letters.
|
||||
AbLegacyAddress.TryParse("PD10:0.SP").ShouldBeNull();
|
||||
AbLegacyAddress.TryParse("MG11:0.LEN").ShouldBeNull();
|
||||
AbLegacyAddress.TryParse("PLS12:0.LEN").ShouldBeNull();
|
||||
AbLegacyAddress.TryParse("BT13:0.RLEN").ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Profiles_advertise_structure_file_support_per_family()
|
||||
{
|
||||
AbLegacyPlcFamilyProfile.Slc500.SupportsPidFile.ShouldBeTrue();
|
||||
AbLegacyPlcFamilyProfile.Slc500.SupportsMessageFile.ShouldBeTrue();
|
||||
AbLegacyPlcFamilyProfile.Slc500.SupportsPlsFile.ShouldBeFalse();
|
||||
AbLegacyPlcFamilyProfile.Slc500.SupportsBlockTransferFile.ShouldBeFalse();
|
||||
|
||||
AbLegacyPlcFamilyProfile.Plc5.SupportsPidFile.ShouldBeTrue();
|
||||
AbLegacyPlcFamilyProfile.Plc5.SupportsMessageFile.ShouldBeTrue();
|
||||
AbLegacyPlcFamilyProfile.Plc5.SupportsPlsFile.ShouldBeTrue();
|
||||
AbLegacyPlcFamilyProfile.Plc5.SupportsBlockTransferFile.ShouldBeTrue();
|
||||
|
||||
AbLegacyPlcFamilyProfile.MicroLogix.SupportsPidFile.ShouldBeFalse();
|
||||
AbLegacyPlcFamilyProfile.MicroLogix.SupportsMessageFile.ShouldBeFalse();
|
||||
AbLegacyPlcFamilyProfile.MicroLogix.SupportsPlsFile.ShouldBeFalse();
|
||||
AbLegacyPlcFamilyProfile.MicroLogix.SupportsBlockTransferFile.ShouldBeFalse();
|
||||
|
||||
AbLegacyPlcFamilyProfile.LogixPccc.SupportsPidFile.ShouldBeFalse();
|
||||
AbLegacyPlcFamilyProfile.LogixPccc.SupportsMessageFile.ShouldBeFalse();
|
||||
AbLegacyPlcFamilyProfile.LogixPccc.SupportsPlsFile.ShouldBeFalse();
|
||||
AbLegacyPlcFamilyProfile.LogixPccc.SupportsBlockTransferFile.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// PID Float members.
|
||||
[InlineData(AbLegacyDataType.PidElement, "SP", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float32)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "PV", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float32)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "KP", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float32)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "KI", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float32)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "KD", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float32)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "OUT", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float32)]
|
||||
// PID status bits.
|
||||
[InlineData(AbLegacyDataType.PidElement, "EN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "DN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "MO", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "PE", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
// MG Int32 control words.
|
||||
[InlineData(AbLegacyDataType.MessageElement, "RBE", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int32)]
|
||||
[InlineData(AbLegacyDataType.MessageElement, "LEN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int32)]
|
||||
// MG status bits.
|
||||
[InlineData(AbLegacyDataType.MessageElement, "EN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
[InlineData(AbLegacyDataType.MessageElement, "DN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
[InlineData(AbLegacyDataType.MessageElement, "TO", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
// PLS LEN.
|
||||
[InlineData(AbLegacyDataType.PlsElement, "LEN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int32)]
|
||||
// BT control words + status bits.
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "RLEN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int32)]
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "DLEN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int32)]
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "EN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "DN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
public void Structure_subelements_resolve_to_expected_driver_type(
|
||||
AbLegacyDataType type, string sub, ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType expected)
|
||||
{
|
||||
AbLegacyDataTypeExtensions.EffectiveDriverDataType(type, sub).ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// PD bits in word 0.
|
||||
[InlineData(AbLegacyDataType.PidElement, "EN", 0)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "PE", 1)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "DN", 2)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "MO", 3)]
|
||||
// MG/BT share the same 8..15 layout.
|
||||
[InlineData(AbLegacyDataType.MessageElement, "TO", 8)]
|
||||
[InlineData(AbLegacyDataType.MessageElement, "EN", 15)]
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "TO", 8)]
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "EN", 15)]
|
||||
public void Structure_status_bit_indices_match_rockwell(
|
||||
AbLegacyDataType type, string sub, int expectedBit)
|
||||
{
|
||||
AbLegacyDataTypeExtensions.StatusBitIndex(type, sub).ShouldBe(expectedBit);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// PD: PE + DN + SP_VAL/SP_LL/SP_HL are PLC-set (read-only); EN + MO + AUTO + MAN are
|
||||
// operator-controllable.
|
||||
[InlineData(AbLegacyDataType.PidElement, "PE", true)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "DN", true)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "SP_VAL", true)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "EN", false)]
|
||||
[InlineData(AbLegacyDataType.PidElement, "MO", false)]
|
||||
// MG/BT: ST/DN/ER/CO/EW/NR/TO are PLC-set; EN is operator-driven.
|
||||
[InlineData(AbLegacyDataType.MessageElement, "DN", true)]
|
||||
[InlineData(AbLegacyDataType.MessageElement, "ER", true)]
|
||||
[InlineData(AbLegacyDataType.MessageElement, "TO", true)]
|
||||
[InlineData(AbLegacyDataType.MessageElement, "EN", false)]
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "DN", true)]
|
||||
[InlineData(AbLegacyDataType.BlockTransferElement, "EN", false)]
|
||||
public void Structure_plc_set_status_bits_are_marked_read_only(
|
||||
AbLegacyDataType type, string sub, bool expected)
|
||||
{
|
||||
AbLegacyDataTypeExtensions.IsPlcSetStatusBit(type, sub).ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user