feat(z2m): Zigbee2MQTT driver (v0.1)#6
Merged
Merged
Conversation
New shared package gohome-driverkit/colorconv/ holds CIE-xy ↔ RGB, HSV ↔ RGB, gamut clamping, and packed RGB helpers. Extracted from drivers/hue/internal/bridge/colormath.go to be consumed by Hue and Z2M drivers (and third-party drivers). Hue still owns its own copy in this commit; Task 2 migrates it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hue's private color math moves to gohome-driverkit/colorconv (added in the previous commit). bridge.ColorXY and bridge.Gamut become type aliases for backwards compatibility on JSON wire types; the math functions are dropped from the bridge package. This unblocks the Z2M driver from sharing the same pure-math code path without importing from another driver's internal/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the drivers/z2m/ directory tree (cmd, internal/mqtt, internal/z2m, internal/state) with doc.go package headers. Pulls in paho.mqtt.golang for production use and mochi-mqtt/server/v2 for in-process broker testing. No driver logic yet — subsequent commits fill in topics, payloads, state translation, the MQTT wrapper, and main wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
drivers/z2m/internal/z2m/ models the Z2M topic namespace (BridgeDevices, BridgeState, BridgeEvent, DeviceTopics) and the JSON payload shapes (Device, Definition, Expose, BridgeState, AvailabilityState, StatePayload). Decoding is verified against a captured bridge/devices fixture covering a colour light, multi-sensor, contact sensor, smart plug, and coordinator. Pure types, no I/O. Used by the reconciler and main wiring in later tasks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lights → light.z2m_<last8hex>; sensors → <kind>.z2m_<last8hex>_<prop>. Short, stable across friendly_name changes, collision-free within one Z2M instance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Walks the device's exposes tree, collapsing 'light' composites into one light.* entity and fanning per-property numeric/binary leaves into numeric_sensor.* / binary_sensor.* entities. Applies a blocklist for noisy properties (linkquality, voltage, update_available, last_seen) and skips writable non-light properties in v0.1 (Switch class out of scope) with INFO log. Verified against the captured bridge/devices fixture: colour light yields one light.* with four capabilities; the multi-sensor yields one binary_sensor and three numeric_sensor entities; the contact sensor yields one binary_sensor and one numeric_sensor; the smart plug yields only its read-only power; coordinator yields zero. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Maps Carport capabilities (turn_on, turn_off, set_brightness,
set_color_temp, set_color) to the JSON payloads Z2M's /set topic
expects. Range validation runs before any network I/O so bad input
surfaces as CARPORT_INTERNAL synchronously.
set_color emits color: {hex: "#RRGGBB"} — Z2M understands hex
across every vendor and avoids per-bulb gamut clamping (Z2M does
its own).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-kind dispatch: Light merges state/brightness/color_temp/color
fields (with mutual exclusivity between color and color_temp);
NumericSensor sets value preserving unit; BinarySensor accepts
both bool and string ("ON"/"OFF") payloads, since Z2M devices
disagree on representation.
Unknown light properties are no-ops rather than errors — a
multi-property state push fans out to several entities and each
ignores keys it doesn't recognise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AddEntity / UnregisterEntity / UpdateAttrs cover the three mutations the bridge/devices retained topic produces: new pairings, removed devices, and friendly_name renames. Adds come before removes in the ordered list so subscribe happens before registration on a swap (avoids retained-state race), with UpdateAttrs last. Composition changes (device firmware grows a property) are deferred — user restarts the driver to pick them up; documented in README later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thin facade exposing Connect/Subscribe/Unsubscribe/Publish/Close plus OnConnect/OnDisconnect callbacks. paho's auto-reconnect is left on; the OnConnect callback re-asserts subscriptions so reconnects don't silently drop them. Tested against an in-process mochi-mqtt broker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reads Z2M_BROKER_URL / Z2M_USERNAME / Z2M_PASSWORD / Z2M_BASE_TOPIC / Z2M_CLIENT_ID / Z2M_TLS_SKIP_VERIFY from env; dials the broker via the internal/mqtt wrapper; constructs the driver and a stateCache; forwards broker connect/disconnect to driver events. The reconciliation handlers (subscribeBridgeTopics) land next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires up the four bridge subscriptions (devices, state, event, plus per-device state/availability) into the Reconcile output: AddEntity registers entity + capability handlers + subscribes state/availability; UnregisterEntity unsubscribes + drops the entity; UpdateAttrs re-emits last attrs with the new label. State-topic payloads fan out to every entity that listens on the topic (a multi-property device hands the same payload to several entities, each consuming its own property). Integration-tested against an in-process mochi-mqtt broker: initial reconciliation, turn_on round-trip via /set, hot add/remove on bridge/devices republish, bridge-offline propagation to per-entity Available=false. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds drivers/z2m/README.md with quick-start + caveats and updates the existing zigbee2mqtt entry in docs/docs/drivers/first-party.md to reflect the actual driver (driver.z2m, env-var config, three device classes, known limitations). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both drivers previously read driver-specific env vars (HUE_BRIDGE_ADDRESS, Z2M_BROKER_URL, ...) directly. The supervisor only sets the four GOHOME_CARPORT_* env vars when launching a driver, so those reads worked only via accidental shell inheritance — without per-instance scoping. Switch both drivers to the canonical mechanism: parse the JSON blob in GOHOME_CARPORT_INSTANCE_CONFIG into a typed config struct. Secrets stay out of the config blob — they live in named env vars referenced by *_env fields (password_env, api_key_env), matching the existing convention in the first-party catalog page and the MQTT driver entry. Operational toggles (HUE_LOG_LEVEL, Z2M_LOG_LEVEL) remain env vars since they're per-process, not per-instance. READMEs and the first-party catalog page updated to document the JSON shape with example TOML config_json blocks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- go.mod / go.sum: paho.mqtt.golang and mochi-mqtt/server/v2 promoted to direct deps (they're imported by drivers/z2m); kr/text and creack/pty pruned, jinzhu/copier checksum added. - drivers/z2m/cmd/z2m-driver/main.go: split main into run() returning an error so os.Exit no longer skips deferred cleanup (gocritic); reformat stateCache field alignment (goimports); drop ineffectual available=true initializer in onDeviceAvailability (ineffassign). - drivers/z2m/internal/state/mapping.go: preallocate EntitiesFor's out slice (prealloc). - drivers/z2m/internal/state/reconcile_test.go: drop redundant int type from var declaration (staticcheck QF1011). - gohome-driverkit/colorconv/colorconv_test.go: gofmt cleanup of TestHSVToRGBKnownPoints case alignment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
drivers/z2m/(Carportdriver.z2m) — MQTT-based Zigbee2MQTT integration. Surfaces lights, numeric sensors, and binary sensors from a Z2M deployment with hot add/remove via the retainedbridge/devicestopic.gohome-driverkit/colorconv/— pure CIE-xy ↔ RGB / HSV ↔ RGB / gamut clamping. Hue migrated to consume it; Z2M uses it forcolorstate merges.Implements the plan at
docs/design/plans/2026-04-30-z2m-driver.md.Architecture
internal/z2m/— pure topic constructors + JSON payload types (Device,Expose,BridgeStatePayload,StatePayload).internal/state/— pure state translation:EntityID,EntitiesFor,MergeState,Reconcile,CommandToPayload. No I/O.internal/mqtt/— thinpaho.mqtt.golangwrapper with auto-reconnect and subscription re-assertion on connect.cmd/z2m-driver/—main.gowires everything via the driverkit;main_test.goruns end-to-end against an in-process mochi broker.What gets surfaced (v0.1)
light.*entity per device withturn_on,turn_off,set_brightness,set_color_temp,set_color.numeric_sensor.*.binary_sensor.*.Out of scope in v0.1: switches/smart-plug actuators (read-only sub-properties still surface), action sensors, climate/cover/lock/fan classes, Z2M network management.
Test plan
go test ./gohome-driverkit/... ./drivers/...— 49 tests, 0 failuresgo test ./drivers/z2m/cmd/z2m-driver/... -race -count=2 -timeout 120s— cleango run ./drivers/z2m/cmd/z2m-driverwith no env → fails withZ2M_BROKER_URL is required, exit 1🤖 Generated with Claude Code