@johanwistbacka/node-red-contrib-sw-light 0.1.4 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.9 — Label target development candidate
4
+
5
+ - Adds Entity/Label targets, stable Label ID autocomplete, and structured `msg.target` overrides; old Entity flows keep their default behavior.
6
+ - Resolves direct entity/device labels and HA's labeled-area relationships, filters non-light/hidden/disabled entries and deduplicates members. Area is not exposed as a target.
7
+ - Extracts the existing light-control path into one controller per entity, preserving independent override, capability filtering, automatic limits and Adaptive Lighting.
8
+ - Adds cached membership, registry/reconnect invalidation, idle refresh and reconciled state observation; close/redeploy cleans node-owned resources.
9
+ - Adds aggregate Label results/status, per-entity partial failures and empty/deleted target events.
10
+ - Documents the APIs, refresh/lifecycle contract and exact physical multi-light verification sequence in `docs/labels.md`.
11
+ - Local check and full tests include the published HA contrib 0.80.3 integration. Real HA/physical-device verification is pending. No npm publication or HA installation.
12
+
13
+ ## 0.1.8 — 2026-09-30
14
+
15
+ - Simplify Adaptive Lighting to an On/Off checkbox with collapsed advanced profile selection.
16
+ - Use the supported color-only `adaptive_lighting.apply` action with a fresh provider calculation, only after automatic OFF → ON on CT-capable lights. SW Light retains brightness, maximum, override protection and transitions.
17
+ - Explicit message colors and manual commands suppress adaptation; already-on brightness changes do not reapply it. Add `msg.adaptive_lighting` opt-in/out.
18
+ - Discover only unique exact `configuration.lights` matches. Missing, ambiguous, unavailable, running and RGB sleep profiles fall back with structured warnings. Non-CT lights retain normal ON.
19
+ - Require the HA profile main switch off for one-shot ownership. Preserve inactive legacy nodes; migrate former optional-apply opt-in.
20
+ - Document inspected upstream API/source, two-action timing limitations and the exact live verification sequence.
21
+ - Full syntax/editor/example check and 42 tests passed, including all 3 isolated published-provider integration tests. No live HA/device verification or publication.
22
+
23
+ ## 0.1.7 — local default threshold adjustment
24
+
25
+ - Sets the runtime/editor default manual override threshold to 98% (HA brightness 250 or above), retaining the 94% automatic maximum.
26
+ - Updates example flows and documentation. Existing saved thresholds remain unchanged.
27
+ - Verifies the new default boundary and release below threshold; preserves explicit 100% threshold coverage.
28
+ - No npm publication.
29
+
30
+ ## 0.1.6 — local HA state normalization fix
31
+
32
+ - Omits null/non-array observed HA color attributes instead of spreading them, preventing `a[k] is not iterable` during observation and commands.
33
+ - Keeps valid color arrays as independent copies; retains brightness and manual override behavior.
34
+ - Adds null/non-array normalization coverage and exercises all three override regressions with null HA color attributes.
35
+ - Full checks and isolated Node-RED/HA contrib integration: 33 tests passed. No npm publication; physical-device verification pending.
36
+
37
+ ## 0.1.5 — local override fix candidate
38
+
39
+ - Starts per-light HA observation after flow startup, before the first input.
40
+ - Retries temporary provider initialization failures without freezing the node; preserves the shared-client/readiness adapter.
41
+ - Fixes idle OFF nodes missing external brightness changes: observed 255 activates override, observed 204 releases the default threshold, and explicit manual OFF bypasses the guard.
42
+ - Adds real-adapter regressions for automatic 94% → external 100% → suppressed OFF, external 80% release, manual OFF, and entity isolation.
43
+ - Full suite including isolated Node-RED/HA contrib 0.80.3 integration: 32 tests passed. Physical-device verification of this candidate is pending. No npm publication.
44
+
3
45
  ## 0.1.4 — scoped distribution candidate
4
46
 
5
47
  - Changes npm identity to `@johanwistbacka/node-red-contrib-sw-light`.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # SW Light — v0.1.4
1
+ # SW Light — v0.1.9
2
2
 
3
- A reusable Node-RED node for one Home Assistant `light.*` entity. Select an
4
- existing HA server, select a light, deploy in a **development** environment,
3
+ A reusable Node-RED node for a Home Assistant `light.*` Entity or Label. Select an
4
+ existing HA server, select a target, deploy in a **development** environment,
5
5
  and select Action **On**, **Off**, **Toggle**, or **From msg**. Display name:
6
6
  **SW Light**; flow type: `sw-light`.
7
7
 
@@ -10,12 +10,39 @@ selected light, checks manual override and calls the HA light service itself.
10
10
  No external HA Action node is required; the two outputs are for observation
11
11
  and integration only. With a fixed Action, a normal timestamp Inject is enough.
12
12
 
13
+ ## Label targets in 0.1.9
14
+
15
+ Choose **Target → Entity** (the default, including existing flows) or **Label**.
16
+ Label autocomplete displays the HA name and stores the stable Label ID. Current
17
+ members are resolved from entity, device and labeled-area registry relationships;
18
+ non-lights are excluded and duplicate light IDs are removed. Area is not a target
19
+ choice in this version.
20
+
21
+ Each resolved light reuses the same controller with independent override,
22
+ capabilities, brightness limits and Adaptive Lighting. One overridden/unavailable
23
+ member does not stop the others. Output 1 for Labels contains `target`,
24
+ `resolved_entity_ids`, per-light `results` and success/skipped/failure/manual counts;
25
+ output 2 identifies the actual entity for per-light events. Status aggregates
26
+ last observed states.
27
+
28
+ Membership refreshes on provider registry/reconnect events and a 30-second cache
29
+ expiry/idle reconciliation fallback. One filtered state subscription per node is
30
+ reconciled without per-light subscription leaks. Optional `msg.target =
31
+ {type: "label", id: "label_id"}` or `{type: "entity", id: "light.example"}` overrides
32
+ configuration for one input; that target remains observed until the next input.
33
+ Departed target members discard their controller's explicit latch.
34
+
35
+ See [Label architecture, API evidence and exact multi-light test sequence](docs/labels.md).
36
+ This is a local development candidate; no npm publication or HA installation is
37
+ part of its preparation. Physical Label behavior still needs the documented test.
38
+
13
39
  ## Package identity and migration
14
40
 
15
41
  The permanent npm package name is `@johanwistbacka/node-red-contrib-sw-light`.
16
42
  The Node-RED type stays `sw-light` and its display name stays **SW Light**.
17
- The scoped package starts at version 0.1.4; version numbers are independent
18
- for each npm package name. No runtime/editor or HA integration behavior changes.
43
+ The scoped package started at version 0.1.4; version numbers are independent
44
+ for each npm package name. Version 0.1.5 fixes observation before the first command; 0.1.6 also tolerates
45
+ null/non-array observed HA color attributes.
19
46
 
20
47
  If upgrading from the old unscoped `node-red-contrib-sw-light`, export your
21
48
  flows first. Remove the old package from the Node-RED user directory before
@@ -25,7 +52,7 @@ or, with Node-RED stopped, run these commands in its user directory:
25
52
 
26
53
  ```sh
27
54
  npm uninstall node-red-contrib-sw-light
28
- npm install @johanwistbacka/node-red-contrib-sw-light@0.1.4
55
+ npm install @johanwistbacka/node-red-contrib-sw-light@0.1.9
29
56
  ```
30
57
 
31
58
  Restart Node-RED and reload the editor. Existing flow nodes retain type
@@ -51,7 +78,7 @@ are enabled. Test with a development light before relying on this node.
51
78
  After this version is published, install it in your Node-RED user directory:
52
79
 
53
80
  ```sh
54
- npm install @johanwistbacka/node-red-contrib-sw-light@0.1.4
81
+ npm install @johanwistbacka/node-red-contrib-sw-light@0.1.9
55
82
  ```
56
83
 
57
84
  After publication, the current development release is available under npm latest:
@@ -74,7 +101,7 @@ For edits, use npm's local directory installation/link or reinstall an archive
74
101
  created with `npm pack`. Restart the isolated Node-RED runtime after changes.
75
102
  There is no build step or additional runtime dependency.
76
103
 
77
- The current development release uses the normal SemVer version `0.1.4` and
104
+ The current development release uses the normal SemVer version `0.1.9` and
78
105
  npm tag `latest` for Node-RED compatibility. This version remains experimental;
79
106
  it is not a SemVer prerelease. Every installation/publication candidate needs a
80
107
  unique version. Use `npm version patch --no-git-tag-version` for the next normal
@@ -86,7 +113,7 @@ After publication, submit the package through the
86
113
  [Node-RED Flow Library](https://flows.nodered.org/add/node), or request a refresh
87
114
  if it is already listed.
88
115
 
89
- ## Temporary runtime diagnostics in 0.1.4
116
+ ## Temporary runtime diagnostics in 0.1.9
90
117
 
91
118
  This build logs `[SW Light debug]` at construction and on the first input,
92
119
  then when the relevant structure/readiness changes. It reports version,
@@ -96,7 +123,7 @@ It never logs server IDs, entity IDs, credentials, hosts, full objects or state
96
123
  snapshots. Provider initialization errors are reduced to a generic message.
97
124
 
98
125
  After installation, restart Node-RED and verify a log line containing
99
- `[SW Light debug] version=0.1.4`. Inject once and retain the prefixed lines,
126
+ `[SW Light debug] version=0.1.9`. Inject once and retain the prefixed lines,
100
127
  along with any error/stack. Those lines distinguish missing configuration,
101
128
  a missing provider client and a connected client with unexpected methods.
102
129
  The real installation problem remains unverified until this build is tested
@@ -119,11 +146,10 @@ there is no `require.cache` scan or search for alternate package copies.
119
146
  | On/off transition | 1s each | Sent only with light transition capability |
120
147
  | Manual override | Enabled | Pause automatic commands while override is active |
121
148
  | Detection | Brightness threshold + flag | Alternative: explicit flag only |
122
- | Threshold | 100% | Lamp brightness at/above this value activates override |
123
- | Color mode | Adaptive Lighting | No configured color sent during ordinary ON |
124
- | Adaptive discovery | Enabled | Unique membership match in switch attributes |
125
- | Adaptive apply on ON | Disabled | Optional color-only `adaptive_lighting.apply` |
126
- | Respect adaptive manual control | Enabled | Skip apply when light is manually controlled |
149
+ | Threshold | 98% | Lamp brightness at/above this value activates override |
150
+ | Color mode | Keep current | Normal behavior and fallback |
151
+ | Adaptive Lighting | Off | Current temperature once on automatic OFF → ON |
152
+ | Advanced profile | Automatic | Unique exact light membership; explicit selection if ambiguous |
127
153
 
128
154
  Require `minimum <= on brightness <= automatic maximum`. When threshold
129
155
  detection is enabled, automatic maximum must be below the threshold.
@@ -180,6 +206,7 @@ validated**, even those that lose precedence.
180
206
  | `rgbww_color`, `rgbw_color`, `rgb_color` | Arrays of 5, 4, 3 integer bytes 0–255 |
181
207
  | `hs_color`, `xy_color`, `color_name` | [0–360,0–100], [0–1,0–1], HA color name |
182
208
  | `transition` | 0–3600 seconds |
209
+ | `adaptive_lighting` | Boolean; overrides Adaptive Lighting for one command |
183
210
 
184
211
  The complete color precedence follows the table top to bottom. `color_name`
185
212
  is passed to HA for name resolution/validation, only on a color-capable lamp.
@@ -210,15 +237,21 @@ This hard guard requires brightness exactly **255/255**, so 94%, 95%, 99% and
210
237
  is disabled or set to explicit-only. Lowering the light below 255 allows
211
238
  automatic control again unless an additional configured override is active.
212
239
 
213
- Threshold detection observes HA state, not requested brightness. The configured
214
- threshold is converted to a byte; default 100% means exactly 255, not 254 rounded
215
- for display. With defaults, automation uses 1–94%, and observed 100% activates
240
+ Threshold detection observes HA state, not requested brightness. Each node starts
241
+ its state subscription after flow startup, before its first command, and retries
242
+ temporary provider initialization failures. This includes separate ON/OFF nodes
243
+ for the same physical light. The configured
244
+ threshold is converted to a byte; default 98% means brightness 250 or above.
245
+ With defaults, automation uses 1–94%, and observed brightness at/above this threshold activates
216
246
  override. It pauses **all automatic commands**, including ON, OFF, toggle and
217
247
  brightness zero, so an automatic ON cannot accidentally lower and release a
218
248
  manual setting. A manually lowered observed brightness releases the threshold
219
249
  flag. OFF releases all flags. Missing/unknown/unavailable state preserves flags
220
250
  and rejects commands.
221
251
 
252
+ Existing nodes retain their saved threshold. Change a saved 100% threshold to
253
+ 98% manually to use the new default behavior.
254
+
222
255
  An explicit latch is independent of the threshold. It persists until explicit
223
256
  release or observed OFF. A release flag clears both flags for its command;
224
257
  subsequent threshold observations can reactivate override. Flags are in memory,
@@ -228,49 +261,112 @@ detection. `source` is the v0.1 command-origin contract.
228
261
 
229
262
  ## Adaptive Lighting
230
263
 
231
- Ordinary ON in Adaptive mode sends brightness and supported transition, **no
232
- color or temperature**. Adaptive Lighting retains ownership of color. A message
233
- color explicitly overrides that behavior for one command, and suppresses optional
234
- apply even when the color is unsupported and filtered.
235
-
236
- Manual switch selection takes precedence. Discovery uses the light's exact
237
- membership in `attributes.configuration.lights` or `attributes.lights` on
238
- switch entities with adaptive-like attributes. It only selects one unique
239
- candidate; it does not infer names or configure HA. This is best-effort attribute
240
- discovery, not a registry-backed guarantee that a switch belongs to the integration.
241
- No actual installation was available for validating its attribute schema.
242
- Use the switch picker/manual entity ID when discovery fails or is ambiguous.
243
-
244
- Optional apply runs **after** successful turn-on, only for an enabled selected
245
- switch, without explicit message color. It calls `adaptive_lighting.apply` with
246
- `adapt_brightness: false`, `adapt_color: true`, `turn_on_lights: false`. It respects
247
- `manual_control` / `manual_control_color` lists by default and never clears them,
248
- turns on an adaptive switch or changes integration settings. No discovery is
249
- required when apply is disabled: the ordinary HA light action works on its own.
250
- An apply failure emits output 2 with `phase: "adaptive_apply"` and
251
- `light_service_completed: true`; the successful light result still emits.
252
-
253
- Adaptive Lighting may interpret explicit brightness commands as manual control
254
- depending on its own `take_over_control` and related settings. Its background
255
- brightness adaptation may also exceed this node's maximum; the ceiling only
256
- applies to **SW Light's outgoing commands**. Configure the integration's own
257
- brightness limits/ownership in your dev environment and test that combination.
258
- SW Light never repeatedly corrects brightness or fights its adaptation.
259
-
260
- ## Test the updated 0.1.4 in your HA Node-RED installation
264
+ The primary editor control is **Adaptive Lighting: On / Off** (off for new nodes).
265
+ Color mode independently defines normal/fallback color behavior; the default is
266
+ Keep current. Advanced profile selection is collapsed. Discovery selects only
267
+ one exact light-membership match in a main switch's `configuration.lights`.
268
+ No entity names or undocumented `lights` attributes are guessed. Configuration
269
+ attributes must be exposed in HA (`include_config_in_attributes`) for discovery;
270
+ otherwise select the main switch explicitly. Multiple matches never choose the
271
+ first profile. Group membership expansion is not performed.
272
+
273
+ Keep the selected Adaptive Lighting **main switch OFF** to use its calculation
274
+ without continuous control or ON interception. The supported `adaptive_lighting.apply`
275
+ action works with that switch off and calculates fresh values for every call.
276
+ SW Light never toggles that switch, changes profile settings, clears manual
277
+ control or starts a continuous adaptation loop. Choose a profile using
278
+ color-temperature sleep color; RGB sleep profiles are rejected with a warning.
279
+
280
+ Precedence for each command:
281
+
282
+ 1. Explicit message color/temperature wins, including unsupported color that
283
+ is omitted with an event. It always suppresses Adaptive Lighting.
284
+ 2. Otherwise, Adaptive Lighting runs only after a successful automatic OFF → ON
285
+ service, on a color-temperature-capable light.
286
+ 3. Otherwise, normal configured SW Light color behavior applies.
287
+ 4. Keep current/lamp default omit color and leave normal HA/device behavior.
288
+
289
+ `msg.adaptive_lighting` (boolean) overrides the checkbox for that command.
290
+ Brightness, automatic maximum, override protection and fade stay with SW Light.
291
+ An already-ON brightness command, manual command, OFF or zero-brightness command
292
+ never invokes Adaptive Lighting. Configured fixed color still follows normal
293
+ SW Light behavior on these commands. Legacy adaptive flows with optional apply
294
+ already enabled retain their opt-in; former inactive adaptive flows stay off.
295
+ Legacy discover/respect-manual controls are retired.
296
+
297
+ After `light.turn_on`, SW Light calls `adaptive_lighting.apply` for exactly one
298
+ light with `adapt_brightness: false`, `adapt_color: true`, `prefer_rgb_color: false`,
299
+ `turn_on_lights: true`, and the SW Light command transition (0 if unsupported).
300
+ The true flag avoids silently skipping color when HA has not yet observed the
301
+ successful ON. Adaptive Lighting clamps temperature to the light's range.
302
+ The two actions are not atomic: a brief initial/default color may be visible,
303
+ and an external OFF between them can race with the second action. Service
304
+ acknowledgement reports an accepted action, not physical temperature confirmation.
305
+
306
+ Missing/unavailable/ambiguous/running profiles, a missing apply action or its
307
+ failure emit output 2 `warning`, with `phase: "adaptive_apply"`, structured
308
+ `reason`, and `light_service_completed: true`. Normal ON and output 1 survive,
309
+ retaining the configured color fallback. Non-CT lights emit `unsupported` and
310
+ still turn on without temperature parameters. A running HA profile or other
311
+ external automation can still alter lights independently; turn those off for
312
+ this ownership model. SW Light cannot cancel their behavior.
313
+
314
+ Investigation: [upstream apply documentation](https://adaptive-lighting.nijho.lt/services/)
315
+ and [source inspected on 2026-09-30](https://github.com/basnijholt/adaptive-lighting/blob/283fec08ae7d34901d13c32e91fb001f92395fe7/custom_components/adaptive_lighting/switch.py).
316
+ The main switch exposes last calculated `color_temp_kelvin` when on, but null
317
+ when off; reading that cache cannot deliver the requested fresh calculation.
318
+ `apply` recalculates in `prepare_adaptation_data` even while off. No dedicated
319
+ read-only temperature calculation service is exposed. Calculations remain in
320
+ Adaptive Lighting, including sun/time and sleep settings.
321
+
322
+ ### Real-world verification
323
+
324
+ 1. Install the local 0.1.9 archive, restart Node-RED, and verify its startup
325
+ version. Use a test light with `color_temp` support. Connect both outputs to Debug.
326
+ 2. In HA, expose the profile's configuration attributes for automatic discovery
327
+ (or select it under Advanced). Keep its main switch off, use color-temperature
328
+ sleep color, and disable any other automatic controllers for this test light.
329
+ 3. In SW Light select Action On, brightness/max 94%, Keep current, Adaptive Lighting
330
+ On. Turn the light off in HA and wait until Node-RED observes OFF. Trigger ON.
331
+ 4. Verify `adaptive.applied: true` and brightness 239/255 (about 93.7%). After the
332
+ fade, inspect HA light state and physically check the light. To get a comparison
333
+ value while the profile is off, call HA's `adaptive_lighting.apply` yourself
334
+ with the same profile/light, brightness false, color true, RGB preference false,
335
+ turn-on true and transition 0; verify the light's temperature stays the same
336
+ (allow calculation changes around a time boundary and device rounding).
337
+ 5. Turn the light off and wait for OFF. Repeat at another time, or toggle the
338
+ profile's sleep-mode helper with a distinct configured sleep temperature.
339
+ Trigger ON; verify the new value rather than the previous temperature is used.
340
+ Restore the sleep helper after testing.
341
+ 6. While ON, send another automatic ON with `brightness_pct: 80`. Verify brightness
342
+ changes and `adaptive.applied: false`; with Keep current, temperature stays put.
343
+ 7. From OFF, send ON with `color_temp_kelvin: 3000`, then a supported explicit
344
+ color command. Verify each wins and no adaptive action runs. Try a manual ON
345
+ and verify the same suppression.
346
+ 8. Set brightness to 100% externally. Automatic OFF must remain blocked. Lower
347
+ below the configured threshold and verify OFF works again.
348
+ 9. Test missing/ambiguous profiles, the main switch ON, and a non-CT light. Confirm
349
+ normal ON plus the corresponding warning/unsupported event. Redeploy/reconnect
350
+ and verify fresh discovery, safe observation and no duplicate adaptive calls.
351
+
352
+ Actual Adaptive Lighting version, exposed configuration, sleep behavior, lamp
353
+ latency/rounding and the two-action race still require verification on your HA
354
+ installation. Automated tests mock that service boundary, not its algorithm.
355
+
356
+ ## Test the updated 0.1.9 in your HA Node-RED installation
261
357
 
262
358
  1. Save/export your current test flow. Transfer the rebuilt local
263
- `johanwistbacka-node-red-contrib-sw-light-0.1.4.tgz` to the Node-RED host. Check that HA contrib
359
+ `johanwistbacka-node-red-contrib-sw-light-0.1.9.tgz` to the Node-RED host. Check that HA contrib
264
360
  is 0.80.3; do not upgrade it for this task.
265
361
  2. In a terminal for that Node-RED instance, find **User directory** in its
266
362
  startup log. Change to that directory (not a global npm directory) and run
267
- `npm install ./johanwistbacka-node-red-contrib-sw-light-0.1.4.tgz --ignore-scripts --no-audit --no-fund`.
363
+ `npm install ./johanwistbacka-node-red-contrib-sw-light-0.1.9.tgz --ignore-scripts --no-audit --no-fund`.
268
364
  Use the add-on's supported local package installation mechanism if its terminal
269
365
  does not expose that user directory/npm. Restart Node-RED/the Node-RED add-on
270
366
  and reload the editor so the updated runtime and HTML are both loaded.
271
367
  3. Add a timestamp Inject directly into SW Light. Choose your **existing HA
272
368
  server**, one `light.*` entity, Action **On**, and the desired On brightness
273
- (94% by default). Keep optional Adaptive apply disabled for this basic test.
369
+ (94% by default). Keep Adaptive Lighting off for this basic test.
274
370
  Remove downstream HA Action nodes; leave outputs unconnected or use Debug.
275
371
  4. Deploy. SW Light constructs without resolving HA and initially shows
276
372
  `HA NOT READY`. With the light OFF, click Inject: the light should turn on at the
@@ -279,7 +375,7 @@ SW Light never repeatedly corrects brightness or fights its adaptation.
279
375
  5. Set the lamp to 100% manually in HA (verify attribute `brightness: 255`).
280
376
  Try On, Off and Toggle: no automatic light service should run, and the node
281
377
  should show `MANUAL · 100% · BLOCK ...`. Lower the light manually to 94%, 95%
282
- or 99%, then retry; automatic control should resume with default override settings.
378
+ then retry; automatic control should resume with default override settings.
283
379
  6. Select **From msg** and use Inject properties `action` (string) with `on`,
284
380
  `off` and `toggle`. Also test `auto_light.action` when top-level action is
285
381
  absent. A missing/invalid action should show an error, emit output 2, and
@@ -310,13 +406,14 @@ Output 1 follows successful light service acknowledgement:
310
406
  "service_data": { "brightness": 115, "transition": 1 },
311
407
  "ignored_conflicts": [], "state_confirmation": "last_observed",
312
408
  "observed_at": "2026-09-30T10:00:00Z",
313
- "adaptive": { "entity_id": null, "active": false, "manual_control": false, "state": null }
409
+ "adaptive": { "entity_id": null, "active": false, "state": null, "candidates": [], "applied": false, "reason": "not_requested" }
314
410
  }
315
411
  }
316
412
  ```
317
413
 
318
- `adaptive_lighting` means **configured mode**. `adaptive.active` means a resolved
319
- switch is ON. `adaptive.applied` is present when optional apply is evaluated.
414
+ `adaptive_lighting` means the command's effective enablement (message overrides node).
415
+ `adaptive.active` means a resolved profile is available with its main switch OFF.
416
+ `adaptive.applied` reports successful action acknowledgement, not measured Kelvin.
320
417
  The state is the **last observed HA snapshot**, possibly preceding the action
321
418
  or an unfinished transition. Service acknowledgement does not confirm a physical
322
419
  lamp change. No requested value is fabricated as observed state. The first input
@@ -357,7 +454,7 @@ Output 2 has a stable versioned event envelope:
357
454
  ```
358
455
 
359
456
  Types: `manual_override`, `override_released`, `unavailable`, `unsupported`,
360
- `error`. Details include `reason`, `parameters`, `message`, `phase` as applicable.
457
+ `warning`, `error`. Details include `reason`, `parameters`, `message`, `phase` as applicable.
361
458
  Unsupported parameters are omitted and the supported action still runs.
362
459
  Blocked commands emit only output 2. Invalid input and transport failures also
363
460
  call Node-RED `done(error)` for Catch-node handling. Connection/state events have
@@ -381,7 +478,7 @@ npm pack --dry-run
381
478
  Run these checks from the source checkout; test tooling is not included in the npm package.
382
479
  Tests use Node's built-in test runner without HA or npm dependencies. They cover
383
480
  numeric ranges, precedence, max/min, manual override, color translation,
384
- unsupported capabilities, adaptive discovery/manual control, runtime outputs,
481
+ unsupported capabilities, one-shot adaptive discovery/fallback, runtime outputs,
385
482
  async errors, reconnect readiness and listener cleanup. The source checkout retains a separate development integration report; it is
386
483
  excluded from the public npm package. Importable examples are in `examples/`; select your
387
484
  own HA server and light before deploying them in a dev instance.
@@ -395,14 +492,14 @@ own HA server and light before deploying them in a dev instance.
395
492
  - A v0.1 raw state subscription receives all state changes and filters locally;
396
493
  one is created per SW Light node. It is removed on close, and HA's websocket
397
494
  resubscription handles reconnects. Sharing subscriptions is a v0.2 improvement.
398
- - Adaptive attributes are not standardized. Existing manual-control detection
399
- and automatic brightness ownership require testing with the actual integration.
495
+ - Adaptive discovery requires exposed configuration and exact light membership.
496
+ Use Advanced selection when discovery is unavailable; verify the installed profile.
400
497
  - Keep current depends on HA/device behavior. Color gamut correction and XY to
401
498
  other color-space conversion are not implemented. ON/OFF lights remain usable
402
499
  without brightness/color capabilities.
403
500
 
404
501
  Before live use, test server selection/autocomplete, each lamp capability,
405
- external brightness changes, threshold/explicit release, adaptive takeover,
502
+ external brightness changes, threshold/explicit release, adaptive one-shot behavior,
406
503
  long transitions, deletion/unavailability, reconnect and repeated redeployment
407
504
  in your real editor with a development light. No live deployment, global config
408
505
  change, npm publication or GitHub release is part of this implementation.
package/docs/labels.md ADDED
@@ -0,0 +1,198 @@
1
+ # Label targets — 0.1.9 development candidate
2
+
3
+ ## Investigated APIs and membership
4
+
5
+ The installed/published HA contrib **0.80.3** artifact was inspected, including
6
+ `Websocket.send`, `subscribeMessage`, registry collections, registry event names,
7
+ private editor selector and HTTP routes. It already owns registry subscriptions
8
+ but does not expose a public custom-node Label selector or Label HTTP endpoint.
9
+ SW Light continues to share its authenticated client through the existing adapter.
10
+ No separate connection, credentials or provider shutdown are introduced.
11
+
12
+ HA official WebSocket commands used:
13
+
14
+ - `config/label_registry/list`: `label_id`, human-readable `name`.
15
+ - `config/entity_registry/list`: `entity_id`, `labels`, `device_id`, `area_id`,
16
+ `hidden_by`, `disabled_by`, `entity_category`.
17
+ - `config/device_registry/list`: `id`, `labels`, `area_id`.
18
+ - `config/area_registry/list`: `area_id`, `labels`.
19
+
20
+ Membership follows HA 2026.9 service target semantics: directly labeled entities,
21
+ entities of directly labeled devices, and entities reached through labeled areas.
22
+ An entity's explicit area takes precedence over its device's area. A label on a
23
+ parent device is not inherited through `via_device_id`. Hidden entries are excluded;
24
+ indirect device/area expansion excludes config/diagnostic entities, whereas a
25
+ directly labeled entity may have a category. SW Light additionally excludes disabled
26
+ entries and non-`light.*` IDs, and deduplicates the final list. These are registry
27
+ entities; SW Light does not expand a light group's members or custom integration
28
+ relationships. A `light` group can therefore coexist with its physical members;
29
+ avoid putting both in the test Label if independent physical control is required.
30
+
31
+ Primary sources:
32
+
33
+ - [HA Labels](https://www.home-assistant.io/docs/organizing/labels/)
34
+ - [HA 2026.9.0 target resolution](https://github.com/home-assistant/core/blob/2026.9.0/homeassistant/helpers/target.py)
35
+ - [HA WebSocket API](https://developers.home-assistant.io/docs/api/websocket/)
36
+ - [HA contrib 0.80.3](https://www.npmjs.com/package/node-red-contrib-home-assistant-websocket/v/0.80.3)
37
+
38
+ Area relationships are necessary for Label semantics. **Area is not an exposed
39
+ target type**, and no Area/Room configuration, occupancy, scene or mode logic is added.
40
+
41
+ ## Architecture and editor
42
+
43
+ `targets.js` validates `{type,id}`, resolves registry membership and builds aggregate
44
+ status. `controller.js` owns the existing execution path, one Override detector per
45
+ entity, command planning, capability filtering and Adaptive Lighting. `sw-light.js`
46
+ coordinates controllers and output. `ha-adapter.js` alone knows provider internals.
47
+ Adding a future Area branch to target validation/resolution will reuse these same
48
+ controllers; it does not require another light-control implementation.
49
+
50
+ Target choices are **Entity** (default) and **Label**. Missing `targetType` in old
51
+ flows means Entity; `entityId` and old outputs/status remain compatible. Label uses
52
+ `labelId`, a stable HA ID. Autocomplete displays `name (label_id)` and a separate
53
+ name helper; it stores only the ID. Changing server aborts requests and ignores
54
+ obsolete responses. Cancel/save destroys autocompletes and handlers.
55
+
56
+ Entity autocomplete still uses HA contrib's `homeassistant/states` route. Label
57
+ uses `/sw-light/labels/:serverId`, protected by Node-RED `server.read`, fetching
58
+ `config/label_registry/list` through the shared adapter. A deployed connected server
59
+ is required for suggestions; entering a known stable Label ID remains possible.
60
+
61
+ ## Refresh, subscriptions and overrides
62
+
63
+ A node caches an immutable registry snapshot for **30 seconds**. Resolved lists are
64
+ cached per snapshot and Label ID, so repeated brightness inputs do not rescan all
65
+ registries. Provider events `labels_updated`, `entity_registry_updated`,
66
+ `devices_updated`, `areas_updated` invalidate the snapshot and debounce reconciliation
67
+ by 100 ms. The provider itself throttles registry updates. Ready/connect/state-loaded
68
+ and disconnect events also invalidate it. A 30-second timer reconciles idle nodes,
69
+ providing a fallback if an update event is missed; commands refresh expired snapshots.
70
+ An invalidated in-flight snapshot is rejected rather than installed as fresh data.
71
+
72
+ There is **one raw state_changed subscription per node**, with a reconciled Set of
73
+ member IDs, rather than a subscription for every light. Departed IDs stop updating
74
+ that node; added IDs get their own controller. Remaining controllers retain their
75
+ explicit/threshold flags. Removed controllers are discarded; rejoining lights start
76
+ with fresh flags and observe their actual state. Registry resolution failure clears
77
+ the watched membership and reports TARGET UNAVAILABLE; existing controllers are
78
+ retained until successful reconciliation. No stale members receive a command.
79
+
80
+ The shared WebSocket library handles reconnect resubscription. Closing/redeploying
81
+ removes this node's listeners, subscription, timers and controllers; it leaves the
82
+ provider's listeners and connection intact. Close during an in-flight service prevents
83
+ later light/adaptive calls and output from that node.
84
+
85
+ `msg.target = {type:"label", id:"cozy"}` or
86
+ `msg.target = {type:"entity", id:"light.example"}` overrides configuration for that
87
+ input. Its members remain observed until the next input; an input without `target`
88
+ restores configured targeting. Reconciliation discards controllers that leave the
89
+ active target, including their explicit latch. Use a stable target when retaining
90
+ explicit latches across commands. Unknown types, including Area, fail validation.
91
+
92
+ Each light retains independent state, automatic maximum, capability filtering,
93
+ override flags and Adaptive eligibility. With max 94%, threshold 98%, window 94%,
94
+ table 100% and floor 70%, automatic OFF controls window/floor and skips table only.
95
+ Lowering table below threshold releases table only. Explicit latches still require
96
+ explicit release or observed OFF; manual-source commands keep their existing bypass.
97
+ The brightness-255 guard remains independent of the configured detector.
98
+
99
+ ON/OFF-only lights receive no brightness/color parameters. RGB, CT, brightness-only
100
+ and transition support are inspected per actual entity. Unsupported parameters emit
101
+ entity-specific events and are omitted. A member's range/transport/availability error
102
+ does not prevent later members from running.
103
+
104
+ Adaptive Lighting behavior remains unchanged: eligible automatic observed OFF → ON
105
+ first executes the light action, then applies one-shot CT adaptation for that light
106
+ only. Profile discovery uses that light's exact configuration membership. Unsupported
107
+ members turn on normally; missing/running/ambiguous profiles or failed apply emit
108
+ warnings while the successful light action stays successful. No solar algorithm is
109
+ copied or shared between members.
110
+
111
+ ## Outputs and status
112
+
113
+ Entity output 1 retains the prior normalized result; blocked Entity commands still
114
+ emit their event without a result. Label output 1 retains the incoming message and
115
+ adds `sw_light` with `version:1`, `target:{type,id}`, `resolved_entity_ids`, `results`
116
+ and `counts:{succeeded,skipped,failed,manual}`. Each result identifies `entity_id`,
117
+ observed state/override and its outcome. Successful results include service data and
118
+ Adaptive details; skipped results explain manual override; failed results contain
119
+ error type/message and whether the light action completed. Adaptive warnings alone
120
+ are not counted as light-action failure.
121
+
122
+ Success means service completion, **not confirmed physical state**. Output and Label
123
+ status use last observed HA states. The aggregate can show `2 on · 1 manual · 2 off`
124
+ or `4 on · 1 unavailable`; manual members are counted separately from automatic ON.
125
+ Empty Labels emit `target_empty`, an empty aggregate and LABEL EMPTY. Deleted Labels
126
+ or missing registries emit `target_resolution_failure`, TARGET UNAVAILABLE and Catch
127
+ error handling, without controlling old members. Per-member partial failure returns
128
+ an aggregate and entity-specific output 2 events without failing the whole input.
129
+
130
+ ## Exact real-world verification sequence
131
+
132
+ This sequence is pending; do it after separately authorizing/installing the local
133
+ candidate in your development HA/Node-RED environment.
134
+
135
+ 1. Export the test flow and note the currently installed version. Restart Node-RED
136
+ after installing 0.1.9, reload the editor and verify the startup `version=0.1.9`.
137
+ Keep one old Entity node in the flow and confirm it operates without reconfiguration.
138
+ 2. Create one HA Label named **SW Light test**, record its generated stable ID, and
139
+ use physical test lights (not a group entity): a dimmable CT light A, an RGB or
140
+ brightness-only light B, and an ON/OFF light C where available. Add A directly,
141
+ B through its device, and C directly. Also add one sensor. If A's device is shared
142
+ with other lights, labeling it deliberately includes those primary lights too.
143
+ 3. Select Target **Label**, choose SW Light test by name, save/reopen and verify the
144
+ stable ID remains selected. Configure From msg, brightness/max 94%, threshold 98%,
145
+ 1-second fades. Connect both outputs to complete-message Debug nodes.
146
+ 4. Start all members OFF. Inject `{action:"on"}`. Confirm all physical members turn
147
+ on; A/B receive at most 94%, C receives plain ON. Check IDs, independent service
148
+ data, success counts and subsequent observed aggregate status.
149
+ 5. Set A to approximately 94%, B to 100% manually in HA, and leave C ON. Wait for
150
+ output 2's B `manual_override` event. Inject `{action:"off"}`. A and C must turn
151
+ OFF while B remains ON; aggregate must show B skipped/manual and two successes.
152
+ Optionally use three dimmable lights with values 94/100/70 to match the exact example.
153
+ 6. Lower B to 70% through HA and wait for its `override_released`. Inject automatic
154
+ ON and OFF; B now follows both. Raise A to 100%, leave B below threshold, repeat
155
+ OFF and confirm only A is protected. Inject `{action:"off",source:"manual"}` and
156
+ confirm the manually overridden member also turns OFF.
157
+ 7. Start A/B/C OFF. Enable Adaptive Lighting on SW Light and keep the relevant
158
+ profile main switch OFF, CT sleep color configured, configuration attributes
159
+ enabled. Inject ON. Confirm A receives its own one-shot CT apply while B/C turn on
160
+ normally. Verify no Adaptive brightness change, correct per-light profiles and
161
+ warnings for unsupported members. Already-ON brightness changes must not reapply.
162
+ 8. Add a fourth physical light D to the same Label while the node is idle. Wait for
163
+ reconciliation (normally provider event delay + 100 ms; fallback about 30 seconds)
164
+ and confirm D appears in status/next result. Remove C; changing C in HA must no
165
+ longer produce events for this node. No flow edit should be necessary.
166
+ 9. Label A's device as well as A directly; confirm A appears once. Where convenient,
167
+ label a test area with the same Label and verify the documented area precedence
168
+ and extra primary lights. Remove that area assignment afterward.
169
+ 10. Make B unavailable through the test device/integration's normal controls. Inject
170
+ automatic ON/OFF. Available members must still succeed; output identifies B as
171
+ failed/unavailable. Restore B and verify recovery.
172
+ 11. Disconnect/reconnect the HA connection in the development environment, then send
173
+ a fresh command after ready. Verify refreshed membership, external override
174
+ observation and no duplicate events. Redeploy only the SW Light node, then remove
175
+ it; confirm removed nodes no longer emit events/status or light actions.
176
+ 12. Remove all light assignments while retaining the Label/sensor: expect LABEL EMPTY
177
+ and no calls. Delete the Label: expect target_resolution_failure and no old calls.
178
+ Recreate a Label and select its actual new ID; do not assume the name restores identity.
179
+
180
+ Still requires real HA verification: actual registry permissions/schema on the
181
+ installed HA version, rendered autocomplete/save/reopen, physical service/state timing,
182
+ live membership update latency, real network reconnect/redeploy and Adaptive profile
183
+ behavior. Local tests simulate transport and never connect to or install into HA.
184
+
185
+ ## Changed files and local checks
186
+
187
+ Runtime/editor: `nodes/sw-light.js`, `nodes/sw-light.html`, `lib/light.js`,
188
+ `lib/ha-adapter.js`, new `lib/controller.js`, new `lib/targets.js`.
189
+ Package/documentation: `package.json`, `README.md`, `CHANGELOG.md`,
190
+ `docs/integration.md`, new `docs/labels.md`.
191
+ Tests: `test/published-integration.test.js`, new `test/targets.test.js`,
192
+ `test/label-runtime.test.js`, `test/label-adapter.test.js`, `test/label-editor.test.js`.
193
+ Existing Adaptive Lighting implementation and example flows are unchanged.
194
+
195
+ `npm run check` passed. Full suite with the isolated 0.80.3 installation enabled:
196
+ **65 tests passed, zero failures/skips**. Version is prepared through
197
+ `npm version 0.1.9 --no-git-tag-version`. No Git repository exists in this workspace;
198
+ no commit/tag/release/publication/HA deployment is part of this candidate.
@@ -139,7 +139,8 @@
139
139
  "sw-example-basic-events"
140
140
  ]
141
141
  ],
142
- "action": "msg"
142
+ "action": "msg",
143
+ "overrideThreshold": 98
143
144
  },
144
145
  {
145
146
  "id": "sw-example-basic-result",
@@ -11,7 +11,7 @@
11
11
  "type": "comment",
12
12
  "z": "sw-example-manual-adaptive",
13
13
  "name": "Select HA server + light in SW Light before deploy",
14
- "info": "Adaptive apply is off by default. Select your Adaptive Lighting switch and enable optional color-only apply if desired.",
14
+ "info": "Adaptive Lighting is off by default. Enable the checkbox for automatic OFF to ON; keep the HA profile main switch off. Advanced selection is only needed when discovery is ambiguous or unavailable.",
15
15
  "x": 350,
16
16
  "y": 60,
17
17
  "wires": []
@@ -193,7 +193,8 @@
193
193
  "sw-example-manual-adaptive-events"
194
194
  ]
195
195
  ],
196
- "action": "msg"
196
+ "action": "msg",
197
+ "overrideThreshold": 98
197
198
  },
198
199
  {
199
200
  "id": "sw-example-manual-adaptive-result",