Fix water-purifier hot-water lock mislabel, add range-hood after-run support

Water purifier (#144, #145): /favorite/hotwater/vs/0's switchHotwater field
is a Locked/Unlocked hot-water lock, not a "favorite enabled" flag. It's the
same lock as LOCK.hotwater_lock, just surfaced through this href on boards
that don't populate /status/lock/vs/0's hotwaterLock -- the two now share
the hotwater_lock key/translation, gated so only one is ever active.

Range hood (#147): binds /afterrun/vs/0 (after-run activation state,
progress, and a cancel button), clearing the last unbound href for
AHD-WW-TP1-22-COMMON.
This commit is contained in:
Marc Billow committed 2026-07-28 02:00:57 +00:00
1 parent c19800ac00
commit 5055d6bb72
10 files changed
+137 -20

No files matched your search

@@ -11,6 +11,7 @@ REGISTRY = DeviceRegistry(
range_hood.HOOD_ALARMS,
common.ENERGY_METER,
common.FIRMWARE_UPDATE,
range_hood.AFTER_RUN,
range_hood.HOOD_FAN,
range_hood.HOOD_LAMP,
range_hood.HOOD_FILTER,
@@ -12,6 +12,7 @@ from datetime import datetime, timezone
from ..capability import Capability
from ..entities import (
BinarySensorDesc,
ButtonDesc,
FanDesc,
SelectDesc,
SensorDesc,
@@ -179,6 +180,47 @@ HOOD_FILTER = Capability(
)
# After Run (issue #147): the hood keeps the fan running at low speed for a
# while after it's switched off, to clear residual cooking smoke. No
# supported-values list is advertised for activationState, so it's modeled
# read-only (monitoring, not an invented "enable" write) per the 'don't
# guess' rule; runningCancel's only observed value is the command name
# itself ('Cancel'), the same self-describing command-field shape as
# operational.STOP_BUTTON.
AFTER_RUN = Capability(
href='/afterrun/vs/0',
poll_tier='warm',
entities=(
BinarySensorDesc(
key='after_run_active',
field='x.com.samsung.da.activationState',
icon='mdi:fan-clock',
entity_category='diagnostic',
value_fn=lambda value: str(value).lower() == 'on',
),
SensorDesc(
key='after_run_progress',
field='x.com.samsung.da.runningProgress',
unit='%',
state_class='measurement',
icon='mdi:fan-clock',
entity_category='diagnostic',
value_fn=int_or_none,
),
ButtonDesc(
key='after_run_cancel',
field='',
payload='Cancel',
icon='mdi:fan-off',
write_fn=lambda p, rep, href=None: (
['afterrun', 'vs', '0'],
{'x.com.samsung.da.runningCancel': p},
),
),
),
)
AIR_QUALITY = Capability(
href='/sensors/vs/0',
poll_tier='warm',
@@ -102,20 +102,30 @@ FAVORITE_CAPACITY = Capability(
),
)
# Coffee-capable variant (issue #107) -- same "favorite" enable-toggle +
# supported-list select shape as FAVORITE_CAPACITY above, but for the hot
# water dispensed alongside brewing rather than the pour capacity.
# Coffee-capable variant (issue #107) -- a "favorite" supported-list select
# for the hot water dispensed alongside brewing, same shape as
# FAVORITE_CAPACITY above.
FAVORITE_HOTWATER = Capability(
href='/favorite/hotwater/vs/0',
poll_tier='cold',
entities=(
SwitchDesc(key='favorite_hotwater_enabled', field='x.com.samsung.da.switchHotwater',
icon='mdi:star-outline',
# Despite the resource/field naming, switchHotwater's value domain is
# Locked/Unlocked, not an enable flag (issue #144) -- it's the same
# hot-water lock as LOCK.hotwater_lock below, just surfaced through
# this href on boards that don't populate /status/lock/vs/0's
# hotwaterLock field. Shares that descriptor's key so only one "Hot
# water lock" entity ever appears; exists_fn activates this fallback
# only when the primary field is absent, so a board reporting both
# can't collide.
SwitchDesc(key='hotwater_lock', field='x.com.samsung.da.switchHotwater',
device_class='lock',
entity_category='config',
value_fn=lambda v: v != 'Locked',
value_fn=lambda v: v != 'Unlocked',
exists_fn=lambda rep, resources: 'x.com.samsung.da.hotwaterLock' not in (
resources.get('/status/lock/vs/0') or {}),
write_fn=lambda p, rep, href=None: (
['favorite', 'hotwater', 'vs', '0'],
{'x.com.samsung.da.switchHotwater': 'Unlocked' if p == 'On' else 'Locked'})),
{'x.com.samsung.da.switchHotwater': 'Locked' if p == 'On' else 'Unlocked'})),
SelectDesc(key='favorite_hotwater_temperature',
field='x.com.samsung.da.favorite.defaultTemperature',
icon='mdi:thermometer',
@@ -1,6 +1,9 @@
{
"entity": {
"binary_sensor": {
"after_run_active": {
"name": "After run active"
},
"any_burner_active": {
"name": "Any burner active"
},
@@ -93,6 +96,9 @@
}
},
"button": {
"after_run_cancel": {
"name": "Cancel after run"
},
"diagnosis_start": {
"name": "Start diagnosis"
},
@@ -529,6 +535,9 @@
}
},
"sensor": {
"after_run_progress": {
"name": "After run progress"
},
"air_filter_usage": {
"name": "Filter usage"
},
@@ -886,9 +895,6 @@
"favorite_coffee_enabled": {
"name": "Favorite coffee"
},
"favorite_hotwater_enabled": {
"name": "Favorite hot water"
},
"fridge_sound": {
"name": "Sound"
},
@@ -1,6 +1,9 @@
{
"entity": {
"binary_sensor": {
"after_run_active": {
"name": "Nadraaien actief"
},
"any_burner_active": {
"name": "Een brander actief"
},
@@ -93,6 +96,9 @@
}
},
"button": {
"after_run_cancel": {
"name": "Nadraaien annuleren"
},
"diagnosis_start": {
"name": "Diagnose starten"
},
@@ -529,6 +535,9 @@
}
},
"sensor": {
"after_run_progress": {
"name": "Nadraaien voortgang"
},
"air_filter_usage": {
"name": "Filterverbruik"
},
@@ -886,9 +895,6 @@
"favorite_coffee_enabled": {
"name": "Favoriete koffie"
},
"favorite_hotwater_enabled": {
"name": "Favoriet warm water"
},
"fridge_sound": {
"name": "Geluid"
},