node-red-contrib-omron-eip 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,472 @@
1
+ # node-red-contrib-omron-eip
2
+
3
+ Node-RED nodes for reading and writing **Omron NX / NJ Sysmac** controller tags over
4
+ EtherNet/IP, by symbolic tag name — no memory maps, offsets, or PLC-side gateway blocks. Built
5
+ on the [`omron-eip`](https://www.npmjs.com/package/omron-eip) library (installed automatically).
6
+
7
+ The package adds two working nodes plus one connection node:
8
+
9
+ - **omron-plc** — a *configuration node* that holds one controller connection (its IP address
10
+ and messaging mode). You create it once and every read/write node shares it, so they all use
11
+ a single, auto-reconnecting connection to the controller.
12
+ - **omron read** — reads one or more tags. Can run when a message arrives, on a repeating
13
+ timer, and/or once shortly after deploy.
14
+ - **omron write** — writes one or more tags. The value(s) can come from the incoming message or
15
+ be fixed in the node.
16
+
17
+ Tags are addressed exactly as they are named in Sysmac Studio, including nested ones —
18
+ `Counter`, `MyArray[3]`, `MyStruct.Speed`, `Machine.Axes[2].Position`, and so on.
19
+
20
+ ---
21
+
22
+ ## Requirements
23
+
24
+ - **Node-RED 3.0 or newer**, on **Node.js 16 or newer**.
25
+ - An Omron **NX/NJ** controller reachable on the network from the machine running Node-RED.
26
+ - In **Sysmac Studio**, each tag you want to reach must be a **global variable** with
27
+ **Network Publish = "Publish Only"** (or "Input/Output"), and the project must be
28
+ **transferred to the controller**. A variable that isn't published cannot be read or written
29
+ by any EtherNet/IP client — this is the single most common reason a tag "isn't found."
30
+
31
+ ---
32
+
33
+ ## Install
34
+
35
+ From the Node-RED editor: **Menu → Manage palette → Install**, search
36
+ `node-red-contrib-omron-eip`, and click **Install**.
37
+
38
+ Or from the command line, in your Node-RED user directory (e.g. `~/.node-red`):
39
+
40
+ ```bash
41
+ npm install node-red-contrib-omron-eip
42
+ ```
43
+
44
+ The `omron-eip` library is pulled in automatically as a dependency. Restart Node-RED; the nodes
45
+ appear under the **Omron** category in the palette. (Running in Docker? See
46
+ **[DOCKER_INSTALL_AND_TEST.md](./DOCKER_INSTALL_AND_TEST.md)**.)
47
+
48
+ ---
49
+
50
+ ## UCMM vs Class 3 — which connection to choose
51
+
52
+ EtherNet/IP explicit messaging can run two ways, and you pick one when you set up the PLC
53
+ connection:
54
+
55
+ - **UCMM (Unconnected Messaging)** — the **default and recommended** choice for almost
56
+ everyone, especially when the PC/Node-RED machine talks directly to the controller. Requests
57
+ are sent without setting up a persistent session, they can run in parallel, and there is no
58
+ limit on large array reads.
59
+ - **Class 3 (Connected Messaging)** — opens one persistent connection and sends requests over
60
+ it. This only helps in specific topologies where traffic is **routed through a gateway, a
61
+ communications module, or across a backplane**, where keeping a connection open avoids
62
+ re-resolving the route on every request. On a direct connection it is **slower** (a single
63
+ connection serializes requests) and it **cannot read very large arrays**. If a controller
64
+ declines Class 3, the library automatically falls back to UCMM, so enabling it can never break
65
+ a connection — at worst it simply doesn't help.
66
+
67
+ **Rule of thumb: leave it on UCMM** unless you specifically know your network path benefits from
68
+ Class 3.
69
+
70
+ These nodes have been tested against **NX1P2**, **NX102**, and **NX502** controllers, in both
71
+ UCMM and Class 3 modes, reading and writing scalars, strings, structures, arrays, and Omron
72
+ date/time types.
73
+
74
+ ---
75
+
76
+ ## Walkthrough
77
+
78
+ A complete first-time setup, from connecting to a controller through reading and writing tags.
79
+
80
+ ### 1. The two nodes, and the "not configured" marker
81
+
82
+ ![Read and write nodes, showing the unconfigured marker](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/1%20Connect%20to%20PLC.png)
83
+
84
+ Dragging the package onto your canvas gives you the two nodes you'll use: a **read** node and a
85
+ **write** node. Before either can do anything, it needs to know *which controller* to talk to —
86
+ that's the job of the PLC connection. A node that has no controller attached shows a small **red
87
+ triangle** in the corner (Node-RED's "this node isn't fully configured" marker). We'll clear it
88
+ by creating a PLC connection. You can do this from inside either node; here we'll start in a
89
+ **read** node.
90
+
91
+ ### 2. Add a PLC connection
92
+
93
+ ![Adding a new PLC configuration](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/2%20Connect%20to%20PLC%202.png)
94
+
95
+ Open the read node (double-click it). Next to the **PLC** field, click the pencil/add button to
96
+ create a new controller connection. This opens the PLC configuration panel.
97
+
98
+ ### 3. Name it, set the address, and choose the messaging mode
99
+
100
+ ![Naming the PLC, entering the IP, choosing UCMM or Class 3](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/3%20Connect%20to%20PLC%203.png)
101
+
102
+ Give the connection a **Name** you'll recognize (for example `Line 3 NX102`), and enter the
103
+ controller's **IP address** (for example `192.168.250.1`).
104
+
105
+ Two things to get right here:
106
+
107
+ - **Networking.** The machine running Node-RED must be able to reach that IP. The PLC and the
108
+ PC/server need to be on the **same network and subnet** (or have a route between them). A good
109
+ sanity check is to `ping` the controller's IP from the machine running Node-RED before going
110
+ further — if you can't ping it, the node won't connect either.
111
+ - **Messaging mode (UCMM or Class 3).** Choose **UCMM** unless you have a specific reason not
112
+ to — it's faster on a direct connection, runs requests in parallel, and has no large-array
113
+ limit. Pick **Class 3** only when you're reaching the controller *through* a routing
114
+ gateway/comms module or across a backplane, where a held-open connection saves re-resolving
115
+ the route each time. (See the "UCMM vs Class 3" section above. If in doubt, UCMM.)
116
+
117
+ ### 4. Save, then **Deploy** before testing
118
+
119
+ ![Deploy before testing reads and writes](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/4%20Connect%20to%20PLC%204.png)
120
+
121
+ Close the configuration panel (Done/Add), then click **Deploy** in the top-right of Node-RED.
122
+ This is important: the PLC connection only actually connects to the controller **after you
123
+ deploy**. Several features below — loading the tag list, validating names, test reads/writes —
124
+ talk to the *live, running* connection, so they won't work until you've deployed at least once.
125
+
126
+ ### 5. Point a read (or write) node at the connection
127
+
128
+ ![Selecting the PLC connection in a read or write node](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/5%20Connect%20PLC%20to%20Read%20or%20Write.png)
129
+
130
+ Open your read node again. In the **PLC** dropdown, select the connection you just created. The
131
+ red triangle clears — the node now knows which controller it belongs to. Any other read or write
132
+ node can select the same connection; they'll all share that one connection to the controller.
133
+
134
+ ### 6. Test Connection, Load and Validate Published Variables
135
+
136
+ ![The Test Connection / Load / Validate button](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/6%20Load%20Tags.png)
137
+
138
+ Click **Test Connection, Load and Validate Published Variables**. This one button does three
139
+ useful things at once:
140
+
141
+ 1. **Tests the connection** to the controller (confirming the IP, network, and that it's
142
+ reachable).
143
+ 2. **Loads the controller's published tag list** — every variable you marked *Network Publish =
144
+ Publish Only* in Sysmac Studio. These names then become **auto-complete suggestions** as you
145
+ type tag names.
146
+ 3. **Validates the tags you've already entered** — each gets a green check (✓) if it exists on
147
+ the controller, or a red X (✗) if the name wasn't found (a typo, or a tag that isn't
148
+ published).
149
+
150
+ Remember it talks to the live controller, so you must have **deployed first** (step 4).
151
+
152
+ ### 7. Add the tags you want to read
153
+
154
+ ![Adding variables, with the dropdown suggestions](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/7%20Add%20the%20tags%20you%20want%20to%20read.png)
155
+
156
+ Add one row per tag you want to read. After loading the tag list (step 6) you can pick names
157
+ from the **dropdown** instead of typing them, which avoids spelling mistakes. You can read:
158
+
159
+ | You want | Type this |
160
+ |---|---|
161
+ | a single value | `Counter` |
162
+ | one element of an array | `MyArray[3]` |
163
+ | a whole array at once | `MyArray` |
164
+ | one field of a structure | `MyStruct.Speed` |
165
+ | a whole structure at once | `MyStruct` |
166
+ | something deeply nested | `Machine.Axes[2].Position` |
167
+
168
+ Reading a **whole** array or structure in one row (just its name, no `[index]` or `.field`) is
169
+ the fastest way to pull a lot of data — it's a single request rather than many.
170
+
171
+ ### 8. Read output — one message, or one message per tag
172
+
173
+ ![Read output shape options](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/8%20Read%20output%20options.png)
174
+
175
+ The **Output** setting controls how the readings are packaged into the message(s) the node
176
+ sends out. (A "message" is the data packet that travels along the wires to the next node.) Say
177
+ you read three tags — `Counter = 42`, `Speed = 1500`, `Temp = 21.5`:
178
+
179
+ **One message, `payload = { name: value }`** (the default)
180
+ All tags come out together in a **single message**, as an object keyed by tag name:
181
+
182
+ ```json
183
+ { "Counter": 42, "Speed": 1500, "Temp": 21.5 }
184
+ ```
185
+
186
+ Use this when you want all the readings together — to show on a dashboard, build a record, write
187
+ a log row, or hand a complete snapshot to a function node. It's the most common choice.
188
+
189
+ **One message per tag (`topic` = tag name)**
190
+ Each tag comes out as its **own separate message**. Reading three tags emits three messages:
191
+
192
+ ```
193
+ message 1 → msg.topic = "Counter" msg.payload = 42
194
+ message 2 → msg.topic = "Speed" msg.payload = 1500
195
+ message 3 → msg.topic = "Temp" msg.payload = 21.5
196
+ ```
197
+
198
+ Use this when the next node expects **one value at a time**, with the name in `msg.topic` — the
199
+ classic example is the Node-RED **chart** node (it uses `topic` as the series name and `payload`
200
+ as the point), or when you want to route each tag to a different place with a switch node.
201
+
202
+ **If you're not sure, leave it on "one message."**
203
+
204
+ ### 9. Msg override — let an incoming message choose what to read
205
+
206
+ ![Dynamic variable read via msg override](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/9%20Dynamic%20Variable%20Read.png)
207
+
208
+ This is the one people find confusing, so here it is plainly.
209
+
210
+ **Normally**, the node reads the fixed list of tags you typed in step 7. **Msg override** lets a
211
+ *different* part of your flow decide what to read, on the fly, by putting the tag name(s) inside
212
+ the message that triggers the node. The box just names **which property of the incoming message
213
+ to look at** — the default is `variables`, meaning the node checks `msg.variables`.
214
+
215
+ How it behaves: when a message arrives, if that property is present, the node reads **those**
216
+ tags instead of its own list; if the property is absent, it reads its configured list as normal.
217
+
218
+ What you can put in `msg.variables` (from a function node, a change node, a dashboard control,
219
+ etc.):
220
+
221
+ ```js
222
+ // read several tags, named in an array:
223
+ msg.variables = ["Pressure", "FlowRate", "MotorMode"];
224
+
225
+ // read just one tag, as a string:
226
+ msg.variables = "Pressure";
227
+
228
+ // it accepts nested names too:
229
+ msg.variables = ["Machine.Axes[0].Position", "Recipe.Speed"];
230
+ ```
231
+
232
+ **Why use it:** so one read node can serve many situations instead of building a separate node
233
+ for each set of tags. For example, an operator picks a machine from a dashboard dropdown; a
234
+ function node sets `msg.variables` to that machine's tag names; the single read node reads
235
+ whatever was chosen.
236
+
237
+ **If you don't need any of that, leave this field alone** — with no `msg.variables` on the
238
+ incoming message, the node simply reads the list you configured.
239
+
240
+ ### 10. Show output JSON — preview the exact output
241
+
242
+ ![Show output JSON preview](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/10%20show%20read%20output%20format.png)
243
+
244
+ Clicking **Show output JSON** previews exactly what this node will send, in the shape you chose
245
+ in step 8. If you ran **Test Connection / Load / Validate** first, the preview fills in the real
246
+ *kinds* of values your tags hold (whole numbers, true/false, text, lists, structures); if you
247
+ haven't connected yet, it shows generic placeholders. This is just a preview to help you set up
248
+ the nodes that come *after* this one — it doesn't read the controller or change anything. There's
249
+ a **Copy** button to paste the example elsewhere.
250
+
251
+ ### 11. Read triggers — Repeat, and Read once after deploy
252
+
253
+ ![Read trigger options](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/11%20alternative%20triggers.png)
254
+
255
+ By default a read node reads whenever a message arrives at its input. Two optional triggers let
256
+ it read on its own:
257
+
258
+ - **Repeat (ms)** — makes the node read **by itself, repeatedly, on a timer**, with no input
259
+ wired in. The number is the interval in milliseconds: `1000` = once per second, `500` = twice
260
+ per second, `250` = four times per second. (It will *also* still read when a message arrives.)
261
+ Leave it at **0** to read **only** when triggered by an incoming message. *Tip:* don't poll
262
+ faster than you actually need — very small intervals flood the controller. Reading a modest
263
+ batch every 250 ms is comfortable on most controllers; tighter loops should read only a few
264
+ tags.
265
+ - **Read once shortly after deploy** — when ticked, the node performs **one** read
266
+ automatically about 1.5 seconds after you Deploy (or after Node-RED restarts). This is handy
267
+ for grabbing an initial value at startup instead of waiting for the first trigger or timer.
268
+
269
+ (`Deploy` is the Node-RED button that saves and starts your flow running.)
270
+
271
+ ---
272
+
273
+ Now the **write** node. It shares the same PLC connection, tag-name syntax, validate button, and
274
+ trigger options as the read node, so this section focuses on what's different about writing.
275
+
276
+ ### 12. The write node
277
+
278
+ ![Write node with the unconfigured marker](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/12%20write%20setup.png)
279
+
280
+ Drag a **write** node onto the canvas. Like the read node, it starts with the red "not
281
+ configured" triangle until you attach a PLC connection.
282
+
283
+ ### 13. Select the PLC, then choose where values come from
284
+
285
+ ![PLC selected; Source = From incoming message vs Fixed value(s)](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/13%20from%20incoming%20message%20input.png)
286
+
287
+ Select the **PLC** connection you already made (same one the read node uses — they share it).
288
+ The key write-only setting is **Source**, which controls where the value to write comes from:
289
+
290
+ - **From incoming message** — the value is supplied by the message that triggers the node.
291
+ - **Fixed value(s) set here** — you type the value into the node itself.
292
+
293
+ We'll cover **From incoming message** first (highlighted), then **Fixed** in step 16.
294
+
295
+ ### 14. From incoming message — add the tag names
296
+
297
+ ![Validate + add a variable in message mode](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/14%20from%20incoming%20message%20setup.png)
298
+
299
+ In **From incoming message** mode, you list **only the tag names** to write (no value boxes) —
300
+ the values will arrive on the message. Just like the read node, use **Test Connection, Load and
301
+ Validate Published Variables** to load the tag list and get dropdown suggestions and ✓/✗
302
+ checking, then add the tag(s) you want to write.
303
+
304
+ ### 15. Show input JSON — the exact message this node expects
305
+
306
+ ![Show input JSON, two-variable example](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/15%20from%20incoming%20message%20input%20format.png)
307
+
308
+ Click **Show input JSON** to see exactly what message to send this node. This is a **reference**
309
+ for whatever feeds the write node (an inject node, a dashboard control, a function node) — it
310
+ shows the `msg.payload` format for the tags you've added. The format depends on how many tags
311
+ you're writing:
312
+
313
+ ```js
314
+ // ONE variable — send the value directly as the payload:
315
+ msg.payload = 1500;
316
+
317
+ // MULTIPLE variables — send an object keyed by tag name:
318
+ msg.payload = { "Setpoint": 1500, "Mode": 3, "Enable": true };
319
+
320
+ // a whole structure or array — send an object/array as that tag's value:
321
+ msg.payload = { "Recipe": { "Speed": 100, "Steps": [1, 2, 3] } };
322
+ ```
323
+
324
+ So whatever sends a message into this node must put the value(s) in `msg.payload` in this shape.
325
+ As with the read node, if you've validated first, the preview shows the real value types for
326
+ your specific tags.
327
+
328
+ ### 16. Fixed value(s) — set the value right in the node
329
+
330
+ ![Fixed value mode with a "value to write" field per tag](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/16%20fixed%20value%20type.png)
331
+
332
+ Switch **Source** to **Fixed value(s) set here** and each tag row gains a **value to write**
333
+ field. Now the node writes those exact values every time it's triggered — the triggering message
334
+ is just a "go" signal and its contents are ignored.
335
+
336
+ The value field is a standard Node-RED typed input, so you can enter either:
337
+
338
+ - **a static value** — type a constant: a number like `1500`, text, true/false, etc. (choose the
339
+ matching type in the small dropdown — see step 17), **or**
340
+ - **a value pulled from elsewhere** — switch the field's type to `msg.`, `flow.`, or `global.`
341
+ to write whatever is in a message property or a context variable at the moment the node fires
342
+ (for example `flow.targetSpeed`).
343
+
344
+ Use **Fixed** when the value is constant (forcing a mode, a test value, a startup state), or
345
+ when you want to pull from flow/global context rather than the triggering message's payload.
346
+
347
+ ### 17. Data type reference
348
+
349
+ ![Data type mapping table](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/17%20data%20type%20table.png)
350
+
351
+ The **Data type reference** button opens a table mapping every Omron/Sysmac tag type to what you
352
+ should enter. The important idea: the small **type dropdown** beside a fixed value
353
+ (number / true-false / text / JSON) describes the kind of value **you are entering** — it is
354
+ **not** the controller's tag type. You don't have to match the exact Omron type; the node
355
+ converts your value to whatever the real tag is. As a guide:
356
+
357
+ - numeric tags (SINT, INT, DINT, REAL, LREAL, etc.) → enter a **number**
358
+ - BOOL → **true/false**
359
+ - STRING → **text**
360
+ - a whole structure or array → **JSON** (an object or array)
361
+ - **64-bit integers** (LINT, ULINT) → numbers within JavaScript's safe range work directly; for
362
+ very large values, be aware precision can be lost (these are large 64-bit values).
363
+ - **Date / time types** (DATE, DATE_AND_TIME, TIME, TIME_OF_DAY) → enter a **number of
364
+ nanoseconds**. DATE / DATE_AND_TIME use nanoseconds since 1970 (epoch ms × 1,000,000)
365
+ and read back as a date; TIME / TIME_OF_DAY are a nanosecond duration (e.g. `5000000000` =
366
+ 5 seconds). These are reliable to **millisecond** precision.
367
+
368
+ ### 18. Verified write
369
+
370
+ ![Verified write option](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/18%20verify%20write.png)
371
+
372
+ When **Verified write** is ticked, right after writing, the node **reads the value back and
373
+ checks it matches** what you sent — and reports an error if it doesn't.
374
+
375
+ Why bother, when a normal write already gets an "accepted/error" reply from the controller (which
376
+ is enough almost every time)? Verified write catches the rarer case where the controller says
377
+ *accepted* but the stored value still isn't what you intended — for example:
378
+
379
+ - the controller's **own program limits/clamps** the value (you send `5000`, but program logic
380
+ caps it at `4000`), or
381
+ - **another device or the program overwrites** that tag a split second later.
382
+
383
+ Turn it on for **important values** — setpoints, recipe parameters, mode changes — where you want
384
+ positive proof the value landed. The trade-off is one extra read per tag, so it's slightly
385
+ slower; leave it **off** for ordinary or high-rate writes. (One limit: it checks only the instant
386
+ right after writing — a change made later can't be detected.)
387
+
388
+ ### 19. Write triggers — Repeat, and Write once after deploy
389
+
390
+ ![Write trigger options](https://raw.githubusercontent.com/ucsballen/omron-eip/main/images/19%20trigger.png)
391
+
392
+ Same two optional triggers as the read node:
393
+
394
+ - **Repeat (ms)** — writes **by itself on a timer**, with nothing wired in; the number is the
395
+ interval (`1000` = once per second). Combined with a **Fixed** value, this can continuously
396
+ hold a tag at a set value — a "heartbeat" signal, or keeping an enable bit on. Leave at **0**
397
+ to write **only** when triggered. Use this deliberately: most writes should happen in response
398
+ to an event, not constantly on a timer.
399
+ - **Write once shortly after deploy** — writes **one** time about 1.5 seconds after Deploy (or a
400
+ restart). With a **Fixed** value, it's a simple way to put a tag into a known starting state at
401
+ startup, with nothing wired into the input.
402
+
403
+ ---
404
+
405
+ ## Outputs and error handling
406
+
407
+ Both nodes have **two output ports**:
408
+
409
+ 1. **Result** — the successful read result (read node) or a pass-through with `payload` set to
410
+ what was written (write node).
411
+ 2. **Error** — anything that failed comes out here, so you can wire error handling without it
412
+ interrupting the success path.
413
+
414
+ Each node also calls `node.error()` (so flow-wide **Catch** nodes work) and shows the last
415
+ status on the node's **status badge** under its icon. Common controller errors are decoded into
416
+ plain language — for example *"variable not found on the controller — check the name and that it
417
+ is published to the network"* for CIP status `0x05`.
418
+
419
+ ---
420
+
421
+ ## Reading & writing arrays, structures, and nested data
422
+
423
+ Addressing is symbolic, so you just type the path — there are no special modes:
424
+
425
+ | You want | Tag name |
426
+ |---|---|
427
+ | whole array | `MyArray` |
428
+ | one array element | `MyArray[3]` |
429
+ | whole structure | `MyStruct` |
430
+ | a structure member | `MyStruct.Speed` |
431
+ | array element inside a structure | `MyStruct.History[2]` |
432
+ | member of a structure inside an array | `Machine.Axes[2].Position` |
433
+
434
+ For writes, a whole structure/array takes an object/array value; a member/element takes a single
435
+ value. Writing a single member (`MyStruct.Speed`) is safer than rewriting the whole structure,
436
+ because writing the whole structure replaces **every** field in it — including ones another
437
+ writer may have changed.
438
+
439
+ ---
440
+
441
+ ## Troubleshooting
442
+
443
+ - **Red triangle on a node** → it has no PLC connection selected. Open it and pick (or create)
444
+ one in the **PLC** field.
445
+ - **Validate / read / write does nothing or errors right after editing** → you haven't
446
+ **Deployed** since the change. These features talk to the live connection, which only exists
447
+ after a deploy.
448
+ - **"variable not found" (CIP 0x05)** → the tag name is wrong, **or** the tag isn't published.
449
+ In Sysmac Studio set it to a global variable with **Network Publish = Publish Only** and
450
+ **transfer the project** to the controller.
451
+ - **Can't connect at all** → check the network. From the machine running Node-RED, confirm you
452
+ can `ping` the controller's IP, and that both are on the same subnet/route. In Docker, the
453
+ *container* must be able to reach the PLC (see the Docker guide).
454
+ - **Large array fails under Class 3** → switch the connection to **UCMM**; Class 3 can't carry
455
+ very large array replies. (UCMM is the recommended default anyway.)
456
+
457
+ ---
458
+
459
+ ## More documentation
460
+
461
+ - **[MANUAL.md](./MANUAL.md)** — a complete, plain-language reference: every field on every node,
462
+ message formats, flow recipes, and troubleshooting.
463
+ - **[DOCKER_INSTALL_AND_TEST.md](./DOCKER_INSTALL_AND_TEST.md)** — installing into a Docker-based
464
+ Node-RED, plus a full feature-test sequence against a real controller.
465
+ - **[omron-eip](https://www.npmjs.com/package/omron-eip)** — the underlying library, if you want
466
+ to use it directly from Node.js.
467
+
468
+ ---
469
+
470
+ ## License
471
+
472
+ GPL-2.0, matching the `omron-eip` library it is built on.