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
