Mosquitto:
- Change restart: always (was unless-stopped) to survive daemon restarts
- Add healthcheck (pub/sub ping) for depends_on conditions
- All downstream services use condition: service_healthy
Party-lock:
- Add MQTT connection retry loop (was crashing on DNS race after restart)
Zigbee2mqtt:
- Add entrypoint wrapper that auto-applies BLZ bridge patch at startup
- Bridge patch no longer needs manual 'just z2m-patch-bridge' after image pulls
Mopidy:
- Add python3-gi + python3-gi-cairo via apt (fixes 'No module named gi')
- Set PYTHONPATH to expose system gi module to Python 3.12
Document the ThirdReality Smart Button (3RSB22BZ) pairing mode quirk
that bit us today: the button has Standard ZigBee (blue only) and
Echo ZigBee (blue + red) modes, and only the former works with
zigbee2mqtt. Switching requires 5 quick presses of the back reset
button (firmware >= 1.00.22), NOT holding the front button.
Also document the Smart Plug Zigbee PROD pairing procedure and the
BLE->ZigBee mode switch.
The Pine64 BLZ fork's zigbee2mqtt image crashes on start because
Bridge.start() calls firstCoordinatorEndpoint().deviceIeeeAddress
before the onMQTTMessage listener is registered. The BLZ coordinator
does not register its endpoint (the coordinator is the dongle itself,
no ZDO loopback), so firstCoordinatorEndpoint() returns undefined.
The crash happens in publishInfo() BEFORE onMQTTMessage is wired up,
which means every bridge MQTT request (permit_join, device rename,
group ops, etc.) silently no-ops — even though the message is
received.
Add infra/zigbee2mqtt/scripts/blz-bridge-patch.js that applies two
surgical fixes (idempotent):
1. Optional chain on the .deviceIeeeAddress access so the missing
endpoint doesn't throw.
2. Move onMQTTMessage registration BEFORE publishInfo/devices/
groups/definitions, and wrap each publish in try/catch so
Bridge.start() completes even if a single publish fails.
Add 'just z2m-patch-bridge' to apply the patch on the live container
after fresh image pulls.
- FLASH_INTERVAL_SECONDS 3 -> 2 (user preference)
- Add network_key / pan_id / ext_pan_id to zigbee2mqtt configuration
so paired devices can rejoin after a config wipe. Network key
preserved from previous working state on RPi.
- Doorbell listener flash logic with even toggle count so plug ends
at starting state
zigbee-monitor.yaml:
- Single click (action=single) toggles Squiggle, Sideboard Lamp,
Bamboo Lights
- Press-hold-release (action=release after hold) toggles DJ Booth
only
- Two filter functions: "Single click" and "Hold release", button-in
fan-out wires to both
- DJ Booth added to zigbee-state-in and ui-text widgets for visibility
- "plugs:" list replaced with "single_plugs:" and "hold_plugs:" for
declarative clarity (generator doesn't use them, but documents intent)
infra/zigbee2mqtt/configuration.yaml:
- Removed DJ Booth from "all_plugs" group (now Squiggle, Sideboard
Lamp, Bamboo Lights only)
- Pushed same change to live /root/zigbee2mqtt/configuration.yaml and
restarted zigbee2mqtt container
Diagnostics in justfile + scripts/:
justfile:
- Constants: RPI_HOST, MQTT_HOST, MQTT_PORT, RPI_DIR, NR_PORT
- rpi, mqtt-pub, mqtt-sub, mqtt-watch, mqtt-button
- nr-show, nr-diff, nr-logs, nr-editor, nr-dashboard
- z2m-devices, z2m-logs, z2m-state
- mqtt-logs, restart, restart-all
scripts/mqtt.py:
- MQTT publish/subscribe/watch helpers using paho-mqtt v2.x
- Run via uv run --with paho-mqtt (no global install needed)
scripts/diag.py:
- nr-show: live flow summary with mqtt-out topic list
- nr-diff: structural diff deployed vs repo, normalizes random uuids
- z2m-devices: paired devices + group membership
- mqtt-button: publish action and watch /set topics (E2E test)
Verified end-to-end via `just mqtt-button`:
single → 3 messages on .../set (Squiggle, Sideboard Lamp, Bamboo Lights)
release → 1 message on .../set (DJ Booth)
Captures everything needed to rebuild the stack from scratch:
infra/compose.yaml
- mosquitto (eclipse-mosquitto:latest)
- zigbee2mqtt (ghcr.io/pine64/zigbee2mqtt:latest-dev, BLZ fork)
- nodered (nodered/node-red:latest)
infra/mosquitto/config/mosquitto.conf
- anonymous listener on 1883, file logging, persistence on
infra/zigbee2mqtt/
- configuration.yaml: 5 paired devices with friendly names, 1 group
- coordinator_backup.json: zigbee network key + pan id (recovery)
- state.json: per-device persisted state (ON/OFF, linkquality)
infra/nodered/
- settings.js: nodered defaults (flowFile: flows.json, no auth)
- start.sh: patches Dashboard 2.0 SPA with <base href> at startup
Also:
- Updated z2m groups section to rename Laser & Fog -> DJ Booth
(matches the friendly_name already in devices section)
- Pushed the same change to live /root/zigbee2mqtt/configuration.yaml
and restarted the zigbee2mqtt container
- .gitignore: output/ (regenerated on every build, churns uuid ids)
- README.md: link to infra/README.md for rebuild procedure