RS-485 home sensors
Radon sensors and power meters on RS-485/Modbus, bridged to MQTT by Wemos D1 Mini boards and shown in Homey. This page covers wiring, flashing and setup for every part, and how to move it all to a new server or Homey.
At runtime the server only runs the MQTT broker. The radon sensors work on their own (the LCD shows ID, status and radon level) with no network at all. Build tools are only needed to flash firmware or install the Homey app.
Folder in ~/arduino/arduino/ | Contents |
|---|---|
RadonSensorArduino_v2/ | Uno firmware, setup guide, meter manuals |
WemosD1MiniRS485ModbusMqtt/ | Bridge firmware, one codebase for every bridge (git) |
com.radonsensor.mqtt/ | Homey app (git) |
MQTT server
Mosquitto receives everything the bridges publish and passes it to Homey. It needs a fixed address and a login for every client.
- Give the server a fixed IP.
The bridges and the Homey app store the broker address, so it must never change: use a DHCP reservation in the router, or a static IP. This guide writes it as
<broker-ip>. Avoid addresses ending in.0or.255: on a typical /24 network those are not usable host addresses. - Install Mosquitto.
sudo apt update sudo apt install mosquitto mosquitto-clients sudo systemctl enable --now mosquitto
- Listen on the LAN and require a login.
Create
/etc/mosquitto/conf.d/local.conf:listener 1883 allow_anonymous false password_file /etc/mosquitto/passwd
Leave the package's
mosquitto.confas is: it already enables persistence, so retained messages (link state, backlight, WiFi signal) survive a broker restart. - Create one user per client.
One for the bridges, one for Homey, so either can be revoked on its own.
# -c only for the first user: it creates the file sudo mosquitto_passwd -c /etc/mosquitto/passwd wemos sudo mosquitto_passwd /etc/mosquitto/passwd homey sudo chown mosquitto:mosquitto /etc/mosquitto/passwd sudo chmod 640 /etc/mosquitto/passwd sudo systemctl restart mosquitto - Allow MQTT from the LAN only.
If
ufwis active, with your LAN subnet in place of<lan-subnet>. Never expose port 1883 to the internet: it is unencrypted.sudo ufw allow from <lan-subnet> to any port 1883 proto tcp # e.g. 192.168.1.0/24 - Install the build tools (only on the machine you flash from).
sudo usermod -aG dialout $USER # serial ports; log in again curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh arduino-cli config init arduino-cli config set directories.user ~/arduino/arduino arduino-cli config add board_manager.additional_urls \ https://arduino.esp8266.com/stable/package_esp8266com_index.json arduino-cli core update-index arduino-cli core install arduino:avr@1.8.8 esp8266:esp8266@3.1.2 arduino-cli lib install "ModbusMaster@2.0.1" "PubSubClient@2.8" # Homey CLI via Node.js 24 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash nvm install 24 && npm install -g homey
The Uno needs the fmalpartida "New LiquidCrystal" library, which is not in the Library Manager: copy
~/arduino/arduino/libraries/LCDfrom the old machine.
Check
Anonymous access must be refused (not authorised). With a login, every bridge's messages appear within a minute:
mosquitto_sub -h <broker-ip> -u homey -P '<password>' -t 'rs485/#' -v
Topics
Everything is under rs485/<bridge>/. A device state always carries link (1/0); other fields appear only when read successfully in that poll, otherwise error says what failed.
| Topic | Payload | Retained |
|---|---|---|
status | online / offline (last will) | yes |
wifi/rssi | signal in dBm, e.g. -57 | yes |
button/<1-3> | pressed | no |
radon/<id>/state | {"link":1,"radon":85.9,"backlight":"on"} | yes |
dtsu666/<id>/state | power, energy_import/export, voltage_l1–l3, current_l1–l3 | yes |
sdm120/<id>/state | power, energy_import/export, voltage, current | yes |
radon/<id>/backlight/set | on / off (command to the bridge) | no |
Radon sensor
A standalone sensor that shows its ID, status and radon level on the LCD, and answers as a Modbus slave on the radon bus.
| Signal | Uno pin | Notes |
|---|---|---|
| RD200M TX → Uno | D2 | SoftwareSerial RX, 19200 baud |
| Uno → RD200M RX | D3 | SoftwareSerial TX |
| LCD SDA / SCL | A4 / A5 | I2C address 0x27 |
| MAX485 DE + RE (tied) | D7 | HIGH = transmit |
| MAX485 RO → Uno | D8 | SoftwareSerial RX, 9600 8N1 |
| Uno → MAX485 DI | D9 | SoftwareSerial TX |
| ID straps | D10–D12 ↔ D13 | read once at boot |
- Set the bus ID with the straps.
Use IDs 1–7, unique on the bus. Never use ID 0: it is the Modbus broadcast address and can never answer.
- Flash the firmware.
arduino-cli board list # find the port, e.g. /dev/ttyACM0 cd ~/arduino/arduino/RadonSensorArduino_v2 arduino-cli compile --fqbn arduino:avr:uno . arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno .The firmware needs no configuration.
- Wire it onto the radon bus.
A to A, B to B, with a common ground between all nodes. Terminate the two physical ends of the bus with 120 Ω.
Check
After boot, line 1 shows the status and the ID as its last character, and line 2 shows the radon level in Bq/m³. The RD200M only updates every 10 minutes, so the value changes slowly.
Wemos bridge
The Modbus master for one bus. It polls every device on it, publishes their state to MQTT, and has three pushbuttons for Homey flows. Every bridge runs the same firmware.
| Signal | Wemos pin | Notes |
|---|---|---|
| MAX485 DE + RE (tied) | D0 | HIGH = transmit |
| MAX485 RO → Wemos | D1 | SoftwareSerial RX, must be 3.3 V |
| Wemos → MAX485 DI | D2 | SoftwareSerial TX |
| Status LED | D4 | active low; blinks on polls, errors and presses |
| Buttons 1 / 2 / 3 | D5 / D6 / D7 | to GND, 10 kΩ pull-up to 3V3 |
| USB serial | RX / TX | kept free for the debug log, 115200 baud |
- Create the bridge's
secrets.h.cd ~/arduino/arduino/WemosD1MiniRS485ModbusMqtt cp secrets.example.h secrets.h # gitignored, never commit itSetting Radon bridge Power bridge WIFI_SSID,WIFI_PASSWORDyour WiFi network MQTT_HOST<broker-ip>, the broker's fixed IPMQTT_USERNAME,MQTT_PASSWORDthe wemosuserMQTT_CLIENT_IDhome-esp8266-rs485-01home-esp8266-rs485-02BRIDGE_ID"radon""power"BRIDGE_PROFILEBRIDGE_PROFILE_RADONBRIDGE_PROFILE_POWERBRIDGE_IDbecomes the topic prefixrs485/<BRIDGE_ID>/. Don't change it after pairing in Homey. The profile sets the bus format: radon 8N1, power 8N2. - List the devices on this bus in
devices.h, in the table for the bridge's profile.{7, DeviceType::Radon}, // radon profile {10, DeviceType::Dtsu666}, // power profile {11, DeviceType::Sdm120},Only list connected devices: each one that doesn't answer blocks polling for about 4 s per register block. Radon sensors are polled every 60 s, meters every 10 s.
- Flash it. Close any serial monitor first: an upload that competes for the port can report success while the board keeps the old firmware.
arduino-cli compile --fqbn esp8266:esp8266:d1_mini_pro . arduino-cli upload -p /dev/ttyUSB0 --fqbn esp8266:esp8266:d1_mini_pro .
Check
The serial log (115200 baud) shows the bridge ID and device list, WiFi with signal strength, then the MQTT connection. On the broker you should see rs485/<bridge>/status online, wifi/rssi every minute, a state per device, and button/<n> pressed for each press. For button trouble, set kButtonPinDiagnostics = true in the .ino to log raw pin levels.
Power meters
Three-phase and single-phase DIN-rail meters on their own bus, read by the power bridge every 10 seconds.
One-time meter setup
Both meters must run 9600 baud, 8 data bits, no parity, 2 stop bits, with a unique ID (1–247), before they can share the bus.
- DTSU666: switch it to Modbus.
It ships in DL/T 645 mode, not Modbus. In the meter's menu (password
701, thenConn) setAddrto its ID andbAudto 9600. In Modbus mode it uses no parity and 2 stop bits.UNKNOWN How to switch the protocol: the quick manual lists a "Protocol changing-over" register (
0x002C) but doesn't show it in the menu. Check the full manual or the meter's menu before installing. - SDM120: set baud, framing and ID.
It ships at 2400 baud, ID 1. Hold its button for 3 s until the display shows
-SET-, then write the holding registers over Modbus at its current 2400 baud, for example from the server with a USB-RS485 adapter andmbpoll:Register Value Meaning 4002929600 baud 400193no parity, 2 stop bits 40021new ID 1–247 Values are 32-bit floats, written with function 0x10. Power-cycle the meter afterwards. UNKNOWN The manual gives both "none" and "even" as the default parity; check the display.
- Set the real IDs in the power profile of
devices.h(the placeholders are 10 and 11) and flash the power bridge. - Compare the first readings with each meter's own display.
Three DTSU666 details are not settled by its manual. Each is a one-line switch at the top of
meters.cpp:- UNKNOWN Function code: 0x03 assumed (
kDtsuFunctionCode). If it times out, try 0x04. - UNKNOWN Float word order: high word first assumed (
kDtsuWordsSwapped). Nonsense values mean flip it. - UNKNOWN Energy scaling: 1.0 assumed (
kDtsuEnergyScale). Correct it if the kWh reading differs from the display.
- UNKNOWN Function code: 0x03 assumed (
What each meter reports
| Value | DTSU666 (3-phase) | SDM120 (1-phase) |
|---|---|---|
| Power now | total W, negative when exporting | W |
| Energy imported / exported | kWh / kWh | kWh / kWh |
| Voltage | L1, L2, L3 (V) | V |
| Current | L1, L2, L3 (A) | A |
| Registers read | 0x2006–0x2013, 0x401E–0x4029 | 0x0000–0x000D, 0x0048–0x004B (FC 04) |
| Raw scaling | V ×0.1, A ×0.001, W ×0.1 (direct connection, no CT) | none, already in units |
With external current transformers on the DTSU666, current and power must also be multiplied by the CT ratio.
Homey
The "RS-485 Bridge (MQTT)" app turns every bridge and device into a Homey device. It needs a Homey Pro on the same LAN as the broker; Homey Cloud cannot run it.
- Point the app at the broker.
Set
MQTT_HOSTinconfig.jsto<broker-ip>, and create the gitignoredenv.jsonwith thehomeyuser. Keep it out of anything you share.{ "MQTT_USERNAME": "homey", "MQTT_PASSWORD": "<password>" } - Install it on a Homey. To move to another Homey, run the same commands with that Homey selected. Devices, flows and Insights history do not move with the app.
cd ~/arduino/arduino/com.radonsensor.mqtt npm install homey login homey select homey app validate --level debug homey app install
- Pair the devices (Add device → RS-485 Bridge (MQTT)). The bridges must already be running: pairing only lists what has published since the app started, so allow a minute.
Driver Pairs as Shows Radon sensor Radon sensor 7 (radon)radon Bq/m³, LCD backlight on/off 3-phase power meter Power meter 10 (power)power, energy in/out, V and A per phase 1-phase power meter Power meter 11 (power)power, energy in/out, V, A RS-485 bridge RS-485 bridge (radon)buttons 1–3, WiFi signal, online state
Check
Each device becomes available once its first state arrives, and shows "Link lost" when its bus stops answering. The bridge device's flow card Button [1/2/3] was pressed fires on a physical press and on a tap on the tile.