Add ThinQ2 device handler for CST_570004_WW (LG ceiling-cassette AC) - #101
Add ThinQ2 device handler for CST_570004_WW (LG ceiling-cassette AC)#1013735943886 wants to merge 4 commits into
Conversation
CST_570004_WW is an LG ceiling-cassette IDU (deviceType 401), typically
installed as several IDUs on one multi-split ODU. It speaks the same DualCool
AC TLV protocol as RAC_056905_WW, so this handler extends RAC and patches the
CST-specific differences, driving features from the device's real capability
frame wherever possible:
- 0xA7 async/query UART header (vs 0x87) is normalized before delegating.
- Feature bitmap: CST omits RAC's 0x2CC but reports the same bitmap under the
adjacent tag 0x2CB, so it is read as the feature source (bit2 masked, since
auto-dry is exposed as a duration select, not RAC's binary sensor).
- Temperature range is read from caps 0x2E1/0x2E2 (16-30 C), fulfilling RAC's
existing 'TODO: read 0x2e1-0x2ec' instead of the hardcoded 18 C minimum.
- HVAC modes: CST advertises {0,1,2,3} in caps 0x2C1 and reports 0x1F9=3 for
auto, so mode uses cool=0/dry=1/fan_only=2/auto=3 (no heat) rather than RAC's
table (which decodes wire 3 as undefined and writes auto as an unsupported 6).
- Filter: CST reports 0x2F1 'no filter' yet its basic-filter priv-command
returns real data, so the filter is probed regardless (with retries).
- Cooling-only: jet is suppressed (CST's 0x2CD is not RAC's jet/swing bitmap).
- CST-specific fan scale (1/2/4/6/7/8) and on/off vertical/horizontal swing.
- Extra entities: humidity (0x336), always-on Power (0x2B3), auto-dry duration
select (0x20E) + remaining sensor (0x225), display brightness (0x21F),
comfort energy saving (0x23F), and a wind-mode select (comfort airflow).
Verified on 4 live IDUs (LG multi-split). Includes a unit test driving real
caps/values/filter captures through the handler.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7bab4f5 to
97cea98
Compare
…mand filter RAC reads filter usage from a basic-filter priv-command (0x02/0x02). On CST that command returns an unpopulated counter — all four IDUs reply identically used=0/life=720 regardless of actual usage — so it does not match the app. The real filter usage is in value tags: 0x356 = rated life (2400 h, constant), 0x355 = remaining hours. valuesReceived now skips the priv-command probe (so RAC creates none of its priv filter entities), and setConfig derives a 'Filter remaining' (%) and 'Filter used time' (h) sensor from 0x355/0x356 via a read hook on the live 0x355 counter. Verified against the app on all four units (e.g. 0x355=1172 -> 1173 h remaining / 1227 used / 49%). These cassettes' app has no filter reset and no last-reset date, so RAC's reset button and changed-date entities (both priv-command based) are dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Setting the HVAC mode alone is ignored by the unit while it is powered off, so selecting a mode in HA (off -> cool/dry/fan_only/auto) did nothing. Verified on hardware: the app turns on by sending 0x1f7=1 together with the mode/fan/temp in one frame. The mode write now forces power on (0x1f7=1) and attaches it, so a single atomic frame turns the unit on in the selected mode — no separate power command or inter-frame delay needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the PR. The decision to base this implementation from RAC sounds good, but I think a better course of action would be to split out an This change could also be useful for #89 |
Sounds good, I'll do that. |
|
Closing this — I'll open a new PR refactored onto a shared ac_common base class. |
|
Note that you don't need to close & open a new PR. Each push (or force-push) to a branch updates an existing PR. You can reopen this one when you're ready as well. Also, I recommend rebasing to follow the base branch instead of merging, this makes the commit history cleaner |
Splits the RAC-family TLV logic into an ac_common base class (as suggested in review of the earlier ceiling-cassette attempt, anszom#101) instead of translating packets to RAC's expectations. CST_170004_WW is its first user; every enum was swept end to end on a live unit with bridge mode on. korean.ts carries the Korean label helper these Korean-market drivers share. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Adds MQTT translation for
CST_570004_WW, an LG ceiling-cassette indoor unit(deviceType 401), typically installed as several IDUs on one multi-split ODU.
Previously these only worked in bridge mode.
It speaks the same DualCool AC TLV protocol as
RAC_056905_WW, so per thepreferred direction in #73 this extends
RACrather than duplicating it, anddrives features from the device's real capability frame wherever possible —
not hardcoded per-model tables. It does not modify
tlv_device.ts, andCST_570004_WWis a new modelId, so no existing device is affected.Why a plain
CST_570004_WW: RAC_056905_WWalias doesn't workI tried the alias first (as suggested in #73). It fails because CST diverges from
RAC in ways proven by the capability dump below:
byte6 = 0xA7instead of0x87, sotlv_devicedrops every frame. Handled inCST's own
processData(same approach asPOT_056905_WW), not in the shared base.0x2CCentirely.autois wire value 3, not RAC's6; RACdecodes wire 3 as
undefined(blank mode) and writesautoas the unsupported 6.that returns an unpopulated counter on CST (used=0, life=720); the real usage is in value
tags
0x356(rated life 2400 h) /0x355(remaining).heatmode thiscooling-only unit doesn't have.
Capability-driven decoding (read, don't hardcode)
Rather than synthesize, the handler reads what CST actually advertises. Aligned caps
dump (RAC's test frame vs a live CST frame):
Concretely:
0x2CB(mapped into RAC's0x2CCslot, masking bit2since CST exposes auto-dry as a duration select), replacing an earlier presence heuristic.
0x2E1/0x2E2, fulfilling RAC's existingTODO: some devices report these temp ranges via tags 0x2e1 - 0x2ec, falling backto RAC's 18–30 when absent.
auto=3), confirmed by both0x2C1bit3 and alive
0x1F9=3capture.0x356(rated life 2400 h) /0x355(remaining),exposed as a remaining-% and a used-time (h) sensor. RAC's basic-filter priv-command counter
is unpopulated on CST (used=0, life=720), and this model's app exposes no filter reset or
last-reset date, so RAC's priv-command filter entities (including the reset button) are dropped.
0x205/0x206,because CST's
0x2CDis not RAC's jet/swing bitmap. This one genuinely can't be readcleanly — documented rather than guessed.
Extra entities CST exposes
Humidity (
0x336, raw/10 = %RH), always-on Power (0x2B3), auto-dry duration select(
0x20E) + remaining sensor (0x225, minutes), display brightness (0x21F), comfortenergy saving (
0x23F), and a wind-mode / comfort-airflow select(
0x290/0x291/0x3D6/0x3D7, mutually exclusive one-hot flags).Testing
tests/cloud/devices/CST_570004_WW.test.tsdrives real captured packets(caps / values / filter reply) through the handler and asserts: 0xA7 normalization,
caps-derived temp range and energy-save,
autowire value 3 (read + write), jet/heatsuppression, CST fan scale, and the extra components.
npm run test→ 302/302;prettier --checkandnpm run buildclean.app; filter remaining/used match the app across all 4 units (2400 h rated);
automode setfrom HA actually engages auto on the unit; power reads 0 W idle and rises while cooling.
Known limitations (not guessed)
0x2C2's fan bitmap is understood but has an unresolved off-by-one at CST's lowest speed,so the fan list is the hand-verified
{1,2,4,6,7,8}rather than auto-derived.the Power (W) sensor.
🤖 Generated with Claude Code