Merge b626-ablegacy-focas (batch #626+)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 14:43:44 -04:00
17 changed files with 946 additions and 176 deletions
+40
View File
@@ -67,6 +67,9 @@ pieces for driver-side routing (e.g. deciding a bit needs read-modify-write):
| `ST9:0` | String file (82-byte fixed-length) |
| `T4:0.ACC` / `C5:0.PRE` | Timer / counter sub-element |
| `I:0/0` / `O:1/2` / `S:1` | Input / output / status system files (no file number) |
| `I:017/07` | **PLC-5 only:** `I:`/`O:` word and bit indices are **octal** (#244); a digit 8/9 is a parse error |
| `RTC:0.HR` / `HSC:0.ACC` | **MicroLogix only:** function files RTC, HSC, DLS, MMI, PTO, PWM, STI, EII, IOS, BHI (#245) |
| `N7:[N7:0]` / `N[N7:0]:5` | Indirect (index word read from the PLC) — parsed, **not supported at runtime** (#247) |
`AbLegacyDataType` covers the corresponding PCCC types: `Bit`, `Int` (N), `Long`
(L), `Float` (F), `AnalogInt` (A), `String` (ST), and the `TimerElement` /
@@ -85,6 +88,42 @@ this is an inherent limitation of any software-level RMW. Input-image (`I`) bit
writes are forwarded to the PLC as-is; the device drives the input image from
physical inputs and may reject the write.
### Family-specific address forms (#244 / #245 / #247)
`AbLegacyAddress.TryParse(text, family)` is family-aware; the driver passes the device's `PlcFamily`
(the one-argument overload is the family-neutral grammar: decimal, no function files).
- **PLC-5 octal I/O (#244).** RSLogix 5 shows `I:`/`O:` word and bit indices in octal, so `I:017/07` is word 15,
bit 7 and `I:017/17` is bit 15. Only `I`/`O` on PLC-5 (`OctalIoAddressing` on the family profile) — SLC,
MicroLogix and Logix-PCCC stay decimal, and PLC-5 `N`/`B`/... files stay decimal. The record stores decimal and
`ToLibplctagName()` **emits decimal**, so the libplctag `name=` string and the string the native transport
re-parses carry identical numbers. This is not a double conversion: libplctag's `pccc.c`
(`parse_pccc_elem_num` / `parse_pccc_bit_num`) parses decimal numerals only (a FIXME there says octal is
unsupported), so the driver must convert. Pinned by `Plc5_io_indices_are_octal` (`I:017/17` -> `I:15/15`).
The connectivity-probe address is normalised the same way.
- ⚠️ **Behaviour change / migration.** Before this change a PLC-5 `I:`/`O:` address was passed to libplctag as
written, i.e. read as **decimal**. An address copied from RSLogix in octal (e.g. `I:017`) therefore read the
wrong word, and an authored decimal such as `I:1/12` now means bit 10 (octal 12). A previously "working"
address containing the digits 8 or 9 (`I:018`, `O:1/9`) is no longer valid on PLC-5. Re-author any PLC-5
I/O tags in RSLogix octal. Such a tag is refused (see below), never silently misread.
- **A malformed / family-invalid address** (also: a function-file letter off MicroLogix) reads and writes
`BadNodeIdUnknown` for that tag only, creates no runtime, leaves driver health alone, and logs **one**
Warning per tag. The same check (`AbLegacyAddress.Validate`) runs in the AdminUI tag editor (family-independent:
rejects only what *no* family accepts) and the deploy gate (`EquipmentTagConfigInspector`, using the device's
`DeviceConfig.PlcFamily`, absent = SLC 500, unrecognised = unknown) — so `I:018` on a PLC-5 device warns at
deploy (an error under `TagConfigValidationMode.Error`), and the editor is never stricter than the gate.
- **MicroLogix function files (#245).** `RTC`, `HSC`, `DLS`, `MMI`, `PTO`, `PWM`, `STI`, `EII`, `IOS`, `BHI`
(single fixed instance — no file number — with a `.SUBELEMENT`; no bit index) parse on MicroLogix devices only.
`AbLegacyFunctionFile.SubElementType` carries the sub-element catalogue (flags → Boolean, words → Int32; unknown
members default to Int32). ⚠️ **Wire-unverified on both transports:** there is no capture and no simulator
coverage; the libplctag path passes the name through and the native transport has no file-type code for them, so
it refuses the tag as `Unsupported` (`BadNotSupported` to the client; see Wire evidence).
- **Indirect addressing (#247).** `N7:[N7:0]` (word from another address) and `N[N7:0]:5` (file from another
address) parse, one level deep (the inner address must be direct, and carries the family's octal rule).
**Runtime resolution is not implemented** (it needs a pre-read of the index word and per-resolved-address
runtimes): reads and writes of an indirect tag answer `BadNotSupported` before any runtime is created, on both
transports.
## Configuration
`AbLegacyDriverOptions` (`Driver.AbLegacy.Contracts/AbLegacyDriverOptions.cs`)
@@ -172,6 +211,7 @@ Everything below is against the `ab_server` simulator; no real PCCC controller h
| N-file write (`N7:5`) | capture `slc500-write-n7` + native `AbLegacyReadSmokeTests` write/read round trip (SLC 500) |
| F-file read (`F8`) | one capture, `slc500-read-f8` (codec parity only; no native F write on a wire) |
| S (incl. the default probe `S:0`), B, T, C, R, L, ST, A, O, I | **none** — encoded from the PCCC file-type table |
| MicroLogix function files (RTC/HSC/...), PLC-5 octal I/O, indirect addressing | **none** — parsed only; native refuses function files and indirect names as Unsupported |
| T/C/R sub-elements (`.PRE`/`.ACC`/`.LEN`/`.POS`), 6-byte T/C/R element layout | **none** |
### Native known gaps
+4
View File
@@ -91,6 +91,10 @@ on.
| `MACRO:500` | Custom macro variable | System / user macro variable number |
| `FIXED:Identity/SeriesNumber` | Fixed-tree field | A path into the [fixed node tree](#fixed-node-tree) — read-only |
PMC letters are series-gated by `FocasCapabilityMatrix.PmcLetters`; the 16i accepts `X Y F G R D` (F/G are
the CNC-to-PMC / PMC-to-CNC handshake groups its ladders use, #265 — reads only, FOCAS stays read-only), while
`M C E A K T` remain 0i-F / 30i-family.
The first three name a location the driver asks the CNC for on demand.
`FIXED:` does not: it names a field of the per-device fixed-tree snapshot the
driver's own poll loop already maintains, so a `FIXED:` read issues no wire