Skip to content

Commit 817f801

Browse files
committed
chore: commit workspace changes
1 parent af42803 commit 817f801

8 files changed

Lines changed: 407 additions & 310 deletions

File tree

CLAUDE.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ Tests mock the entire `homeassistant` module in `tests/conftest.py` and don't re
6565

6666
| File | Purpose |
6767
|------|---------|
68-
| `__init__.py` | Entry point: registers services via loop, initializes `LightController` and `PresetManager`, uses `_get_param()` helper for call/options merging |
69-
| `controller.py` | Light control: `ensure_state()``_expand_entities()``_build_targets()``_group_by_settings()` → send → verify → retry |
70-
| `preset_manager.py` | Preset CRUD, stores in `ConfigEntry.data[CONF_PRESETS]`, `activate_preset_with_options()` for shared activation logic |
68+
| `__init__.py` | Entry point: registers services individually in `async_setup()`, initializes `LightController` and `PresetManager`, uses `_get_param()` and `_service_response()` helpers |
69+
| `controller.py` | Light control: `ensure_state()``_expand_entities()``_build_targets()``_group_by_settings_with_transition()` → send → verify → retry |
70+
| `preset_manager.py` | Preset storage in `ConfigEntry.data[CONF_PRESETS]`, `activate_preset_with_options()` for shared activation logic |
7171
| `config_flow.py` | Menu-based options flow: settings (collapsible sections), add_preset (multi-step with per-entity config), manage_presets (edit/delete with confirmation) |
7272
| `button.py` / `sensor.py` | Preset entities: button activates preset via `preset_manager.activate_preset_with_options()`, sensor tracks status |
7373
| `const.py` | All `CONF_*`, `ATTR_*`, `DEFAULT_*`, `PRESET_*` constants |
@@ -118,20 +118,15 @@ brightness_tolerance = _get_param(data, options, ATTR_BRIGHTNESS_TOLERANCE, CONF
118118

119119
`_expand_entity()` resolves `light.*` groups and `group.*` helper groups to individual `light.` entities. Uses `_get_state()` directly for attribute access.
120120

121-
### Service Registration Loop
121+
### Service Registration
122122

123-
Services registered via loop with lambda capture for cleanup:
123+
Services are registered individually in `async_setup()` (not `async_setup_entry()`). This ensures they persist across config entry reloads. Each handler resolves the active entry at call time via `_get_loaded_entry()`:
124124

125125
```python
126-
services = [
127-
(SERVICE_ENSURE_STATE, async_handle_ensure_state, SERVICE_ENSURE_STATE_SCHEMA),
128-
(SERVICE_ACTIVATE_PRESET, async_handle_activate_preset, SERVICE_ACTIVATE_PRESET_SCHEMA),
129-
# ...
130-
]
131-
132-
for service_name, handler, schema in services:
133-
hass.services.async_register(DOMAIN, service_name, handler, schema=schema, supports_response=SupportsResponse.OPTIONAL)
134-
entry.async_on_unload(lambda svc=service_name: hass.services.async_remove(DOMAIN, svc))
126+
hass.services.async_register(
127+
DOMAIN, SERVICE_ENSURE_STATE, async_handle_ensure_state,
128+
schema=SERVICE_ENSURE_STATE_SCHEMA, supports_response=SupportsResponse.OPTIONAL,
129+
)
135130
```
136131

137132
### Preset Activation Helper

SOFTWARE_DESIGN_DOCUMENT.md

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@ The integration sits between Home Assistant's service layer and the underlying l
153153

154154
| Component | File | Lines | Responsibility |
155155
|-----------|------|-------|---------------|
156-
| Entry Point | `__init__.py` | 647 | Service registration, parameter merging, lifecycle management |
157-
| Controller | `controller.py` | 888 | Core light control: expand, target, group, send, verify, retry |
158-
| Preset Manager | `preset_manager.py` | 442 | Preset CRUD, storage, status tracking, activation delegation |
159-
| Config Flow | `config_flow.py` | 1027 | UI-based setup and options: settings, preset creation/editing |
160-
| Constants | `const.py` | 119 | All `CONF_*`, `ATTR_*`, `DEFAULT_*`, `PRESET_*` constants |
161-
| Button Platform | `button.py` | 201 | Preset activation button entities |
162-
| Sensor Platform | `sensor.py` | 197 | Preset status sensor entities |
156+
| Entry Point | `__init__.py` | 692 | Service registration, parameter merging, lifecycle management |
157+
| Controller | `controller.py` | 909 | Core light control: expand, target, group, send, verify, retry |
158+
| Preset Manager | `preset_manager.py` | 418 | Preset storage, status tracking, activation delegation |
159+
| Config Flow | `config_flow.py` | 1026 | UI-based setup and options: settings, preset creation/editing |
160+
| Constants | `const.py` | 118 | All `CONF_*`, `ATTR_*`, `DEFAULT_*`, `PRESET_*` constants |
161+
| Button Platform | `button.py` | 200 | Preset activation button entities |
162+
| Sensor Platform | `sensor.py` | 196 | Preset status sensor entities |
163163

164164
### 3.3 Dependency Graph
165165

@@ -213,6 +213,10 @@ class LightControllerData:
213213
type LightControllerConfigEntry = ConfigEntry[LightControllerData]
214214
```
215215

216+
**Response Building** — The `_service_response()` helper constructs standardized response dicts for all non-controller service responses (errors, preset operations, create/delete results). It mirrors the `OperationResult.to_dict()` structure, ensuring a consistent response schema regardless of whether the response originates from the controller or a service handler.
217+
218+
**Optional String Helper**`_get_optional_str()` retrieves optional string parameters, treating empty strings as `None`. Used for parameters where the absence of a value is semantically different from an empty string (e.g., effect names).
219+
216220
#### Service Schema Validation
217221
All service inputs are validated through Voluptuous schemas at the HA service layer boundary. Once data passes schema validation, internal code trusts it without redundant checks.
218222

@@ -271,7 +275,9 @@ The `ensure_state()` method implements a linear pipeline:
271275
│ ├─ _send_commands_per_target() (transition on first attempt only)
272276
│ ├─ await asyncio.sleep(delay)
273277
│ ├─ _verify_light() for each target
274-
│ └─ Filter: keep only targets not yet SUCCESS or UNAVAILABLE
278+
│ ├─ _build_dispatch_batches() → rebuild batch groupings
279+
│ └─ Filter at batch level: if ANY target in a batch failed,
280+
│ retry the ENTIRE batch (minus unavailable entities)
275281
276282
6. Result Assembly
277283
@@ -294,6 +300,14 @@ Result: 2 service calls instead of 3
294300

295301
For ON targets, grouping uses a composite key of `(brightness_pct, rgb_color, color_temp_kelvin, effect, transition)`. For OFF targets, all entities are batched into a single `turn_off` call regardless of original settings.
296302

303+
#### Batch-Level Retry
304+
305+
During the retry loop, `_build_dispatch_batches()` reconstructs the batch groupings used during dispatch. Verification is then applied at batch granularity: if **any** target in a batch fails verification, the **entire batch** is re-sent (excluding unavailable entities). This avoids sending partial groups, which could cause visual inconsistencies when lights in the same batch should have identical settings.
306+
307+
#### Logbook Integration
308+
309+
`_log_to_logbook()` writes operation results to Home Assistant's logbook service. Logbook entries are written for failures (always) and successes (when `log_success` is enabled). Calls are non-blocking (`blocking=False`) to avoid delaying the response.
310+
297311
#### Verification Logic
298312

299313
Verification checks are layered:
@@ -312,7 +326,7 @@ This prevents false failures for lights that don't support the requested color m
312326
### 4.3 Preset Manager (`preset_manager.py`)
313327

314328
#### Purpose
315-
Manages the full lifecycle of presets: creation, storage, retrieval, activation, and deletion.
329+
Manages presets: creation, storage, retrieval, activation, and deletion. Lookup by ID or name is provided via `find_preset()`, which first tries by ID then falls back to case-insensitive name matching.
316330

317331
#### Storage Design
318332

@@ -684,8 +698,10 @@ Three-option menu:
684698
│ 2. Send commands (transition 1st only) │
685699
│ 3. Sleep(delay) │
686700
│ 4. Verify each target │
687-
│ 5. Filter out SUCCESS/UNAVAILABLE │
688-
│ 6. attempt++ │
701+
│ 5. Rebuild dispatch batches │
702+
│ 6. For each batch: if ANY target failed │
703+
│ → retry entire batch (skip unavail) │
704+
│ 7. attempt++ │
689705
│ │
690706
└──────────────────┬───────────────────────┘
691707

0 commit comments

Comments
 (0)