The adventure to a remote Z-Wave USB - Part 2
This is part two in the Remote Z-Wave series which is the last part of my quest to decouple my Z-Wave controller from my Kubernetes nodes. For part one and some more background on why I went on this journey see here: The adventure to a remote Z-Wave USB - Part 1
At the end of this post everything is working as supposed too, so that is a massive win! But there is a part three coming since there somehow always pops up a better solution when you finished something.
Where we left off
The first part ended with the cry for help to the OpenZWave developers since I was completly stuck. I updated my remote serial issue on Github and the response what what I expected, something between OpenZWave and Zwave adapter isn’t working properly:
The data that OZW is receiving is corrupt. So it’s still something at your CUSE/ESP level. I’d go back to validating that really is passing packets etc without drops and so on. – @Fishwaldo
So what is between OpenZWave and the remote serial? The tty-nvt container and that is where I got stuck for a long time. My search on the internet had that as one of the only options that can do remote serial to an emulated hardware device. So if it does not work, what will?
The working setup
Since I work in the IT my whole day I am searching for answers on the internet, and one day I was researching something unrelated to this problem and stumbled up on this question:
How can I set up a “USB proxy” for /dev/ttyUSB0 over the network? – Eduard Florinescu
The first answer was exactly what I was looking for!
Then on 1st PC you can connect to 2nd PC with socat and provide the data on a pseudo terminal /dev/ttyVUSB0 for your application: socat PTY,raw,echo=0,link=/dev/ttyVUSB0 tcp:<ip_of_pc2>:8888 – FloHimself
This looked promising, Socat can create a hardware device just like tty-nvt. So I went to work and setup a socat docker container: LANsible/docker-socat.
In the usual fashion completly static compiled and in a scratch container with one unpriviliged user. This means no shell and nothing else then the static binary, this reduces the attack surface since there is nothing to use. This combined with the
readOnlyRootFilesystem option on Kubernetes, which makes the whole container fileystem readonly improves the security enormously.
For the Kubernetes deployment I created a
Daemonset, this is a deployment that will run on each node, comparable with a systemd service installed on the OS.
--- spec: containers: - name: zwave-socat image: lansible/socat:184.108.40.206 imagePullPolicy: IfNotPresent args: # ptmx: Establishes communication with the sub process using a pseudo terminal created by opening /dev/ptmx or /dev/ptc instead of the default (socketpair). # -d -d -d: Prints fatal, error, warning, notice, info, and debug messages - -d - -d - -d # link: Generates a symbolic link that points to the actual pseudo terminal (pty) # raw, echo: raw and echo set the consolecqs and ttyS0cqs terminal parameters to practicable values # wait-slave: Blocks the open phase until a process opens the slave side of the pty # https://linux.die.net/man/1/socat - pty,link=/dev/ttyNVT0,raw,echo=0,group=dialout,mode=660 - tcp:192.168.1.23:23 securityContext: # run as root and enable privileged # both needed to read the tty device on the host # Related: https://github.com/kubernetes/kubernetes/issues/60748 privileged: true readOnlyRootFilesystem: true resources: requests: memory: 100Mi volumeMounts: - name: dev mountPath: /dev volumes: - name: dev hostPath: path: /dev
So in the end this was the result. Each node has an emulated TTY device which connects to the remote adapter. This makes it possible to let the Zwave2MQTT pod run and failover between nodes and still connect to an USB device.
This setup worked somewhat reasonable for a year or so. Once in a while I needed to restart the Zwave2MQTT container because it somehow lost the connection. It still is a workaround for the shortcoming that OpenZwave couldn’t read from a remote serial and expects a host device.
So again, to be continued!