# 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](#features) - [Hardware](#hardware) - [Quick start](#quick-start) - [Installation](#installation) - [Debian / Raspberry Pi OS (.deb)](#debian--raspberry-pi-os-deb) - [From source](#from-source) - [Configuration](#configuration) - [Running](#running) - [MQTT topics](#mqtt-topics) - [Home Assistant](#home-assistant) - [Verifying the serial protocol](#verifying-the-serial-protocol) - [Troubleshooting](#troubleshooting) - [Project layout](#project-layout) - [Development](#development) - [Contributing](#contributing) - [License](#license) - [Acknowledgements](#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 `--debug` flag - Hardened `systemd` unit and `.deb` packages for `arm64`, `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](#verifying-the-serial-protocol). ## Quick start ```sh # 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: ```sh sudo apt install ./ezcoo-usb-control__.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: ```sh 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 ```sh # 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: ```yaml 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 ```sh ezcoo-usb-control --config /etc/ezcoo-usb-control/config.yaml ``` Flags: | Flag | Description | |---|---| | `--config ` | Path to YAML config (optional) | | `--debug` | Enable debug-level logging | Under `systemd` the service is started with: ```sh sudo systemctl enable --now ezcoo-usb-control sudo journalctl -u ezcoo-usb-control -f ``` To observe what the bridge publishes while running: ```sh 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`](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: ```sh 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`](pkg/ezcoo/utils.go). ## Troubleshooting - **`permission denied: /dev/ttyACM0`** — the `ezcoo` user must be in the group that owns the device (`dialout` on Debian/RPi OS). The `.deb` adds 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 on `homeassistant/device/ezcoo_matrix/config`. - **State never changes** — run with `--debug` and watch for `state update` log lines. If polling never returns matches, the regex likely needs adjusting (see above). - **Bridge reports `offline` after connecting** — check that the systemd unit has access to the serial char device; the unit ships with `DeviceAllow=char-ttyACM rw` and `DeviceAllow=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 ```sh 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: 1. Run `go vet ./...` and `go test ./...`. 2. Keep changes focused and the commit history clean. 3. Update this README if you add a flag, change a topic, or alter the config schema. ## License Released under the [MIT License](LICENSE.md) ## Acknowledgements - The EZCOO ASCII command set documented in the EZ-MX42HAS-ARC manual. - [`paho.mqtt.golang`](https://github.com/eclipse/paho.mqtt.golang) for the MQTT client. - [`go.bug.st/serial`](https://github.com/bugst/go-serial) for cross-platform serial I/O. - Home Assistant's [MQTT discovery](https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery) schema.