8.9 KiB
ezcoo-usb-control
MQTT bridge for the EZCOO EZ-MX42HAS-ARC 4×2 HDMI matrix, with Home Assistant auto-discovery.
ezcoo-usb-control is a small Go daemon that talks to an EZCOO HDMI matrix
over USB-UART using its ASCII command set and exposes the routing state to
an MQTT broker. It publishes Home Assistant MQTT discovery payloads so the
matrix shows up automatically as two select entities — one per output —
ready to be used in dashboards and automations.
Built for, and running on, a Raspberry Pi 4, but the binary is plain Go and works on any Linux/arm64, Linux/armhf or Linux/amd64 host that can see the matrix as a serial device.
Table of contents
- Features
- Hardware
- Quick start
- Installation
- Configuration
- Running
- MQTT topics
- Home Assistant
- Verifying the serial protocol
- Troubleshooting
- Project layout
- Development
- Contributing
- License
- Acknowledgements
Features
- Two-way control of a 4-in / 2-out EZCOO HDMI matrix over USB-UART
- Home Assistant MQTT auto-discovery — zero manual entity wiring
- Periodic polling so external changes (front panel, IR remote) are reflected in MQTT
- LWT-based availability (
online/offline) - Structured logging via
log/slog, with--debugflag - Hardened
systemdunit and.debpackages forarm64,armhf,amd64
Hardware
Tested with:
- EZCOO EZ-MX42HAS-ARC (4 HDMI inputs, 2 HDMI outputs, ARC) — exposed
as a USB CDC-ACM serial port (typically
/dev/ttyACM0, 57600 8N1) - Raspberry Pi 4 (Raspberry Pi OS, 64-bit) as the host
Other EZCOO matrices that share the same EZG OUT0 VS / EZS OUTx VS INy
ASCII protocol should work; if your unit speaks a slightly different dialect
see Verifying the serial protocol.
Quick start
# 1. Build a Raspberry Pi 4 (arm64) .deb on any Linux host with Go installed
make deb-arm64 VERSION=0.1.0
# 2. Copy and install on the Pi
scp dist/ezcoo-usb-control_0.1.0_arm64.deb pi@raspberrypi:~
ssh pi@raspberrypi 'sudo apt install ./ezcoo-usb-control_0.1.0_arm64.deb'
# 3. Configure and start
ssh pi@raspberrypi 'sudoedit /etc/ezcoo-usb-control/config.yaml'
ssh pi@raspberrypi 'sudo systemctl restart ezcoo-usb-control'
ssh pi@raspberrypi 'sudo journalctl -u ezcoo-usb-control -f'
Installation
Debian / Raspberry Pi OS (.deb)
Pre-built .deb packages are produced for each release; download the
matching architecture from the project's releases page and install:
sudo apt install ./ezcoo-usb-control_<version>_<arch>.deb
The package installs:
| Path | Purpose |
|---|---|
/usr/bin/ezcoo-usb-control |
Binary |
/etc/ezcoo-usb-control/config.yaml |
Default config (marked conffile) |
/lib/systemd/system/ezcoo-usb-control.service |
systemd unit |
A dedicated ezcoo system user is created with dialout as a supplementary
group so the daemon can open the serial device without root.
To build a .deb yourself for any supported architecture:
make deb-arm64 VERSION=0.1.0 # Raspberry Pi 4 / 5 (64-bit)
make deb-armhf VERSION=0.1.0 # Raspberry Pi 2/3 / Zero 2 (32-bit)
make deb-amd64 VERSION=0.1.0 # x86_64
make deb-all VERSION=0.1.0 # all three
Requires dpkg-dev on the build host (sudo apt install dpkg-dev).
From source
# native build
make build
./build/ezcoo-usb-control --config cmd/ezcoo-usb-control/config.example.yaml
# or directly with go
go build -o ezcoo-usb-control ./cmd/ezcoo-usb-control
Requires Go ≥ 1.26 (see go.mod).
Configuration
Copy cmd/ezcoo-usb-control/config.example.yaml to /etc/ezcoo-usb-control/config.yaml
(the .deb package does this for you) and edit:
device:
port: /dev/ttyACM0 # serial device exposed by the matrix
baud: 57600 # EZCOO default
poll_interval: 15s # how often to query routing state
mqtt:
broker: tcp://192.168.1.10:1883
username: ""
password: ""
client_id: ezcoo-usb-control
base_topic: ezcoo # all state/set topics live under this
discovery_prefix: homeassistant # Home Assistant discovery root
All fields have sensible defaults — the minimum viable config is just
mqtt.broker. The config file path is passed via --config; if omitted,
defaults are used as-is.
Running
ezcoo-usb-control --config /etc/ezcoo-usb-control/config.yaml
Flags:
| Flag | Description |
|---|---|
--config <path> |
Path to YAML config (optional) |
--debug |
Enable debug-level logging |
Under systemd the service is started with:
sudo systemctl enable --now ezcoo-usb-control
sudo journalctl -u ezcoo-usb-control -f
To observe what the bridge publishes while running:
mosquitto_sub -h localhost -t 'homeassistant/#' -v
mosquitto_sub -h localhost -t 'ezcoo/#' -v
MQTT topics
Topic names assume the default base_topic: ezcoo and discovery_prefix: homeassistant.
| Topic | Direction | Payload | Description |
|---|---|---|---|
ezcoo/availability |
publish (retained, LWT) | online / offline |
Bridge liveness |
ezcoo/output1/state |
publish (retained) | IN1..IN4 |
Current input routed to OUT1 |
ezcoo/output2/state |
publish (retained) | IN1..IN4 |
Current input routed to OUT2 |
ezcoo/output1/set |
subscribe | IN1..IN4 |
Switch OUT1 to the given input |
ezcoo/output2/set |
subscribe | IN1..IN4 |
Switch OUT2 to the given input |
homeassistant/device/ezcoo_matrix/config |
publish (retained) | JSON | HA device discovery payload |
Home Assistant
If Home Assistant is connected to the same MQTT broker, the matrix appears
automatically as a single device with two select entities — no YAML
required. See docs/home-assistant.md for
discovery details, example automations (including a "mirror both outputs"
recipe), and a Lovelace card snippet.
Verifying the serial protocol
Before first deployment, probe the exact command set of your unit:
picocom -b 57600 /dev/ttyACM0
# then type: EZH (dumps all supported commands)
# then type: EZG OUT0 VS (shows current routing of all outputs)
The bridge expects responses in the form OUT1 VS IN3. If your unit
replies differently, adjust the reOutVS regular expression in
pkg/ezcoo/utils.go.
Troubleshooting
permission denied: /dev/ttyACM0— theezcoouser must be in the group that owns the device (dialouton Debian/RPi OS). The.debadds this automatically; for custom installs, do it manually.- Entities don't appear in Home Assistant — confirm the discovery prefix
matches HA's MQTT integration setting (default
homeassistant), and that the broker shows a retained payload onhomeassistant/device/ezcoo_matrix/config. - State never changes — run with
--debugand watch forstate updatelog lines. If polling never returns matches, the regex likely needs adjusting (see above). - Bridge reports
offlineafter connecting — check that the systemd unit has access to the serial char device; the unit ships withDeviceAllow=char-ttyACM rwandDeviceAllow=char-ttyUSB rw.
Project layout
.
├── cmd/ezcoo-usb-control/ # main entrypoint, config loader, example config
├── pkg/
│ ├── bridge/ # MQTT side: connect, subscribe, publish, HA discovery
│ └── ezcoo/ # serial side: device driver, protocol, polling loop
├── packaging/
│ ├── DEBIAN/ # control, postinst, prerm, postrm, conffiles
│ └── systemd/ # hardened service unit
├── .gitea/workflows/ # CI: build .deb packages on release
└── Makefile # build + multi-arch .deb targets
Development
go build ./...
go vet ./...
go test ./...
Contributing
Issues and pull requests are welcome. For non-trivial changes please open an issue first so the design can be discussed. When submitting a PR:
- Run
go vet ./...andgo test ./.... - Keep changes focused and the commit history clean.
- Update this README if you add a flag, change a topic, or alter the config schema.
License
Released under the MIT License
Acknowledgements
- The EZCOO ASCII command set documented in the EZ-MX42HAS-ARC manual.
paho.mqtt.golangfor the MQTT client.go.bug.st/serialfor cross-platform serial I/O.- Home Assistant's MQTT discovery schema.