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:
@@ -153,6 +153,81 @@ public sealed class L5kIngestTests
|
||||
Should.Throw<InvalidOperationException>(() => new L5kIngest().Ingest(doc));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AOI_member_Usage_maps_to_AoiQualifier_through_ingest()
|
||||
{
|
||||
// PR abcip-2.6 — L5K AOI parameters carry a Usage := Input / Output / InOut attribute.
|
||||
// Ingest must map those values onto AbCipStructureMember.AoiQualifier so the discovery
|
||||
// layer can group AOI members under sub-folders. Plain DATATYPE members get Local.
|
||||
const string body = """
|
||||
ADD_ON_INSTRUCTION_DEFINITION ValveAoi
|
||||
PARAMETERS
|
||||
PARAMETER Cmd : BOOL (Usage := Input) := 0;
|
||||
PARAMETER Status : DINT (Usage := Output) := 0;
|
||||
PARAMETER Buffer : DINT (Usage := InOut) := 0;
|
||||
PARAMETER Local1 : DINT := 0;
|
||||
END_PARAMETERS
|
||||
END_ADD_ON_INSTRUCTION_DEFINITION
|
||||
DATATYPE PlainUdt
|
||||
MEMBER Speed : DINT := 0;
|
||||
END_DATATYPE
|
||||
TAG
|
||||
Valve_001 : ValveAoi;
|
||||
Tank1 : PlainUdt;
|
||||
END_TAG
|
||||
""";
|
||||
var doc = L5kParser.Parse(new StringL5kSource(body));
|
||||
|
||||
var result = new L5kIngest { DefaultDeviceHostAddress = DeviceHost }.Ingest(doc);
|
||||
|
||||
var aoiTag = result.Tags.Single(t => t.Name == "Valve_001");
|
||||
aoiTag.Members.ShouldNotBeNull();
|
||||
aoiTag.Members!.Single(m => m.Name == "Cmd").AoiQualifier.ShouldBe(AoiQualifier.Input);
|
||||
aoiTag.Members.Single(m => m.Name == "Status").AoiQualifier.ShouldBe(AoiQualifier.Output);
|
||||
aoiTag.Members.Single(m => m.Name == "Buffer").AoiQualifier.ShouldBe(AoiQualifier.InOut);
|
||||
aoiTag.Members.Single(m => m.Name == "Local1").AoiQualifier.ShouldBe(AoiQualifier.Local);
|
||||
|
||||
// Plain UDT members default to Local — no Usage attribute to map.
|
||||
var plainTag = result.Tags.Single(t => t.Name == "Tank1");
|
||||
plainTag.Members.ShouldNotBeNull();
|
||||
plainTag.Members!.Single().AoiQualifier.ShouldBe(AoiQualifier.Local);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void L5x_AOI_member_Usage_maps_to_AoiQualifier_through_ingest()
|
||||
{
|
||||
// Same mapping as the L5K case above, exercised through the L5X parser to confirm both
|
||||
// formats land at the same downstream representation.
|
||||
const string body = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<RSLogix5000Content>
|
||||
<Controller Name="C">
|
||||
<AddOnInstructionDefinitions>
|
||||
<AddOnInstructionDefinition Name="MyAoi">
|
||||
<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>
|
||||
<Tags>
|
||||
<Tag Name="Valve_001" TagType="Base" DataType="MyAoi" />
|
||||
</Tags>
|
||||
</Controller>
|
||||
</RSLogix5000Content>
|
||||
""";
|
||||
var doc = L5xParser.Parse(new StringL5kSource(body));
|
||||
|
||||
var result = new L5kIngest { DefaultDeviceHostAddress = DeviceHost }.Ingest(doc);
|
||||
|
||||
var aoiTag = result.Tags.Single();
|
||||
aoiTag.Members.ShouldNotBeNull();
|
||||
aoiTag.Members!.Single(m => m.Name == "Cmd").AoiQualifier.ShouldBe(AoiQualifier.Input);
|
||||
aoiTag.Members.Single(m => m.Name == "Status").AoiQualifier.ShouldBe(AoiQualifier.Output);
|
||||
aoiTag.Members.Single(m => m.Name == "Buffer").AoiQualifier.ShouldBe(AoiQualifier.InOut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NamePrefix_is_applied_to_imported_tags()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user