Tutorial — From zero to live values
By the end you’ll have the adapter polling a Modbus simulator and publishing value changes onto MQTT, and you’ll have read and written a signal from a client. No hardware required.
1. Prerequisites
Section titled “1. Prerequisites”- Python 3.9+, and a local MQTT broker on
localhost:1883(docker run -d -p 1883:1883 emqx/emqx). - From the repo root:
pip install -e . -r requirements-test.txt.
2. Start the simulator
Section titled “2. Start the simulator”python validation/modbus_sim_server.py --port 5020It serves a Modbus/TCP slave (unit 1) with a known register map and a counter/ramp that change every half second (see the script’s docstring for the map).
3. Run the adapter
Section titled “3. Run the adapter”In another shell:
python main.py --platform HOST --transport MQTT validation/messaging-local.json \ -c FILE validation/config.json -t modbus-thingYou should see it connect, coalesce the configured signals into read blocks, and start. The config
(validation/config.json) defines one instance (plc1) polling holding/coil/discrete/input signals.
4. Watch values flow
Section titled “4. Watch values flow”Subscribe to the UNS data class (any MQTT client) — one wildcard covers the whole fleet:
mosquitto_sub -t 'ecv1/+/+/+/data/#' -vYou’ll see SouthboundSignalUpdate messages on ecv1/modbus-thing/modbus-adapter/plc1/data/{signal}
for the changing signals (e.g. Counter16, Temp), each with a value, normalized quality, a
Modbus address ({unitId, table, address, type}), and the top-level identity. (Also try
ecv1/+/+/+/state for the keepalive and ecv1/+/+/+/metric/# for southbound_health plus the
ModbusConnection, ModbusInventory, ModbusPoll, ModbusPublish, and ModbusCommand operational
metric groups.)
5. Read a signal on demand
Section titled “5. Read a signal on demand”Read/write go through the command inbox (ecv1/{device}/modbus-adapter/cmd/{verb}); set
header.name to the verb and reply_to to a topic you subscribe. With a EdgeCommons client this is one
request() call; raw MQTT:
publish ecv1/modbus-thing/modbus-adapter/cmd/sb/read {"header":{"name":"sb/read","reply_to":"app/r","correlation_id":"1"},"body":{"signals":[{"name":"Scaled"}]}}subscribe app/r → { "ok": true, "result": { "reads": [ { "value": 25.0, ... } ] } } # raw 250 × scale 0.16. Write a signal
Section titled “6. Write a signal”publish ecv1/modbus-thing/modbus-adapter/cmd/sb/write {"header":{"name":"sb/write","reply_to":"app/r","correlation_id":"2"}, "body":{"writes":[{"name":"RWFloat32","value":42.5}]}} # a writable holding/coil signalRead it back to confirm. (In config.json, RWFloat32 / RWInt16 / RWString / RunCmd are
writable scratch signals.)
7. Prove it end-to-end
Section titled “7. Prove it end-to-end”python validation/validate.py # poll→publish, read, write round-trip, control — ALL PASSNext: the how-to guides for defining your own register map, tuning rates, and deploying; the reference for every option; the explanation for the model.