Commit Graph

635 Commits

Author SHA1 Message Date
Joseph Doherty
c689ac58b1 Auto: ablegacy-7 — array contiguous block addressing
Closes #250
2026-04-25 23:36:01 -04:00
05528bf71c Merge pull request '[abcip] AbCip — Logical-blocking / non-blocking strategy selector' (#369) from auto/abcip/3.3 into auto/driver-gaps 2026-04-25 23:18:47 -04:00
Joseph Doherty
01f4ee6b53 Auto: abcip-3.3 — read-strategy selector (WholeUdt / MultiPacket / Auto)
Closes #237
2026-04-25 23:16:06 -04:00
8a8dc1ee5a Merge pull request '[abcip] AbCip — Symbolic vs logical (instance-ID) addressing toggle' (#368) from auto/abcip/3.2 into auto/driver-gaps 2026-04-25 23:01:13 -04:00
Joseph Doherty
0c6a0d6e50 Auto: abcip-3.2 — symbolic vs logical addressing toggle
Closes #236
2026-04-25 22:58:33 -04:00
73ff10b595 Merge pull request '[abcip] AbCip — Configurable CIP Connection Size per device' (#367) from auto/abcip/3.1 into auto/driver-gaps 2026-04-25 22:41:51 -04:00
Joseph Doherty
f6c26db609 Auto: abcip-3.1 — configurable CIP connection size per device
Closes #235
2026-04-25 22:39:05 -04:00
7cbddd4b4a Merge pull request '[twincat] TwinCAT — Symbol-version invalidation listener' (#366) from auto/twincat/2.3 into auto/driver-gaps 2026-04-25 22:18:47 -04:00
Joseph Doherty
4098d72bbb Auto: twincat-2.3 — symbol-version invalidation listener
Closes #312
2026-04-25 22:16:05 -04:00
569001364f Merge pull request '[twincat] TwinCAT — Handle-based access with caching' (#365) from auto/twincat/2.2 into auto/driver-gaps 2026-04-25 22:06:01 -04:00
Joseph Doherty
b67eb6c8d0 Auto: twincat-2.2 — handle-based access with caching
Closes #311
2026-04-25 22:03:20 -04:00
4a071b6d5a Merge pull request '[twincat] TwinCAT — ADS Sum-read / Sum-write' (#364) from auto/twincat/2.1 into auto/driver-gaps 2026-04-25 21:46:21 -04:00
Joseph Doherty
931049b5a7 Auto: twincat-2.1 — ADS Sum-read / Sum-write
Closes #310
2026-04-25 21:43:32 -04:00
fa2fbb404d Merge pull request '[s7] S7 — Block-read coalescing for contiguous DBs' (#363) from auto/s7/PR-S7-B2 into auto/driver-gaps 2026-04-25 21:25:56 -04:00
Joseph Doherty
17faf76ea7 Auto: s7-b2 — block-read coalescing for contiguous DBs
Closes #293
2026-04-25 21:23:06 -04:00
5432c49364 Merge pull request '[s7] S7 — Multi-variable PDU packing' (#362) from auto/s7/PR-S7-B1 into auto/driver-gaps 2026-04-25 21:06:55 -04:00
Joseph Doherty
d7633fe36f Auto: s7-b1 — multi-variable PDU packing
Replaces the per-tag Plc.ReadAsync loop in S7Driver.ReadAsync with a
batched ReadMultipleVarsAsync path. Scalar fixed-width tags (Bool, Byte,
Int16/UInt16, Int32/UInt32, Float32, Float64) are bin-packed into ≤18-item
batches at the default 240-byte PDU using S7.Net.Types.DataItem; arrays,
strings, dates, 64-bit ints, and UDT-shaped types stay on the legacy
ReadOneAsync path. On batch-level failure each tag in the batch falls
back to ReadOneAsync so good tags still produce values and the offender
gets its per-item StatusCode (BadDeviceFailure / BadCommunicationError).

100 scalar reads now coalesce into ≤6 PDU round-trips instead of 100.

Closes #292
2026-04-25 21:04:32 -04:00
69d9a6fbb5 Merge pull request '[opcuaclient] OpcUaClient — Method node mirroring + Call passthrough' (#361) from auto/opcuaclient/9 into auto/driver-gaps 2026-04-25 20:55:09 -04:00
Joseph Doherty
07abee5f6d Auto: opcuaclient-9 — method node mirroring + Call passthrough
Adds the 9th capability interface (IMethodInvoker) so the OPC UA Client
driver can mirror upstream OPC UA Method nodes into the local address
space and forward Call invocations as Session.CallAsync. Method-bearing
servers (e.g. ProgramStateMachine, Acknowledge / Confirm methods, custom
control surfaces) now show up downstream instead of being silently
filtered out.

- Core.Abstractions: IMethodInvoker + MethodCallResult; default no-op
  IAddressSpaceBuilder.RegisterMethodNode + MirroredMethodNodeInfo +
  MethodArgumentInfo. Default impls keep tag-based drivers and existing
  builders compiling without forced overrides.
- OpcUaClientDriver: BrowseRecursiveAsync now lifts the Method node-class
  filter; for each method it walks HasProperty to pick up InputArguments
  + OutputArguments and decodes the Argument extension objects into
  MethodArgumentInfo. Status-codes pass through verbatim (cascading
  quality), local NodeId-parse + lost-session faults surface as
  BadNodeIdInvalid / BadCommunicationError.
- 7 new unit tests cover the capability surface, the DTO shapes, and the
  back-compat default no-op for RegisterMethodNode. Suite green at
  160/160.

Server-side OnCallMethod dispatch (wiring local MethodNode handlers to
IMethodInvoker.CallMethodAsync inside DriverNodeManager) is deferred to
a follow-up — the driver-side surface + browse mirror ship cleanly here.

Closes #281

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:52:39 -04:00
0f3abed4c7 Merge pull request '[opcuaclient] OpcUaClient — Type definition mirroring' (#360) from auto/opcuaclient/8 into auto/driver-gaps 2026-04-25 20:40:49 -04:00
Joseph Doherty
cc21281cbb Auto: opcuaclient-8 — type definition mirroring
Adds an opt-in pass-3 walk of the upstream TypesFolder (i=86) so the OPC UA
Client driver can mirror upstream type definitions into the local address
space. Honours the curation rules from PR-7 (#359). Structural mirror only —
binary-encoding priming via LoadDataTypeSystem is tracked as a follow-up
because that helper was removed from the public ISession surface in
OPCFoundation.NetStandard 1.5.378+.

- IAddressSpaceBuilder.RegisterTypeNode (default no-op for back-compat)
- MirroredTypeNodeInfo + MirroredTypeKind in Core.Abstractions
- OpcUaClientDriverOptions.MirrorTypeDefinitions (default false)
- DiscoverAsync pass-3: FetchTypeTreeAsync + recursive HasSubtype walk per
  branch (ObjectTypes/VariableTypes/DataTypes/ReferenceTypes), best-effort
  IsAbstract read, IncludePaths/ExcludePaths still applied
- 6 new unit tests; all 153 OpcUaClient unit tests pass

Closes #280
2026-04-25 20:38:17 -04:00
5e164dc965 Merge pull request '[opcuaclient] OpcUaClient — Selective import + namespace remap' (#359) from auto/opcuaclient/7 into auto/driver-gaps 2026-04-25 20:23:14 -04:00
Joseph Doherty
02d1c85190 Auto: opcuaclient-7 — selective import + namespace remap
Adds OpcUaClientCurationOptions on OpcUaClientDriverOptions.Curation with:
- IncludePaths/ExcludePaths globs (* and ? only) matched against the
  slash-joined BrowsePath segments collected during BrowseRecursiveAsync.
  Empty Include = include all so existing deployments are unaffected;
  Exclude wins over Include. Pruned folders are skipped wholesale, so
  descendants don't reach the wire.
- NamespaceRemap (URI -> URI) applied to DriverAttributeInfo.FullName when
  registering variables. Index-0 / standard nodes round-trip unchanged;
  remapped nodes serialise via ExpandedNodeId so downstream clients see
  the local-side URI.
- RootAlias replaces the hard-coded "Remote" folder name at the top of
  the mirrored tree.

Closes #279
2026-04-25 20:20:47 -04:00
1d3e9a3237 Merge pull request '[opcuaclient] OpcUaClient — Discovery URL FindServers' (#358) from auto/opcuaclient/6 into auto/driver-gaps 2026-04-25 20:13:21 -04:00
Joseph Doherty
0f509fbd3a Auto: opcuaclient-6 — Discovery URL FindServers
Adds optional `DiscoveryUrl` knob to OpcUaClientDriverOptions. When set,
the driver runs `DiscoveryClient.CreateAsync` + `FindServersAsync` +
`GetEndpointsAsync` against that URL during InitializeAsync and prepends
the discovered endpoint URLs (filtered to matching SecurityPolicy +
SecurityMode) to the failover candidate list. De-duplicates URLs that
appear in both discovered and static lists (case-insensitive). Discovery
failures are non-fatal — falls back to statically configured candidates.

The doc comment notes that FindServers requires SecurityMode=None on the
discovery channel per OPC UA spec, even when the data channel uses Sign
or SignAndEncrypt.

Closes #278

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:10:59 -04:00
e879b3ae90 Merge pull request '[focas] FOCAS — Bulk PMC range read coalescing' (#357) from auto/focas/F2-d into auto/driver-gaps 2026-04-25 20:04:36 -04:00
Joseph Doherty
4d3ee47235 Auto: focas-f2d — PMC range coalescing
Closes #266
2026-04-25 20:02:10 -04:00
9ebe5bd523 Merge pull request '[focas] FOCAS — PMC F/G letters for 16i' (#356) from auto/focas/F2-c into auto/driver-gaps 2026-04-25 19:51:39 -04:00
Joseph Doherty
63099115bf Auto: focas-f2c — PMC F/G for 16i
Capability-matrix correctness fix: real 16i ladders use F (CNC->PMC) and
G (PMC->CNC) signal groups for handshakes, but PmcLetters(Sixteen_i) was
returning {X,Y,R,D} only. Widen to {X,Y,F,G,R,D}; M/C/E/A/K/T remain
0i-F / 30i-only. Updated the matching test row.

Closes #265
2026-04-25 19:49:25 -04:00
7042b11f34 Merge pull request '[focas] FOCAS — Multi-path/multi-channel CNC' (#355) from auto/focas/F2-b into auto/driver-gaps 2026-04-25 19:45:27 -04:00
Joseph Doherty
2f3eeecd17 Auto: focas-f2b — multi-path/multi-channel CNC
Adds optional `@N` path suffix to FocasAddress (PARAM:1815@2, R100@3.0,
MACRO:500@2, DIAG:280@2/1) with PathId defaulting to 1 for back-compat.
Per-device PathCount is discovered via cnc_rdpathnum at first connect and
cached on DeviceState; reads with PathId>PathCount return BadOutOfRange.
The driver issues cnc_setpath before each non-default-path read and
tracks LastSetPath so repeat reads on the same path skip the wire call.

Closes #264
2026-04-25 19:42:58 -04:00
3b82f4f5fb Merge pull request '[focas] FOCAS — DIAG: address scheme' (#354) from auto/focas/F2-a into auto/driver-gaps 2026-04-25 19:34:08 -04:00
Joseph Doherty
451b37a632 Auto: focas-f2a — DIAG: address scheme
New FocasAreaKind.Diagnostic parsed from DIAG:nnn (whole-CNC) and
DIAG:nnn/axis (per-axis), validated against a per-series
FocasCapabilityMatrix.DiagnosticRange table (16i: 0-499; 0i-F family:
0-999; 30i/31i/32i: 0-1023; Power Motion i: 0-255; Unknown: permissive
per existing matrix convention).

IFocasClient gains ReadDiagnosticAsync(diagNumber, axisOrZero, type,
ct) with a default returning BadNotSupported so older transport
variants degrade gracefully. FwlibFocasClient implements it via a new
cnc_rddiag P/Invoke that reuses the IODBPSD struct (same shape as
cnc_rdparam). FocasDriver.ReadAsync dispatches Diagnostic addresses
through the new path; non-Diagnostic kinds keep the existing
ReadAsync route unchanged.

Tests: parser positives (DIAG:1031, DIAG:280/2, case-insensitive,
zero, axis-8) + negatives (malformed, axis>31), capability matrix
boundaries per series, driver-level dispatch verifying axis index
threads through, init-time rejection on out-of-range, and
BadNotSupported fallback when the wire client doesn't override the
default. 266/266 pass in Driver.FOCAS.Tests.

Closes #263

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:31:49 -04:00
6743d51db8 Merge pull request '[ablegacy] AbLegacy — ST string verification + length guard' (#353) from auto/ablegacy/6 into auto/driver-gaps 2026-04-25 19:21:13 -04:00
Joseph Doherty
0044603902 Auto: ablegacy-6 — ST string verification + length guard
Verifies libplctag's GetString/SetString round-trips ST file strings (1-word
length prefix + 82 ASCII bytes) end-to-end through the driver, and adds a
client-side length guard so over-82-char writes return BadOutOfRange instead
of being silently truncated by libplctag.

- LibplctagLegacyTagRuntime.EncodeValue: throws ArgumentOutOfRangeException
  for >82-char String writes (StFileMaxStringLength constant).
- AbLegacyDriver.WriteAsync: catches ArgumentOutOfRangeException and maps to
  BadOutOfRange.
- AbLegacyStringEncodingTests: 16 unit tests covering empty / 41-char /
  82-char / embedded-NUL / non-ASCII reads + writes; over-length writes
  return BadOutOfRange and never call WriteAsync; both Slc500 and Plc5
  family paths exercised.

Closes #249

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:18:55 -04:00
2fc71d288e Merge pull request '[ablegacy] AbLegacy — PD/MG/PLS/BT structure files' (#352) from auto/ablegacy/5 into auto/driver-gaps 2026-04-25 19:11:11 -04:00
Joseph Doherty
286ab3ba41 Auto: ablegacy-5 — PD/MG/PLS/BT structure files
Adds PD (PID), MG (Message), PLS (Programmable Limit Switch) and BT
(Block Transfer) file types to the PCCC parser. New AbLegacyDataType
enum members (PidElement / MessageElement / PlsElement /
BlockTransferElement) plus per-type sub-element catalogue:

  - PD: SP/PV/CV/KP/KI/KD/MAXS/MINS/DB/OUT as Float32; EN/DN/MO/PE/
        AUTO/MAN/SP_VAL/SP_LL/SP_HL as Boolean (word-0 status bits).
  - MG: RBE/MS/SIZE/LEN as Int32; EN/EW/ER/DN/ST/CO/NR/TO as Boolean.
  - PLS: LEN as Int32 (bit table varies per PLC).
  - BT: RLEN/DLEN as Int32; EN/ST/DN/ER/CO/EW/TO/NR as Boolean.

Per-family flags on AbLegacyPlcFamilyProfile gate availability:

  - PD/MG: SLC500 + PLC-5 (operator + status bits both present).
  - PLS/BT: PLC-5 only (chassis-IO block transfer is PLC-5-specific).
  - MicroLogix + LogixPccc: rejected — no legacy file-letter form.

Status-bit indices match Rockwell DTAM / 1747-RM001 / 1785-6.5.12:
PD word 0 bits 0-8, MG/BT word 0 bits 8-15. PLC-set status bits
(PE/DN/SP_*; ST/DN/ER/CO/EW/NR/TO) are surfaced as ViewOnly via
IsPlcSetStatusBit, matching the Timer/Counter/Control pattern from
ablegacy-3.

LibplctagLegacyTagRuntime decodes PD non-bit members as Float32 and
MG/BT/PLS non-bit members as Int32; status bits route through GetBit
with the bit-position encoded by the driver via StatusBitIndex.

Tests: parser positive cases per family + negative cases per family,
catalogue + bit-index + read-only-bit assertions.

Closes #248
2026-04-25 19:08:51 -04:00
5ca2ad83cd Merge pull request '[abcip] AbCip — AOI input/output handling' (#351) from auto/abcip/2.6 into auto/driver-gaps 2026-04-25 19:01:03 -04:00
Joseph Doherty
e3c0750f7d 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
2026-04-25 18:58:49 -04:00
177d75784b Merge pull request '[abcip] AbCip — Online tag-DB refresh trigger' (#350) from auto/abcip/2.5 into auto/driver-gaps 2026-04-25 18:48:14 -04:00
Joseph Doherty
6e244e0c01 Auto: abcip-2.5 — online tag-DB refresh trigger
Add IDriverControl capability interface in Core.Abstractions with a
RebrowseAsync(IAddressSpaceBuilder, CancellationToken) hook so operators
can force a controller-side @tags re-walk without restarting the driver.

AbCipDriver now implements IDriverControl. RebrowseAsync clears the UDT
template cache (so stale shapes from a pre-download program don't
survive) then runs the same enumerator + builder fan-out as
DiscoverAsync, serialised against concurrent discovery / rebrowse via
a new SemaphoreSlim.

Driver.AbCip.Cli ships a `rebrowse` subcommand mirroring the existing
probe / read shape: connects to a single gateway, runs RebrowseAsync
against an in-memory builder, and prints discovered tag names so
operators can sanity-check the controller's symbol table from a shell.

Tests cover: two consecutive RebrowseAsync calls bump the enumerator's
Create / Enumerate counters once each, discovered tags reach the
supplied builder, the template cache is dropped on rebrowse, and the
driver exposes IDriverControl. 313 AbCip unit tests + 17 CLI tests +
37 Core.Abstractions tests pass.

Closes #233
2026-04-25 18:45:48 -04:00
27878d0faf Merge pull request '[abcip] AbCip — CSV tag import/export' (#349) from auto/abcip/2.4 into auto/driver-gaps 2026-04-25 18:36:17 -04:00
Joseph Doherty
08d8a104bb Auto: abcip-2.4 — CSV tag import/export
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
2026-04-25 18:33:55 -04:00
7ee0cbc3f4 Merge pull request '[abcip] AbCip — Descriptions to OPC UA Description' (#348) from auto/abcip/2.3 into auto/driver-gaps 2026-04-25 18:25:59 -04:00
Joseph Doherty
e5299cda5a Auto: abcip-2.3 — descriptions to OPC UA Description
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
2026-04-25 18:23:31 -04:00
e5b192fcb3 Merge pull request '[abcip] AbCip — L5X (XML) parser + ingest' (#347) from auto/abcip/2.2 into auto/driver-gaps 2026-04-25 18:13:14 -04:00
Joseph Doherty
cfcaf5c1d3 Auto: abcip-2.2 — L5X (XML) parser + ingest
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
2026-04-25 18:10:53 -04:00
2731318c81 Merge pull request '[abcip] AbCip — L5K parser + ingest' (#346) from auto/abcip/2.1 into auto/driver-gaps 2026-04-25 18:03:31 -04:00
Joseph Doherty
86407e6ca2 Auto: abcip-2.1 — L5K parser + ingest
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>
2026-04-25 18:01:08 -04:00
2266dd9ad5 Merge pull request '[twincat] TwinCAT — ENUM and ALIAS at discovery' (#345) from auto/twincat/1.5 into auto/driver-gaps 2026-04-25 17:51:08 -04:00