@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 +42 -0
- package/README.md +157 -60
- package/docs/labels.md +198 -0
- package/examples/basic.json +2 -1
- package/examples/manual-adaptive.json +3 -2
- package/lib/adaptive.js +33 -23
- package/lib/controller.js +98 -0
- package/lib/ha-adapter.js +51 -7
- package/lib/light.js +18 -11
- package/lib/targets.js +54 -0
- package/nodes/sw-light.html +61 -25
- package/nodes/sw-light.js +121 -89
- package/package.json +5 -2
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.
|
|
1
|
+
# SW Light — v0.1.9
|
|
2
2
|
|
|
3
|
-
A reusable Node-RED node for
|
|
4
|
-
existing HA server, select a
|
|
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
|
|
18
|
-
for each npm package name.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 |
|
|
123
|
-
| Color mode |
|
|
124
|
-
| Adaptive
|
|
125
|
-
|
|
|
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.
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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, "
|
|
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
|
|
319
|
-
|
|
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/
|
|
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
|
|
399
|
-
|
|
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
|
|
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.
|
package/examples/basic.json
CHANGED
|
@@ -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
|
|
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",
|