MODBUS and Home Assistant with just a Bash script, mbpoll and MQTT Discovery(Swegon CASA)

I have done some MODBUS interfaces before using Node-RED with both MODBUS TCP and RTU. But getting fond of Home Assistant(HA) with MQTT and autodiscovery it was time to challenge the Node-RED solution.

So no Node-RED and no extra software to babysit. One small Bash script, run from cron, that reads a Swegon CASA FTX over MODBUS RTU and shows up in Home Assistant as a ready-made device.

Intro
In my first post about the Swegon CASA, HRV (FTX), rotary heat exchanger and MODBUS I used Node-RED with the MODBUS nodes to read the data. In the second one, MODBUS RTU gateway, WiFi, Ethernet, WEB server, I put a small MODBUS server, also built in Node-RED, on the Raspberry Pi (RPi) next to the CASA, so my central RPi could fetch the registers with a plain HTTP request.

That works well, but it means that Node-RED has to be installed, updated and kept running on both ends. When the data is going to Home Assistant (HA) anyway, there is a simpler way: read the registers with a command line tool "mbpoll" and publish them with "mosquitto_pub" using MQTT Discovery. HA then creates the sensors by itself. The whole solution is one Bash script and a cron job. The same pattern as in my Lightweight DIY Linux Server Monitoring post, just with MODBUS as the data source.

What you need
  • A RPi, or any Linux box, connected to the CASA via the SEM interface and a USB to RS 485 converter, exactly as in the first post
  • An MQTT broker that HA is connected to, in my case Mosquitto within HA
  • HA with the MQTT integration and discovery enabled (default prefix is homeassistant)

Install the two tools:

sudo apt install mbpoll mosquitto-clients

"mbpoll" is a free command line MODBUS master. It is the open source cousin of the well known "modpoll", and it is found in the standard repositories, so no manual download is needed.

The serial port
Instead of "/dev/ttyUSB0", which can change to "ttyUSB1" after a reboot or when you plug in another USB device, use the persistent name of the converter:
ls -l /dev/serial/by-id/

It looks something like "usb-FTDI_FT232R_USB_UART_<serial number>-if00-port0" and it is tied to the converter itself. Please note that only one program at a time can use the serial port, so if you have a Node-RED MODBUS client running on the same device, stop it first.

Test with mbpoll
Before any scripting, check that the communication works. This reads the 12 temperature registers:

mbpoll -m rtu -a 1 -b 38400 -P none -d 8 -s 1 -t 3 -0 -r 6200 -c 12 -1 /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_xxxx-if00-port0

The flags are the same settings as in the first post, just in another form:
  • -m rtu -b 38400 -d 8 -s 1 -P none is MODBUS RTU, 38400 baud, 8 data bits, 1 stop bit, no parity
  • -a 1 is the unit id
  • -t 3 is input registers. Nice detail: mbpoll uses the same "3x / 4x" naming as Swegon does, so -t 3 is the Swegon 3x registers (FC 4 in Node-RED) and -t 4 is the 4x registers
  • -0 means that the first register is address 0, the same addressing as I use in the Node-RED flow. If your values look shifted by one register, that is the first thing to check
  • -r 6200 -c 12 is start register and number of registers, and -1 means poll once and exit

The output is one line per register, something like [6202]: 198.

Registers used
All are input registers (3x), read with the addresses below.

Supply air temp    6202 0.1 °C, signed
Exhaust air temp   6204 0.1 °C, signed
Extract air temp   6205 0.1 °C, signed
Outdoor temp       6210 0.1 °C, signed
Humidity           6213 %
Supply fan         6302 %
Exhaust fan        6303 %
Post heater        6317 %
Rotor              6331 %
Mode              6433 0 Stopped, 1 Traveling, 2 Away, 3 Home, 4 Home +, 5 Boost,
                        6 Fireplace

The temperatures are 16 bit signed values in tenths of a degree, so a value above 32767 is below zero. That is handled with one line of awk.

The heat recovery efficiency is not a register, it is calculated from four of the temperatures, with the same formula as in my Node-RED flow, and published in real percent. It is set to 0 when the unit or the rotor is stopped, or when the result is unreasonable (negative, above 120 % or a division by zero).
The script
The reading part is a small function that returns one value per line, and the values are put in an array:
read_regs() {  # start, count -> one value per line
    mbpoll -m rtu -a "$UNIT" -b "$BAUD" -d 8 -s 1 -P none -t 3 -0 \
           -r "$1" -c "$2" -1 -o 2 "$PORT" 2>/dev/null \
        | awk '/^\[[0-9]+\]:/ {print $NF}'
}

mapfile -t TEMP < <(read_regs 6200 12)

If a read fails, the script publishes "offline" on the status topic and exits. Otherwise all values are published in one go.

One JSON message instead of many topics
Instead of one MQTT topic per value, everything is published as a single JSON message on "swegonFtx/state:"
{"temp_outdoor":4.2,"temp_supply":19.8,"temp_extract":21.5,"temp_exhaust":6.1,"efficiency":84.3,"fan_supply":45,"fan_exhaust":45,"rotor":100,"heater":0,"humidity":38,"mode":"Home"}

That is one connection to the broker instead of a dozen, and all values belong to the same measurement. The price is that every sensor needs a value_template in the discovery message, but that is just one extra line per sensor.

MQTT Discovery, the generic part
This is the part that can be reused for any Bash script that publishes to MQTT. With discovery, HA creates the entities from a retained configuration message on "homeassistant/sensor/<node>/<sensor>/config" so there is nothing to configure in HA.

The whole trick is one small helper function:

disc() {  # key, name, extra json (starts with a comma)
    mosquitto_pub "${MQTT_ARGS[@]}" -r -q 1 \
        -t "homeassistant/sensor/$NODE_ID/$1/config" \
        -m "{\"name\":\"$2\",\"unique_id\":\"${NODE_ID}_$1\",\"state_topic\":\"$BASE/state\",\"value_template\":\"{{ value_json.$1 }}\",\"availability_topic\":\"$BASE/status\",\"expire_after\":600$3,\"device\":$DEVICE}"
}

disc temp_outdoor "Outdoor temperature" ',"device_class":"temperature","unit_of_measurement":"°C","state_class":"measurement"'

To use it for something else, change the names, the reading part and the list of disc lines, and you are done. All sensors share one device block, so they end up under one device, "Swegon FTX", in HA. Details about the discovery format are found in the HA MQTT documentation.

Discovery once a day, or when the script changes
The configuration messages are retained, so they survive a restart of HA and don't have to be sent every time. The script keeps an md5 sum of itself and a timestamp in a small state file, and sends the discovery messages
  • the first time it runs
  • when the script file has changed, for example when you add a sensor
  • every 24 hours

It can also be forced with "./swegonFtx_HA.sh --discovery"

Availability
Since the script is not running between the cron jobs, there is no MQTT Last Will here. Instead the script publishes "online" on "swegonFtx/status" after every successful run, and "offline" if the MODBUS read fails. All sensors also have expire_after: 600, so if cron stops, or the whole RPi is down, the entities become unavailable in HA after ten minutes instead of showing old values.

Cron
The script is run every second minute. It takes a lock with flock, so two runs never use the serial port at the same time.
*/2 * * * * /home/pi/swegonFtx_HA.sh
The user running it must be a member of the "dialout" group to be allowed to use the serial port.

sudo usermod -aG dialout $USER

Result
After the first run, a new device, Swegon FTX, shows up under Settings, Devices & services, MQTT, with these entities:
  • Outdoor, supply, extract and exhaust air temperature
  • Heat recovery efficiency
  • Supply fan, exhaust fan, rotor and post heater in %
  • Humidity
  • Mode (Stopped, Traveling, Away, Home, Home +, Boost, Fireplace)

From there it is standard HA: history graphs, dashboards and automations.

Remarks

  • The script opens and closes the serial port for each read, which is no problem with a two minute interval. With a much faster poll rate a permanently running solution, like Node-RED, would be a better choice.
  • With Node-RED you get a live dashboard with just a few nodes. Here you use the HA dashboards and history instead.
  • The "mbpoll" options can be a bit different between versions, so run the test command above first and check that the output looks as expected before you start the script.
  • Full script: Gist link !
  • "mbpoll" could also be used with MODBUS TCP so it could be something like this 
mbpoll -m tcp -a 1 -p 502 -t 3 -0 -r 6433 -c 1 -1 192.168.nn.nn


Conclusion
With "mbpoll, mosquitto_pub" and some Bash, the CASA is reached from HA with no Node-RED at all, and with MQTT Discovery the sensors create themselves. Less software to install, update and keep running, and the discovery helper can be reused for whatever else you want to push into HA from a Bash script.

Comments

Popular posts, last 30 days

Clone the Raspberry boot disk

Installing Ubuntu 24.04 or 26.04 on an old MacBook Air(2011)

SeaTalk1 to NMEA 0183 converter, DIY

Raspberry Pi OS Lite with GUI, RDP, VNC, Chromium

IKEA Bäve dimmer hack II

iCloud drive sync, or mount, with a Linux, Ubuntu, device

Raspberry Pi watchdog made simple

Home Assistant with VirtualBox or Proxmox or Linux KVM ???

IVT, Bosch heat pump easy integration, Home Assistant