Skip to content

Add LG Styler ST_B_E4H01Y_APL - #115

Open
tunarabuuu wants to merge 1 commit into
anszom:masterfrom
tunarabuuu:dev-styler
Open

Add LG Styler ST_B_E4H01Y_APL#115
tunarabuuu wants to merge 1 commit into
anszom:masterfrom
tunarabuuu:dev-styler

Conversation

@tunarabuuu

Copy link
Copy Markdown
Contributor

Third of the per-device-class splits from #111.

ST_B_E4H01Y_APL — an AA..BB ThinQ2 Styler / garment-care unit (deviceType 203). The byte layout was recovered by injecting single-byte-changed state frames through the management API and reading the LG cloud's own decode back, one offset per observation (three of the bytes are bitfields, split bit by bit).

Addressing your review notes from #111:

  • processAABB — reads now go through the base class's processAABB instead of a custom processData; the frame arrives with its leading AA/FF and trailing CRC/BB stripped, so the record offsets are simply two less than the whole-frame positions.
  • logging utility — the stray console.warn calls now use log(...).
  • header comment — trimmed to what's needed to read the code.

All labels are English. npm test passes; tsc clean.

@tunarabuuu
tunarabuuu force-pushed the dev-styler branch 2 times, most recently from 95b0228 to 250d3a1 Compare August 2, 2026 22:13
BluSyn pushed a commit to BluSyn/rethink-rs that referenced this pull request Aug 21, 2026
Port from upstream Node PR #115
(anszom/rethink#115). AABB class 0x31 with
40-byte records (last record is current). Field map and F024/F026
command frames from 2026-07-28 LG-cloud oracle and convert/control
captures, including Standard/Quick start blocks.
17: '8000000000000000000000000000001e780000000000000000000000000000000000000000', // Timed Dry 30
18: '8000000000000000000000000000003c780000000000000000000000000000000000000000', // Timed Dry 60
19: '8000000000000000000000000000005a780000000000000000000000000000000000000000', // Timed Dry 90
20: '8000000000000000000000000000003c780000000000000000000000000000000000000000', // Timed Dry 120

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is identical to Timed Dry 60. Is this correct?

Comment thread cloud/devices/ST_B_E4H01Y_APL.ts Outdated
Comment on lines +569 to +573
// Replaced by the two settable halves above. Removing a component from the
// config is not enough to retire the entity Home Assistant already made —
// a config carrying nothing but its platform is what says "this is gone",
// and it has to carry that, or HA rejects the whole device payload.
night_care_window: { platform: 'sensor' } as ComponentInfo,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That entity was never exposed by a released version of rethink, so we don't need to include code to withdraw it.

Comment thread cloud/devices/ST_B_E4H01Y_APL.ts Outdated
Comment on lines +526 to +528
remaining: minutes('remaining', 'Time remaining', { icon: 'mdi:timer-sand' }),
total: minutes('total', 'Total time', { icon: 'mdi:timer-outline' }),
reserved_at: sensor('reserved_at', 'Reserved time', { icon: 'mdi:calendar-clock' }),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the naming conventions of other devices (remaining_time, reserve_time)

Comment thread cloud/devices/ST_B_E4H01Y_APL.ts Outdated
...HADevice.config(meta, { name: 'LG Styler' }),
components: {
power: toggle('power', 'Power', { icon: 'mdi:power' }),
status: sensor('status', 'State', { icon: 'mdi:hanger' }),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other devices use the 'Status' label

import HADevice from './base'
import AABBDevice from './aabb_device'

/*

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header is overly verbose. Keep only the most important protocol details, definitely drop mentions of older revisions that we have never seen.

describe(MODEL_ID, () => {
/*
* Writable exactly where a command frame was captured off LG's own capability API and seen to
* take (CL-0000). Deliberately absent: `remoteControlStatus setRemoteControlType`, which

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fields claimed to be absent do appear to be present...

const { ha } = makeDevice()
const components = ha.devices[DEVICE_ID].config!.components as Record<string, Record<string, unknown>>
for (const [name, comp] of Object.entries(components)) {
if (comp.unique_id === undefined) continue // a removal, not an entity

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should not be needed if we remove the night_care_window reference entirely.

Comment on lines +576 to +582
test('the retired night-care summary is published as a removal', () => {
const { ha } = makeDevice()
const components = ha.devices[DEVICE_ID].config!.components as Record<string, Record<string, unknown>>
// Its two settable halves replace it, but dropping it from the config would leave the old
// entity behind, frozen at whatever it last read.
assert.deepEqual(components.night_care_window, { platform: 'sensor' })
})

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be needed if we remove the night_care_window reference entirely.

Comment thread cloud/devices/ST_B_E4H01Y_APL.ts Outdated
Comment on lines +584 to +587
energy: sensor('energy', 'Energy monitoring', {
icon: 'mdi:flash',
entity_category: 'diagnostic',
}),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this in a useful real-world unit?

Comment thread cloud/devices/ST_B_E4H01Y_APL.ts Outdated
Comment on lines +592 to +598
internal_light: sensor('internal_light', 'Interior light setting', {
icon: 'mdi:lightbulb-outline',
entity_category: 'diagnostic',
// LG's snapshot exposes styler.internalLightingTime but this model's
// modelJSON does not declare its value list, so the raw code is published
// rather than an invented label. 0 read back NO_SETTING, 1 read LIGHTING_TIME_0.
}),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the meaning of this entity? If it has only two values, would a binary_sensor be a better choice?

@anszom

anszom commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Also please add a README entry just as for the other devices.

An AA..BB ThinQ2 garment-care unit (deviceType 203). The byte layout was
recovered by injecting single-byte-changed state frames through the management
API and reading the LG cloud's decode back, one offset per observation.

Reads go through the base class's processAABB — the frame arrives with its
leading AA/FF and trailing CRC/BB already stripped, so the record offsets are
two less than the whole-frame positions the probes recorded. Stray warnings go
through the logging utility rather than console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tunarabuuu

Copy link
Copy Markdown
Contributor Author

All addressed, one commit, rebased on master.

  • Header too verbose (:9) — cut from 105 lines to about 70, and everything that narrated this driver's own history is gone ("the earlier note said control was refused", "which the first pass got wrong", the retelling of what an older revision published). What is left is the read map, the frame/record shape, the write vocabulary and the course-block rule — the parts you need to read the code. The same treatment went to the styler.state / styler.error comment blocks and to one comment in the test file.

  • Outdated Korean references (:264) — gone. Where the source of a label matters the comment now says it is an English translation of LG's ko-KR pack, which is what the tables actually hold.

  • Timed Dry 120 identical to Timed Dry 60 (:218) — it is correct, and it surprised me too, so I checked it against the model file rather than my transcription. LG's modelJSON gives course 20 a Drying1_Time default of 60, exactly as course 18 does; I regenerated the whole table from Course[id].function with the documented rule and every one of the 21 entries came back byte for byte identical to what is in the PR. The run length evidently comes from the course id, not from this block. There is now a comment saying so.

  • Naming conventions (:528)remainingremaining_time, totalinitial_time, reserved_atreserve_time, matching F_V8_Y___W.B_2QEUK and friends. reserve_time is now a duration in minutes rather than a clock string: LG declares Reserve_Time_H as a 3..19 hour range, so it is a delay before the course starts, and 0 means no reservation.

  • 'Status' label (:515) — done.

  • night_care_window withdrawal (:573, tests :124 and :582) — you are right, that entity never shipped. The tombstone component, the test for it, and the unique_id === undefined skip in the writability test are all gone.

  • Energy monitoring in a useful unit? (:587) — no, and I could not make it one. The two probes only establish that 34..35 is a big-endian 16-bit value; LG's EnergyMonitoring block for this model is a powertable keyed by level, which does not say what the counter counts. Rather than publish a number with no unit I dropped the entity (and its probe fixture). The offset stays documented in the header.

  • internal_light meaning (:598) — dropped for the same reason. styler.internalLightingTime has no value list in this model's modelJSON and nothing in its language pack, and the two codes I saw (0 → NO_SETTING, 1 → LIGHTING_TIME_0) do not settle whether "time 0" means the light is off or on — so a binary sensor would have a coin-flip polarity. If a capture ever pins it down it is a two-line addition.

  • Test :99 — the comment was stale: it claimed remoteControlStatus and course start/pause/resume were deliberately absent, while all four are in the writable set. It now says what the rule actually is — writable where a frame the appliance accepts was established, either captured from LG's converter or (for remote-maintain ON, which the converter never emits) built from the same shape, acknowledged, and read back on the next state frame.

442 tests passing (409 on master + 33 here), tsc --noEmit and prettier --check clean. README entry added as S5BBP (ST_B_E4H01Y_APL).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants