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

  • 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.

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 — 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

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

Acknowledgements

S
Description
No description provided
Readme 51 KiB
v1.0.0 Latest
2026-05-24 15:57:15 +00:00
Languages
Go 76.8%
Makefile 13.2%
Shell 10%