LifeSmart Local Mode#
Overview#
Local mode allows LifeSmart hubs to operate over a direct LAN TCP connection, bypassing cloud connectivity entirely. Instead of the OAPI/WebSocket cloud path, the integration uses LifeSmartLocalTCPClient β a persistent TCP socket client β to communicate with the hub over a custom binary protocol.
The two integration modes are selected in config_flow.py via a CONN_CLASS_LOCAL_PUSH vs CONN_CLASS_CLOUD_PUSH choice . Local mode follows a simpler, single-step configuration flow compared to cloud mode.
Setup: Required Configuration Fields#
Local mode requires four fields :
| Field | Default | Notes |
|---|---|---|
CONF_HOST | (empty) | LAN IP address of the hub |
CONF_PORT | 3000 | TCP port |
CONF_USERNAME | admin | Hub username |
CONF_PASSWORD | admin | Hub password (masked) |
During setup, validate_local_input() creates a temporary LifeSmartLocalTCPClient and calls check_login(), which opens a TCP connection (5 s timeout) and verifies credentials. ConfigEntryAuthFailed is raised for bad credentials; ConfigEntryNotReady for network timeouts or OS errors.
Multi-Hub Configuration#
Each hub is a separate Home Assistant config entry, titled "Local Hub (<host>)". There is no limit on entries; repeat the configuration flow for each hub. Each entry maintains its own independent LifeSmartLocalTCPClient instance and background async_connect task .
Connection Lifecycle#
async_connect() is the main loop, running as a background HA task:
- TCP connect β
asyncio.open_connectionwith a 5 s timeout. - Login β sends a
build_login_packetand parses theretfield. On failure, setsdisconnected = Trueand aborts. - Load devices (
loadingstage) β sendsbuild_get_config_packet, parses theepsdict intoself.devices, and sets thedevice_readyasyncio.Event. - Live push (
loadedstage) β receives_schgstate-change pushes. Supports both the 6-part (agt/me/ep/<devid>/m/<idx>) and legacy 5-part (agt/ep/<devid>/m/<idx>) path formats . - Reconnect β on any socket error, sleeps 5 s and retries from step 1 .
get_all_device_async() waits on the device_ready event (default 10 s timeout) before returning the device list.
Idle Timeout & Heartbeat#
IDLE_TIMEOUT = 65.0 seconds. During the loaded stage, if no data arrives within this window, a build_get_config_packet is sent as a heartbeat probe. A write failure breaks the loop and triggers reconnect. During login or loading stages, an idle timeout is treated as a hard error and forces immediate reconnect .
Protocol Encoding (protocol.py)#
Packets are built by LifeSmartPacketFactory and decoded by LifeSmartProtocol, both in core/protocol.py.
_pack_valuerecursively serializes Python objects to binary using type markers (0x02/0x03booleans,0x13enums,0x12dicts/lists).enum:prefixed strings are resolved first viaREVERSE_KEY_MAPPING(named keys β byte id), then viaint(key)for numeric ids like"enum:91".decodehandles both uncompressed (GL00header) and gzip-compressed (ZZ00) packets.- Key packet builders:
build_login_packet,build_get_config_packet,build_epset_packet/build_multi_epset_packetfor device control.
Known Issue: Numeric Enum ID Regression (Fixed in PR #120)#
A regression in _pack_value caused numeric enum ids like "enum:91" (used in build_get_config_packet) to be sent as the literal UTF-8 string "enum:91" instead of the correct 0x13 + int binary encoding. The local gateway rejected this with err: 'ENADF1:<node>/me/ep:enum:91', causing hub.py:246 to abort with ConfigEntryNotReady .
Because login packets use no numeric enum ids, the connection appeared to authenticate successfully β only the subsequent get_config round-trip failed. This was the root cause behind issues #117 and #119.
Fix (merged 2026-06-01): PR #120 added a numeric fallback inside the enum: branch of _pack_value. Versions after v25.07.30 contain the regression; upgrade to the patched build (commit 502e5ac or later) to resolve it.
Unsupported Operations in Local Mode#
The following raise PlatformNotReady because the local TCP protocol has no equivalent API :
- Scene list queries
- Room list queries
- IR remote list queries
Hub list queries return only the single connected hub .
Key Files#
| File | Purpose |
|---|---|
core/local_tcp_client.py | TCP client β connection loop, packet dispatch, device cache |
core/protocol.py | Binary packet encoding/decoding |
hub.py | Hub setup β instantiates LifeSmartLocalTCPClient, starts background task |
config_flow.py | UI flow β form fields, validate_local_input, config entry creation |