Auto: ablegacy-1 — PLC-5 octal I/O addressing

Closes #244
This commit is contained in:
Joseph Doherty
2026-04-25 13:25:22 -04:00
parent 651d6c005c
commit 8f7265186d
5 changed files with 146 additions and 15 deletions

View File

@@ -1,6 +1,7 @@
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
@@ -65,4 +66,77 @@ public sealed class AbLegacyAddressTests
a.ShouldNotBeNull();
a.ToLibplctagName().ShouldBe(input);
}
// ---- PLC-5 octal I:/O: addressing (Issue #244) ----
//
// RSLogix 5 displays I:/O: word + bit indices as octal. `I:001/17` means rack 1, bit 15
// (octal 17). Other PCCC families (SLC500, MicroLogix, LogixPccc) keep decimal indices.
// Non-I/O file letters are always decimal regardless of family.
[Theory]
[InlineData("I:001/17", 1, 15)] // octal 17 → bit 15
[InlineData("I:0/0", 0, 0)] // boundary: octal 0
[InlineData("O:1/2", 1, 2)] // octal 1, 2 happen to match decimal
[InlineData("I:010/10", 8, 8)] // octal 10 → 8 (both word + bit)
[InlineData("I:007/7", 7, 7)] // boundary: largest single octal digit
public void TryParse_Plc5_parses_io_indices_as_octal(string input, int expectedWord, int expectedBit)
{
var a = AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.Plc5);
a.ShouldNotBeNull();
a.WordNumber.ShouldBe(expectedWord);
a.BitIndex.ShouldBe(expectedBit);
}
[Theory]
[InlineData("I:8/0")] // word digit 8 illegal in octal
[InlineData("I:0/9")] // bit digit 9 illegal in octal
[InlineData("O:128/0")] // contains digit 8
[InlineData("I:0/18")] // bit field octal-illegal because of '8'
public void TryParse_Plc5_rejects_octal_invalid_io_digits(string input)
{
AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.Plc5).ShouldBeNull();
}
[Theory]
// Non-I/O files stay decimal even on PLC-5 (e.g. N7:8 is integer 7, word 8).
[InlineData("N7:8", 7, 8)]
[InlineData("F8:9", 8, 9)]
public void TryParse_Plc5_keeps_non_io_indices_decimal(string input, int? expectedFile, int expectedWord)
{
var a = AbLegacyAddress.TryParse(input, AbLegacyPlcFamily.Plc5);
a.ShouldNotBeNull();
a.FileNumber.ShouldBe(expectedFile);
a.WordNumber.ShouldBe(expectedWord);
}
[Fact]
public void TryParse_Slc500_keeps_io_indices_decimal_back_compat()
{
// SLC500 has OctalIoAddressing=false — the digits are decimal as before.
var a = AbLegacyAddress.TryParse("I:10/15", AbLegacyPlcFamily.Slc500);
a.ShouldNotBeNull();
a.WordNumber.ShouldBe(10);
a.BitIndex.ShouldBe(15);
// Decimal '8' that PLC-5 would reject is fine on SLC500.
var b = AbLegacyAddress.TryParse("I:8/0", AbLegacyPlcFamily.Slc500);
b.ShouldNotBeNull();
b.WordNumber.ShouldBe(8);
}
[Fact]
public void TryParse_MicroLogix_and_LogixPccc_keep_io_indices_decimal()
{
AbLegacyAddress.TryParse("I:9/0", AbLegacyPlcFamily.MicroLogix).ShouldNotBeNull();
AbLegacyAddress.TryParse("I:9/0", AbLegacyPlcFamily.LogixPccc).ShouldNotBeNull();
}
[Fact]
public void Plc5Profile_advertises_octal_io_addressing()
{
AbLegacyPlcFamilyProfile.Plc5.OctalIoAddressing.ShouldBeTrue();
AbLegacyPlcFamilyProfile.Slc500.OctalIoAddressing.ShouldBeFalse();
AbLegacyPlcFamilyProfile.MicroLogix.OctalIoAddressing.ShouldBeFalse();
AbLegacyPlcFamilyProfile.LogixPccc.OctalIoAddressing.ShouldBeFalse();
}
}