Configuration¶
wiz2mqtt uses pydantic-settings for configuration. Settings resolve in this
order:
- CLI flags
- Environment variables with the
WIZ2MQTT_prefix - A
.envfile in the working directory wiz2mqtt.tomlfor the bulb and group inventory- Built-in defaults
Bulb Inventory¶
The bulb list lives in wiz2mqtt.toml as [[bulbs]] entries:
[[bulbs]]
name = "desk"
ip = "10.0.0.10"
[[bulbs]]
name = "lamp"
ip = "10.0.0.11"
mac = "a8bb5006033d"
power_source = "lamp-power"
restore_previous_state = true
| Field | Required | Description |
|---|---|---|
name |
yes | MQTT topic segment for the bulb |
ip |
yes | Literal IPv4 address of the bulb |
mac |
no | Bare 12-hex-digit MAC verified on first successful contact; a mismatch rejects that contact, while a missing device MAC is warned as unverifiable |
power_source |
no | Name of a [[power_sources]] entry that powers this bulb directly. Wins over any [[power_sources]] block that claims the bulb's group (ADR-007) |
restore_previous_state |
no | Restore the bulb's previous desired state when it returns to reachability with no queued command (default false, ADR-008) |
Legacy when_unreachable (bulb-level, removed)¶
The old bulb-level when_unreachable field was removed in favour of
[[power_sources]] (ADR-007). A wiz2mqtt.toml that still sets it is
migrated automatically at startup, with a warning logged for each bulb:
when_unreachable = "off"becomes an implicit single-bulb power source (name = "<bulb>-power",members = ["<bulb>"],when_unreachable = "no_power").when_unreachable = "unavailable"(the old default) is simply dropped — it is now the implicit default behaviour when no power source claims the bulb.- Any other value still raises a validation error.
Update wiz2mqtt.toml to remove the bulb-level key and declare the
equivalent [[power_sources]] entry directly; the migration is a
compatibility shim, not a long-term feature.
Groups¶
Optional [[groups]] entries keep consumer-side membership beside the bulbs:
Names use [A-Za-z0-9_-]+ (at most 64 characters), must be unique, and cannot
collide with bulb names. Each group needs at least one member; members must be
distinct, declared bulb names. A bulb can belong to multiple groups.
Groups create no MQTT entities or topics and do not change Home Assistant discovery. They are rendered only for openHAB; HA groups remain HA configuration.
Power Sources¶
Optional [[power_sources]] entries model a mains circuit (ADR-007) that one
or more bulbs sit behind, e.g. a smart relay or wall switch feeding several
WiZ bulbs. This inventory and its validation are available now. Deriving or
publishing a powered belief, subscribing to signal_topic, and sending
power requests are reserved configuration only until their separately tracked
ADR-007/ADR-009 runtime work lands.
[[power_sources]]
name = "lamp-power"
members = ["lamp"]
when_unreachable = "no_power"
[[power_sources]]
name = "downstairs-power"
group = "downstairs"
signal_topic = "openhab/relay/downstairs/state"
when_unreachable = "fault"
enable_power_on_request = true
enable_power_off_request = true
power_off_idle_delay = 300
wiz_bulbs_only = true
| Field | Required | Description |
|---|---|---|
name |
yes | Unique name, [A-Za-z0-9_-]+, at most 64 characters; must not collide with a bulb or group name |
group |
one of group/members |
Name of an existing [[groups]] entry this source powers |
members |
one of group/members |
Bulb names powered by this source directly |
signal_topic |
no | Reserved retained MQTT relay-signal topic (on/off); subscription is deferred until the ADR-007 runtime work lands |
when_unreachable |
no | What an unreachable member bulb means with no better evidence: fault (default, availability = offline) or no_power (bulb stays available, publishes {"state": "OFF"}) |
enable_power_on_request |
no | Reserve future power-on requests; runtime support is deferred (default false) |
enable_power_off_request |
no | Reserve future power-off requests; runtime support is deferred and requires wiz_bulbs_only = true (default false) |
power_off_idle_delay |
no | Reserved seconds every member bulb must be idle before a future power-off request; runtime support is deferred (default 600) |
wiz_bulbs_only |
no | Operator declaration that every device on this circuit is a WiZ bulb wiz2mqtt controls; must be true before enable_power_off_request may be true (default false) |
Exactly one of group or members must be set. A bulb resolves to at most
one power source: its own power_source field (see Bulb
Inventory) always wins over an implicit claim through
members or the bulb's group; two power sources implicitly claiming the
same bulb (through members and/or group) is a configuration error.
Like [[bulbs]] and [[groups]], power_sources is TOML-only — there is no
environment-variable form.
MQTT Settings¶
wiz2mqtt inherits the standard cosalette MQTT settings as a nested mqtt
model. Common environment variables are:
| Setting | Environment Variable | Default |
|---|---|---|
| Host | WIZ2MQTT_MQTT__HOST |
localhost |
| Port | WIZ2MQTT_MQTT__PORT |
1883 |
| Username | WIZ2MQTT_MQTT__USERNAME |
unset |
| Password | WIZ2MQTT_MQTT__PASSWORD |
unset |
| Topic prefix | WIZ2MQTT_MQTT__TOPIC_PREFIX |
wiz2mqtt |
| TLS | WIZ2MQTT_MQTT__TLS |
true (set to false by the shipped deployment) |
Transport security is a per-deployment setting. cosalette defaults tls to
true; the shipped compose.yml defaults WIZ2MQTT_MQTT__TLS to false for
its bundled plaintext broker. Set WIZ2MQTT_MQTT__TLS=true in .env or a
Compose override when your broker expects TLS.
Command Queueing¶
| Setting | Environment Variable | Default | Description |
|---|---|---|---|
queued_command_ttl |
WIZ2MQTT_QUEUED_COMMAND_TTL |
86400.0 (seconds) |
Reserved command-queue TTL (ADR-008); queue runtime support is deferred, so this currently has no effect. |
Config-file and environment interplay¶
The TOML file owns the bulb inventory, while environment variables are the
usual place for deployment-specific MQTT details. Environment variables outrank
the config file, so WIZ2MQTT_MQTT__HOST=broker.local overrides any broker
settings implied elsewhere without rewriting wiz2mqtt.toml.
Publication Behaviour¶
Publication is push-driven and has no configuration surface. State reaches MQTT
when the bulb sends a UDP push; the 60-second interval= tick is a heartbeat
and liveness probe rather than the publication driver.
Two related values are fixed constants in the code, not settings:
| Value | Where | Behaviour |
|---|---|---|
| Heartbeat tick, 60 s | main._TICK_INTERVAL_SECONDS |
Polls only when last_push is stale; otherwise reuses the push cache |
| Push-staleness threshold, 60 s | adapters.wizlight._DEFAULT_PUSH_STALENESS_THRESHOLD |
A read falls back to polling the bulb when its own last_push clock is older than this |
They are deliberately equal, and the poll decision reads the bulb's own
last_push clock rather than the adapter's push-cache timestamp: a bulb
that keeps heartbeating (a syncPilot, changed or suppressed) proves its
own liveness, so no poll is needed while that traffic arrives. Only a
bulb that has gone genuinely silent for 60 s trips the fallback, and that
poll is then a real network read, not a no-op.
The bulb entity is declared triggerable="local", so the wake is in-process
only — wiz2mqtt subscribes no trigger topic. The only inbound topic is each
bulb's set command topic documented in
mqtt-topics.md.
Consumer Integration¶
Consumer wiring is derived from the bulb inventory and optional group membership.
Home Assistant discovery¶
main calls app.discovery(), so on the first successful MQTT connect wiz2mqtt
publishes retained homeassistant/<component>/wiz2mqtt/.../config payloads built
from its live registry. Each [[bulbs]] entry becomes one HA device with a
light (schema: json), an effect-speed number, and a power sensor; one
binary_sensor bridge entity is published for the app. Dropping a bulb from
wiz2mqtt.toml clears its retained discovery topics on the next start. Nothing in
Home Assistant needs configuring.
The light metadata is capability-accurate per bulb: supported_color_modes,
effect_list, and min_kelvin/max_kelvin reflect what each bulb actually
supports, so a tunable-white or dimmable-white bulb no longer shows an RGB picker
or effect list it cannot honour. Because bulb capabilities are auto-detected at
runtime — never declared in config — the accurate values are only known after
wiz2mqtt has contacted the bulb once. Discovery is published on connect, before
any bulb is reached, so a freshly onboarded or newly swapped bulb advertises a
safe wire-format superset on its first run and the accurate, narrowed metadata
from the next restart onward (see "Adding or replacing a bulb" in
Getting Started). This is Mechanism B in
ADR-003.
openHAB generation¶
For your deployment, run from the repository root:
task wiz2mqtt:openhab -- --config-file wiz2mqtt.toml --output things > wiz2mqtt.things
task wiz2mqtt:openhab -- --config-file wiz2mqtt.toml --output items > wiz2mqtt.items
The config path is relative to apps/wiz2mqtt/; an absolute path also works.
Installed deployments can run wiz2mqtt-openhab with the same options.
Use --broker-uid <id> to match your openHAB MQTT broker Thing (default broker).
Install the outputs in openHAB's things/ and items/ directories.
This command resolves the actual TOML inventory through cosalette's schema CLI
without contacting bulbs or MQTT. Each group becomes one plain openHAB Group
whose members are the bulbs' Color command Items. openHAB fans on/off, dimming,
and HSB color commands out through their existing MQTT channels. Groups have no
aggregate state; individual telemetry Items remain available for state display.
Effect and color-temperature commands are not added to these groups. See
openHAB Items for group semantics.
Regenerate both files after inventory or membership changes. Names that collapse
to the same openHAB identifier are rejected by the deployment generator.
task wiz2mqtt:schema:openhab renders a Generic MQTT Thing and matching Items
file from the checked-in sample docs/schema.yaml; it does not use deployment
groups. Both paths are offline only; wiz2mqtt never talks to openHAB at
runtime. Regenerate docs/schema.yaml with task wiz2mqtt:schema:generate --yes
after changing the state model; task wiz2mqtt:schema:check is the drift gate.
See mqtt-topics.md for the channel layout.