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
CsvTagImporter / CsvTagExporter parse and emit Kepware-format AB CIP tag
CSVs (Tag Name, Address, Data Type, Respect Data Type, Client Access,
Scan Rate, Description, Scaling). Import maps Tag Name → AbCipTagDefinition.Name,
Address → TagPath, Data Type → DataType, Description → Description,
Client Access → Writable. Skips blank rows + ;/# section markers; honours
column reordering via header lookup; RFC-4180-ish quoting.
CsvImports collection on AbCipDriverOptions mirrors L5kImports/L5xImports
and is consumed by InitializeAsync (declared > L5K > L5X > CSV precedence).
CLI tag-export command dumps the merged tag table from a driver-options JSON
to a Kepware CSV — runs the same import-merge precedence the driver uses but
without contacting any PLC.
Tests cover R/W mapping, blank-row skip, quoted comma, escaped quote, name
prefix, unknown-type fall-through, header reordering, and a load → export →
reparse round-trip.
Closes#232
Threads tag/UDT-member descriptions captured by the L5K (#346) and L5X
(#347) parsers through AbCipTagDefinition + AbCipStructureMember into
DriverAttributeInfo, so the address-space builder sets the OPC UA
Description attribute on each Variable node. L5kMember and L5xParser
also now capture per-member descriptions (via the (Description := "...")
attribute block on L5K and the <Description> child on L5X), and
L5kIngest forwards them. DriverNodeManager surfaces
DriverAttributeInfo.Description as the Variable's Description property.
Description is added as a trailing optional parameter on
DriverAttributeInfo (default null) so every other driver continues
to construct the record unchanged.
Closes#231
Adds Import/L5xParser.cs that consumes Studio 5000 L5X (XML) controller
exports via System.Xml.XPath and produces the same L5kDocument bundle as
L5kParser, so L5kIngest handles both formats interchangeably.
- Controller-scope and program-scope <Tag> elements with Name, DataType,
TagType, ExternalAccess, AliasFor, and <Description> child.
- <DataType>/<Members>/<Member> with Hidden BOOL-host (ZZZZZZZZZZ*) skip.
- AddOnInstructionDefinitions surfaced as L5kDataType entries so AOI-typed
tags pick up a member layout the same way UDT-typed tags do; hidden
EnableIn/EnableOut parameters skipped. Full directional Input/Output/InOut
modelling stays deferred to PR 2.6.
AbCipDriverOptions gains parallel L5xImports collection (mirrors
L5kImports field-for-field). InitializeAsync funnels both through one
shared MergeImport helper that differs only in the parser delegate.
Tests: 8 L5X fixtures cover controller- and program-scope tags, alias skip,
UDT layout fan-out, AOI-typed tag, ZZZZZZZZZZ host skip, hidden AOI param
skip, missing-ExternalAccess default, and an empty-controller no-throw.
Closes#230
Pure-text parser for Studio 5000 L5K controller exports. Recognises
TAG/END_TAG, DATATYPE/END_DATATYPE, and PROGRAM/END_PROGRAM blocks,
strips (* ... *) comments, and tolerates multi-line entries + unknown
sections (CONFIG, MOTION_GROUP, etc.). Output records — L5kTag,
L5kDataType, L5kMember — feed L5kIngest which converts to
AbCipTagDefinition + AbCipStructureMember. Alias tags and
ExternalAccess=None tags are skipped per Kepware precedent.
AbCipDriverOptions gains an L5kImports collection
(AbCipL5kImportOptions records — file path or inline text + per-import
device + name prefix). InitializeAsync merges the imports into the
declared Tags map, with declared tags winning on Name conflicts so
operators can override import results without editing the L5K source.
Tests cover controller-scope TAG, program-scope TAG, alias-tag flag,
DATATYPE with member array dims, comment stripping, unknown-section
skipping, multi-line entries, and the full ingest path including
ExternalAccess=None / ReadOnly / UDT-typed tag fanout.
Closes#229
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>