Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,187 changes: 4,187 additions & 0 deletions docs/design/plans/2026-04-30-z2m-driver.md

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions docs/docs/drivers/first-party.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,31 @@ A general-purpose MQTT publish/subscribe driver. Register any topic as an entity

---

### Zigbee2MQTT (`driver.zigbee2mqtt`)
### Zigbee2MQTT (`driver.z2m`)

!!! status-alpha "Alpha — shipped, interface evolving"

Integrates with a running [Zigbee2MQTT](https://www.zigbee2mqtt.io/) bridge. Devices paired to the bridge are automatically discovered and registered as gohome entities. State changes and commands flow through the bridge's MQTT topics using the Zigbee2MQTT API.
Mirrors a [Zigbee2MQTT](https://www.zigbee2mqtt.io/) deployment into gohome over the MQTT broker that Z2M publishes to. Discovers all paired devices on startup, then reconciles live via the retained `bridge/devices` topic. v0.1 surfaces three device classes: lights (`light.*`), numeric sensors (`numeric_sensor.*`), and binary sensors (`binary_sensor.*`).

**Config fields**

| Field | Type | Required | Description |
|---|---|---|---|
| `mqtt_broker_url` | `string` | yes | URL of the MQTT broker used by Zigbee2MQTT |
| `base_topic` | `string` | no | Zigbee2MQTT base topic. Defaults to `zigbee2mqtt` |
| `username` | `string` | no | MQTT username |
| `broker_url` | `string` | yes | `tcp://host:1883` or `ssl://host:8883` |
| `username` | `string` | no | MQTT broker username |
| `password_env` | `string` | no | Env var containing the MQTT password |
| `friendly_name_prefix` | `string` | no | Prefix stripped from Zigbee2MQTT friendly names when forming entity IDs |
| `permit_join` | `bool` | no | If `true`, the driver can send permit-join commands via CLI |
| `base_topic` | `string` | no | Z2M's `mqtt.base_topic` setting (default `zigbee2mqtt`) |
| `client_id` | `string` | no | MQTT client identifier (default `gohome-z2m-<random8>`) |
| `tls_skip_verify` | `bool` | no | Skip TLS verification (default `false`) |

**Known caveats**

- Requires Zigbee2MQTT to be running independently; the driver does not manage the bridge process.
- Entity discovery happens on startup and when new devices are paired — no hot-reload without driver restart.
- Device availability is polled via the `zigbee2mqtt/bridge/devices` topic; brief disconnects may leave entities in a stale state until the next availability update.
- New devices paired in Z2M are picked up automatically; no driver restart needed.
- Smart-plug actuators (writable `state`) are out of scope in v0.1 — read-only sub-properties (`power`, `energy`) still surface.
- Per-device availability depends on Z2M's availability feature being enabled server-side; otherwise entities default to `Available=true`.
- `/set` publishes are best-effort: a successful publish is reported as `ok=true` even if Z2M silently ignores the command (no MQTT 5 request/response in v0.1).

[Source repo](https://github.com/fynn-labs/driver-zigbee2mqtt)
[Source repo](https://github.com/fdatoo/gohome/tree/main/drivers/z2m)

---

Expand Down
32 changes: 26 additions & 6 deletions drivers/hue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,33 @@ The `username` field is your API key. Store it in your secret manager and refere

### 3. Configure the driver

The driver reads three environment variables, set by `gohomed`:
The driver receives its instance config as a JSON blob in the `GOHOME_CARPORT_INSTANCE_CONFIG` environment variable, which `gohomed` populates from the carport instance's `config_json` TOML field.

| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
| `bridge_address` | string | yes | — | IP or hostname of the bridge |
| `api_key_env` | string | yes | — | Name of an env var holding the API key (referenced indirectly so secrets stay out of config files) |
| `tls_skip_verify` | bool | no | `true` | The bridge ships a self-signed cert |

Example carport instance TOML:

```toml
[[carport.instances]]
id = "hue-living-room"
binary = "/usr/local/bin/hue-driver"
config_json = '''
{
"bridge_address": "192.168.1.10",
"api_key_env": "HUE_API_KEY"
}
'''
```

Operational env vars (independent of the JSON config):

| Variable | Required | Default | Purpose |
|---|---|---|---|
| `HUE_BRIDGE_ADDRESS` | yes | — | IP or hostname of the bridge. |
| `HUE_API_KEY` | yes | — | Application key from step 2. |
| `HUE_TLS_SKIP_VERIFY` | no | `true` | The bridge ships a self-signed cert. |
| Variable | Default | Purpose |
|---|---|---|
| `HUE_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |

## Known caveats

Expand Down
52 changes: 32 additions & 20 deletions drivers/hue/cmd/hue-driver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ package main

import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
Expand All @@ -32,7 +32,7 @@ func main() {
os.Exit(1)
}

client, err := bridge.New(cfg.Address, cfg.APIKey, cfg.TLSSkipVerify)
client, err := bridge.New(cfg.Address, cfg.APIKey, *cfg.TLSSkipVerify)
if err != nil {
fmt.Fprintf(os.Stderr, "hue-driver: bridge: %v\n", err)
os.Exit(1)
Expand Down Expand Up @@ -92,31 +92,43 @@ func parseLogLevel(s string) slog.Level {
}
}

// config holds parsed environment variables.
// config is the JSON shape carried in GOHOME_CARPORT_INSTANCE_CONFIG.
// APIKey is resolved at load time from the env var named by APIKeyEnv;
// it is never serialized. TLSSkipVerify uses a pointer so the JSON-omitted
// case is distinguishable from an explicit false (default is true — the
// bridge ships a self-signed cert).
type config struct {
Address string
APIKey string
TLSSkipVerify bool
Address string `json:"bridge_address"`
APIKeyEnv string `json:"api_key_env"`
TLSSkipVerify *bool `json:"tls_skip_verify,omitempty"`

APIKey string `json:"-"`
}

func loadConfig() (config, error) {
addr := os.Getenv("HUE_BRIDGE_ADDRESS")
if addr == "" {
return config{}, errors.New("HUE_BRIDGE_ADDRESS is required")
raw := os.Getenv("GOHOME_CARPORT_INSTANCE_CONFIG")
if raw == "" {
return config{}, errors.New("GOHOME_CARPORT_INSTANCE_CONFIG is required")
}
key := os.Getenv("HUE_API_KEY")
if key == "" {
return config{}, errors.New("HUE_API_KEY is required")
var c config
if err := json.Unmarshal([]byte(raw), &c); err != nil {
return config{}, fmt.Errorf("parse instance config: %w", err)
}
skip := true
if v := os.Getenv("HUE_TLS_SKIP_VERIFY"); v != "" {
b, err := strconv.ParseBool(v)
if err != nil {
return config{}, errors.New("HUE_TLS_SKIP_VERIFY must be a boolean")
}
skip = b
if c.Address == "" {
return config{}, errors.New("bridge_address is required")
}
if c.APIKeyEnv == "" {
return config{}, errors.New("api_key_env is required")
}
c.APIKey = os.Getenv(c.APIKeyEnv)
if c.APIKey == "" {
return config{}, fmt.Errorf("api_key_env %q is unset or empty", c.APIKeyEnv)
}
if c.TLSSkipVerify == nil {
t := true
c.TLSSkipVerify = &t
}
return config{Address: addr, APIKey: key, TLSSkipVerify: skip}, nil
return c, nil
}

// reachabilityTracker debounces bridge_unreachable / bridge_recovered
Expand Down
144 changes: 0 additions & 144 deletions drivers/hue/internal/bridge/colormath.go

This file was deleted.

Loading
Loading