Commit Graph

309 Commits

Author SHA1 Message Date
Joseph Doherty
0444cb699d Auto: twincat-4.1 — nested UDT browse via online type walker
Closes #315
2026-04-26 07:28:52 -04:00
Joseph Doherty
baf1d65875 Auto: s7-d3 — instance-DB / FB parameter resolution
Closes #301
2026-04-26 07:04:40 -04:00
Joseph Doherty
5f8d84db43 Auto: s7-d2 — UDT / STRUCT / nested-DB fan-out
Closes #300
2026-04-26 06:50:26 -04:00
Joseph Doherty
a908dff7b5 Auto: s7-d1 — TIA Portal CSV + STEP 7 Classic AWL symbol import
Closes #299
2026-04-26 06:32:18 -04:00
Joseph Doherty
5c72deb839 Auto: opcuaclient-11 — reverse connect (server-initiated)
Closes #283
2026-04-26 06:08:30 -04:00
Joseph Doherty
86f3fc2733 Auto: focas-f4d — password / unlock parameter
Closes #271
2026-04-26 05:45:13 -04:00
Joseph Doherty
54c09d4d5d Auto: focas-f4c — pmc_wrpmcrng with bit-level RMW
Closes #270
2026-04-26 05:15:52 -04:00
Joseph Doherty
f48f31cfc7 Auto: focas-f4b — cnc_wrmacro + cnc_wrparam writes
Closes #269
2026-04-26 04:54:28 -04:00
Joseph Doherty
1bfe8fba0e Auto: focas-f4a — write infrastructure + per-tag opt-in
Closes #268
2026-04-26 04:32:43 -04:00
Joseph Doherty
4e8df38bb2 Auto: ablegacy-11 — RSLogix 500/PLC-5 CSV symbol import
Closes #254
2026-04-26 04:13:13 -04:00
Joseph Doherty
42472b5549 Auto: ablegacy-10 — diagnostic counters as tags
Closes #253
2026-04-26 03:50:47 -04:00
Joseph Doherty
c292dcc1db Auto: ablegacy-9 — per-device timeout / retry overrides
Closes #252
2026-04-26 03:32:45 -04:00
Joseph Doherty
e0e5e04e48 Auto: abcip-4.4 — _RefreshTagDb writeable system tag
Closes #241
2026-04-26 03:16:28 -04:00
Joseph Doherty
901a5b9b21 Auto: abcip-4.3 — diagnostic / system tags as browseable variables
Closes #240
2026-04-26 02:55:56 -04:00
Joseph Doherty
da9936f7f0 Auto: abcip-4.2 — write deadband / write-on-change
Closes #239
2026-04-26 02:31:50 -04:00
Joseph Doherty
b45713622f Auto: abcip-4.1 — per-tag scan rate / scan group bucketing
Closes #238
2026-04-26 02:15:50 -04:00
Joseph Doherty
24a3cda56a Auto: twincat-3.2 — cycle-time / jitter / PLC-state diagnostics
Closes #314
2026-04-26 01:59:56 -04:00
Joseph Doherty
fb57717f6f Auto: twincat-3.1 — per-tag MaxDelay tuning
Closes #313
2026-04-26 01:45:12 -04:00
Joseph Doherty
64a11ef285 Auto: s7-c5 — pre-flight PUT/GET enablement test
Closes #298
2026-04-26 01:31:48 -04:00
Joseph Doherty
06b39a28fa Auto: s7-c4 — deadband / on-change with thresholds
Closes #297
2026-04-26 01:14:59 -04:00
Joseph Doherty
162c82b8d9 Auto: s7-c3 — per-tag scan group / publish rate
Closes #296
2026-04-26 01:03:00 -04:00
Joseph Doherty
3b98e4d366 Auto: s7-c2 — TSAP / Connection Type selector
Closes #295
2026-04-26 00:49:10 -04:00
Joseph Doherty
6540bbe1ef Auto: s7-c1 — surface negotiated PDU size via DriverHealth.Diagnostics
Closes #294
2026-04-26 00:35:49 -04:00
Joseph Doherty
ab3ed6b6a3 Auto: opcuaclient-10 — auto re-import on ModelChangeEvent
Closes #282
2026-04-26 00:24:24 -04:00
Joseph Doherty
7f9d6a778e Auto: focas-f3a — cnc_rdalmhistry alarm-history extension
Adds FocasAlarmProjection with two modes (ActiveOnly default, ActivePlusHistory)
that polls cnc_rdalmhistry on connect + on a configurable cadence (5 min default,
HistoryDepth=100 capped at 250). Emits historic events via IAlarmSource with
SourceTimestampUtc set from the CNC's reported timestamp; dedup keyed on
(OccurrenceTime, AlarmNumber, AlarmType). Ships the ODBALMHIS packed-buffer
decoder + encoder in Wire/FocasAlarmHistoryDecoder.cs and threads
ReadAlarmHistoryAsync through IFocasClient (default no-op so existing transport
variants stay back-compat). FocasDriver now implements IAlarmSource.

13 new unit tests cover: mode switch, dedup, distinct-timestamp emission,
type-as-key behaviour, OccurrenceTime passthrough (not Now), HistoryDepth
clamp/fallback, and decoder round-trip. All 341 FOCAS unit tests still pass.

Docs: docs/drivers/FOCAS.md (new), docs/v2/focas-deployment.md (new),
docs/v2/implementation/focas-wire-protocol.md (new),
docs/v2/implementation/focas-simulator-plan.md (new),
docs/drivers/FOCAS-Test-Fixture.md (alarm-history bullet appended).

Closes #267
2026-04-26 00:07:59 -04:00
Joseph Doherty
eb5286148e Auto: ablegacy-8 — per-tag deadband / change filter
Closes #251
2026-04-25 23:50:07 -04:00
Joseph Doherty
c689ac58b1 Auto: ablegacy-7 — array contiguous block addressing
Closes #250
2026-04-25 23:36:01 -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
Joseph Doherty
0c6a0d6e50 Auto: abcip-3.2 — symbolic vs logical addressing toggle
Closes #236
2026-04-25 22:58:33 -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
Joseph Doherty
4098d72bbb Auto: twincat-2.3 — symbol-version invalidation listener
Closes #312
2026-04-25 22:16:05 -04:00
Joseph Doherty
b67eb6c8d0 Auto: twincat-2.2 — handle-based access with caching
Closes #311
2026-04-25 22:03:20 -04:00
Joseph Doherty
931049b5a7 Auto: twincat-2.1 — ADS Sum-read / Sum-write
Closes #310
2026-04-25 21:43:32 -04:00
Joseph Doherty
17faf76ea7 Auto: s7-b2 — block-read coalescing for contiguous DBs
Closes #293
2026-04-25 21:23:06 -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
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
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
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
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
Joseph Doherty
4d3ee47235 Auto: focas-f2d — PMC range coalescing
Closes #266
2026-04-25 20:02:10 -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
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
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
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
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
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
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
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
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
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