@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user