Repository navigation
Modbus Mirroring
nc-intezra edited this page Oct 19, 2026
·
1 revision
A modbus module copies a PLC's holding registers and coils across
the diode. On the IT side they are served from a Modbus TCP server, so a
SCADA system, historian or HMI can read live values without any connection
into the OT network.
sequenceDiagram
participant PLC as PLC (OT)
participant P as Input box: poller
participant D as Diode
participant M as Output box: mirror
participant C as SCADA / HMI (IT)
loop every 'interval' (1 s)
P->>PLC: read holding registers and coils (Modbus TCP)
PLC-->>P: values
P->>D: one JSON message (seq n), DYO1-framed UDP
D->>M: datagrams, reassembled and CRC-checked
M->>M: write values into its datastore
end
C->>M: read registers (Modbus TCP, port_out 502)
M-->>C: latest mirrored values
-
Polling. The input box keeps one TCP connection open to the PLC
(
ip,plc_port502,unit1) and reads every configured range eachintervalseconds. Large ranges are split to respect Modbus limits (125 registers or 2000 coils per request). After any failure it reconnects, and failures are logged once rather than every second. - Sending. All values from one poll travel as one message, with a sequence number. A message is either received whole and CRC-checked, or dropped whole, so a SCADA client never sees half an update.
-
Serving. The output box listens on
dyode_out.ip:portfor updates and runs a Modbus TCP server onport_out(default 502, all interfaces, orbind_out). Values out of range or outside the configured datastore are rejected and logged. -
Staleness. If no update arrives for
stale_afterseconds (default 10), the output box logs "no update for Ns, serving stale values". The server keeps answering with the last values, because Modbus has no way to say "stale", so the log is the only signal. Watch for it. - One way only. A client may write to the output box's server, but those writes only change the mirror. Nothing can reach the PLC.
Ranges are written start-end with the end excluded:
registers:
- 0-100 # holding registers 0..99
- 400-450 # 400..449
coils:
- 0-10 # coils 0..9flowchart LR
PLC1["PLC 1"] --> POLL["dyode_in.py<br/>one process,<br/>polls every module"]
PLC2["PLC 2"] --> POLL
POLL -->|"newline-delimited JSON,<br/>tagged with module name"| OPTO{{"Serial line<br/>through optocoupler"}}
OPTO --> SPLIT["dyode_out.py<br/>one reader"]
SPLIT --> M1["Mirror 1<br/>Modbus server"]
SPLIT --> M2["Mirror 2<br/>Modbus server"]
DYODE v2 supports only modbus modules. A single process owns the serial
port (serial.device, default /dev/serial0, 57600 baud) and multiplexes
every module over it. Any other module type in a v2 config is ignored with
a warning.
DYODE Python 3 edition · documents release 1.5.0 · source pages are kept in docs/wiki/ of the main repository · GPLv3
DYODE (Python 3)
Modules
Running it
Repository