Commit Graph
18 Commits
Author SHA1 Message Date
Marc Billow fb5ed32b0f Add discrete freezer setpoint support for TP1X_REF_21K (issue #229)
The reporter's fridge/freezer combo reports the issue #186 discrete
definite-setpoint pattern on both compartments, but only the cooler half
was modeled -- /temperature/definite/freezer/vs/0 was unbound. Adds
DEFINITE_TEMPERATURE_FREEZER, identical shape to the existing cooler
capability (same fields, just negative supportedList values).
2026-08-03 19:24:59 +00:00
Marc Billow 07061c734d Fix more pre-existing ty diagnostics in test files
Continues narrowing SamsungEntityDescription accesses to the correct
subclass and asserting Optional write_fn/match_fn/exists_fn fields are
set before calling them, per the pattern established in the previous
commit.
2026-08-03 00:03:08 +00:00
Marc Billow daf7e3787f Add ruff (lint + format) and ty (type checking) to the project
Adds [tool.ruff] and [tool.ty] config to pyproject.toml with a curated
ruff rule set (E, F, W, I, UP, B, C4, SIM, RUF, ASYNC, LOG, G, PIE, RET,
PERF, N), pins ruff/ty in requirements-dev.txt, reformats the whole tree
with `ruff format`, and fixes the pre-existing lint and type-check debt
those tools surfaced so both run clean.

Production-code type fixes include: HA's ConfigFlowResult vs. the
generic FlowResult in config_flow.py, narrowing BoundEntity.desc to its
platform-specific subclass (SelectDesc/NumberDesc/SensorDesc/etc.) via
cast() instead of an unchecked annotation, converting HA device_class
strings to their proper enum types, a resolve_registry callback typed
as `object` instead of `DeviceRegistry | None`, and a couple of other
narrow correctness fixes (CA key type validation, an index-out-of-bounds
false positive from an empty-tuple fallback, a bool/dict argument swap).

Test-file fixes are mechanical: narrowing SamsungEntityDescription to
the correct subclass via isinstance()/cast() before accessing
subclass-only fields, and asserting Optional write_fn/unit_fn fields
are set before calling them.
2026-08-02 23:56:38 +00:00
Marc Billow b5c5c056f4 feat(fridge): expose discrete cooler temperature setpoint (#186)
RT42DG6630B1FZ is a single-door "cooler only" fridge that reports its
setpoint on /temperature/definite/cooler/vs/0 -- a vendor resource outside
both TEMP_CURRENT_GENERIC's '/temperature/current/' and TEMP_SETPOINT's
'/temperature/desired/' href prefixes, so it was entirely unbound and the
setting stayed app-only. Its supportedList (1/2/3/4/7 °C) isn't a
contiguous range, so this is modeled as a select over the device's own
live options rather than a NumberDesc that would let a user pick an
unsupported value like 5 or 6.
2026-07-29 13:11:07 +00:00
Marc Billow cc3ce12aa4 Fix range hood fan power targeting and kimchi mode write validation
- _speed_zero_is_off now keys off the hood resource's own
  settableMinFanSpeed/supportedFanSpeed fields instead of asking whether
  the device has any power resource at all, so a combi appliance's cavity
  /power/0 can no longer be toggled off by turning off just the vent fan.
- async_turn_on() no longer resets an already-running fan to its lowest
  speed when called without a percentage.
- _has_separate_power() reads through the O(1) resource cache instead of
  copying the full resource snapshot on every property access.
- _kimchi_mode_write rejects values the compartment didn't advertise in
  supportMode instead of writing them blind.
- kimchi_ripening_status no longer lowercases its value, since it has no
  enum catalog entry to translate the lowercased token back through.
- Documented why KIMCHI_DOOR_GENERIC isn't deduped against the /doors/vs/0
  aggregate fallback on the one fixture that reports both.

Adds regression tests for the combi-appliance power targeting, the
already-on turn_on no-op, kimchi mode write validation, and a kimchi
select display/write casing round-trip; a translation-coverage guard for
kimchi_zone_mode codes mirroring the existing AC preset one.
2026-07-28 02:10:01 +00:00
Marc Billow 265d94eded Add kimchi-refrigerator compartment coverage (issue #26)
TP2X_REF_20K-class 3-compartment kimchi refrigerators report each
compartment's storage mode and ripening status/timer on
/status/kimchi/<slot>/vs/0, plus a top-compartment door sensor on
/kimchidoors/top/vs/0 -- all previously unbound. Bind them as pattern
capabilities (fridge.KIMCHI_ZONE, fridge.KIMCHI_DOOR_GENERIC), deriving
the per-compartment entity key and display name from the href's
top/middle/bottom segment, the same way DOOR_GENERIC/TEMP_CURRENT_GENERIC
already do.

Storage-mode option labels were translated directly from the reporter's
own SmartThings app screenshots rather than guessed from the raw device
codes or their English paraphrase, confirming the on-screen option order
matches supportMode's array order (including the freezer triplet's
-19/-21/-17°C -> Standard/Strong/Weak mapping).

Also tighten FLEX_ZONE's exists_fn: this device's /mode/vs/0 also
populates modes/supportedOptions, but with a token shape that never
overlaps (a "_[n]:[n]" suffix supportedOptions carries that modes never
repeats), so the existing "supportedOptions is nonempty" check let the
entity bind anyway and get stuck permanently on "unknown". Requiring an
actual resolvable value keeps it working for the RF9000/Bespoke-class
fridges it was built for while leaving it absent here.
2026-07-28 00:35:21 +00:00
Marc Billow 8b45d8cbf6 Merge branch 'main' into claude/device-support-issue-77-freezer 2026-07-26 21:40:51 -05:00
Marc Billow 643aac92f8 Lock in the same ARTIK051_DONGLE_REF fix for the fridge half (issue #78)
RR40M7165WW is the fridge half of the same household dongle setup as
issue #77's freezer -- identical pipe-delimited ARTIK051_DONGLE_REF
modelNum, so the previous commit's detection and door-sensor fixes
already cover it with no further code changes. Add its fixture as a
second, independent regression case: it exercises the 'cooler' instance
segment instead of 'freezer' for both the temperature pattern caps and
DOOR_GENERIC, and notably reports /door/onedoorfreezer/vs/0 despite
being a single-door fridge (shared firmware naming across the product
line, not an actual second compartment) -- worth having its own golden
so that stays working too.
2026-07-27 00:41:25 +00:00
Marc Billow 8194ea9e87 Fix ARTIK051_DONGLE_REF freezer type detection and door sensor (issue #77)
RZ32M713EWW/EE (an ARTIK051-dongle standalone freezer) reports no
oneUiVersion and a pipe-delimited modelNum
('ARTIK051_DONGLE_REF|<rest>') -- REF is the last underscore segment
before the pipe, not wrapped in underscores on both sides like the
'..._REF_...' shape for_device_by_model's substring check expected, so
the device fell through to 'unknown' with only common capabilities:
no door sensor, no temperature sensors, nothing fridge-specific.

Replace the substring check with a segment-based one
(_model_num_segments splits the pipe-delimited prefix on '_') that
catches both shapes. Same root cause, same fix, and same device family
independently reported and root-caused in issue #83 (which also covers
two further bugs -- config-flow/coordinator serial collisions on a
second symptom of this firmware, 'Nothing(SVC)' as a literal serial --
not needed here since this reporter has a single unit; left for #83).

Once routed to the refrigerator registry, fridge.py's existing pattern
capabilities pick up /temperature/current/freezer/0 and
/temperature/desired/freezer/0 for free -- they were never the actual
problem, just unreachable under the 'unknown' fallback (which never
tries pattern capabilities at all). The door sensor needed one more
fix: /door/onedoorfreezer/vs/0 reports the vendor-prefixed
x.com.samsung.da.openState, not the bare openState DOOR_GENERIC read,
so the entity existed but stayed permanently unavailable. Check both
field names via rep_fn.
2026-07-27 00:39:33 +00:00
vmvarga 2fd8ff8260 merge temp_setpoint 2026-07-26 11:05:13 +02:00
Rob Martin 9cf1e46bf6 fix(fridge): stop switch turn-off silently sending 'On'
Six fridge SwitchDesc write_fns built their payload with
`'On' if p else 'Off'`. The switch platform passes the literal
string 'Off' on turn-off, which is truthy, so the guard always
produced 'On' -- turning these switches off silently re-sent On
and they could never be turned off:

  - ICEMAKER_NIGHTTIME (ice.night.status)
  - STATUS_LOCK helper (devicecontrol + device.sound)
  - DEFROST_DELAY (delayDefrost)
  - WELCOME_LIGHTING (status)
  - CABINET_LIGHT dim (light.dimming.status)
  - ICEMAKER_STATUS_FALLBACK (iceMaker)

Compare `p == 'On'` instead, matching the pattern the other
capability files already use. Adds a regression test asserting
every affected write_fn sends 'Off' on 'Off' and 'On' on 'On'.
2026-07-24 12:11:53 -06:00
Marc Billow 3d5263d792 Add common.UNIVERSAL/POWER bundles to stop hand-duplicating capabilities
FIRMWARE_UPDATE and ALARMS were already copy-pasted into all 6 device-type
registries by hand; POWER/KIDS_LOCK/REMOTE_CONTROL into 5 of 6. Consolidate
into two bundles in common.py, unpacked via *common.UNIVERSAL / *common.POWER
the same way ignored.IGNORED already is:

- UNIVERSAL: ALARMS, ENERGY_METER, FIRMWARE_UPDATE (moved from fridge.py),
  SELF_CHECK (moved from fridge.py), AI_ENERGY_LEVEL, and the kids-lock/
  remote-control pairs. Safe everywhere -- discover() only binds a href
  actually present in a device's dump, so a capability with no known
  conflicting family is a no-op where the href is absent and a real,
  wanted entity where it's present. This also broadens AI_ENERGY_LEVEL,
  ENERGY_METER, and SELF_CHECK to device types they weren't confirmed on
  before, on the same reasoning.
- POWER: just POWER_GENERIC/POWER_VS_FALLBACK, applied to the 5 non-AC
  registries. Airconditioner keeps its own opt-out: its climate entity
  already owns /power/0 and /power/vs/0 via a bare, no-entity claim
  (airconditioner.COVERAGE), and a real power capability on the same
  href would make _build() raise (a href with multiple caps requires
  every cap to have rt_filter or match_fn; the bare COVERAGE cap has
  neither).

Full test suite (327 tests, all 6 device-type golden fixtures) passes
unchanged -- none of the newly-broadened capabilities bind on any existing
fixture, confirming the no-op reasoning held in practice, not just theory.
2026-07-23 00:44:58 +00:00
Marc Billow d6639c99d4 Bind AI energy level on washer; switch/select split, drop translations
Issue #40: /energy/ailevel/vs/0 was unbound on a plain washer. The
capability already existed for fridges but was gated off entirely on
single-level hardware (the common case), so it's moved to common.py
(cross-family, like fridge + washer now) and split into two entities:
a switch when supportedAiLevel has exactly one entry (aiLevel is really
just an on/off toggle there), and a select otherwise, with '0' (off)
synthesized back into the select's options since supportedAiLevel never
lists it but it's a real observed value.

Also drops the translation_key/strings.json entries -- aiLevel's raw
digit values already render fine untranslated, and translating a
handful of levels can't cover devices with more.
2026-07-23 00:44:57 +00:00
Tim Schindler 0cc9d8e511 fix: render selfcheck_error as no-value on empty list; document ai_energy_level level labels 2026-07-22 09:04:24 +02:00
Tim Schindler 2a0a9f32ee feat: add AI energy level select and self-check error sensor for fridges
Adds two small, additive fridge capabilities:

- AI_ENERGY_LEVEL: a select on /energy/ailevel/vs/0 exposing the AI
  energy-saving level, gated behind supportedAiLevel actually offering
  more than one choice (previously globally ignored since every dump
  seen only reported a single supported level).
- selfcheck_error: a diagnostic sensor on the existing SELF_CHECK
  capability surfacing x.com.samsung.da.error from the last self-check,
  for hardware that reports it.

This does not touch FLEX_ZONE, /mode/vs/0, or /icemaker/status/0.
2026-07-22 08:42:31 +02:00
Marc Billow 9a899080fa fix: fridge/washer coverage gaps from the #19-#27 dump batch
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.
2026-07-22 06:11:15 +00:00
themaanda 6f53b3043b Cover TP1X refrigerator native resource mirrors 2026-07-21 16:58:42 -05:00
Marc Billow e875266405 fix(washer,fridge,oven): correct energy/temperature-unit bugs from issues #6/#7
Washer (#6): instantaneousPower is a dead sentinel ('-500') on every
TP1-class washer dump collected so far, and cumulativePower is absent
outright on at least one model. WASHER_ENERGY_METER now hides both
sensors instead of showing a misleading "0 W"/perpetual "unavailable".

Fridge (#7): temperature sensors/setpoints hardcoded '°F', ignoring the
unit each device actually reports per-reading -- fixed via a new
unit_fn hook read live from the resource. Also corrects
DEFROST_BLOCK_STATUS's polarity (DEFROST_BLOCK_ON means actively
defrosting, not "blocked", confirmed against live dumps) and adds
REFRIGERATION_FALLBACK for /refrigeration/0, closing the last unbound
href surfaced by issue #7's diagnostic dump.

Oven: applies the same live-unit-reading fix defensively to
OVEN_SETPOINT, which shares the same aggregate resource shape.
2026-07-15 11:41:21 -05:00