Installation & migration guide

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.

Bus RS-485 · Modbus RTU · 9600 baud Broker Mosquitto 2.0 Homey Pro, app com.radonsensor.mqtt
System overview: two RS-485 buses, two Wemos bridges, one MQTT broker, one Homey Radon sensor ×1–7 Uno + RD200M + LCD RS-485 · 8N1 Wemos bridge BRIDGE_ID "radon" Power meters DTSU666 · SDM120 RS-485 · 8N2 Wemos bridge BRIDGE_ID "power" Mosquitto broker Ubuntu server :1883 WiFi · MQTT WiFi · MQTT LAN · MQTT Homey Pro RS-485 Bridge (MQTT)
Radon sensors and power meters sit on separate buses. The Uno firmware reacts to stray bytes in other devices' traffic and only supports 8N1, while the DTSU666 needs 8N2.

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)
Ubuntu server

MQTT server

Mosquitto receives everything the bridges publish and passes it to Homey. It needs a fixed address and a login for every client.

  1. 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 .0 or .255: on a typical /24 network those are not usable host addresses.

  2. Install Mosquitto.
    sudo apt update
    sudo apt install mosquitto mosquitto-clients
    sudo systemctl enable --now mosquitto
  3. 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.conf as is: it already enables persistence, so retained messages (link state, backlight, WiFi signal) survive a broker restart.

  4. 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
  5. Allow MQTT from the LAN only.

    If ufw is 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
  6. 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/LCD from 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.

TopicPayloadRetained
statusonline / offline (last will)yes
wifi/rssisignal in dBm, e.g. -57yes
button/<1-3>pressedno
radon/<id>/state{"link":1,"radon":85.9,"backlight":"on"}yes
dtsu666/<id>/statepower, energy_import/export, voltage_l1–l3, current_l1–l3yes
sdm120/<id>/statepower, energy_import/export, voltage, currentyes
radon/<id>/backlight/seton / off (command to the bridge)no
Arduino Uno · RD200M · 16×2 LCD

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.

Radon sensor wiring: RD200M to Uno D2/D3, LCD on I2C A4/A5, MAX485 on D7/D8/D9, ID straps D10–D12 to D13 RD200M radon module TX RX Supply per RD200M datasheet (not recorded in this project) Arduino Uno D2 (RX) D3 (TX) A4 SDA A5 SCL D7 D8 D9 D10 D11 D12 D13 TX → D2 RX ← D3 · 19200 LCD 16×2 I2C 0x27 SDA SCL MAX485 DE+RE RO DI A B A B to bus ID straps: each jumper to D13 sets a bit. D10 = 1, D11 = 2, D12 = 4. All three = ID 7. 5V / GND LCD and MAX485 VCC/GND from the Uno
Pins as built. The RS-485 A and B lines run to every node on the radon bus and to the radon bridge.
SignalUno pinNotes
RD200M TX → UnoD2SoftwareSerial RX, 19200 baud
Uno → RD200M RXD3SoftwareSerial TX
LCD SDA / SCLA4 / A5I2C address 0x27
MAX485 DE + RE (tied)D7HIGH = transmit
MAX485 RO → UnoD8SoftwareSerial RX, 9600 8N1
Uno → MAX485 DID9SoftwareSerial TX
ID strapsD10–D12 ↔ D13read once at boot
  1. 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.

  2. 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.

  3. 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 Ω.

  4. Add the ID to the radon bridge in devices.h (see Wemos bridge), for example {5, DeviceType::Radon}.

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 D1 Mini Pro · ESP8266 · MAX485

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.

Wemos bridge wiring: MAX485 on D0/D1/D2, buttons on D5/D6/D7 with pull-ups, status LED on D4 Wemos D1 Mini Pro D0 D1 D2 3V3 D4 D5 D6 D7 GND MAX485 DE+RE RO DI A B A B GND common with MAX485 3V3 LED + 220–1000 Ω to 3V3 active low: D4 LOW = on BTN1 BTN2 BTN3 10 kΩ pull-ups buttons switch to GND
The firmware also enables the ESP8266's internal pull-ups on D5–D7 as a backup, but fit the external 10 kΩ resistors.
Check the MAX485 output level. ESP8266 pins do not tolerate 5 V. If the MAX485 module runs on 5 V and drives RO at 5 V, use a 3.3 V-compatible module or a level shifter on RO.
SignalWemos pinNotes
MAX485 DE + RE (tied)D0HIGH = transmit
MAX485 RO → WemosD1SoftwareSerial RX, must be 3.3 V
Wemos → MAX485 DID2SoftwareSerial TX
Status LEDD4active low; blinks on polls, errors and presses
Buttons 1 / 2 / 3D5 / D6 / D7to GND, 10 kΩ pull-up to 3V3
USB serialRX / TXkept free for the debug log, 115200 baud
  1. Create the bridge's secrets.h.
    cd ~/arduino/arduino/WemosD1MiniRS485ModbusMqtt
    cp secrets.example.h secrets.h   # gitignored, never commit it
    SettingRadon bridgePower bridge
    WIFI_SSID, WIFI_PASSWORDyour WiFi network
    MQTT_HOST<broker-ip>, the broker's fixed IP
    MQTT_USERNAME, MQTT_PASSWORDthe wemos user
    MQTT_CLIENT_IDhome-esp8266-rs485-01home-esp8266-rs485-02
    BRIDGE_ID"radon""power"
    BRIDGE_PROFILEBRIDGE_PROFILE_RADONBRIDGE_PROFILE_POWER

    BRIDGE_ID becomes the topic prefix rs485/<BRIDGE_ID>/. Don't change it after pairing in Homey. The profile sets the bus format: radon 8N1, power 8N2.

  2. 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.

  3. 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.

Chint DTSU666 · Eastron SDM120-M

Power meters

Three-phase and single-phase DIN-rail meters on their own bus, read by the power bridge every 10 seconds.

Power meter bus: power bridge MAX485 daisy-chained to DTSU666 terminals 15/16 and SDM120 terminals 10/9/8, 120 ohm at both ends Wemos bridge "power" + MAX485 A B signal ground / common reference 120 Ω 120 Ω DTSU666 3-phase · Modbus 9600 n.2 15 = A 16 = B SDM120-M 1-phase · set to 9600 8N2 10 = A 9 = B 8 = GND
Daisy-chain the meters with twisted pair: A to A, B to B, terminated with 120 Ω at the two physical ends. Terminal numbers are from the manufacturers' manuals.
Mains voltage. The meters' measuring side is connected to 230/400 V. The DTSU666 manual requires installation by qualified professionals with the inputs and supply disconnected; treat the SDM120 the same way.

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.

  1. DTSU666: switch it to Modbus.

    It ships in DL/T 645 mode, not Modbus. In the meter's menu (password 701, then Conn) set Addr to its ID and bAud to 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.

  2. 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 and mbpoll:

    RegisterValueMeaning
    4002929600 baud
    400193no parity, 2 stop bits
    40021new ID1–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.

  3. Set the real IDs in the power profile of devices.h (the placeholders are 10 and 11) and flash the power bridge.
  4. 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.

What each meter reports

ValueDTSU666 (3-phase)SDM120 (1-phase)
Power nowtotal W, negative when exportingW
Energy imported / exportedkWh / kWhkWh / kWh
VoltageL1, L2, L3 (V)V
CurrentL1, L2, L3 (A)A
Registers read0x2006–0x2013, 0x401E–0x40290x0000–0x000D, 0x0048–0x004B (FC 04)
Raw scalingV ×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 Pro · app com.radonsensor.mqtt

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.

  1. Point the app at the broker.

    Set MQTT_HOST in config.js to <broker-ip>, and create the gitignored env.json with the homey user. Keep it out of anything you share.

    {
      "MQTT_USERNAME": "homey",
      "MQTT_PASSWORD": "<password>"
    }
  2. 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
  3. 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.
    DriverPairs asShows
    Radon sensorRadon sensor 7 (radon)radon Bq/m³, LCD backlight on/off
    3-phase power meterPower meter 10 (power)power, energy in/out, V and A per phase
    1-phase power meterPower meter 11 (power)power, energy in/out, V, A
    RS-485 bridgeRS-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.