How to gain Root to Futurehome Hub & Activate Z-Wave JS UI with Futurehome App as a Fallback

Hi,
Thought I would share this little hack I made.
(apologies for the messy formating, this was copied from my project notes file)

Here is a guide I have written on how to gain access to the Linux system on your Futurehome hub (tested on v1). It includes scripts that will allow you to access the onboard Z-Wave chip, enabling you to include all devices into a Z-Wave JS UI instance without the need to exclude and re-include everything.

As a bonus, there is also a service that monitors the Z-Wave JS server. If it goes down, the hub will automatically return control of the Z-Wave chip to Futurehome until the server comes back online.

Additionally, a web endpoint is created, providing manual control and status of this modification.

If this is useful to anyone, here is the code. Be sure to review it carefully and ask if you have any uncertainties.

The process consists of two parts:

  1. Rooting the device
  2. Logging into the newly created SSH shell and copying/pasting the second part of the script, which will configure everything for you automatically.

ROOT ACCESS

## ROOT FUTUREHOME Hub (v1)
# this will provide SSH access
# be sure to run line by line and find the right /dev/*mount* and insert sshkey

# Define your public SSH key (ssh-keygen)
$SSH_KEY = ""

# Connect to rpi CM1 module: open device and connect micro usb to linux machine and then power
sudo apt update
sudo apt install usbutils git libusb-1.0-0-dev
git clone --depth=1 https://github.com/raspberrypi/usbboot
cd usbboot
make
sudo ./rpiboot

# Mount filesystem, find mount using 'lsblk'
sudo mount /dev/sda2 /mnt/rpi

# Enable SSH
sudo ln -s /lib/systemd/system/ssh.service /mnt/rpi/etc/systemd/system/multi-user.target.wants/ssh.service
sudo sed -i '/^[[:space:]]*exit 0/i iptables -I INPUT -p tcp --dport 22 -j ACCEPT' /mnt/rpi/etc/rc.local

# Insert SSH key
sudo mkdir -p /mnt/rpi/root/.ssh
echo $SSH_KEY | sudo tee -a /mnt/rpi/root/.ssh/authorized_keys
sudo chmod 700 /mnt/rpi/root/.ssh
sudo chmod 600 /mnt/rpi/root/.ssh/authorized_keys
sudo chown -R 0:0 /mnt/rpi/root/.ssh

sudo umount /mnt/rpi
  • disconnect USB and reboot
  • CONNECT to cube.local:22 using SSH and certificate defined erlier, login as ‘root’ (no password needed)

CONFIGURE / INSTALL SERVICES

#### PASTE >>>


# CONFIG
mkdir /etc/ser2net-server
tee /etc/ser2net-server/config.sh <<EOF
AUTOSTART=yes
FALLBACK=yes
PORT=8091
HOST=
EOF


# INSTALL ser2net
wget -O /tmp/ser2net_2.9.1-1_armhf.deb \
  http://legacy.raspbian.org/raspbian/pool/main/s/ser2net/ser2net_2.9.1-1_armhf.deb
dpkg -i /tmp/ser2net_2.9.1-1_armhf.deb
echo "3333:raw:0:/dev/SER2NET:115200 8DATABITS NONE 1STOPBIT" >> /etc/ser2net.conf
service ser2net restart
sed -i '/^[[:space:]]*exit 0/i iptables -I INPUT -p tcp --dport 3333 -j ACCEPT' /etc/rc.local


## INSTALL ser2net-fallback script
cat <<'EOF' >/usr/local/bin/ser2net-fallback
#!/bin/bash

while true; do
  source /etc/ser2net-server/config.sh
  
  if [ -z "$HOST" ] || [ -z "$PORT" ]; then
    sleep 60
    continue
  fi
  
  if [ "$FALLBACK" != "yes" ]; then
    break
  fi
  
  HOST_UP=$(curl -s --head "http://$HOST:$PORT" > /dev/null && echo "true" || echo "false")
  SER2NET=$(ls -l /dev/SER2NET &>/dev/null && echo 'true' || echo 'false')
  FUTUREHOME=$(ls -l /dev/futurehome/Z-Wave &>/dev/null && echo 'true' || echo 'false')

  if [ "$HOST_UP" = "true" ] && [ "$SER2NET" = "false" ]; then
    /usr/local/bin/ser2net-control start
  
  elif [ "$HOST_UP" = "false" ] && [ "$FUTUREHOME" = "false" ]; then
    /usr/local/bin/ser2net-control pause
  fi
  
  sleep 60
done
EOF
chmod +x /usr/local/bin/ser2net-fallback


## INSTALL ser2net-control script
cat <<'EOF' >/usr/local/bin/ser2net-control
#!/bin/bash
source /etc/ser2net-server/config.sh

if [ "$FALLBACK" = "yes" ] && [ "$1" = "start" ]; then
  if ! pgrep -f "ser2net-fallback" >/dev/null; then
    /usr/local/bin/ser2net-fallback &
  fi
fi

case "$1" in
  boot)
    if [ "$AUTOSTART" = "yes" ]; then
      /usr/local/bin/ser2net-control start
    else
      /usr/local/bin/ser2net-control stop
    fi
    ;;
  
  status)
    echo "FUTUREHOME GATEWAY"
    echo "==== STATUS ===="
    
    if [ -n "$HOST" ] && [ -n "$PORT" ]; then
      if pgrep -f "ser2net-fallback" >/dev/null; then
        echo "FALLBACK: running"
        #STATUS_SER="paused"
        #STATUS_FIMP="active (fallback)"
      else
        echo "FALLBACK: stopped"
      fi
      
      curl -s --head "http://$HOST:$PORT" >/dev/null && \
        echo "FALLBACK TEST: success" || echo "FALLBACK TEST: host unreachable"
    else
      echo "FALLBACK: missing HOST/PORT"
    fi
    #TODO: paused, fallback ...
    echo "SER2NET: $(ls -l /dev/SER2NET &>/dev/null && echo 'active' || echo 'deactivated')"
    echo "FUTUREHOME: $(ls -l /dev/futurehome/Z-Wave &>/dev/null && echo 'active' || echo 'deactivated')"

 
    echo "==== CONFIG ===="
    cat /etc/ser2net-server/config.sh
    ;;
    
  start)
    rm -f /dev/futurehome/Z-Wave &>/dev/null || true
    fuser -k /dev/ttyS0 &>/dev/null || true

    ln -s /dev/ttyS0 /dev/SER2NET &>/dev/null || true
    systemctl restart ser2net &>/dev/null || true
    echo -e "Active: SER2NET\n"
    ;;
    
  stop)
    killall "ser2net-fallback" &>/dev/null || true
    /usr/local/bin/ser2net-control pause
    ;;
    
  pause)
    rm -f /dev/SER2NET &>/dev/null || true
    ln -s /dev/ttyS0 /dev/futurehome/Z-Wave &>/dev/null || true
    killall zipgateway &>/dev/null || true
    echo -e "Active: Futurehome\n"
    ;;
    
  *)
    echo "Usage: $0 {boot|start|stop|status}"
    exit 1
    ;;
esac
EOF
chmod +x /usr/local/bin/ser2net-control


## INSTALL Web control HTTP server
cat <<'EOF' >/usr/local/bin/ser2net-web.py
#!/usr/bin/env python3
import http.server
import socketserver
import subprocess
import shlex
from urllib.parse import urlparse, parse_qs

PORT = 8888

subprocess.check_call(["/usr/local/bin/ser2net-control", "boot"])

class MyHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        url_data = urlparse(self.path.lower())
        command = url_data.path.lstrip('/')
        
        # Config updates
        fields=parse_qs(url_data.query)
        if fields:
          config_file = "/etc/ser2net-server/config.sh"
          with open(config_file, "r") as file:
            lines = file.readlines()
          with open(config_file, "w") as file:
            for line in lines:
              key, sep, _ = line.partition("=")
              if key.strip().lower() in ['host', 'port', 'fallback', 'autostart']:
                  value = fields.get(key.lower(), [None])[0]
              if value is not None:
                  value = 'yes' if value in ['yes', 'true', '1'] else ('no' if value in ['no', 'false', '0'] else value)
                  file.write("{key}={value}\n".format(key=key, value=shlex.quote(value)))
                  continue
              file.write(line)

        # Command execution
        if command in ['start', 'stop', 'status']:
            response = subprocess.check_output(["/usr/local/bin/ser2net-control", command], universal_newlines=True)
            
            self.send_response(200)
            self.end_headers()
            self.wfile.write(response.encode('utf-8'))
        else:
            self.send_error(404, "Not Found")

def main():
    httpd = socketserver.TCPServer(("", PORT), MyHandler)
    print("Serving HTTP on port {}".format(PORT))
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        httpd.server_close()

if __name__ == "__main__":
    main()
EOF
chmod +x /usr/local/bin/ser2net-web.py
sed -i '/^[[:space:]]*exit 0/i iptables -I INPUT -p tcp --dport 8888 -j ACCEPT' /etc/rc.local


## CONFIGURE Web control service (autostart)
cat <<'EOF' >/lib/systemd/system/ser2net_web.service
[Unit]
Description=HTTP server to control ser2net/zwave-js / futurehome functions
After=network.target

[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/ser2net-web.py
Restart=always

[Install]
WantedBy=multi-user.target
EOF

ln -sf /lib/systemd/system/ser2net_web.service /etc/systemd/system/multi-user.target.wants/ser2net_web.service
systemctl daemon-reload
systemctl enable ser2net_web.service
systemctl start ser2net_web.service


## EXTRA
# Expose Z/IP ports (for silicon Labs access)
sed -i '/^[[:space:]]*exit 0/i ip6tables -t nat -A PREROUTING -p udp --dport 42242 -j DNAT --to-destination [fd00:aaaa::03]:4123' /etc/rc.local
sed -i '/^[[:space:]]*exit 0/i ip6tables -I INPUT -p udp --dport 42242 -j ACCEPT' /etc/rc.local

#### << PASTE

CONFIGURE Z-Wave JS UI → ser2net

OPTION 1 - using tcp

  • Set ‘serial port’ to
    tcp://cube.local:3333

OPTION 2 - using virtual device

  • Create virtual device on docker host
    sudo apk add --no-cache socat
    sudo socat -d -d pty,link=/dev/ttyFIMP,raw,echo=0,user=$(whoami),group=dialout,mode=660 TCP:cube.local:3333

  • Add expose device in docker compose file
    devices:

    • /dev/ttyFIMP:/dev/ttyzwave
  • In Z-Wave JS UI set ‘serial port’ to
    /dev/ttyzwave

DONE

You should now have your Zwave adpater and devices located in your Futurehome Hub visible in Z-Wave JS UI

REMOTECONTROL Futurehome ser2net behaviour

parameters passed to url are persistent.

here you might find the S0 key
xxd -p /var/spool/futurehome/migration/zwave-s0.key | tr -d '\n'