# 05-tests — Modbus verification harness Two tools. One reads the PLC and checks it against the register map; the other *is* a PLC, serving deliberately distinctive values so the SCADA side can be tested on its own. ```bash python verify_modbus.py --host 10.0.0.17 --port 502 --unit 1 python verify_modbus.py --write-test # also prove the 15 RW points accept writes python verify_modbus.py --quiet # failures and summary only ``` Requires `pymodbus` (`pip install pymodbus`). Exit code is 0 only if everything passed, so it works as a gate. `verify_modbus.py` reads every row of `../03-plc/register-map.csv` from the live runtime, using the function code each object type implies — FC1 coils, FC2 discrete inputs, FC3 holding registers, FC4 input registers. ## If you cannot reach 10.0.0.17 That address is only routable from inside the VNet or over the WireGuard VPN. From anywhere else, poll from a throwaway container on the host's own Docker network: ```bash ssh lin001 "docker run --rm --network openplc-net python:3.12-alpine \ sh -c 'pip install -q pymodbus && python - < [!CAUTION] > **Do not run `fake_plc.py` on port 502 of the live host.** The real PLC is > serving there and CI Server is polling it. Use a spare port, or stop the > container first and remember that stopping it stops the demo. ## Notes - **`%QW17` is decoded unsigned**; bit 15 does not fit a signed INT. Every other register is treated as signed, per the map — including `%QW7`, which genuinely goes negative. - **`--write-test` writes each RW point back with the value it already holds.** No value changes. See the caveat in the script docstring about `%MW1`, the command word — a non-zero value there is a live command. - **If everything fails to connect**, the cause is usually not this harness. The runtime opens its Modbus slave only while a program is **running**, and only if the Editor project defines a Modbus **Server**. See `03-plc/DEPLOY.md` §A3. A refusal looks like a firewall drop and is not: Docker DNATs to the container, which returns RST because nothing is bound inside. ## Verified 2026-09-02 `fake_plc.py` served on a spare port and `verify_modbus.py` read it end to end: ``` read : 69/69 points OK writable : 15/15 RW points OK PASS : the runtime matches the register map ``` Both tools work against the current 69-point map.