Auto: abcip-2.6 — AOI input/output handling

AOI-aware browse paths: AOI instances now fan out under directional
sub-folders (Inputs/, Outputs/, InOut/) instead of a flat layout. The
sub-folders only appear when at least one member carries a non-Local
AoiQualifier, so plain UDT tags keep the pre-2.6 flat structure.

- Add AoiQualifier enum (Local / Input / Output / InOut) + new property
  on AbCipStructureMember (defaults to Local).
- L5K parser learns ADD_ON_INSTRUCTION_DEFINITION blocks; PARAMETER
  entries' Usage attribute flows through L5kMember.Usage.
- L5X parser captures the Usage attribute on <Parameter> elements.
- L5kIngest maps Usage strings (Input/Output/InOut) to AoiQualifier;
  null + unknown values map to Local.
- AbCipDriver.DiscoverAsync groups directional members under
  Inputs / Outputs / InOut sub-folders when any member is non-Local.
- Tests for L5K AOI block parsing, L5X Usage capture, ingest mapping
  (both formats), and AOI-vs-plain UDT discovery fan-out.

Closes #234
This commit is contained in:
Joseph Doherty
2026-04-25 18:58:49 -04:00
parent 177d75784b
commit e3c0750f7d
9 changed files with 373 additions and 9 deletions

View File

@@ -191,6 +191,35 @@ public sealed class L5xParserTests
tag.Members!.Select(m => m.Name).ShouldBe(["Cmd", "Status"]);
}
[Fact]
public void AOI_parameter_Usage_attribute_is_captured()
{
// PR abcip-2.6 — Usage attribute on <Parameter> elements (Input / Output / InOut) flows
// through to L5kMember.Usage so the ingest layer can map it to AoiQualifier.
const string body = """
<?xml version="1.0" encoding="UTF-8"?>
<RSLogix5000Content>
<Controller Name="C">
<AddOnInstructionDefinitions>
<AddOnInstructionDefinition Name="MyAoi" Revision="1.0">
<Parameters>
<Parameter Name="Cmd" DataType="BOOL" Usage="Input" />
<Parameter Name="Status" DataType="DINT" Usage="Output" />
<Parameter Name="Buffer" DataType="DINT" Usage="InOut" />
</Parameters>
</AddOnInstructionDefinition>
</AddOnInstructionDefinitions>
</Controller>
</RSLogix5000Content>
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
var aoi = doc.DataTypes.Single(d => d.Name == "MyAoi");
aoi.Members.Single(m => m.Name == "Cmd").Usage.ShouldBe("Input");
aoi.Members.Single(m => m.Name == "Status").Usage.ShouldBe("Output");
aoi.Members.Single(m => m.Name == "Buffer").Usage.ShouldBe("InOut");
}
[Fact]
public void Empty_or_minimal_document_returns_empty_bundle_without_throwing()
{