Connect RFXCom/Zwave and DSMR USB over the network to a dockerized Home-Assistant container

Introduction

In my quest to containerize every application that I use at home I stumbled across a problem. As a domotica controller I use Home-Assistant as the central controller. From Home-Assistant I wanted to control all my devices based on z-wave, hue, rfx and DSMR (Dutch Smart Meter Requirements) but I didn’t want to connect them to my Synology DS918+. The reason for this is that my Synology is setup on the attic and the rfxtrx signal can’t reach the whole house from there. The other reason is that the DSMR is directly connected to the smartmeter which is in the maintenace closet on the first floot.

My first attempt was to place a raspberry PI with HASSIO and connected all the usb devices to it. I tried to synchronize everything with mqmt but I came across all kind of synchronization problems and slow response when using z-wave.

When I was tired of trying to fix it I found another solution based on ser2net and socat. The solution consists of using the raspberry pi as a network USB host server where I configure ser2net to publish the usb devices over TCP/IP. On my synology I use a custom container based on the official Home-Assistant docker container. As a start I used the forepe/homeassistant-socat docker container from Peter Foreman as an example. Peter created a docker container containing the following software but also created a few scripts that makes the container aware if the connection with the network usb host, mysql server or a mqmt server is broken and automatically restart home-assistant in the container:

  • Home-assistant
  • Socat client
  • MQTT client
  • MySQL client

The Setup

I used Peter Foremans files from github. I changed and added some files.

  • Add ser2net application so DSMR can connect directly from Home-Assistant
  • Added timestamps to the logging
  • Added a rfxcom socat connect file
  • Added extra environment variables to the docker-compose file
  • Added a mariadb container to docker-compose for home-assistant recorder

Raspberry PI network USB server

I used a Raspberry Pi 2 with the standard raspbian Jessie image. I installed the ser2net packages by starting these commands:

sudo apt-get update
sudo apt-get install -y ser2net

I could have used a docker image like this  on the PI, but I will only use the PI as a USB host so I thought this is less complex.

After all the files are installed we have to edit the ser2net.conf.

sudo nano /etc/ser2net.conf

Add the following lines at the end. This will configure the configuration as followed:

  • Aoetec zwave usb connected to /dev/ttyACM0 can be reached on TCP:x.x.x.x:4000
  • rfxtrx usb connected to /dev/ttyUSB0 can be reached on TCP:x.x.x.x:5000
  • DSMR usb connected to /dev/ttyUSB1 can be reached on TCP:x.x.x.x:6000

As you can see in the code block below every device has a different configuration. This is needed for the device to work correctly. I found the configuration with help from google and trial and error.

#ZWAVE
4000:raw:0:/dev/ttyACM0:115200 8DATABITS NONE 1STOPBIT

#RFXCom - Settings found on https://community.home-assistant.io/t/rfxcom-network-support/9384
#3334:raw:15:/dev/ttyUSB0:38400 8DATABITS NONE 1STOPBIT
5000:raw:0:/dev/ttyUSB0:38400 8DATABITS NONE 1STOPBIT

#DSMR v2.2
#6000:raw:600:/dev/ttyUSB1:9600 EVEN 1STOPBIT 7DATABITS XONXOFF LOCAL -RTSCTS

#DSMR v4
6000:raw:0:/dev/ttyUSB1:115200 NONE 1STOPBIT 8DATABITS XONXOFF LOCAL -RTSCTS

To make ser2net automatically start at boot we have to edit /etc/rc.local.

sudo nano /etc/rc.local

And add the following line at the end

/usr/local/sbin/ser2net -n

Home-Assistant docker container

The docker container is a custom built container from the official Home-Assistant container. To build this container the Dockerfile below is used. This will create a new docker container based on the homeassistant but with added applications socat, ser2net, a mysql client and a mqtt client. It will also copy the custom run scripts to start the socat connections to the network usb server and home-assistant (re)start scripts.

The following folder structure is used:

  • homeassistant/docker-compose.yaml
  • homeassistant/hass/docker/Dockerfile
  • homeassistant/hass/docker/runwatch/100.socat-zwave.enabled.sh
  • homeassistant/hass/docker/runwatch/101.socat-rfxcom.enabled.sh
  • homeassistant/hass/docker/runwatch/200.home-assistant.enabled.sh
  • homeassistant/hass/docker/runwatch/run.sh
  • homeassistant/hass/volumes/config
  • homeassistant/mariadb_hass/volumes/data

Dockerfile

FROM homeassistant/home-assistant:latest
LABEL maintainer="Christian Douma <christian.douma@hotmail.com>"

# Install socat (for zwave, rfxcom), ser2net (for DSMR), mosquitto-client (for mqqt connection available check) and mysql-client (for mysql connection available check)
RUN apt-get update && \
    apt-get -y install socat ser2net mosquitto-clients mysql-client && \
    apt-get clean
RUN mkdir /runwatch

# Copy all the run and check files
COPY runwatch/* /runwatch/
CMD [ "bash","/runwatch/run.sh" ]

100.socat-zwave.enabled.sh

#!/usr/bin/env bash

if [[ -z "${SOCAT_ZWAVE_TYPE}" ]]; then
  SOCAT_ZWAVE_TYPE="tcp"
fi
if [[ -z "${SOCAT_ZWAVE_LOG}" ]]; then
  SOCAT_ZWAVE_LOG="-lf \"$SOCAT_ZWAVE_LOG\""
fi
if [[ -z "${SOCAT_ZWAVE_LINK}" ]]; then
  SOCAT_ZWAVE_LINK="/dev/zwave"
fi

BINARY="socat"
PARAMS="$INT_SOCAT_LOG-d pty,link=$SOCAT_ZWAVE_LINK,raw,user=root,mode=777 $SOCAT_ZWAVE_TYPE:$SOCAT_ZWAVE_HOST:$SOCAT_ZWAVE_PORT"

######################################################

CMD=$1

if [[ -z "${CONFIG_LOG_TARGET}" ]]; then
  LOG_FILE="/dev/null"
else
  LOG_FILE="${CONFIG_LOG_TARGET}"
fi

case $CMD in

describe)
    echo "Sleep $PARAMS"
    ;;

## exit 0 = is not running
## exit 1 = is running
is-running)
    if pgrep -f "$BINARY $PARAMS" >/dev/null 2>&1 ; then
        exit 1
    fi
    # stop home assistant if socat is not running 
    if pgrep -f "python -m homeassistant" >/dev/null 2>&1 ; then
        now=$(date +"%T")
        echo "$now ## stopping home assistant since socat is not running"
        kill -9 $(pgrep -f "python -m homeassistant")
    fi
    exit 0
    ;;

start)
    now=$(date +"%T")
    echo "$now ## Starting... $BINARY $PARAMS" >> "$LOG_FILE"
    $BINARY $PARAMS 2>$LOG_FILE >$LOG_FILE &
    # delay other checks for 5 seconds
    sleep 5
    ;;

start-fail)
    now=$(date +"%T")
    echo "$now ## Start failed! $BINARY $PARAMS"
    ;;

stop)
    now=$(date +"%T")
    echo "$now ## Stopping... $BINARY $PARAMS"
    kill -9 $(pgrep -f "$BINARY $PARAMS")
    ;;

esac

101.socat-rfxcom.enabled.sh

#!/usr/bin/env bash

if [[ -z "${SOCAT_RFXCOM_TYPE}" ]]; then
  SOCAT_RFXCOM_TYPE="TCP"
fi
if [[ -z "${SOCAT_RFXCOM_LOG}" ]]; then
  SOCAT_RFXCOM_LOG="-lf \"$SOCAT_RFXCOM_LOG\""
fi
if [[ -z "${SOCAT_RFXCOM_LINK}" ]]; then
  SOCAT_RFXCOM_LINK="/dev/rfxcom"
fi

BINARY="socat"
PARAMS="pty,link=${SOCAT_RFXCOM_LINK},b38400,raw,user=root,mode=777 ${SOCAT_RFXCOM_TYPE}:${SOCAT_RFXCOM_HOST}:${SOCAT_RFXCOM_PORT}"

######################################################

CMD=$1

if [[ -z "${CONFIG_LOG_TARGET}" ]]; then
  LOG_FILE="/dev/null"
else
  LOG_FILE="${CONFIG_LOG_TARGET}"
fi

now=$(date +"%T")
case $CMD in

describe)
    echo "Sleep $PARAMS"
    ;;

# exit 0 = is not running
# exit 1 = is running
is-running)
    if pgrep -f "$BINARY $PARAMS" >/dev/null 2>&1 ; then
        exit 1
    fi
    # stop home assistant if socat is not running 
    if pgrep -f "python -m homeassistant" >/dev/null 2>&1 ; then
        echo "$now ## stopping home assistant since socat rfxcom is not running"
        kill -9 $(pgrep -f "python -m homeassistant")
    fi
    exit 0
    ;;

start)
    echo "$now ## Starting... $BINARY $PARAMS" >> "$LOG_FILE"
    $BINARY $PARAMS 2>$LOG_FILE >$LOG_FILE &
    # delay other checks for 5 seconds
    sleep 5
    ;;

start-fail)
    echo "$now ## Start failed! $BINARY $PARAMS"
    ;;

stop)
    echo "$now ## Stopping... $BINARY $PARAMS"
    kill -9 $(pgrep -f "$BINARY $PARAMS")
    ;;

esac

200.home-assistant.enabled.sh

#!/usr/bin/env bash

BINARY="python"
PARAMS="-m homeassistant --config /config"

if [[ -z "${MYSQL_PORT}" ]]; then
  MYSQL_PORT=3306
fi

######################################################

CMD=$1

if [[ -z "${CONFIG_LOG_TARGET}" ]]; then
  LOG_FILE="/dev/null"
else
  LOG_FILE="${CONFIG_LOG_TARGET}"
fi

case $CMD in

describe)
    echo "Sleep $PARAMS"
    ;;

## exit 0 = is not running
## exit 1 = is running
is-running)
    if pgrep -f "$BINARY $PARAMS" >/dev/null 2>&1 ; then
        exit 1
    fi
    exit 0
    ;;

start)
    now=$(date +"%T")
    echo "$now ## Starting... $BINARY $PARAMS" >> "$LOG_FILE"
    echo "$now ## Starting... $BINARY $PARAMS"
    echo "$now ## Checking socat..."
    SOCATCHECK=`pgrep -f "socat"`
    now=$(date +"%T")
    if [ "${SOCATCHECK}" = "" ] >/dev/null 2>&1 ; then
        echo "$now ##### socat is not running, skipping start of home assistant"
        exit 1
    else
        echo "$now ##### Socat is running"
    fi


    if [ "${MYSQL_HOST}" != "" ]; then
        now=$(date +"%T")
        echo "$now ## Checking mysql..."
        MYSQLCHECK=`mysql -h ${MYSQL_HOST} -u ${MYSQL_USER} -P ${MYSQL_PORT} -p${MYSQL_PASS} -e';'`
        now=$(date +"%T")
        if [ $? != 0 ]; then
            echo "$now ##### MySQL is not running, skipping start of home assistant"
            exit 1
        else
            echo "$now ##### MySQL is running"
        fi
    fi

    if [ "${MQTT_HOST}" != "" ]; then
        now=$(date +"%T")
        echo "$now ## Checking mqtt..."
        MQTTCHECK=`mosquitto_pub -h ${MQTT_HOST} -u ${MQTT_USER} -P ${MQTT_PASS} -n -t /test`
        now=$(date +"%T")
        if [ $? != 0 ]; then
            echo "$now ##### MQTT is not running, skipping start of home assistant"
            exit 1
        else
            echo "$now ##### MQTT is running"
        fi
    fi

    # Everything is running, start homeassistant
    cd /usr/src/app
    $BINARY $PARAMS 2>$LOG_FILE >$LOG_FILE &
    now=$(date +"%T")
    echo "$now ## Started... $BINARY $PARAMS"
    exit 0
    ;;

start-fail)
    now=$(date +"%T")
    echo "$now ## Start failed! $BINARY $PARAMS"
    ;;

stop)
    now=$(date +"%T")
    echo "$now ## Stopping... $BINARY $PARAMS"
    cd /usr/src/app
    kill -9 $(pgrep -f "$BINARY $PARAMS")
    ;;

esac

Run.sh

#!/usr/bin/env bash
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
B=$(which bash)

if [[ -z "${DEBUG_VERBOSE}" ]]; then
  CONFIG_DEBUG_VERBOSE=0
else
  CONFIG_DEBUG_VERBOSE="${DEBUG_VERBOSE}"
fi

if [[ -z "${PAUSE_BETWEEN_CHECKS}" ]]; then
  CONFIG_PAUSE_BETWEEN_CHECKS=2
else
  CONFIG_PAUSE_BETWEEN_CHECKS="${PAUSE_BETWEEN_CHECKS}"
fi

if [[ -z "${LOG_TARGET}" ]]; then
  CONFIG_LOG_TARGET="/dev/stdout"
else
  CONFIG_LOG_TARGET="${LOG_TARGET}"
fi

echo "---- Config:"
echo "DEBUG_VERBOSE        $CONFIG_DEBUG_VERBOSE"
echo "PAUSE_BETWEEN_CHECKS $CONFIG_PAUSE_BETWEEN_CHECKS"
echo "LOG_TARGET           $CONFIG_LOG_TARGET"
echo "---- Files:"
cd "$DIR"
for FILE in `ls *.enabled.sh | sort`; do
  echo "> $FILE"
done
echo "===="
echo ""

# trap ctrl-c and call ctrl_c()
trap ctrl_c INT

function ctrl_c() {
    echo "---- STOPPING PROCESSES IN REVERSE ORDER"
    cd "$DIR"
    for FILE in `ls *.enabled.sh | sort -r`; do
        echo "==== $FILE"
        $B "$FILE" stop
        echo -e '\b\b\b\b OK'
    done
    exit 0
}

export CONFIG_LOG_TARGET
echo "" > "$CONFIG_LOG_TARGET"
while :
do
    cd "$DIR"
    for FILE in `ls *.enabled.sh | sort`; do
        $B "$FILE" is-running
        IS_RUNNING=$?
        if [ $IS_RUNNING == 0 ]; then
            $B "$FILE" start
            $B "$FILE" is-running
            IS_RUNNING=$?
            if [ $IS_RUNNING == 0 ]; then
                $B "$FILE" start-fail
            fi
        fi
    done
    sleep $CONFIG_PAUSE_BETWEEN_CHECKS
done

To build and start the container I used a docker-compose.yaml.

---
version: "3"

services: 
  #Homeassistant database
  mariadb_hass:
    container_name: mariadb_hass
    image: mariadb:latest
    restart: unless-stopped
    network_mode: bridge
    # networks:
    #   - homeassistant
    expose:
      - 3306
    ports:
      - 33060:3306
    environment:
      TZ: Europe/Amsterdam
      MYSQL_ROOT_PASSWORD: **********
      MYSQL_DATABASE: hass
      MYSQL_USER: hass
      MYSQL_PASSWORD: **********
    volumes:
      - ./mariadb_hass/volumes/data:/var/lib/mysql
  
  #Homeassistant instance with ser2net support for usb devices from a raspberry pi
  hass:
    build: ./hass/docker
    container_name: homeassistant
    #image: forepe/homeassistant-socat:latest
    restart: unless-stopped
    depends_on: 
      - mariadb_hass
    network_mode: host    #Needed for discovery of devices to work
    environment:
      - TZ=Europe/Amsterdam
      #HAS
      - DEBUG_VERBOSE=1 #Set to 1 to see more information Default: 0
      - PAUSE_BETWEEN_CHECKS=2 #In seconds, how much time to wait between checking running processes. Default: 2
      - LOG_TARGET=/config/socat-log.log #Path to log file. Omit to write logs to stdout. Default: stdout
      - SOCAT_ZWAVE_TYPE="TCP"
      - SOCAT_ZWAVE_HOST="x.x.x.x" #IP address of the network USB server
      - SOCAT_ZWAVE_PORT="4000" #Where socat should connect to. Will be used as tcp://192.168.5.5:7676
      - SOCAT_ZWAVE_LINK="/dev/zwave" #What the zwave device should be mapped to. Use this in your homeassistant configuration file.
      - SOCAT_RFXCOM_TYPE="TCP"
      - SOCAT_RFXCOM_HOST="x.x.x.x" #IP address of the network USB server
      - SOCAT_RFXCOM_PORT="5000" #Where socat should connect to. Will be used as tcp://192.168.5.5:7676
      - SOCAT_RFXCOM_LINK="/dev/rfxcom" #What the zwave device should be mapped to. Use this in your homeassistant configuration file.
      #MySQL options
      - MYSQL_HOST=x.x.x.x #IP address of the mariadb container
      - MYSQL_USER=hass
      - MYSQL_PORT=33060
      - MYSQL_PASS=**********
      #MQTT options
      # - MQTT_HOST=x.x.x.x #IP address of the MQTT server
      # # - MQTT_USER="hass"
      # # - MQTT_PASS="**********"
      # - MQTT_PORT="1883"
    volumes:
      - ./hass/volumes/config:/config
      - /etc/localtime:/etc/localtime:ro

In Home-Assistant you need to configure the zwave, rfxtrx and DSMR. You can do that by adding the following lines to the Home-Assistant configuration.yaml

zwave:
  usb_path: '/dev/zwave'  #usb2ip setup
  #debug: false
  #refresh_value: true
  #polling_intensity: 1```
  polling_interval: 60000
  autoheal: true

rfxtrx:
  device: /dev/rfxcom
  debug: true
  dummy: false

sensor:
  - platform: dsmr
    port: 6000 # (Optional): Serial port to which Smartmeter is connected (default: /dev/ttyUSB0 (connected to USB port)). For remote (i.e. ser2net) connections, use TCP port number to connect to (i.e. 2001)
    host: x.x.x.x #host string (Optional): Host to which Smartmeter is connected (default: ‘’ (connected via serial or USB, see port)). For remote connections, use IP address of host to connect to (i.e. 192.168.1.13)
    dsmr_version: 4 #(Optional): Version of DSMR used by meter, choices: 2.2, 4 (default: 2.2)

  - platform: rfxtrx
    automatic_add: true

To start and build the containers run the command:

sudo docker-compose up -d --build
Docker container log at startup
Home-Assistant DSMR sensors
Home-Assistant rfxtrx discovered sensors
Home-Assistant zwave discoverd devices

Laat een reactie achter

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *