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.
- 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, PID0xFD08/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
- Go to the GitHub Releases page for this project.
- Download the
.fwfirmware file for your board (e.g.universal_proxy_rpi3.fw). - Write it to a microSD card using fwup or Etcher:
# Using fwup (Linux/macOS)
fwup universal_proxy_rpi3.fw- Insert the microSD card into your board, connect Ethernet, and power on.
- The device will obtain an IP address via DHCP and be discoverable on your network.
Once the device is running, you can upload new firmware over the network:
mix upload universal_proxy.localOr manually with fwup:
cat universal_proxy_rpi3.fw | ssh universal_proxy.local "fwup -aU -d /dev/rootdisk0 -t upgrade && reboot"- Open a browser and navigate to
http://universal_proxy.local(or the device's IP address). - Go to the Connected Devices tab (
/devices). - Each plugged-in USB serial adapter is listed with its description and serial number.
- Click Configure on a device to assign its port type (TTL, RS-232, or RS-485).
- 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.
- Go to the ESPHome Config tab (
/esphome-config). - The current device identity is shown (name, friendly name, MAC address, model, etc.).
- Click Edit to modify any field.
- 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.
Once the device is powered on and connected to the network:
- Home Assistant should auto-discover it via mDNS under Settings > Devices & Services.
- If not, manually add an ESPHome integration pointing to
universal_proxy.local(or the IP). - No API password is required.
- Configured serial adapters appear as serial proxy entities on the device. An auto-detected ZWA-2 appears as a Z-Wave proxy.
- 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
- Clone the repository:
git clone https://github.com/<owner>/universal_proxy.git
cd universal_proxy- Open the project in VS Code or Cursor.
- When prompted, click Reopen in Container (or run the
Dev Containers: Reopen in Containercommand). - 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.
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
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.deployIf you update priv/protos/api.proto, regenerate the Elixir bindings:
mix protobufThis is also run automatically as part of mix compile.
# Set your target board (already set in devcontainer)
export MIX_TARGET=rpi3
# Fetch dependencies
mix deps.get
# Build the firmware image
mix firmwareThe firmware file is written to _build/rpi3_dev/nerves/images/universal_proxy.fw.
Insert a microSD card and run:
mix burnThis uses fwup to write the firmware image. You may be prompted for your
password to access the SD card device.
If the device is already running Nerves firmware on the network:
mix upload universal_proxy.localOr specify an IP address:
mix upload 192.168.1.100The device will reboot with the new firmware automatically.
# Tests run on the host target
mix testAccess the Erlang shell over SSH:
ssh universal_proxy.localFrom 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()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 |