@johanwistbacka/node-red-contrib-sw-light 0.1.4
Home Assistant light control with automatic brightness limits and manual override
SW Light — v0.1.4
A reusable Node-RED node for one Home Assistant light.* entity. Select an
existing HA server, select a light, deploy in a development environment,
and select Action On, Off, Toggle, or From msg. Display name:
SW Light; flow type: sw-light.
The basic flow is Trigger → SW Light → Home Assistant. SW Light reads the selected light, checks manual override and calls the HA light service itself. No external HA Action node is required; the two outputs are for observation and integration only. With a fixed Action, a normal timestamp Inject is enough.
Package identity and migration
The permanent npm package name is @johanwistbacka/node-red-contrib-sw-light.
The Node-RED type stays sw-light and its display name stays SW Light.
The scoped package starts at version 0.1.4; version numbers are independent
for each npm package name. No runtime/editor or HA integration behavior changes.
If upgrading from the old unscoped node-red-contrib-sw-light, export your
flows first. Remove the old package from the Node-RED user directory before
installing the scoped one; do not install both together because they register
the same node type. Use Palette Manager's package removal/installation controls
or, with Node-RED stopped, run these commands in its user directory:
npm uninstall node-red-contrib-sw-light
npm install @johanwistbacka/[email protected]
Restart Node-RED and reload the editor. Existing flow nodes retain type
sw-light; verify the scoped package/version in Palette Manager before testing.
The scoped candidate is not published yet; registry installation becomes
available after its first authorized publication.
Compatibility and installation
Requires Node.js >=18.2.0, Node-RED >=3.1.1 and
node-red-contrib-home-assistant-websocket exactly 0.80.3. This initial
adapter is intentionally pinned because it uses that package's internal
shared-client module. It does not require the HA Node-RED companion integration.
HA contrib itself requires Home Assistant >=2023.12. Numeric bounds and
capabilities are read from the selected light.
This is an experimental development release. Local tests pass, but the runtime integration has not yet been confirmed with the correct installed version in the real Home Assistant environment. The adapter uses internal HA contrib APIs; compatibility outside 0.80.3 is unsupported. Temporary structural diagnostics are enabled. Test with a development light before relying on this node.
After this version is published, install it in your Node-RED user directory:
npm install @johanwistbacka/[email protected]
After publication, the current development release is available under npm latest:
npm install @johanwistbacka/node-red-contrib-sw-light@latest
This candidate has been prepared locally and is not published yet. Restart Node-RED after installation and verify the version in Palette Manager.
Select/create a Home Assistant server using the existing HA contrib config node. A new server must be deployed before its entities are available in the autocomplete. The text field also accepts an entity ID directly. This package does not store tokens, create connections, change global context, or alter the server config. Installation commands above are for your separate dev instance; do not replace a working installation's versions without checking compatibility.
For edits, use npm's local directory installation/link or reinstall an archive
created with npm pack. Restart the isolated Node-RED runtime after changes.
There is no build step or additional runtime dependency.
The current development release uses the normal SemVer version 0.1.4 and
npm tag latest for Node-RED compatibility. This version remains experimental;
it is not a SemVer prerelease. Every installation/publication candidate needs a
unique version. Use npm version patch --no-git-tag-version for the next normal
development version. Separate prerelease builds may use beta tags. Git tags,
GitHub releases and npm publication require an explicit request.
Publishing to npm does not automatically add a node to the Community Catalogue. After publication, submit the package through the Node-RED Flow Library, or request a refresh if it is already listed.
Temporary runtime diagnostics in 0.1.4
This build logs [SW Light debug] at construction and on the first input,
then when the relevant structure/readiness changes. It reports version,
server-ID presence, whether the server resolves, constructor/type checks,
expected method/property presence, provider version and client readiness.
It never logs server IDs, entity IDs, credentials, hosts, full objects or state
snapshots. Provider initialization errors are reduced to a generic message.
After installation, restart Node-RED and verify a log line containing
[SW Light debug] version=0.1.4. Inject once and retain the prefixed lines,
along with any error/stack. Those lines distinguish missing configuration,
a missing provider client and a connected client with unexpected methods.
The real installation problem remains unverified until this build is tested
there. Diagnostics are temporary and intended to be removed after diagnosis.
The adapter follows the accessor used by official Current State and Action,
using one Node module resolution anchored at RED.settings.userDir. HA contrib
0.80.3 exposes no documented public third-party transport API or client method
on its server config node. Its internal dependency remains isolated and pinned;
there is no require.cache scan or search for alternate package copies.
Configuration
| Setting | Default | Meaning |
|---|---|---|
| Action | From msg | On/Off always choose that behavior; Toggle resolves the current state; From msg reads the message |
| On brightness | 94% | Used when no message brightness is supplied |
| Automatic maximum | 94% | Ceiling for every automatic ON/step |
| Minimum | 1% | Floor for nonzero brightness, including manual commands |
| On/off transition | 1s each | Sent only with light transition capability |
| Manual override | Enabled | Pause automatic commands while override is active |
| Detection | Brightness threshold + flag | Alternative: explicit flag only |
| Threshold | 100% | Lamp brightness at/above this value activates override |
| Color mode | Adaptive Lighting | No configured color sent during ordinary ON |
| Adaptive discovery | Enabled | Unique membership match in switch attributes |
| Adaptive apply on ON | Disabled | Optional color-only adaptive_lighting.apply |
| Respect adaptive manual control | Enabled | Skip apply when light is manually controlled |
Require minimum <= on brightness <= automatic maximum. When threshold
detection is enabled, automatic maximum must be below the threshold.
Transitions are 0–3600 seconds. Configuration errors prevent action calls.
Known lamp capabilities disable irrelevant fixed color choices in the editor;
runtime validation is authoritative.
Color modes: Adaptive Lighting, Keep current, Fixed Kelvin, Fixed mired, Lamp
default, Fixed color (#rrggbb). Kelvin and mired are normalized to Kelvin,
validated against the lamp's advertised limits and sent as
color_temp_kelvin. Fixed RGB translates to RGB, HS, XY, RGBW or RGBWW
according to capabilities. White channels are zero when expanding RGB.
White channels supplied explicitly are never silently discarded.
Keep current and Lamp default both omit configured color in v0.1. They use HA/device restoration behavior; Keep current cannot guarantee that a device or HA default profile preserves color across an OFF/ON cycle. Brightness defaults still apply in every color mode.
Input contract
Properties are on the top-level message, not nested under payload:
{ "action": "on" }
{ "action": "off" }
{ "action": "toggle" }
{ "action": "on", "brightness_pct": 45, "transition": 3 }
{ "action": "on", "color_temp_kelvin": 2200 }
{ "action": "on", "source": "manual", "brightness_pct": 100 }
{ "action": "on", "source": "manual", "manual_override": true }
{ "action": "on", "source": "manual", "manual_override": false }
With Action From msg, msg.action is required; msg.auto_light.action
is accepted when the top-level action property is absent. Top-level action
takes precedence, including invalid values (which produce a clear error).
Fixed Action settings ignore message action properties. From msg remains the
default for backward compatibility. source is automatic (default) or manual.
Manual source bypasses the automatic ceiling and automatic command blocking;
it does not automatically latch override. Use the boolean manual_override
with source: "manual" for an explicit latch or release. An invalid source or
flag produces an error before service calls.
Message values override configuration for that command. Within a conflicting
group the first supplied property in the following order wins; other properties
are reported in ignored_conflicts. All supplied recognized values are
validated, even those that lose precedence.
| Group, in precedence order | Accepted values |
|---|---|
brightness_pct, brightness, brightness_step_pct, brightness_step |
0–100, integer 0–255, -100–100, integer -255–255 |
color_temp_kelvin, color_temp |
Integer 1000–40000 K; 25–1000 mired, then lamp limits |
rgbww_color, rgbw_color, rgb_color |
Arrays of 5, 4, 3 integer bytes 0–255 |
hs_color, xy_color, color_name |
[0–360,0–100], [0–1,0–1], HA color name |
transition |
0–3600 seconds |
The complete color precedence follows the table top to bottom. color_name
is passed to HA for name resolution/validation, only on a color-capable lamp.
Unrecognized message fields are preserved and never forwarded as service data.
Scalar numeric strings are accepted (useful for config); empty strings, null,
booleans, NaN, infinity and values outside bounds are errors.
Absolute brightness is clamped to the configured nonzero minimum and source's maximum. Steps resolve against the last observed HA brightness (zero when off), then clamp to 0–100 and the configured bounds. Step commands fail when an ON lamp's brightness is unknown. Resolved zero turns the light off; it passes the same override protection as ordinary OFF. HA uses byte brightness; the automatic ceiling rounds down so 94% sends at most 239/255 (93.7%). Other values round to the nearest byte, so a 1% minimum becomes 3/255 (1.2%).
toggle resolves current ON to light.turn_off, and current OFF to
light.turn_on with ON defaults. Brightness and color parameters supplied with an OFF command or
an ON→OFF toggle are omitted with an unsupported event. Transition is filtered
using the light's supported_features bit 32. Unknown/missing color capabilities
are treated conservatively. RGB/HS can translate between supported RGB/HS/XY
modes. XY and white-channel representations need their matching native mode.
Manual override
ON at 100% = manual override: automatic ON, OFF and Toggle do nothing. This hard guard requires brightness exactly 255/255, so 94%, 95%, 99% and 254/255 do not trigger it. It applies even if the optional threshold detector is disabled or set to explicit-only. Lowering the light below 255 allows automatic control again unless an additional configured override is active.
Threshold detection observes HA state, not requested brightness. The configured threshold is converted to a byte; default 100% means exactly 255, not 254 rounded for display. With defaults, automation uses 1–94%, and observed 100% activates override. It pauses all automatic commands, including ON, OFF, toggle and brightness zero, so an automatic ON cannot accidentally lower and release a manual setting. A manually lowered observed brightness releases the threshold flag. OFF releases all flags. Missing/unknown/unavailable state preserves flags and rejects commands.
An explicit latch is independent of the threshold. It persists until explicit
release or observed OFF. A release flag clears both flags for its command;
subsequent threshold observations can reactivate override. Flags are in memory,
so redeployment rebuilds threshold state from HA but loses explicit latches.
This detector is separated from the planner for future HA-context-based origin
detection. source is the v0.1 command-origin contract.
Adaptive Lighting
Ordinary ON in Adaptive mode sends brightness and supported transition, no color or temperature. Adaptive Lighting retains ownership of color. A message color explicitly overrides that behavior for one command, and suppresses optional apply even when the color is unsupported and filtered.
Manual switch selection takes precedence. Discovery uses the light's exact
membership in attributes.configuration.lights or attributes.lights on
switch entities with adaptive-like attributes. It only selects one unique
candidate; it does not infer names or configure HA. This is best-effort attribute
discovery, not a registry-backed guarantee that a switch belongs to the integration.
No actual installation was available for validating its attribute schema.
Use the switch picker/manual entity ID when discovery fails or is ambiguous.
Optional apply runs after successful turn-on, only for an enabled selected
switch, without explicit message color. It calls adaptive_lighting.apply with
adapt_brightness: false, adapt_color: true, turn_on_lights: false. It respects
manual_control / manual_control_color lists by default and never clears them,
turns on an adaptive switch or changes integration settings. No discovery is
required when apply is disabled: the ordinary HA light action works on its own.
An apply failure emits output 2 with phase: "adaptive_apply" and
light_service_completed: true; the successful light result still emits.
Adaptive Lighting may interpret explicit brightness commands as manual control
depending on its own take_over_control and related settings. Its background
brightness adaptation may also exceed this node's maximum; the ceiling only
applies to SW Light's outgoing commands. Configure the integration's own
brightness limits/ownership in your dev environment and test that combination.
SW Light never repeatedly corrects brightness or fights its adaptation.
Test the updated 0.1.4 in your HA Node-RED installation
- Save/export your current test flow. Transfer the rebuilt local
johanwistbacka-node-red-contrib-sw-light-0.1.4.tgzto the Node-RED host. Check that HA contrib is 0.80.3; do not upgrade it for this task. - In a terminal for that Node-RED instance, find User directory in its
startup log. Change to that directory (not a global npm directory) and run
npm install ./johanwistbacka-node-red-contrib-sw-light-0.1.4.tgz --ignore-scripts --no-audit --no-fund. Use the add-on's supported local package installation mechanism if its terminal does not expose that user directory/npm. Restart Node-RED/the Node-RED add-on and reload the editor so the updated runtime and HTML are both loaded. - Add a timestamp Inject directly into SW Light. Choose your existing HA
server, one
light.*entity, Action On, and the desired On brightness (94% by default). Keep optional Adaptive apply disabled for this basic test. Remove downstream HA Action nodes; leave outputs unconnected or use Debug. - Deploy. SW Light constructs without resolving HA and initially shows
HA NOT READY. With the light OFF, click Inject: the light should turn on at the configured automatic level. Select Action Off, redeploy and inject: it should turn off. Test Toggle from both ON and OFF. - Set the lamp to 100% manually in HA (verify attribute
brightness: 255). Try On, Off and Toggle: no automatic light service should run, and the node should showMANUAL · 100% · BLOCK .... Lower the light manually to 94%, 95% or 99%, then retry; automatic control should resume with default override settings. - Select From msg and use Inject properties
action(string) withon,offandtoggle. Also testauto_light.actionwhen top-level action is absent. A missing/invalid action should show an error, emit output 2, and perform no service call. After HA disconnects, the node showsHA NOT READY; retry after HA reconnects. Initialization failures are retried on the next input. Also inject before HA is ready, then after it becomes ready without redeploying: the second input should succeed. Test partial redeploy and reconnect as well.
These are manual acceptance steps, not evidence of a live deployment.
Outputs and status
Both outputs preserve original message properties. Only sw_light (output 1)
or sw_light_event (output 2) is assigned by this node.
Output 1 follows successful light service acknowledgement:
{
"action": "on",
"sw_light": {
"entity_id": "light.example", "state": "on",
"brightness": 115, "brightness_pct": 45.1,
"color_mode": "color_temp", "color_temp_kelvin": 2700,
"color_temp": null, "color": {},
"manual_override": false, "adaptive_lighting": true,
"action": "turn_on", "requested_action": "on", "source": "automatic",
"service_data": { "brightness": 115, "transition": 1 },
"ignored_conflicts": [], "state_confirmation": "last_observed",
"observed_at": "2026-09-30T10:00:00Z",
"adaptive": { "entity_id": null, "active": false, "manual_control": false, "state": null }
}
}
adaptive_lighting means configured mode. adaptive.active means a resolved
switch is ON. adaptive.applied is present when optional apply is evaluated.
The state is the last observed HA snapshot, possibly preceding the action
or an unfinished transition. Service acknowledgement does not confirm a physical
lamp change. No requested value is fabricated as observed state. The first input
resolves the selected server through the provider accessor and starts observation.
Every subsequent input revalidates the server/client; temporary failures remain
retryable. Once attached, status updates live from HA events (including
brightness-only changes), reconnects and deletions. A replaced provider client
gets fresh listeners; closing/redeploying removes this node's old subscriptions.
Output 1 does not emit every unsolicited state change in v0.1.
Inputs received before HA readiness are rejected explicitly: output 2 reports
unavailable and done(error) triggers Catch-node handling. They are not queued
or replayed. Send a new input after readiness. The node stays alive and retries
resolution on the next input. Readiness observation begins with the first input.
If a constructor stack shows sw-light.js:54, ha-adapter.js:33 and
light.js:16, it identifies the original 0.1.0 implementation, not this
candidate. After installation, restart Node-RED and verify both Palette Manager
and the startup debug version before interpreting a new runtime failure.
Status distinguishes CONFIG ERROR (invalid/missing server or local config),
HA NOT READY (client/connection/states initializing), and ENTITY UNAVAILABLE
(missing, unknown or unavailable light). Blocked commands show
MANUAL · 100% · BLOCK ON/OFF; completed commands show AUTO · ON · 94%
or AUTO · OFF with the actual command level.
Output 2 has a stable versioned event envelope:
{
"sw_light_event": {
"version": 1, "type": "manual_override", "entity_id": "light.example",
"timestamp": "2026-09-30T10:00:00Z", "active": true,
"blocked": true, "requested_action": "off"
}
}
Types: manual_override, override_released, unavailable, unsupported,
error. Details include reason, parameters, message, phase as applicable.
Unsupported parameters are omitted and the supported action still runs.
Blocked commands emit only output 2. Invalid input and transport failures also
call Node-RED done(error) for Catch-node handling. Connection/state events have
no incoming message, so only the event property is supplied.
Architecture and checks
lib/light.js owns config validation, override detection, capabilities, color
normalization and the pure command planner. lib/adaptive.js owns discovery and
apply behavior. lib/ha-adapter.js owns the sole internal HA dependency,
state-cache access, service calls and resubscribing event subscriptions.
nodes/sw-light.js connects Node-RED lifecycle, sequential inputs, status and
the two output contracts. nodes/sw-light.html implements the editor/help.
npm test
npm run check
npm pack --dry-run
Run these checks from the source checkout; test tooling is not included in the npm package.
Tests use Node's built-in test runner without HA or npm dependencies. They cover
numeric ranges, precedence, max/min, manual override, color translation,
unsupported capabilities, adaptive discovery/manual control, runtime outputs,
async errors, reconnect readiness and listener cleanup. The source checkout retains a separate development integration report; it is
excluded from the public npm package. Importable examples are in examples/; select your
own HA server and light before deploying them in a dev instance.
Known limits and next steps
- The actual installed Node-RED/HA/Adaptive Lighting versions and attributes could not be inspected; compatibility outside the stated baseline is unverified.
- Service results contain cached state, without physical confirmation. Rapid relative commands and toggles can use stale state despite serial service calls.
- A v0.1 raw state subscription receives all state changes and filters locally; one is created per SW Light node. It is removed on close, and HA's websocket resubscription handles reconnects. Sharing subscriptions is a v0.2 improvement.
- Adaptive attributes are not standardized. Existing manual-control detection and automatic brightness ownership require testing with the actual integration.
- Keep current depends on HA/device behavior. Color gamut correction and XY to other color-space conversion are not implemented. ON/OFF lights remain usable without brightness/color capabilities.
Before live use, test server selection/autocomplete, each lamp capability, external brightness changes, threshold/explicit release, adaptive takeover, long transitions, deletion/unavailability, reconnect and repeated redeployment in your real editor with a development light. No live deployment, global config change, npm publication or GitHub release is part of this implementation.
Recommended v0.2: verify and add your installed HA contrib version to the adapter's tested compatibility matrix, then add state-confirmation/relative-command coordination and shared per-server subscriptions.