# Configuration Configuration is Python, but typed and validated. ## Expected Module Exports A runtime config module should export: - `SETTINGS`: `ha_deconz_bridge.settings.Settings` - `LIGHTS`: `dict[str, ha_deconz_bridge.lights.VirtualLightSpec]` - `REMOTES`: optional `dict[str, ha_deconz_bridge.automation.RemoteSpec]` - `AUTOMATIONS`: optional `tuple[ha_deconz_bridge.automation.AutomationRule, ...]` ## Example ```python from configs.example_controllers import define_lights LIGHTS = define_lights({ "living_room": { "supported_color_modes": ("rgb", "color_temp"), "controllers": { "7": { "name": "North RGB+CCT", "mireds": (153, 500), "rgb": { "r": {"color": (0.6949, 0.3051), "brightness": 202}, "g": {"color": (0.1414, 0.7181), "brightness": 479}, "b": {"color": (0.1443, 0.0427), "brightness": 124}, }, "color_temp": { "ww": {"color": 417, "brightness": 520}, "cw": {"color": 154, "brightness": 576}, }, }, }, }, }) ``` The controller ID (`"7"`), physical mode names (`"rgb"` / `"color_temp"`), and channel roles (`"r"`, `"g"`, `"b"`, `"ww"`, `"cw"`) are taken from dictionary keys. `color` accepts: - a single number as CT, using mireds below `1000` and Kelvin at or above `1000` - an `(x, y)` tuple - an RGB tuple, either `0..1` floats or `0..255` integers `label` is optional. If omitted, channels inside physical `rgb` mode default to label `rgb`, and channels inside physical `color_temp` mode default to label `color_temp`. Use `label` only for unusual wiring, such as an RGB controller output connected to white LEDs. ## Validation Rules - Supported virtual modes must be one of: - `("onoff",)` - `("brightness",)` - any non-empty subset of `("rgb", "color_temp")` - Controller mode roles must match the controller mode: - `rgb`: `r`, `g`, `b` - `color_temp`: `ww`, `cw` - `brightness`: `w` - `onoff`: `w` - Every channel defines the emitted physical color and max output. The role only describes how the backend controller address is written. - `ChannelSpec.semantic_label` is optional and may be: - `rgb` - `color_temp` - `boost` - If omitted, `rgb` and `color_temp` channels default to their mode name; `brightness` and `onoff` channels default to no label. - Solver priority is staged: - dimmable same-label channels first - then other dimmable channels - then `boost`/unlabeled dimmable channels - then same-label on/off channels - then other on/off channels - then `boost`/unlabeled on/off channels - Controllers may define multiple mutually exclusive modes, and the solver is free to choose one mode or turn that controller fully off. - A virtual light that exposes `color_temp` must have native CT-capable emitters. - A controller that claims a mode unsupported by the real device fails startup validation. ## Solver Responsiveness `Settings.target_cache_scale` controls how precisely requested chromaticity is keyed in the solver cache. The default is `10000`, which keeps adjacent RGB/CT slider positions distinct in normal use. Lower values make nearby requests reuse the same reference solve, but can create visible color dead zones where a small slider movement does not change the achieved color. Treat lower values as an explicit responsiveness experiment, not the default behavior. `Settings.approximate_chroma_band` controls how far a CT-line solution may sit from the ideal blackbody point while still being accepted. The default is `0.025` in uv distance. Lower values make CT requests more color-accurate but may choose RGB compensation more often; higher values prefer broad-spectrum CT output more aggressively. `HA_DECONZ_BRIDGE_NUMERIC_BACKEND` controls the numeric backend used by the solver: - `scipy`: default, current stable backend. - `numba`: try the Numba active-set backend for supported LP solves, with SciPy fallback when needed. - `auto`: same as `numba` when Numba is importable, otherwise SciPy. Numba is an optional dependency. Install it with the `speed` extra before selecting `numba` or `auto`. `HA_DECONZ_BRIDGE_WARMUP_ON_STARTUP` defaults to `true`. When enabled, the service and debugger start a background warmup that runs one representative solve per supported HA mode. This compiles Numba-backed paths without blocking MQTT or the debugger HTTP port. ## Config File Layout The public example entry point is `configs/example.py`. It imports focused files: - `configs/example_controllers.py`: physical controller/channel helper functions and constants. - `configs/example_lights.py`: individual virtual lights and which controllers they abstract. - `configs/example_groups.py`: room/group membership, optional member restrictions, and group balancing policy. - `configs/example_remotes.py`: physical remote sensor IDs mapped to internal `remote_id` values. - `configs/example_actions.py`: scenes, action helpers, and remote automation mappings. Real home configs should stay in ignored local files such as `configs/local/`. The entry point should stay small and only assemble `SETTINGS`, `LIGHTS`, `GROUPS`, `REMOTES`, and `AUTOMATIONS`. For a private local setup, copy the public example into the ignored local directory: ```bash mkdir -p configs/local cp configs/example.py configs/local/lights_config.py cp configs/example_*.py configs/local/ ``` Then edit the copied files. If you keep the split-file imports, update them from `configs.example_*` to `configs.local.example_*`, or rename the copied split files and import those names instead. Run with: ```bash ha-deconz-bridge-service configs/local/lights_config.py ha-deconz-bridge-debug-web configs/local/lights_config.py "Example Mixed North" ``` The runtime accepts either a Python module name or a `.py` file path. Groups are defined with `GroupSpec`. A member can be listed by name, or wrapped in `GroupMemberSpec` to restrict how that member behaves only inside that group: ```python from ha_deconz_bridge.groups import GroupMemberSpec, GroupSpec GROUPS = { "Example Plant": GroupSpec( name="Example Plant", members=( "Example Fixed White A", "Example Fixed White B", GroupMemberSpec("Example Mixed North", fixed_color=182), ), balance="relative_member", ), } ``` By default, a group derives its exposed modes from the union of its members. If any member supports `rgb` or `color_temp`, the group exposes those color modes. If no member supports color but at least one member supports dimming, the group exposes `brightness`. If no member is dimmable, the group is `onoff`. `supported_color_modes` is still available as an explicit override. `fixed_color` makes that member act like a dimmable fixed-color contributor only inside that group. It accepts a single number as color temperature (`<1000` is mireds, `>=1000` is kelvin), an RGB triple, or an xy pair. RGB input is normalized, so `(1.0, 1.0, 1.0)` and `(255, 255, 255)` describe the same color. `fixed_brightness` requires `fixed_color` and makes that member act like an on/off fixed-output contributor inside the group. Values up to `1.0` are treated as relative brightness; larger values are treated as HA `0..255` brightness. On readback, the member counts as on when it reports at least 90% of that fixed output. Direct control of the member light is unchanged. `balance="relative_member"` sends the same relative brightness to each member. The default `passthrough` sends the group command to every compatible member without trying to equalize member brightness. ## Remote Automation Physical remotes are configured by deCONZ sensor ID, but automation rules bind to an internal `remote_id`. ```python from ha_deconz_bridge.automation import ActionSpec, RemoteSpec, automation_rules REMOTES = { "example_sensor_1": RemoteSpec( sensor_id="example_sensor_1", remote_id="example_living", name="Example living remote", model="RWL021", double_press_window=0.45, # optional override ), } AUTOMATION_CONFIG = { "example_living": { "top": { "single": (ActionSpec("set_scene", target="Example Living", brightness=180, color_temp=330),), }, "bottom": { "single": (ActionSpec("turn_off", target="Example Living"),), }, "up": { "hold": (ActionSpec("brightness_step", target="Example Living", brightness_step=16),), }, }, } AUTOMATIONS = automation_rules(AUTOMATION_CONFIG) ``` `RemoteSpec.model` is informational in the current implementation. It documents the remote type and gives us a place to add model-specific validation or decoding later. The current Hue dimmer mapping is for `RWL021`. Supported base gestures are `press`, `single`, `double`, `long`, `double_long`, `hold`, and `double_hold`. Hold-count gesture patterns are also supported: - `hold_x`: trigger only on the x'th hold event. - `double_hold_x`: trigger only on the x'th hold event of a double-hold. - `hold_x_y`: trigger from x through y, inclusive. - `double_hold_x_y`: same for double-hold. - If `y < x`, the trigger starts at x and continues until release. The recommended spelling for an unbounded hold is `hold_x_0` or `double_hold_x_0`. - If `y == x`, the trigger fires only on the x'th hold event. Supported action kinds are: - `turn_on` - `turn_off` - `set_scene` - `brightness_step` - `color_temp_step` Action targets may be exact virtual light names, group names, or group expressions: - `A+B+C`: union of groups/lights A, B, and C. - `A-B`: all members of A except members of B. - `A+B-C+D`: parsed left-to-right. - `All`: all individual virtual lights. - Empty string: same as `All`. - `-A` or `All-A`: all individual virtual lights except A. If the target exactly matches an exposed virtual light or group and contains no `+` or `-`, the action is sent to that virtual entity directly. Compound expressions expand to individual virtual lights. ## Example Groups `configs/example.py` exposes sanitized example groups in addition to individual lights: - `Example Hall`: three RGB+CCT virtual lights. - `Example Kitchen`: one RGB+CCT virtual light. - `Example Bathroom`: one multi-controller ceiling light and one mirror light. - `Example Bedroom`: two RGB+CCT virtual lights. - `Example Living`: fixed-white, mixed RGB/CCT, and on/off examples. - `Example Plant`: fixed-white examples plus one constrained mixed light, using relative member brightness balancing.