node-red-contrib-lg 0.2.1
Node-RED nodes to control and monitor LG ThinQ air conditioners and LG webOS TVs.
node-red-contrib-lg
Node-RED nodes to control and monitor LG ThinQ air conditioners and LG webOS TVs.
- โ๏ธ Air conditioners (LG ThinQ) โ one
lg-acnode: send commands in, get live state out (turn on/off, set mode / temperature / fan). - ๐ก๏ธ AC status โ temperature, power, mode, etc. are emitted on a schedule even when the AC is off, plus real-time push so changes made on the AC itself (its remote, the LG app) emit instantly.
- ๐บ TVs (LG webOS) โ one
lg-tvnode: turn on (Wake-on-LAN) / off / toggle in, on/off events out. - ๐ Automatic auth โ log in once with your LG account; the refresh token is extracted and saved for reuse.
Unofficial project. Not affiliated with or endorsed by LG. Use at your own risk.
Installation
From your Node-RED user directory (usually ~/.node-red):
npm install node-red-contrib-lg
Or use Menu โ Manage palette โ Install and search for node-red-contrib-lg.
Restart Node-RED. Two nodes (LG AC, LG TV) appear under the LG category, plus the
LG ThinQ account config node.
Requires Node.js 18+ and Node-RED 3.0+.
Nodes
| Node | Kind | Purpose |
|---|---|---|
lg-account |
config | LG ThinQ account + shared device poller |
lg-ac |
in + out | Control an AC and emit its state (after commands and on every poll) |
lg-tv |
in + out | Control a webOS TV (on/off/toggle) and emit on/off events |
Each device is a single node: wire your commands into its input, and wire its output to read state. There are ready-made example flows under Menu โ Import โ Examples โ node-red-contrib-lg.
Quick start โ Air conditioners (ThinQ)
1. Configure your account
Add an lg-account config node (it appears when you drop an lg-ac node and open it):
- Country โ the ISO code of the country your LG account is registered in (e.g.
US,GB,PT). - Language โ e.g.
en-US,pt-PT. - Username / Password โ your LG ThinQ account credentials.
- Click Extract refresh token / Test. This logs in, pulls a long-lived refresh token (filled into the field) and lists your devices. Click Done and Deploy.
How the token is handled:
- It is stored as an encrypted Node-RED credential when you use the button.
- At runtime it is also cached to disk at
<userDir>/node-red-contrib-lg/thinq-<nodeId>.token, so restarts never need a fresh username/password login. If the refresh token ever becomes invalid, the node automatically logs in again using the stored username/password.
2. Add an AC โ lg-ac
Pick the account and the AC from the dropdown. The single lg-ac node both takes commands on
its input and emits the AC state on its output.
Input โ commands (msg.payload):
msg.payload |
Effect |
|---|---|
true / "on" |
Turn on |
false / "off" |
Turn off |
"status" |
Just read the current state now |
22 (number) |
Set target temperature to 22 ยฐC |
"cool" / "heat" / "fan" / "dry" / "auto" |
Set mode |
{ "power": true, "mode": "COOL", "temperature": 22, "fan": "HIGH" } |
Set several at once |
{ "verticalVane": 1, "horizontalVane": 1 } |
Aim the louvers (1 = top / left โฆ higher = down / right; "swing" = sweep; "off" = stop) |
{ "swing": "both" } |
Swing shorthand ("vertical" / "horizontal" / "both" / "off") |
{ "raw": { "airState.xxx": n } } |
Escape hatch: send any LG airState.* key directly |
Rapid or concurrent messages to the same AC are serialized (applied one-at-a-time, in order),
so a sequence of changes behaves like the LG app rather than overlapping into 0103 errors.
An LG AC rejects mode/temperature/fan changes while it is off, so the node automatically turns
the AC on first when you change one of those while it is off (power-on is sent before the other
settings, with a short gap so the unit is ready). Turning the AC off ignores any other settings
in the same message. This makes it safe to drive from HomeKit, where power, mode and temperature
arrive as separate messages. Set msg.deviceId to target a different AC at runtime.
Output โ state is emitted after every command and on each poll cycle:
{
"topic": "<deviceId>",
"deviceId": "<deviceId>",
"name": "Living room",
"event": "change",
"changed": ["power"],
"payload": {
"online": true,
"power": true,
"mode": "COOL",
"currentTemperature": 23.5,
"targetTemperature": 22,
"fanSpeed": "HIGH",
"humidity": null,
"verticalVane": 1,
"horizontalVane": 1
}
}
Vane positions are raw LG values: 0 = stop, 1..N = fixed position, 100 = swing. The valid
fixed positions are model-dependent (e.g. vertical 1โ6, horizontal 1โ5).
msg.event tells you why it was emitted: initial / change / periodic (from polling),
command (after a control), or query (after a "status" request). Enable Raw to also get
the raw airState.* snapshot in msg.raw.
Output behaviour:
- Poll (checkbox on the
lg-acnode) โ emit on every poll cycle (a steady heartbeat; always reports the current temperature, even while off). The poll interval lives on the account node (default 60 s, minimum 10 s) and is shared by alllg-acnodes. - Real-time pushes โ changes made on the AC itself (its remote, the LG app) are emitted instantly
(
event: "change") whenever Real-time is enabled on the account node. This is the single MQTT switch; there is no per-device toggle โ everylg-acgets pushes for its device.
For change-only output, turn Poll off (with the account's Real-time on you then receive only
instant change pushes) โ or add a filter/rbe node after the lg-ac node. Turn Poll off and the
account's Real-time off to make the node control-only (no status output).
Quick start โ TVs (webOS)
The lg-tv node is self-contained (no separate config node): it holds the connection, takes
control commands on its input, and emits on/off events on its output.
1. Configure the node โ lg-tv
- IP address โ the TV's IP (give it a static lease on your router).
- MAC โ the TV's MAC address. Required to turn the TV on via Wake-on-LAN.
- Broadcast โ usually
255.255.255.255(or your subnet broadcast, e.g.192.168.1.255). - Reconnect โ retry interval (seconds, default 5) used while the TV is off. On/off detection is event-driven (an off is detected almost immediately); this interval is how quickly an off โ on transition is noticed. Lower it for snappier detection.
- Secure โ leave off to try
ws://โฆ:3000first; enable for TVs that requirewss://โฆ:3001. - Action โ
From msg.payload(default), or hard-wireon/off/toggle.
On the TV, enable Settings โ General โ Mobile TV On (and keep Quick Start+ on) so Wake-on-LAN works while the TV is in standby.
Pairing: the first time Node-RED connects, the TV shows a pairing prompt โ accept it once.
The pairing key is then stored at <userDir>/node-red-contrib-lg/webos-<nodeId>.key.
2. Use it
Input โ commands: with Action set to From msg.payload, send "on", "off", or "toggle".
Output โ state is emitted whenever the TV turns on or off (and after a command):
{
"topic": "Living room TV",
"event": "on",
"payload": { "power": true, "state": "On", "connected": true }
}
msg.event is on / off for status changes, or command right after a control. Detection uses
the webOS power-state subscription when available, and falls back to the websocket connection state
on older TVs.
Command reference
Air conditioner โ lg-ac
Send any of these as msg.payload on the input. Set msg.deviceId to target a different AC than
the one configured. Changing mode/temperature/fan/vane while the AC is off turns it on first;
sending an "off" ignores any other settings in the same message.
Shortcut payloads
msg.payload |
Effect |
|---|---|
true / "on" |
Power on |
false / "off" |
Power off |
"status" (or msg.topic = "status") |
Read current state, change nothing |
a number, e.g. 22 |
Set target temperature (ยฐC) |
"cool" / "heat" / "dry" / "fan" / "auto" / "air_clean" |
Set mode |
Object payload โ combine any of these fields in one object:
| Field | Accepted values |
|---|---|
power |
true / false (also "on", "off", "start", "stop", 1, 0) |
mode |
"COOL", "DRY", "FAN", "HEAT", "AIR_CLEAN", "AUTO" (case-insensitive) or the numeric value |
temperature |
number in ยฐC (sent as-is; valid range is model-dependent, typically 16โ30) |
fan |
"SLOW", "SLOW_LOW", "LOW", "LOW_MID", "MID", "MID_HIGH", "HIGH", "POWER", "AUTO" or the numeric value |
verticalVane |
0 = stop, 1โ6 = fixed position (1 = top), 100 = swing โ or "off" / "swing" |
horizontalVane |
0 = stop, 1โ5 = fixed position (1 = left), 100 = swing โ or "off" / "swing" |
swing |
"vertical", "horizontal", "both", "off" (shorthand for setting both louvers to swing/stop) |
raw |
{ "airState.<key>": value, โฆ } โ sends those LG keys verbatim (escape hatch for anything not modelled) |
Example: { "power": true, "mode": "COOL", "temperature": 22, "fan": "HIGH", "verticalVane": 1, "horizontalVane": 1 }
Numeric value maps (for raw or when sending numbers):
| Mode | # | Fan | # | |
|---|---|---|---|---|
| COOL | 0 | SLOW | 0 | |
| DRY | 1 | SLOW_LOW | 1 | |
| FAN | 2 | LOW | 2 | |
| HEAT | 4 | LOW_MID | 3 | |
| AIR_CLEAN | 5 | MID | 4 | |
| AUTO | 6 | MID_HIGH | 5 | |
| HIGH | 6 | |||
| POWER | 7 | |||
| AUTO | 8 |
Which modes / fan speeds / vane positions a unit actually supports โ and the exact numbers โ is model-dependent; the fan map above is LG's standard enum but some models differ. The authoritative list is the device's model JSON (
modelJsonUri,Value['airState.windStrength'].value_mapping).LOW/MID/HIGH(2 / 4 / 6) are reliable.AUTOfan iswindStrengthvalue8(LG's model JSON labels this value "NATURE", but it is the "auto" fan the app exposes).An unsupported value returns
resultCode 0001. A transientresultCode 0103means the unit couldn't apply the command at that moment (busy, or fan speed while in an auto-managed mode, or just after a power/mode change) โ the node now retries these automatically; if it still fails, the AC was likely in a mode that doesn't allow that change (e.g. fan speed in AUTO).
Output msg (emitted after a command and on every poll / real-time change):
| Field | Description |
|---|---|
payload |
{ online, power, mode, modeValue, currentTemperature, targetTemperature, fanSpeed, fanSpeedValue, humidity, energyWatts, verticalVane, horizontalVane } |
event |
initial / change / periodic (poll) ยท command (after a control) ยท query (after "status") ยท change (real-time MQTT push) |
changed |
array of fields that changed (poll / push) |
deviceId, topic |
the AC device id |
raw |
the raw airState.* snapshot (only if Raw is enabled) |
commands |
labels of the commands that were sent (after a control) |
TV โ lg-tv
Set the node's Action to From msg.payload (default) and send a command, or hard-wire the
Action to on / off / toggle (then the payload is ignored).
Input msg.payload
msg.payload |
Effect |
|---|---|
true / "on" / 1 |
Turn on (Wake-on-LAN) |
false / "off" / 0 |
Turn off |
"toggle" |
Toggle on/off |
{ "power": true } / { "power": false } |
On / off |
The TV node currently controls power only (on / off / toggle). Volume, inputs and app launch are not exposed.
Output msg (emitted whenever the TV turns on/off and after a command):
| Field | Description |
|---|---|
payload |
{ power: boolean, state: string, connected: boolean } (state is the webOS label, e.g. On, Off) |
event |
on / off (status change) or command (after a control) |
topic |
the TV name |
How it works
- ThinQ uses the (unofficial) LG ThinQ v2 cloud API: a gateway lookup, an OAuth login that
yields a refresh token, and periodic polling of
service/homesfor each device's snapshot. AC commands arecontrol-syncSetcalls (airState.operation,airState.opMode,airState.tempState.target,airState.windStrength). - Real-time updates use LG's AWS IoT MQTT broker: the plugin requests a client certificate
(it generates an RSA key + CSR), connects over mutual-TLS MQTT and subscribes to the account's
topics. LG pushes a delta (
{ deviceId, data: { state: { reported } } }) whenever a device changes, which is merged into the cached snapshot and emitted instantly. The poll runs alongside it for periodic temperature and as a fallback if MQTT is unavailable. - webOS uses the local WebSocket protocol (via
lgtv2) for control and power-state subscription, and a built-in Wake-on-LAN magic packet to power on.
The poller and the MQTT connection are shared by all lg-ac nodes on the same account.
Notes & limitations
- This relies on LG's cloud for ThinQ; if LG changes the API it may need updating.
- Supported AC fan-speed values and modes vary by model; you can always pass a raw numeric value.
- TV power-on requires Wake-on-LAN to be enabled on the TV and a reachable broadcast address.
- ThinQ "v1" (older) devices are not specifically handled; this targets ThinQ v2 ACs.
Development
npm install
npm test # unit + node-load tests (no network)
# Optional read-only live checks against a real LG account (never send control commands):
[email protected] LG_PASSWORD=secret LG_COUNTRY=PT LG_LANGUAGE=en-US npm run smoke:thinq
[email protected] LG_PASSWORD=secret LG_COUNTRY=PT LG_LANGUAGE=en-US npm run smoke:mqtt
Credits
Protocol details were learned from the excellent community projects homebridge-lg-thinq and homebridge-webos-tv.
License
MIT ยฉ Miguel Ruivo