Short introduction

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:1.7.4.1
      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.

zwave-socat
zwave-socat
node01
node01
/dev/ttyNVT0
/dev/ttyNVT0
Emulates
Emulates
zwave-socat
zwave-socat
node02
node02
/dev/ttyNVT0
/dev/ttyNVT0
Emulates
Emulates
Runs
Runs
zwave-socat
zwave-socat
node03
node03
/dev/ttyNVT0
/dev/ttyNVT0
Emulates
Emulates
tcp://192.168.1.23:23
tcp://192.168.1.23:23
RazBerry Z-wave
RazBerry Z-wave
tcp://192.168.1.23:23
tcp://192.168.1.23:23
tcp://192.168.1.23:23
tcp://192.168.1.23:23
Runs
Runs
Runs
Runs
Architecture of the setup with socat emulated USB device

Conclusion

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.

But as time moves on newer, more exciting projects turn up. One of them is zwave-js/node-zwave-js, a Javascript based alternative for OpenZwave and able to read from a remote serial (docs)!

So again, to be continued!