Files
lmxopcua/docs/research/drivers/omron.md
T
Joseph Doherty 8fc147d8d4
v2-ci / build (push) Successful in 3m14s
v2-ci / unit-tests (push) Failing after 9m13s
docs: driver-expansion program — 8 research reports + 7 design docs, parallel-reviewed
Adds the driver-expansion program design (umbrella: universal Discover-backed
browser + MTConnect, MQTT/Sparkplug B, BACnet/IP, SQL poll, Omron, Modbus RTU;
MELSEC deferred) plus the per-driver research reports.

All docs went through a 7-agent parallel review against the codebase before
this commit. Highlights fixed in review:

- universal browser: FOCAS FixedTree fills post-connect -> UntilStable settle
  + FixedTree.Enabled patch; MQTT reconciled to bespoke (was contradicting the
  program doc's SupportsOnlineDiscovery=false verdict)
- modbus-rtu: SerialPort.ReadTimeout doesn't bound async BaseStream reads ->
  linked-CTS per-op deadline (R2-01 class); BCL enum reuse would leak
  System.IO.Ports into Contracts
- bacnet: DiscoveryRediscoverPolicy enum name; UDP 47808 contention; live
  suite rewritten around unicast Who-Is + BBMD (broadcast doesn't cross VMs)
- sql-poll: real tier registration via DriverFactoryRegistry.Register;
  blackhole gate must not docker-pause the shared central SQL Server
- mqtt: Sparkplug v3.0 STATE topic form; first-in-repo proto codegen noted
- omron: host hardcodes isIdempotent:false today (retry seam unshipped);
  v1 scopes UDTs to dotted-leaf access
- mtconnect: SecurityClassification.ViewOnly; factory ParseEnum<T> pattern
- program doc: both valid enum-serialization patterns; IRediscoverable is
  change-signal-gated; RTU P2 adds System.IO.Ports; label is host-side
2026-07-15 16:40:36 -04:00

362 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Omron PLC Driver — Research & Implementation Design
**Status:** Research / design proposal (no code yet).
**Author context:** Standard **Equipment-kind driver** for the OtOpcUa server, same shape as
Modbus / S7 / AbCip / TwinCAT / FOCAS — an in-process `IDriver` implementing the composable
capability interfaces (`IReadable` / `IWritable` / `ISubscribable` / `ITagDiscovery` /
`IHostConnectivityProbe`, optionally `IDriverBrowser` in AdminUI). Points are ordinary equipment
`Tag`s bound to the driver via `TagConfig.FullName`, authored on the `/uns` Tags tab.
---
## 0. TL;DR recommendation
- **Support both transports, CIP-first.** Ship an **Omron CIP (EtherNet/IP)** path for modern
**NJ/NX/NY (Sysmac)** controllers as Phase 1, then an **Omron FINS** path for legacy
**CJ/CS/CP/CV** as Phase 2. They cover disjoint hardware generations; neither subsumes the other.
- **Library:** `libplctag` (via `libplctag.NET`, MPL-2.0) for the CIP path — **reuses almost the
entire AbCip wire layer** (`plc=omron-njnx`). For the FINS path, **hand-roll** the framing
(it is a small, well-documented binary protocol) rather than take `HslCommunication` (see §1.3).
- **Browseability:** **CIP NJ/NX is only *conditionally* browsable, and NOT via libplctag's
`@tags` walker** — that is the single most important finding. **FINS is never browsable** (flat
memory banks, no symbol table). Details in §4.
---
## 1. Transport decision
Omron exposes two entirely different Ethernet protocols depending on controller generation.
### 1.1 The two transports and what they cover
| Transport | Port / framing | Controller families | Addressing model | Browsable? |
|---|---|---|---|---|
| **EtherNet/IP CIP** | TCP/UDP **44818** (EtherNet/IP), CIP explicit messaging | **NJ / NX / NY (Sysmac)**, and CJ2/CP-family with a CIP-capable EtherNet/IP unit | **Named variables** (symbolic tags), like Rockwell Logix | **Conditionally** — see §4 |
| **FINS** | **FINS/TCP** and **FINS/UDP** on port **9600** | **CJ / CS / CP / CV**, NSJ; NJ/NX also support *FINS-over-EtherNet/IP* for back-compat | **Memory areas** (CIO/WR/HR/AR/DM/EM banks) + word/bit offset | **Never** (flat memory, no symbol table) |
Key architectural fact: **NJ/NX are CIP-native**. Their "variables" are named tags reached by CIP
explicit messaging using a symbolic-segment request path — the same mechanism ControlLogix uses,
which is why AbCip's libplctag layer already speaks it (`plc=omron-njnx`). Sources:
[Omron store KB — Using CIP to Read/Write Network Variables with NJ/NX](https://store.omron.com.au/knowledge-base/1807412-using-cip-to-read-write-network-variables-with-nj-nx),
[NJ/NX CPU Built-in EtherNet/IP Port User's Manual (W506)](https://files.omron.eu/downloads/latest/manual/en/w506_nj_nx-series_cpu_unit_built-in_ethernet_ip_port_users_manual_en.pdf).
**FINS** is the classic protocol: a compact binary header (ICF/RSV/GCT, destination & source
net/node/unit addresses, SID) followed by a 2-byte command code and its parameters. **Memory Area
Read = `0x0101`**, **Memory Area Write = `0x0102`**. FINS/TCP prepends a 16-byte "FINS" TCP header
(the ASCII magic `46 49 4E 53` = "FINS", a length, and a command/error field) and requires a small
handshake frame to obtain a node address before the first command. Sources:
[Omron W227 FINS Commands Reference Manual](https://www.myomron.com/downloads/1.Manuals/Networks/W227E12_FINS_Commands_Reference_Manual.pdf),
[Wireshark FINS dissector](https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-omron-fins.c),
[FieldServer Omron FINS driver sheet](https://cdn.chipkin.com/assets/uploads/2016/jan/PDS_Omron_FINS.pdf).
### 1.2 FINS memory-area codes (for the TagConfig addressing model)
FINS memory-area codes are **access-width-specific** (a different code for word vs. bit access of
the same bank). Representative word-access codes:
| Bank | Word code | Bit code | Notes |
|---|---|---|---|
| CIO (Core I/O) | `0xB0` | `0x30` | word + bit addressable |
| WR (Work) | `0xB1` | `0x31` | word + bit addressable |
| HR (Holding) | `0xB2` | `0x32` | word + bit addressable |
| AR (Auxiliary) | `0xB3` | `0x33` | word + bit addressable |
| DM (Data Memory) | `0x82` | `0x02` | primary bulk data bank; word addressable |
| EM (Extended, bank 0) | `0xA0` | `0x20` | banked; code varies per EM bank (`0xA0`+bank) |
(Exact codes vary slightly by CPU family — the authoritative table is per-controller in W227 /
the CS/CJ comms manual. The driver must treat the code table as CPU-family-parameterised.) Source:
[Omron memory-area discussion](https://community.oxmaint.com/discussion-forum/omron-plc-memory-areas-understanding-ci-hr-ar-lr-dm-wr-em-extended-em-usage),
[W227 reference](https://www.myomron.com/downloads/1.Manuals/Networks/W227E12_FINS_Commands_Reference_Manual.pdf).
### 1.3 .NET library options — license analysis
| Library | Transports | License | Maturity / .NET 10 | Verdict |
|---|---|---|---|---|
| **`libplctag.NET`** (wraps native `libplctag`) | **CIP** (`plc=omron-njnx`); **also Modbus** | **native core dual-licensed MPL-2.0 / LGPL-2.1; .NET wrapper MPL-2.0** — permissive, commercial-OK, already vetted & shipped in this repo for AbCip | Mature, actively maintained; **already a proven dependency** (AbCip runs on it, net10.0) | **CIP path: adopt.** Zero new license review — same dep AbCip already carries. |
| **`HslCommunication`** | FINS **and** CIP (`OmronFinsNet`, `OmronCipNet`) | **The maintained/commercial builds are NOT free** — the original author moved HSL to a **paid commercial license**; only a frozen old community fork ([`HslCommunication-Community`](https://github.com/HslCommunication-Community/HslCommunication-Community)) remains under the last free (MIT-era) snapshot. The "MIT" claim in casual sources refers to that **stale fork**, not current releases. | Broad but the free fork is unmaintained | **Do NOT take a dependency.** License risk + the free build is abandoned. Use only as a *protocol reference* to cross-check hand-rolled framing. |
| **Hand-rolled FINS** | FINS/TCP + FINS/UDP | our own code, no third-party license | n/a — FINS framing is ~200 lines | **FINS path: hand-roll.** Framing is simple and fully documented (W227 + Wireshark dissector); mirrors how `ModbusTcpTransport` is hand-rolled in this repo. |
| `Sres.Net.EEIP` | EtherNet/IP (generic CIP) | permissive | Focused on generic CIP/assembly, not Omron symbolic variable access; weaker than libplctag for named-tag read/write | Not needed — libplctag already covers CIP. |
| `Plc.Omron.Standard` (NuGet) | FINS | permissive but tiny/low-adoption | Immature | Reference only. |
**Recommendation:** **libplctag.NET for CIP** (reuse AbCip's proven stack), **hand-rolled FINS
transport** for the memory-area path. This keeps the dependency surface identical to what the repo
already ships and dodges the HslCommunication license trap.
---
## 2. Capability mapping
The driver implements the same capability set as AbCip/Modbus. Both transports map cleanly onto the
composable interfaces in `Core.Abstractions`.
### 2.1 CIP path (NJ/NX) — reuses AbCip machinery
- **Connect / `InitializeAsync`:** libplctag Forward-Open per device. Canonical host address form
mirroring AbCip's `ab://gateway[:port]/cip-path`, e.g. `omron://gateway/path` — but for NJ/NX the
libplctag attribute string is `protocol=ab-eip&gateway=<ip>&path=<cip-path>&plc=omron-njnx&name=<tag>`.
(Community-confirmed: NJ/NX needs `plc=omron-njnx` and a CIP-bridging `path`, e.g. `18,<ip>`.)
Source: [libplctag Omron NJ/NX group thread](https://groups.google.com/g/libplctag/c/bU5MVQrPsOw).
- **Read (`IReadable`):** direct reuse of AbCip's per-tag runtime pattern — create tag handle, `ReadAsync`,
`GetStatus`, decode. Batch-read + per-host resilience (`IPerCallHostResolver`) reuse verbatim.
- **Write (`IWritable`):** full read/write. NJ/NX BOOL-in-word and array handling map onto AbCip's
existing `EncodeValue` / bit-RMW code paths.
- **Subscribe (`ISubscribable`):** **poll-based** via the shared `PollGroupEngine` (identical to
AbCip/Modbus — Omron CIP has no native subscription/unsolicited push for explicit messaging).
- **Discover (`ITagDiscovery`):** emit pre-declared tags always; controller-side browse is the
**caveat** — see §4 (libplctag's `@tags` walker does **not** work on Omron).
- **Health/Probe (`IHostConnectivityProbe`):** reuse AbCip's probe-loop pattern (cheap tag read at
interval → `HostState` transitions).
**Reuse estimate: ~7080% of AbCip's driver body is directly reusable for the CIP path.** The
honest way to capture this is to **extract a shared CIP core** (tag runtime, poll wiring, status
mapping, host-address parsing, resilience seam) that both AbCip and OmronCip consume — rather than
copy-paste. The Omron-specific deltas are: the `plc=omron-njnx` attribute string, Omron's data-type
code set (see §2.3), string encoding differences, and the browse story.
### 2.2 FINS path (CJ/CS/CP) — new transport, Modbus-shaped
- **Connect:** open TCP to `:9600`, send the FINS/TCP node-address-request handshake, cache the
assigned client node number. (FINS/UDP variant skips the handshake.)
- **Read:** build a `0x0101` Memory Area Read frame (area code + 3-byte address + word count),
send, parse the 2-byte end-code + payload. Word-oriented; multi-word reads for 32/64-bit types.
- **Write:** `0x0102` Memory Area Write, symmetric. Bit writes use the bit-access area codes; word
writes use the word codes. RMW not required for FINS bit writes (bit codes address bits directly).
- **Subscribe:** poll-based via `PollGroupEngine` (FINS has no subscription).
- **Discover:** **pre-declared tags only** — there is no symbol table to enumerate (§4).
- **Probe:** cheap DM-word read at interval.
The FINS transport is structurally a sibling of `ModbusTcpTransport` (`IModbusTransport`): a thin
framed request/response codec behind an `IFinsTransport` seam, unit-testable against a byte-level
fake.
### 2.3 Data-type mapping
Omron CIP (NJ/NX) uses IEC 61131 variable types that align closely with the existing
`AbCipDataType` / `DriverDataType` set; FINS is raw words that the TagConfig must type explicitly.
| Omron type | CIP (NJ/NX) | FINS (word interpretation) | OPC UA / `DriverDataType` |
|---|---|---|---|
| BOOL | native BOOL | bit-area read, or bit N of a word | Boolean |
| BYTE/USINT/SINT | 1-byte | low byte of word | Byte / SByte |
| WORD/UINT/INT | 2-byte | 1 word | UInt16 / Int16 |
| DWORD/UDINT/DINT | 4-byte | 2 words (endianness matters) | UInt32 / Int32 |
| LWORD/ULINT/LINT | 8-byte | 4 words | UInt64 / Int64 |
| REAL | IEEE-754 32 | 2 words | Float |
| LREAL | IEEE-754 64 | 4 words | Double |
| STRING | CIP string | word-packed ASCII, fixed length | String |
| DATE_AND_TIME/TIME | Sysmac time types | banked words | DateTime (best-effort) |
**Word/byte-order caveat (FINS):** Omron stores multi-word values with a specific word order that
differs from naive concatenation; the FINS codec must expose a per-tag or per-device
byte/word-swap option (same class of concern the S7/Modbus drivers already handle). CIP via
libplctag handles endianness internally.
---
## 3. TagConfig JSON shape
`TagConfig.FullName` (or a JSON blob under it) carries the driver-specific addressing. Following the
AbCip equipment-tag parser convention (`AbCipEquipmentTagParser`: a leading `{` marks a TagConfig
blob; camelCase property names; strict enum reads via `TagConfigJson.TryReadEnumStrict`), the two
transports need distinct shapes discriminated by a `transport` field.
### 3.1 CIP (NJ/NX) — named-variable addressing
```json
{
"transport": "Cip",
"deviceHostAddress": "omron://10.100.0.40/1,0",
"tagName": "Conveyor.Speed",
"dataType": "Real",
"isArray": false,
"arrayLength": 1,
"writable": true
}
```
(`tagName` is the Sysmac global-variable name; near-identical to AbCip's `tagPath`. The CIP path
reuses AbCip's `isArray`/`arrayLength`/`writable`/`dataType` semantics verbatim.)
### 3.2 FINS — memory-area addressing
```json
{
"transport": "Fins",
"deviceHostAddress": "fins://10.100.0.41:9600/0.1.0",
"memoryArea": "DM",
"address": 100,
"bit": null,
"dataType": "Int16",
"wordSwap": false,
"isArray": false,
"arrayLength": 1,
"writable": true
}
```
- `memoryArea``{CIO, WR, HR, AR, DM, EM}` (+ optional `emBank` for extended memory); the driver
maps the pair `(memoryArea, bit==null?word:bit)` to the correct FINS area code (§1.2).
- `address` = word offset; `bit` = 015 for bit access (null ⇒ word access).
- `deviceHostAddress` encodes the FINS destination `net.node.unit` triple.
- `wordSwap` handles Omron multi-word ordering for 32/64-bit types.
---
## 4. BROWSEABILITY VERDICT (transport-dependent) — the critical finding
### 4.1 FINS — **NOT browsable. Full stop.**
FINS addresses flat memory banks with no symbol/metadata table on the wire. There is nothing to
enumerate. The AdminUI must offer only manual entry (memory-area dropdown + word/bit offset). No
`IDriverBrowser` for the FINS transport.
### 4.2 CIP (NJ/NX) — **browsable in principle, but NOT via libplctag's `@tags` walker.**
This is the finding to flag loudly and to **coordinate with the AbCip-browseability audit**:
- The AbCip browse mechanism relies on libplctag's `@tags` pseudo-tag (CIP Symbol Object class
`0x6B`) to walk the controller symbol table. **libplctag's tag-listing works only on
ControlLogix / CompactLogix — it does NOT work on Omron NJ/NX**, returning `ErrorUnsupported` /
`PLCTAG_ERR_NOT_FOUND`. Confirmed upstream:
[libplctag #466 "Support for Omron NX/NJ variable listing"](https://github.com/libplctag/libplctag/issues/466),
[libplctag.NET #371 "List all tags in Omron NJ: ErrorUnsupported"](https://github.com/libplctag/libplctag.NET/issues/371).
- So the naive assumption "CIP controller-tag enumeration IS browsable, reuse the AbCip walker"
is **only half true for Omron**: the *protocol capability* exists (published variables are
reachable), but Omron NJ/NX exposes the tag list through a **different CIP object/service than
Rockwell** and libplctag hasn't implemented the Omron variant.
- **Additional gate:** on NJ/NX a variable is only reachable over the network at all if the
programmer set its **Network Publish** attribute (`Publish Only`) in **Sysmac Studio**. Tags
without Network Publish are invisible to any external client. And when *no* user variables are
published, an enumeration returns only the reserved system variables (names starting with `_`).
Source: [Omron NJ/NX EtherNet/IP User's Manual (W506)](https://files.omron.eu/downloads/latest/manual/en/w506_nj_nx-series_cpu_unit_built-in_ethernet_ip_port_users_manual_en.pdf),
[Omron store KB](https://store.omron.com.au/knowledge-base/1807412-using-cip-to-read-write-network-variables-with-nj-nx).
**Verdict:** CIP-Omron browse is achievable but requires **new protocol work, not free reuse**.
Two viable routes, in preference order:
1. **Import the Sysmac Studio tag export** (offline). NJ/NX projects export the published global
variable table (CSV/tag file). An AdminUI importer that parses this into candidate TagConfigs is
the *lowest-risk, highest-value* browse experience and needs no online CIP enumeration at all.
2. **Implement the Omron CIP tag-list service directly** (online `IBrowseSession`). Omron NJ/NX
supports reading the published-variable list via a CIP service Rockwell-differently; this means
hand-writing the request/response against the NJ/NX EtherNet/IP object model (not going through
libplctag's `@tags`). This is a research-grade effort — feasible (third-party tools like Kepware
discover Omron published tags) but non-trivial.
### 4.3 Browse-seam design (CIP path only)
If/when online CIP browse is implemented, it fits the existing seam exactly (mirroring
`OpcUaClient.Browser` / `Galaxy.Browser`, which live as separate `*.Browser` projects registered in
AdminUI DI and indexed by `DriverType`):
- New project `ZB.MOM.WW.OtOpcUa.Driver.OmronCip.Browser` implementing `IDriverBrowser`
(`DriverType = "OmronCip"`), `OpenAsync(configJson)``IBrowseSession`.
- `IBrowseSession`: `RootAsync` = one folder per device; `ExpandAsync` = published global variables
(and struct-member fan-out for UDT-typed variables, reusing AbCip's template-decode pattern);
`AttributesAsync` = data type / access / array shape side-panel.
- **Share a CIP-browse core with AbCip *only if* the enumeration transport is unified.** Since
Omron's tag-list service differs from Rockwell's, the honest boundary is: **share the
`BrowseNode`/`IBrowseSession` plumbing and struct-fan-out logic; do NOT expect to share the
wire-level enumerator.** Recommend a common `Cip.Browsing` helper library the AbCip browser (if
built) and OmronCip browser both consume for tree-shaping, with transport-specific enumerators
underneath. **Coordinate this boundary with the AbCip-browseability audit** so the two efforts
agree on the shared-core seam.
Note: the driver is still fully usable **without** a browser — like every driver, unmapped types
fall back to the raw-JSON TagConfig editor, and pre-declared tags always work. Browse is a
convenience layer, not a correctness dependency.
---
## 5. Test-fixture strategy
Consistent with the repo's Docker-fixture model (driver sims on the Linux host `10.100.0.35`,
controlled via `lmxopcua-fix`, compose files under `tests/.../Docker/`).
### 5.1 FINS
- **Hand-rolled byte-level fake** for unit tests (mirror the Modbus `IModbusTransport` fake) — the
primary and most valuable test surface; deterministic, no container.
- **Open-source FINS simulators** for an integration fixture:
[`hiroeorz/omron-fins-simulator`](https://github.com/hiroeorz/omron-fins-simulator) (Ruby, UDP
9600) and [`ahmadfarisfs/fins_simulator_omron`](https://github.com/ahmadfarisfs/fins_simulator_omron).
Neither ships a Docker image → wrap in a small `Dockerfile` under
`tests/Drivers/.../Omron/Docker/` and register with the `project=lmxopcua` label. Both are
UDP-oriented; FINS/TCP handshake coverage stays on the hand-rolled fake.
- Omron's own **CX-Simulator** speaks FINS but is Windows-only, license-gated (bundled in CX-One,
not separately purchasable) → **not** a CI fixture; reserve for a manual/live gate against real
hardware if available.
### 5.2 CIP (NJ/NX)
- **No credible free NJ/NX CIP simulator exists.** libplctag's own test harness targets Rockwell.
Options, in order:
- **Hand-rolled `IAbCipTagRuntime`-style fake** (Omron variant) for read/write/decode unit tests —
the same seam AbCip uses (`IAbCipTagRuntime` / factory injection) makes the driver fully
unit-testable without hardware. This is the primary CI surface.
- **Live gate against real NJ/NX hardware** (env-gated `Category=LiveIntegration`, skips cleanly
when the env var is absent — the pattern the HistorianGateway live suite already uses). This is
the only way to truly validate the `plc=omron-njnx` libplctag path, Network-Publish behavior,
and Omron string/array quirks. Flag as an **infra-gated known limitation** until hardware is on
the bench.
---
## 6. Effort / risk / phasing
**Recommended order: CIP-first, then FINS.**
Rationale: (a) CIP targets the modern/current Omron line (NJ/NX/NY) most likely in new installs;
(b) the CIP path **reuses 7080% of the already-shipped, already-hardened AbCip stack** (libplctag,
poll engine, resilience, status mapping), so it is the fastest path to a working read/write driver;
(c) FINS, while simpler as a protocol, is a *net-new* transport codec with no reuse and targets
legacy hardware.
### Phase 1 — Omron CIP read/write (NJ/NX) · *LowMedium effort*
- Extract a shared CIP core from AbCip (or, pragmatically, copy AbCip and specialise) →
`Driver.OmronCip` + `Driver.OmronCip.Contracts`, `DriverType="OmronCip"`, `plc=omron-njnx`.
- Capabilities: Connect/Read/Write/Subscribe(poll)/Probe; pre-declared tags via `ITagDiscovery`.
- Typed tag editor: copy `AbCipTagConfigModel`/editor, register in `TagConfigEditorMap` +
`TagConfigValidator`.
- Tests: hand-rolled runtime fake + env-gated live gate.
- **Risk:** libplctag Omron string/array edge cases (community-reported); word/UDT quirks. Medium.
### Phase 2 — Omron FINS read/write (CJ/CS/CP) · *Medium effort*
- New `IFinsTransport` codec (TCP+UDP, `0x0101`/`0x0102`, area-code table, word-swap) behind the
same `IDriver` shell; memory-area TagConfig shape (§3.2); manual-entry editor (no browse).
- Tests: byte-level fake (primary) + open-source simulator container (secondary).
- **Risk:** per-CPU-family area-code/word-order variance. Medium. No dependency/license risk (hand-rolled).
### Phase 3 — CIP online browse (NJ/NX) · *High effort / research-grade*
- Sysmac Studio tag-export importer first (cheap, high value), then optional online Omron CIP
tag-list `IBrowseSession` (`Driver.OmronCip.Browser`) — **not** libplctag `@tags`.
- **Coordinate the shared CIP-browse-core boundary with the AbCip-browseability audit.**
- **Risk:** High — Omron's tag-list service is undocumented-in-libplctag and Rockwell-different.
### Top risks (summary)
1. **CIP browse is not free reuse.** libplctag's tag-list walker does not work on Omron NJ/NX
(`ErrorUnsupported`); online browse needs net-new Omron-specific CIP work, and even then only
Network-Published variables are visible. Mitigate by shipping the offline Sysmac-export importer
first and treating online browse as a later research phase.
2. **No free NJ/NX CIP simulator** → CIP correctness for the real wire (string/array/UDT/Network-
Publish semantics) can only be proven on live hardware behind an env-gated `LiveIntegration`
suite; treat as an infra-gated known limitation until an NJ/NX is on the bench.
3. **(Secondary) FINS per-family variance** in memory-area codes and multi-word ordering — contain
with a CPU-family-parameterised code table + per-tag `wordSwap`.
---
## Sources
- [Omron store KB — Using CIP to Read/Write Network Variables with NJ/NX](https://store.omron.com.au/knowledge-base/1807412-using-cip-to-read-write-network-variables-with-nj-nx)
- [Omron NJ/NX CPU Built-in EtherNet/IP Port User's Manual (W506)](https://files.omron.eu/downloads/latest/manual/en/w506_nj_nx-series_cpu_unit_built-in_ethernet_ip_port_users_manual_en.pdf)
- [libplctag #466 — Support for Omron NX/NJ variable listing](https://github.com/libplctag/libplctag/issues/466)
- [libplctag.NET #371 — List all tags in Omron NJ: ErrorUnsupported](https://github.com/libplctag/libplctag.NET/issues/371)
- [libplctag Omron NJ/NX group thread (plc=omron-njnx, CIP path)](https://groups.google.com/g/libplctag/c/bU5MVQrPsOw)
- [Omron W227 FINS Commands Reference Manual](https://www.myomron.com/downloads/1.Manuals/Networks/W227E12_FINS_Commands_Reference_Manual.pdf)
- [Omron NX-series CPU Unit FINS Function User's Manual (W596)](https://files.omron.eu/downloads/latest/manual/en/w596_nx-series_-_cpu_unit_fins_function_users_manual_en.pdf)
- [Wireshark FINS dissector (framing reference)](https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-omron-fins.c)
- [FieldServer Omron FINS protocol driver sheet](https://cdn.chipkin.com/assets/uploads/2016/jan/PDS_Omron_FINS.pdf)
- [Omron memory-area overview (CIO/HR/AR/DM/WR/EM)](https://community.oxmaint.com/discussion-forum/omron-plc-memory-areas-understanding-ci-hr-ar-lr-dm-wr-em-extended-em-usage)
- [HslCommunication — commercial-license fork situation](https://github.com/HslCommunication-Community/HslCommunication-Community)
- [omron-fins-simulator (open-source FINS sim)](https://github.com/hiroeorz/omron-fins-simulator)
- [fins_simulator_omron (open-source FINS sim)](https://github.com/ahmadfarisfs/fins_simulator_omron)
</content>
</invoke>