224 lines
10 KiB
Markdown
224 lines
10 KiB
Markdown
# 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.
|