Skip to content

bbangert/universal_proxy

Repository files navigation

Universal Proxy

A Nerves-based firmware that turns a Raspberry Pi (or other supported board) into a universal serial proxy for Home Assistant.

The device appears as a native ESPHome device on the network, advertising itself over mDNS. USB serial adapters plugged into the board are exposed to Home Assistant as serial proxies through the ESPHome Native API. Home Assistant can then open, configure, and stream data from those serial ports exactly as it would with an ESPHome device that has a built-in UART.

Additionally, the Home Assistant Connect ZWA-2 USB Z-Wave controller is supported as a Z-Wave proxy. The proxy speaks the Z-Wave Serial API protocol locally, handling latency-sensitive ACK/NAK/CAN responses at the device while forwarding complete frames to Home Assistant over the network.

This is useful for connecting RS-232, RS-485, TTL serial, or Z-Wave devices to Home Assistant over the network without needing a dedicated ESPHome microcontroller for each one.

Features

  • Speaks the ESPHome Native API (protobuf over TCP on port 6053)
  • Automatic mDNS advertisement -- discovered by Home Assistant like any ESPHome device
  • Serial proxy for TTL, RS-232, and RS-485 USB adapters
  • Z-Wave proxy for USB Z-Wave controllers with local ACK handling
  • Auto-detection for IRDroid / IR Toy USB infrared devices (VID 0x04D8, PID 0xFD08/0xF58B)
  • Web UI for configuration (accessible at http://<device-ip>)
  • USB hotplug detection -- plug/unplug serial adapters at any time
  • DETS-backed persistent device configuration across reboots
  • Graceful handling of unexpected USB disconnects during active sessions

For Users

Installing firmware

  1. Go to the GitHub Releases page for this project.
  2. Download the .fw firmware file for your board (e.g. universal_proxy_rpi3.fw).
  3. Write it to a microSD card using fwup or Etcher:
# Using fwup (Linux/macOS)
fwup universal_proxy_rpi3.fw
  1. Insert the microSD card into your board, connect Ethernet, and power on.
  2. The device will obtain an IP address via DHCP and be discoverable on your network.

Upgrading firmware

Once the device is running, you can upload new firmware over the network:

mix upload universal_proxy.local

Or manually with fwup:

cat universal_proxy_rpi3.fw | ssh universal_proxy.local "fwup -aU -d /dev/rootdisk0 -t upgrade && reboot"

Configuring serial devices

  1. Open a browser and navigate to http://universal_proxy.local (or the device's IP address).
  2. Go to the Connected Devices tab (/devices).
  3. Each plugged-in USB serial adapter is listed with its description and serial number.
  4. Click Configure on a device to assign its port type (TTL, RS-232, or RS-485).
  5. Click Save. The device is now advertised to Home Assistant as a serial proxy.

To remove a device from Home Assistant, click Delete on its configuration.

Saving or deleting a configuration automatically restarts the ESPHome server, causing Home Assistant to reconnect and pick up the updated device list.

The Home Assistant Connect ZWA-2 is auto-detected when plugged in -- no manual configuration needed. It appears in the Connected Devices list with an "Auto-detected" badge. The proxy opens the controller at 115200 baud, parses Z-Wave Serial API framing, and sends ACK/NAK/CAN responses locally (avoiding network round-trip latency). Complete frames are forwarded to Home Assistant's Z-Wave JS integration over the ESPHome API. Only one Home Assistant instance can subscribe to the Z-Wave proxy at a time.

IRDroid / IR Toy USB infrared devices are also auto-detected by USB ID (VID 0x04D8, PID 0xFD08 or 0xF58B) and shown in Connected Devices with an "Auto-detected" badge. When connected, these infrared devices are exposed to Home Assistant via the ESPHome Native API as Infrared entities, supporting infrared transmit and receive operations.

Editing ESPHome device configuration

  1. Go to the ESPHome Config tab (/esphome-config).
  2. The current device identity is shown (name, friendly name, MAC address, model, etc.).
  3. Click Edit to modify any field.
  4. Click Save & Reload to apply changes, or Cancel to discard.

The device name and friendly name control how the device appears in Home Assistant's integrations list. The MAC address is auto-detected from the Ethernet interface on first boot.

Adding to Home Assistant

Once the device is powered on and connected to the network:

  1. Home Assistant should auto-discover it via mDNS under Settings > Devices & Services.
  2. If not, manually add an ESPHome integration pointing to universal_proxy.local (or the IP).
  3. No API password is required.
  4. Configured serial adapters appear as serial proxy entities on the device. An auto-detected ZWA-2 appears as a Z-Wave proxy.

For Developers

Prerequisites

  • Docker (for the devcontainer)
  • VS Code or Cursor with the Dev Containers extension
  • Alternatively: Elixir 1.19+, Erlang/OTP 27+, and Nerves tooling installed locally

Getting started with the devcontainer

  1. Clone the repository:
git clone https://github.com/<owner>/universal_proxy.git
cd universal_proxy
  1. Open the project in VS Code or Cursor.
  2. When prompted, click Reopen in Container (or run the Dev Containers: Reopen in Container command).
  3. The container will build and install all dependencies automatically, including:
    • Elixir and Erlang (via mise)
    • Nerves tooling and bootstrap
    • fwup for firmware packaging
    • protobuf compiler

The devcontainer is preconfigured with MIX_TARGET=rpi3. Change this in .devcontainer/devcontainer.json if targeting a different board.

Project structure

lib/
  universal_proxy/
    uart/                  # UART subsystem (Server, Store, PortConfig, Supervisor)
    esphome/               # ESPHome Native API (Server, Connection, Protocol, Supervisor)
      zwave/               # Z-Wave proxy (Frame, Parser, Server)
  universal_proxy_web/
    live/                  # Phoenix LiveView pages (Home, Connected Devices, ESPHome Config)
priv/
  protos/                  # ESPHome protobuf definitions (api.proto, api_options.proto)
  static/                  # Static web assets
docs/
  plans/                   # Architecture and design plans

Building assets

The web UI uses Tailwind CSS and esbuild. To build assets:

# Install asset tools (first time only)
mix assets.setup

# Build CSS and JS
mix assets.build

# Build minified for production
mix assets.deploy

Regenerating protobuf bindings

If you update priv/protos/api.proto, regenerate the Elixir bindings:

mix protobuf

This is also run automatically as part of mix compile.

Building firmware

# Set your target board (already set in devcontainer)
export MIX_TARGET=rpi3

# Fetch dependencies
mix deps.get

# Build the firmware image
mix firmware

The firmware file is written to _build/rpi3_dev/nerves/images/universal_proxy.fw.

Writing to an SD card

Insert a microSD card and run:

mix burn

This uses fwup to write the firmware image. You may be prompted for your password to access the SD card device.

Uploading to a running device

If the device is already running Nerves firmware on the network:

mix upload universal_proxy.local

Or specify an IP address:

mix upload 192.168.1.100

The device will reboot with the new firmware automatically.

Running tests

# Tests run on the host target
mix test

Connecting to a running device

Access the Erlang shell over SSH:

ssh universal_proxy.local

From the IEx shell, you can inspect the system, view logs, and interact with the application directly:

# View debug logs
RingLogger.attach()

# Enumerate serial devices
Circuits.UART.enumerate()

# Check ESPHome config
UniversalProxy.ESPHome.config()

Supported targets

This project supports all standard Nerves targets:

Target Board
rpi Raspberry Pi A+/B+
rpi0 Raspberry Pi Zero
rpi0_2 Raspberry Pi Zero 2 W
rpi2 Raspberry Pi 2
rpi3 Raspberry Pi 3 B/B+
rpi4 Raspberry Pi 4
rpi5 Raspberry Pi 5
bbb BeagleBone Black
x86_64 Generic x86_64

Learn more

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages