node-red-contrib-lsh-logic 1.6.3
A single, powerful node for advanced automation logic on devices implementing the public LSH MQTT/protocol contract.
Node-RED Contrib LSH Logic
A powerful, high-performance Node-RED node designed to manage advanced automation logic for smart home devices that speak the public LSH MQTT / protocol contract. Built with TypeScript for maximum reliability and type safety.
This node replaces complex Node-RED flows with a single, robust, and stateful component that manages device state, implements distributed click logic (two-phase commit), and actively monitors device health.
The original installation behind it uses Controllino controllers plus ESP32 bridges, but the Node-RED boundary is the LSH protocol contract, not the exact hardware. If another controller / bridge stack implements the same public MQTT topics and payload contract, this node is designed to work there too.
If you are new to the public LSH stack, read the landing reference page first:

Start Here
Use this README according to what you need:
- If you are new to the stack, read the landing reference page and glossary first.
- If you want the shortest answers to adoption questions, skim the landing
FAQ.md. - If you want the shortest end-to-end bring-up path, read the landing
GETTING_STARTED.md. - If startup or click behavior looks inconsistent, use the landing
TROUBLESHOOTING.md. - If you want the shortest path to a working setup, read Installation, then Configuration, then the example configs under
examples/. - If you want the runtime model, startup behavior and watchdog semantics, read How It Works.
- If you want to integrate MsgPack, jump to Advanced: MsgPack Support.
Bundled Examples
The fastest assets to open in this repository are:
- examples/lsh-logic-example.json: example flow with dynamic MQTT subscription management
- examples/system-config.minimal.json: smallest useful
system-config.json - examples/system-config.multi-device.json: richer multi-device topology example
Key Features
- Shared LSH Protocol Support: Uses the generated contract vendored from
lsh-protocol, keeping command IDs, compact keys and examples aligned with the firmware repositories. - Robust Health Monitoring: Features a multi-stage intelligent Watchdog that detects stale or offline devices without generating false positives during startup or temporary network glitches.
- Robust Cold Recovery: If Node-RED restarts, the node first reuses retained
conf/statesnapshots when they are already complete. Only when at least one configured device is missing an authoritative snapshot does it request a single bridge-localBOOTreplay, then repairs missing snapshots and pings any device that is still unreachable. - Distributed Click Logic: Implements a Two-Phase Commit protocol for critical actions (like "Long Clicks"), ensuring commands are executed only when target devices are reachable and currently healthy. Pending clicks expire on a hard timeout; a late confirmation is rejected even if a later cleanup sweep has not run yet.
- Homie & HA Discovery: Fully compliant with the Homie Convention for state tracking and automatically generates Home Assistant Auto-Discovery payloads for seamless integration.
- Config-Driven HA Entity Mapping: Optionally remap Homie actuator nodes to Home Assistant
light,switch, orfanentities and assign friendly names directly fromsystem-config.json. - High Performance: Optimized message routing using direct string parsing and efficient internal state management.
- Declarative Configuration: Define your entire system in a single
system-config.jsonfile. The node automatically hot-reloads configuration changes.
Installation
Install directly from the Node-RED Palette Manager or via npm in your user directory (e.g., ~/.node-red):
npm install node-red-contrib-lsh-logic
Scope And Portability
This package is intentionally LSH-oriented. It is not meant as a completely generic drop-in automation node for arbitrary MQTT devices.
The important dependency is the public LSH protocol + MQTT contract, not the exact hardware used in the original installation.
In practice that means:
- if your devices speak the same LSH topics and payload contract, this node is intended to work with them
- if your devices use a different protocol model entirely, this package is the wrong abstraction layer
- the original Controllino + ESP32 combination is the reference implementation, not a hard runtime requirement
How It Works
This node acts as the central orchestrator for your protocol-compatible smart home devices. It subscribes to MQTT topics, processes incoming telemetry and events, updates its internal state registry, and dispatches commands.
The canonical command IDs, compact wire keys and golden JSON examples are generated from the shared spec in vendor/lsh-protocol/shared/lsh_protocol.md. The LSH payload layer assumes a trusted environment and a cooperative broker.
At startup the node uses retained LSH snapshots when available, but it does not trust retained Homie $state alone as proof of current reachability: that must come from a live Homie transition or live controller-backed LSH traffic. After a short subscription-settle window, the node checks whether every configured device already has an authoritative conf + state snapshot. If yes, it skips the startup BOOT entirely. If not, it requests a single bridge-local BOOT replay, waits for the replay window, and then runs an active verification pass. During that verification, reachable devices receive only the missing snapshot requests, while still-unreachable devices are pinged directly. A later live ready, conf, state, events, or device-level PING response automatically recovers devices that were offline during startup. During this warm-up window the periodic watchdog is intentionally paused; startup reachability is decided by the dedicated verification cycle, not by watchdog alerts racing the initial sync.
The shared maintenance workflow lives in vendor/lsh-protocol/README.md. This README intentionally focuses on Node-RED behavior instead of restating protocol ownership rules.
Operational simplifications:
- If a runtime config reload becomes unreadable or invalid, the node intentionally fails closed: it clears the active config, unsubscribes, and waits for a valid file again.
- Reloading
system-config.jsonalways clears pending network click transactions. In-flight distributed clicks are intentionally failed rather than preserved across a config change. - Runtime config reloads do not restart the startup warm-up/verification cycle. Recovery after reload is best-effort through normal live traffic, retained MQTT data and later watchdog pings.
- Distributed long-click logic requires an authoritative actuator snapshot for every targeted LSH device. If a target is reachable but still missing fresh state, the click fails fast and is retried naturally on the next user action.
- Retained
confandstatesnapshots are treated as the last known authoritative topology/state, not as proof that the device is currently alive. Device health and reachability come only from live Homie transitions, live LSH traffic and watchdog ping responses. - Retained
eventsandbridgepayloads are ignored. They are runtime-only signals and never count as current activity, click traffic or bridge health. - Bridge-local diagnostics published by
lsh-bridgeonbridgeare accepted as informational runtime events, but they do not count as controller traffic or proof of current controller reachability. - Extremely narrow timing races during startup or config reload are handled in best-effort mode rather than with complex transaction recovery logic.
To verify that the Node-RED generated protocol files match the vendored source of truth:
python3 tools/update_lsh_protocol.py --check
Inputs
The node accepts messages from an mqtt-in node. It processes:
- LSH Protocol Topics:
<lshBase>/<device>/conf: Static configuration (actuatorsa, buttonsb).<lshBase>/<device>/state: Live actuator states (s).<lshBase>/<device>/events: Controller-backed runtime events like Clicks and device-levelPINGreplies.<lshBase>/<device>/bridge: Bridge-local runtime events like service-levelPINGreplies and diagnostics.
- Homie Topics:
<homieBase>/<device>/$state: Connectivity status (ready,lost).- Homie attributes (
$mac,$fw/version, etc.) for HA Discovery.
Outputs
The node has five distinct outputs for clear and organized flows:
- LSH Commands: Commands targeting your LSH protocol devices (e.g.,
SET_STATE,PING,NETWORK_CLICK_ACK). - Other Actor Commands: Abstracted commands for controlling 3rd party devices (Tasmota, Zigbee) via other Node-RED flows. The payload contains the listing of target actors and the state to set.
- Alerts: Human-readable health alerts (Markdown formatted) suitable for notifications (Telegram/Slack). The payload also includes machine-readable
event_typeandevent_sourcefields so flows can distinguish lifecycle/reboot alerts from true watchdog outages without parsing the formatted text. - Configuration: Dynamic control messages for the
mqtt-innode. - Debug: Passthrough of original messages for debugging.
Configuration
Node Settings
name: Optional label shown in the Node-RED editor.homieBasePath: Base topic for Homie traffic, for examplehomie/. Must end with/.lshBasePath: Base topic for LSH traffic, for exampleLSH/. Must end with/.serviceTopic: Bridge-scoped service topic used for hop-localPINGand startupBOOTreplay requests. The default public profile usesLSH/Node-RED/SRV.protocol: Payload format for LSH commands and LSH runtime topics. Supported values areJSONandMsgPack.systemConfigPath: Path tosystem-config.json, absolute or relative to the Node-RED user directory.clickTimeout: Hard timeout for the request → ACK → confirm click lifecycle.clickCleanupInterval: Periodic cleanup sweep for stale click transactions.initialStateTimeout: Startup replay window used only when a bridge-localBOOTreplay is actually needed.watchdogInterval: Frequency of periodic device health checks.interrogateThreshold: Silence threshold before the watchdog sends a ping.pingTimeout: How long the watchdog waits for a ping response before treating a device as stale.haDiscovery: Enable or disable Home Assistant auto-discovery output.haDiscoveryPrefix: Home Assistant discovery topic prefix, usuallyhomeassistant. Required only when discovery is enabled.exposeStateContext/exposeStateKey: Optional export of the live internal device registry to flow/global context.exportTopics/exportTopicsKey: Optional export of the generated MQTT topic set to flow/global context.exposeConfigContext/exposeConfigKey: Optional export of the effective loaded runtime config to flow/global context.otherActorsContext: Which context store (floworglobal) is used to read non-LSH actor state.otherDevicesPrefix: Prefix used when looking up external actor state in the selected context store.
The editor help in src/lsh-logic.html documents the same
fields from the Node-RED UI perspective.
system-config.json
This file defines the topology of your smart home. It should be placed in your Node-RED user directory.
Ready-to-copy examples are available in:
- examples/system-config.minimal.json
- examples/system-config.discovery-overrides.json
- examples/system-config.multi-device.json
{
"devices": [
{
"name": "c1",
"haDiscovery": {
"deviceName": "Kitchen Board",
"defaultPlatform": "switch",
"nodes": {
"1": {
"platform": "light",
"name": "Kitchen Ceiling",
"defaultEntityId": "light.kitchen_ceiling"
}
}
},
"longClickButtons": [
{
"id": 1,
"actors": [{ "name": "j1", "allActuators": true }],
"otherActors": ["tasmota_shelf_lamp"]
}
]
},
{ "name": "j1" },
{ "name": "k1" }
]
}
name: Must match the exact device ID used in MQTT topics. With the current reference bridge defaults this is typically a short ID such asc1,j1,k1; the default bridge build allocates 4 characters unlessCONFIG_MAX_NAME_LENGTHis raised.longClickButtons: Optional list of long-click actions handled by the orchestration layer for this device.superLongClickButtons: Optional list of super-long-click actions handled by the orchestration layer for this device.longClickButtons[].id/superLongClickButtons[].id: Numeric button ID that triggers the distributed action.longClickButtons[].actors/superLongClickButtons[].actors: Target LSH devices affected by the action.actors[].name: Target LSH device name.actors[].allActuators:trueto target all actuators on the device,falseto target only a specific subset.actors[].actuators: Required whenallActuatorsisfalse; lists the exact actuator IDs to target.longClickButtons[].otherActors/superLongClickButtons[].otherActors: Optional non-LSH actors, identified by name, to be emitted on the "Other Actor Commands" output.haDiscovery: Optional Home Assistant discovery overrides for this device.haDiscovery.deviceName: Optional Home Assistant device name override.haDiscovery.defaultPlatform: Optional default Home Assistant entity platform for all actuator nodes of the device (light,switch, orfan).haDiscovery.nodes: Optional per-node overrides keyed by the Homie node ID as published under$nodes.haDiscovery.nodes.<id>.platform: Optional per-node Home Assistant entity platform override.haDiscovery.nodes.<id>.name: Optional friendly entity name override shown in Home Assistant.haDiscovery.nodes.<id>.defaultEntityId: Optional Home Assistantdefault_entity_idoverride for first discovery.haDiscovery.nodes.<id>.icon: Optional Home Assistant icon override.
Best Practices
Dynamic MQTT Subscriptions
The most powerful way to use this node is to let it manage your MQTT subscriptions automatically. This creates a "zero-maintenance" flow that adapts to your configuration.
Connect the 4th output ("Configuration") directly to an mqtt-in node.

When you deploy or when system-config.json changes, the lsh-logic node will:
- Send a message to the
mqtt-innode to unsubscribe from all topics. - Send a second message to subscribe to the new, correct list of topics.
This ensures your mqtt-in node is always listening to exactly the right topics without any manual changes.
Advanced: MsgPack Support
To use MsgPack:
- Set LSH Protocol to
MsgPackin the node settings. - Configure your Input MQTT Node to return "a Buffer" instead of a parsed string.
- Ensure your ESP firmware supports decoding MsgPack payloads.
The node handles decoding (Input) and encoding (Output) transparently. In MsgPack mode, non-Buffer inbound LSH payloads are rejected instead of being silently reinterpreted as text or JSON.
Maintainer Notes
Toolchain:
- Runtime compatibility for the published package remains
Node.js >= 18. - Maintainer tooling is validated on modern Node.js and currently expected to run on
Node.js 24for linting, formatting, testing and packaging. python3is only needed for maintainer tasks that runtools/update_lsh_protocol.py.
Runtime path rules:
systemConfigPathis resolved relative to the Node-RED user directory unless you provide an absolute path.- The example flow uses
configs/system-config.jsonon purpose; it is a user/runtime path, not a repository-relative maintainer path.
Protocol maintenance rules:
tools/update_lsh_protocol.pyuses the vendored subtree invendor/lsh-protocolby default.- You can override that source explicitly with
--protocol-rootorLSH_PROTOCOL_ROOTwhen doing manual maintenance work.
Cross-repo contract tests:
- The Jest contract test can validate this package against sibling
lsh-coreandlsh-bridgerepositories when they are available in the same workspace. - If your workspace uses different locations, set
LSH_CORE_ROOTandLSH_BRIDGE_ROOTbefore runningnpm test. - These paths are maintainer-only test inputs; they are not required for normal package runtime.
Contributing
Contributions are welcome!
Development Setup
- Clone the repo:
git clone https://github.com/labodj/node-red-contrib-lsh-logic.git - Install a supported Node.js version (
>= 18) - Install dependencies:
npm install - Build:
npm run build - Test:
npm test - Optional maintainer tooling: ensure
python3is available if you need to runtools/update_lsh_protocol.py
License
Apache 2.0 - See LICENSE for details.