New '-CAWW-' modelNum token for multi-indoor-unit commercial AC
installs -- these report no oneUiVersion, same as the other RAC/PRAC
boards. Once routed to the existing airconditioner registry, every
resource in the reporter's dump already binds except one new
SAC-specific installation-topology blob, now ignored.
Ovens with modelNum like TP1X_DA-KS-OVEN-0107X report no oneUiVersion
and don't match any consumer-prefix or other fallback token in
for_device_by_model, so the device came back as "unknown" and every
resource fell through to the global capability registry instead of
oven.py's own -- explaining the unbound /connected/vs/0 href, the
unrelated-looking entities, and the non-functional controls reported
in the issue. Add a '-OVEN-' modelNum token fallback, mirroring the
existing '-RANGE-' fallback from issue #44.
Adds a by_type registry for the AX60R5080WD/SE air purifier family, verified
against two independent diagnostics dumps (issue #56 and its comment). Binds
power, alarms, energy, diagnosis (reusing dishwasher.DIAGNOSIS), the dust/
fine-dust/super-fine-dust/odor/clean-level sensors off /sensors/vs/0, filter
progress, a device-active diagnostic, and a display-light switch parsed out
of /mode/vs/0's packed options list.
Fan speed/direction (/airflow/0, /airflow/vs/0) and two other /mode/vs/0
tokens (Comode_*, Blooming_*) are exposed as read-only diagnostics rather
than full controls -- neither dump has a supported-values list to confirm
their write contracts, so they're left for a follow-up once that's
clarified in the issue thread.
Hoists range_hood's items[]-sensor-value helper into common.py
(sensor_item_value) since air_purifier now reads the same /sensors/vs/0
shape.
- NumberDesc gains native_min_fn/native_max_fn/step_fn hooks (mirroring the
existing unit_fn pattern) so an entity's slider bounds can track the live
rep instead of staying pinned to whatever unit the descriptor was written
against. Oven setpoint was hardcoded to Celsius bounds (30-270), which
silently capped issue #44's Fahrenheit range at 270F -- below a normal
350F bake temp. Verified Fahrenheit bounds (175-550, step 5) come from
that dump's /mode/vs/0 Bake modeSpec.
- Wire oven.OVEN_SPEC into the oven registry, not just range -- it was only
reachable from range before, so a standalone oven reporting
/oven/spec/vs/0 would have false-tripped the coverage-gap repair.
- Fix a docstring in test_golden_regression.py left over from the
cooktop.py -> range.py rename.
TP1X_DA-KS-RANGE-0102X (model NSI6DG9100SRAA) reports no oneUiVersion and
previously fell through to the unknown-device fallback, leaving /connected,
/cooktop/spec, /cooktop/settings/status, /cooktop/status, and /oven/spec
unbound. Add a 'range' device registry that reuses the oven family's
cavity/setpoint/mode/operational-state/door capabilities and adds a new
cooktop.py module modeling per-burner power level, state, and hot-surface
entities (gated so unreported burner slots don't appear), plus a hot-surface
auto-shutoff config sensor. Route range/cooktop models to it via a
'-RANGE-' modelNum token, mirroring the existing RAC/PRAC air-conditioner
fallback pattern.
The dryer (DA_WM_TP1_21_COMMON) pause/stop buttons mentioned in the same
issue are working as intended -- the reporter confirmed that's an expected
in-person-only limitation, not a bug.
Closes the coverage-gap repair for two more room-AC boards (issues #37,
- TP2X_RAC_20K reports no oneUiVersion and no '_PRAC_' modelNum token, so
its device type went unrecognized -- add a '_RAC_' fallback token to
for_device_by_model.
- Bind the resources both dumps actually populate: mute-once (switch),
display light (switch), self-check (reused from fridge.SELF_CHECK), and
the circuit-breaker current-limit setting (read-only diagnostic sensors,
since its write contract isn't confirmed from the dump).
- Ignore the rest as plumbing/opaque/unconfirmed: control-set descriptor,
keepnormalstate flag, software-reset trigger, AI Sleep handshake,
absence-monitoring stubs, remote-data-control, remote-temperature,
reservation ruleset, and the welcome-temperature tracker.
Adds scrubbed fixtures + goldens for both dumps and extends the AC
capability/golden-regression tests to assert zero unbound hrefs.
Six new diagnostics dumps, six gaps closed:
- refrigerator: bind /diagnosis/vs/0 (reuse dishwasher.DIAGNOSIS -- same
shape) into the refrigerator registry; it was never wired up there,
tripping the coverage repair on any fridge that reports it (#20, #26).
- refrigerator: add PANTRY_ZONE for the Cool Select Zone pantry compartment
(/status/pantry/one/vs/0, x.com.samsung.da.mode/supportedOptions) -- same
shape as BEVERAGE_ZONE but a distinct resource/field set (#20).
- refrigerator: generalize FLEX_ZONE to identify the current mode by list
membership in supportedOptions instead of a hardcoded
CV_TTYPE_RF9000A_ prefix check. TP1X/Bespoke-class fridges use a
CV_FDR_ prefix instead, which the old code didn't recognize -- the
select existed but always read as unknown, and writing to it would have
appended a duplicate CV_FDR_ flag rather than replacing the existing one
(#26, #27; also closes#32).
- common: add energy_saved_kwh (x.com.samsung.da.cumulativeSavedPower) and
power_energy_kwh (x.com.samsung.da.cumulativeConsumption) to the shared
energy meter, plus fridge-only energy_last_month_kwh/energy_this_month_kwh
(monthlyConsumption/thismonthlyConsumption) -- all self-gating on field
presence (#26).
- by_type: add the WV consumer-model prefix (FlexWash twin washers, e.g.
WV55M9600AW) to the by-model fallback map. These report no oneUiVersion
and previously matched no prefix at all, so they fell all the way
through to the unrecognized-device registry with zero capabilities
bound (#19).
- washer: add a self-gating dry_level select to WASHER_SETTINGS for
washer/dryer combo units, which carry a writable dryLevel field
directly on /washer/vs/0 with no separate dryer resource or course
(#22).
The reset-water-filter button and "ice type vs. two named icemakers"
requests from #26/#27 are left alone -- no write contract or exclusivity
behavior is evidenced in either dump, and both icemakers report On
simultaneously on the Bespoke unit, so synthesizing a single-select would
be a guess rather than a fix.
Adds scrubbed fixtures + goldens for FlexWash, a washer/dryer combo,
ARTIK051_REF_17K, and TP2X_REF_20K, plus unit tests for the new/changed
capabilities.
Add support for Samsung room air conditioners (ARTIK051_PRAC-class), the
first device whose core controls map onto a single Home Assistant `climate`
entity rather than a scatter of switches/selects/numbers.
- New `climate` platform + `ClimateDesc`: one composite entity that reads
power, HVAC mode, current/target temperature, fan (wind) strength, swing
(wind direction) and the convenient-mode preset across several OCF
resources and writes back to each. On/off folds into HVACMode.OFF /
TURN_ON/OFF; convenient mode folds into preset_mode. The entity binds one
primary resource (/mode/vs/0) and reads its siblings from the coordinator
snapshot, reusing the cross-resource read pattern from number/select.
- New `airconditioner` capability module + by_type registry, routed via the
`_PRAC_` modelNum token. Reuses common ALARMS/ENERGY_METER,
fridge.FIRMWARE_UPDATE and dishwasher.DIAGNOSIS; air purify and auto clean
as config switches; air dust filter status/usage as diagnostics (usage
normalized to a percentage of rated capacity).
- Climate-consumed and all-zero/ambiguous resources (temperature/wind,
/sensors, /humidity) are declared as AC-scoped coverage so every href in
the dump binds or is covered -- no coverage-gap repair.
- Fan/swing modes map onto HA standard constants (auto-localized); the
custom fan `turbo`, presets `quiet/smart/speed`, and the filter-status
enum get translations in strings.json + translations/en.json.
- Scrubbed fixture, golden, and tests: registry routing, zero unbound
hrefs, the climate write contract, and filter-% normalization.
First full dryer dump (issue #14, DV90BB5245AES1) surfaced 5 unbound
hrefs and an "incomplete capability coverage" repair. Handle them and,
while here, make the washer/dryer/dishwasher families consistent instead
of each carrying a bespoke variant of the same controls.
Dryer coverage:
- /power/0, /kidslock/0, /remotectrl/0: bind via the OCF-native + vendor
fallback pairs (prefer the standard OCF resource, fall back to -vs).
- /buzzersound/vs/0: new Buzzer sound select.
- /course/vs/0: cycle select shared with washer/dishwasher; ignore the
/st/dryercourse/vs/0 re-encoding (mirror of /st/washercourse/vs/0).
Consistency / de-duplication:
- Move generic OCF controls (power/kids-lock/remote-control fallback
pairs, energy meter) into common.py; every registry uses them.
- Move shared laundry controls (buzzer, job-beginning-status, and the
/course/vs/0 cycle-select machinery) into laundry.py; washer and
dishwasher stop hand-rolling their own copies.
- Energy meter is now sentinel-aware everywhere: the dead '-500'
instantaneousPower reading no longer shows a misleading 0 W (fixes it
on dryers and dishwashers, matching the earlier washer fix).
- Job-beginning-status reads x.com.samsung.da.currentStatus, the field
every dump actually carries; the dryer sensor was previously blank.
Adds a scrubbed dryer fixture, golden, and capability tests, plus an
.claude/skills/adding-device-support skill capturing the dump-reading,
OCF-vs-vendor, entity-taxonomy, and coverage workflow. Bumps to 0.6.0.
The DTLS/CoAP transport code that made "ocf" an accurate name moved out to
the smartthings-local package. What's left here (capability.py, entities.py,
discovery.py, adapter.py, identity.py, capabilities/, by_type/, plus the
/device/0 batch parser) is entirely the device capability registry, so name
the package for what it does.
Flattened the redundant ocf/registry/ nesting into a single top-level
registry/ package and updated every import across the platform modules and
test suite accordingly. Verified: full test suite (80/80) passes, and the
Docker dev container reconnects to both live appliances and rediscovers
their entities cleanly after the rename.
Makes the integration fully self-contained for HACS distribution.
Users installing via HACS get everything in one directory; no external
samsung_appliance package needed.
All component imports updated to relative (.ocf.*); all test imports
updated to custom_components.localthings.ocf.*. ocf_root_ca.pem moves
with the package and resolves correctly via Path(__file__).parent.
conftest: _resources_from_dump() prefers /device/0 batch reps; falls
back to flat resources dict. Fixes _parse_device0_batch to include
stub reps (rep={href:...}) as {} so capabilities still bind on
resources with no current data (e.g. /alarms/vs/0 when no alarm active).
test_golden_regression: _new_state_and_uids now uses by_type.for_device
to select per-device-type capabilities + pattern caps.
C1: SwitchDesc MQTT discovery now emits payload_on/off='On'/'Off',
state_on/off='On'/'Off', and a Jinja2 boolean-to-string value_template
so HA can correctly interpret and command switch entities.
C2: POWER write_fn corrected — path is ['power','vs','0'] segment list
(not a single slash-prefixed string), and payload comparison is
p == 'On' (not truthiness) so 'Off' can actually be sent.
I2: Golden regression oracle strengthened to exact equality for both
state_keys and discovery_unique_ids; prior subset-only check allowed
over-production to go undetected. Two golden tests now fail revealing
stale fixtures (dishwasher: +3 keys; refrigerator: +9 keys) — fixtures
intentionally NOT updated, human review required.
Generates regression oracle from DISHWASHER and REFRIGERATOR descriptors:
- tests/fixtures/golden/dishwasher.json: 21 state keys, 23 discovery entities
- tests/fixtures/golden/refrigerator.json: 26 state keys, 24 discovery entities
- tests/test_golden_regression.py: parametrized test that will turn green as
Tasks 9-12 port capabilities into the new registry