feat: dishwasher cycle/settings entities, stale-state fixes, LED/sound controls
Dishwasher:
- Revert remote_control to BinarySensorDesc ("Smart Control", read-only)
- Add cycle SelectDesc on /course/vs/0 via options-array RMW (AI Wash,
Pre blast, Self clean, Normal, Express 60, Heavy, Pots and pans,
Delicate, Plastic, Baby Care)
- Add Storm Wash+ and Auto release dry SwitchDescs on /course/vs/0
- Add Sanitize SwitchDesc and Smart Dry SelectDesc on /dishwasher/vs/0
- Add Sound volume NumberDesc (0–15 step 5) on /settings/sound/volume/vs/0
- Add Door LED night brightness, night start/end time to DOOR_LED capability
- Add SOUND_VOLUME capability to laundry module
Stale-state fixes (firmware leaves values set after cycle ends):
- finish_time: suppress when machine state is not active
- progress: return 'Idle' when machine state is not active
- water filter: gate WATER_FILTER capability on filterStatus != 'notused'
Translations:
- Add ICESTATUS_RUN → "Making ice" to strings.json and en.json
Tests:
- Update dishwasher and refrigerator golden fixtures to reflect current entity set
- Remove dead test_project_extrapolates_remaining_time (on_observation removed)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""Dishwasher device registry."""
|
||||
from ..capabilities import common, fridge, laundry, operational
|
||||
from ..capabilities import common, dishwasher, fridge, laundry, operational
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
@@ -13,8 +13,11 @@ REGISTRY = DeviceRegistry(
|
||||
common.WATER_METER,
|
||||
common.WATER_FILTER,
|
||||
operational.OPERATIONAL_STATE,
|
||||
dishwasher.CYCLE_OPTIONS,
|
||||
dishwasher.DISHWASHER_SETTINGS,
|
||||
laundry.DOOR_LED,
|
||||
laundry.SOUND_MODE,
|
||||
laundry.SOUND_VOLUME,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from . import common, fridge, laundry, operational, oven
|
||||
from . import common, dishwasher, fridge, laundry, operational, oven
|
||||
from ..capability import Capability
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ against live device dumps:
|
||||
/filter/waterfilter/vs/0 -> x.com.samsung.da.filterUsage / filterStatus
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import SensorDesc, SwitchDesc
|
||||
from ..entities import BinarySensorDesc, SensorDesc, SwitchDesc
|
||||
|
||||
|
||||
def _num(v):
|
||||
@@ -58,13 +58,10 @@ KIDS_LOCK = Capability(
|
||||
REMOTE_CONTROL = Capability(
|
||||
href='/remotectrl/vs/0',
|
||||
entities=(
|
||||
SwitchDesc(key='remote_control',
|
||||
field='x.com.samsung.da.remoteControlEnabled',
|
||||
name='Remote control', device_class='connectivity',
|
||||
value_fn=lambda v: str(v).lower() == 'true',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['remotectrl', 'vs', '0'],
|
||||
{'x.com.samsung.da.remoteControlEnabled': 'true' if p == 'On' else 'false'})),
|
||||
BinarySensorDesc(key='remote_control',
|
||||
field='x.com.samsung.da.remoteControlEnabled',
|
||||
name='Smart Control', device_class='connectivity',
|
||||
value_fn=lambda v: str(v).lower() == 'true'),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -112,6 +109,7 @@ WATER_METER = Capability(
|
||||
|
||||
WATER_FILTER = Capability(
|
||||
href='/filter/waterfilter/vs/0',
|
||||
match_fn=lambda rep, _: rep.get('x.com.samsung.da.filterStatus', '').lower() != 'notused',
|
||||
entities=(
|
||||
SensorDesc(key='filter_usage', field='x.com.samsung.da.filterUsage',
|
||||
name='Filter usage', unit='%', state_class='measurement',
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Capabilities specific to dishwasher appliances (DW9000F-class).
|
||||
|
||||
Resources verified against the live device dump at 10.0.0.129.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import SelectDesc, SwitchDesc
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /dishwasher/vs/0 — cycle wash/dry settings
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
DISHWASHER_SETTINGS = Capability(
|
||||
href='/dishwasher/vs/0',
|
||||
entities=(
|
||||
SwitchDesc(key='sanitize', field='x.com.samsung.da.sanitize',
|
||||
name='Sanitize', icon='mdi:bacteria',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['dishwasher', 'vs', '0'],
|
||||
{'x.com.samsung.da.sanitize': 'On' if p == 'On' else 'Off'})),
|
||||
SelectDesc(key='heated_dry', field='x.com.samsung.da.heatedDry',
|
||||
name='Smart Dry', icon='mdi:heat-wave',
|
||||
options_field='x.com.samsung.da.supportedHeatedDry',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['dishwasher', 'vs', '0'],
|
||||
{'x.com.samsung.da.heatedDry': p})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /course/vs/0 — cycle selection and course options (RMW on options array)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Course IDs in editCourseList byte order, matched to Samsung app display names.
|
||||
# IDs are uppercase hex strings matching the Course_XX encoding in options[].
|
||||
_COURSE_ID_TO_NAME: dict[str, str] = {
|
||||
'0E': 'AI Wash',
|
||||
'07': 'Pre blast',
|
||||
'90': 'Self clean',
|
||||
'86': 'Normal',
|
||||
'83': 'Express 60',
|
||||
'84': 'Heavy',
|
||||
'8D': 'Pots and pans',
|
||||
'80': 'Delicate',
|
||||
'8E': 'Plastic',
|
||||
'8F': 'Baby Care',
|
||||
}
|
||||
_COURSE_NAME_TO_ID = {v: k for k, v in _COURSE_ID_TO_NAME.items()}
|
||||
_CYCLE_OPTIONS = tuple(_COURSE_ID_TO_NAME.values())
|
||||
|
||||
|
||||
def _option_value(options, prefix):
|
||||
"""Find `<prefix>_<value>` in options array and return <value>."""
|
||||
for o in (options or []):
|
||||
if isinstance(o, str) and o.startswith(prefix + '_'):
|
||||
return o.split('_', 1)[1]
|
||||
return None
|
||||
|
||||
|
||||
def _replace_in_options(options, prefix, new_value):
|
||||
return [f"{prefix}_{new_value}" if isinstance(o, str) and o.startswith(prefix + '_') else o
|
||||
for o in options]
|
||||
|
||||
|
||||
def _cycle_write(p, rep, href=None):
|
||||
course_id = _COURSE_NAME_TO_ID.get(p)
|
||||
if not course_id:
|
||||
return None
|
||||
opts = list(rep.get('x.com.samsung.da.options') or [])
|
||||
if not opts:
|
||||
return None
|
||||
return ['course', 'vs', '0'], {
|
||||
'x.com.samsung.da.options': _replace_in_options(opts, 'Course', course_id),
|
||||
}
|
||||
|
||||
|
||||
def _storm_wash_write(p, rep, href=None):
|
||||
if p not in ('On', 'Off'):
|
||||
return None
|
||||
opts = list(rep.get('x.com.samsung.da.options') or [])
|
||||
if not opts:
|
||||
return None
|
||||
return ['course', 'vs', '0'], {
|
||||
'x.com.samsung.da.options': _replace_in_options(opts, 'StormWashZone', p),
|
||||
}
|
||||
|
||||
|
||||
def _auto_release_write(p, rep, href=None):
|
||||
if p not in ('On', 'Off'):
|
||||
return None
|
||||
opts = list(rep.get('x.com.samsung.da.options') or [])
|
||||
if not opts:
|
||||
return None
|
||||
return ['course', 'vs', '0'], {
|
||||
'x.com.samsung.da.options': _replace_in_options(opts, 'AutoDoorRelease', p),
|
||||
}
|
||||
|
||||
|
||||
CYCLE_OPTIONS = Capability(
|
||||
href='/course/vs/0',
|
||||
entities=(
|
||||
SelectDesc(key='cycle', name='Cycle', icon='mdi:dishwasher',
|
||||
options=_CYCLE_OPTIONS,
|
||||
rep_fn=lambda rep: _COURSE_ID_TO_NAME.get(
|
||||
_option_value(rep.get('x.com.samsung.da.options'), 'Course')
|
||||
),
|
||||
write_fn=_cycle_write),
|
||||
SwitchDesc(key='storm_wash', name='Storm Wash+', icon='mdi:weather-lightning-rainy',
|
||||
rep_fn=lambda rep: _option_value(
|
||||
rep.get('x.com.samsung.da.options'), 'StormWashZone') == 'On',
|
||||
write_fn=_storm_wash_write),
|
||||
SwitchDesc(key='auto_release_dry', name='Auto release dry', icon='mdi:door-open',
|
||||
exists_fn=lambda rep: any(
|
||||
isinstance(o, str) and o.startswith('AutoDoorRelease_')
|
||||
for o in (rep.get('x.com.samsung.da.options') or [])
|
||||
),
|
||||
rep_fn=lambda rep: _option_value(
|
||||
rep.get('x.com.samsung.da.options'), 'AutoDoorRelease') == 'On',
|
||||
write_fn=_auto_release_write),
|
||||
),
|
||||
)
|
||||
@@ -4,8 +4,10 @@ Resources verified against the DW9000F-class dump at 10.0.0.129.
|
||||
Note: door-LED keys use NO `x.com.samsung.da.` prefix — `setBrightness` and
|
||||
`setNightLight` — preserved exactly as they appear in the OCF resource rep.
|
||||
"""
|
||||
from datetime import time as dt_time
|
||||
|
||||
from ..capability import Capability
|
||||
from ..entities import SelectDesc, SwitchDesc
|
||||
from ..entities import NumberDesc, SelectDesc, SwitchDesc, TimeDesc
|
||||
|
||||
_LED_LEVELS = ('Low', 'High')
|
||||
_SOUND_MODES = ('voice', 'tone', 'mute')
|
||||
@@ -23,6 +25,16 @@ def _led_night_write(p, rep, href=None):
|
||||
return ['doorled', 'light', 'vs', '0'], {'setNightLight': p}
|
||||
|
||||
|
||||
def _parse_hm(v):
|
||||
if not v:
|
||||
return None
|
||||
try:
|
||||
h, m = v.split(':')
|
||||
return dt_time(int(h), int(m))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _sound_mode_write(p, rep, href=None):
|
||||
if p not in _SOUND_MODES:
|
||||
return None
|
||||
@@ -41,6 +53,27 @@ DOOR_LED = Capability(
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=_led_night_write),
|
||||
SelectDesc(key='led_night_brightness', field='setNightLightBrightness',
|
||||
name='Door LED night brightness', icon='mdi:brightness-4',
|
||||
entity_category='config',
|
||||
options=_LED_LEVELS,
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['doorled', 'light', 'vs', '0'],
|
||||
{'setNightLightBrightness': p})),
|
||||
TimeDesc(key='led_night_start', field='setNightLightTimeStart',
|
||||
name='Door LED night start', icon='mdi:clock-start',
|
||||
entity_category='config',
|
||||
value_fn=_parse_hm,
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['doorled', 'light', 'vs', '0'],
|
||||
{'setNightLightTimeStart': f'{p.hour:02d}:{p.minute:02d}'})),
|
||||
TimeDesc(key='led_night_end', field='setNightLightTimeEnd',
|
||||
name='Door LED night end', icon='mdi:clock-end',
|
||||
entity_category='config',
|
||||
value_fn=_parse_hm,
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['doorled', 'light', 'vs', '0'],
|
||||
{'setNightLightTimeEnd': f'{p.hour:02d}:{p.minute:02d}'})),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -53,3 +86,17 @@ SOUND_MODE = Capability(
|
||||
options=_SOUND_MODES, write_fn=_sound_mode_write),
|
||||
),
|
||||
)
|
||||
|
||||
SOUND_VOLUME = Capability(
|
||||
href='/settings/sound/volume/vs/0',
|
||||
entities=(
|
||||
NumberDesc(key='sound_volume', field='level',
|
||||
name='Sound volume', icon='mdi:volume-medium',
|
||||
entity_category='config',
|
||||
native_min=0, native_max=15, step=5,
|
||||
value_fn=lambda v: int(v) if v is not None else None,
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['settings', 'sound', 'volume', 'vs', '0'],
|
||||
{'level': str(int(p))})),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -64,18 +64,33 @@ OPERATIONAL_STATE = Capability(
|
||||
# cycle_active is a bool derived from machine_state; used by the
|
||||
# adapter to gate oven writes (cycle_active_field='cycle_active').
|
||||
# Harmless for non-oven appliances — just an extra bool in state.
|
||||
BinarySensorDesc(key='cycle_active', field='x.com.samsung.da.state',
|
||||
name='Cycle active', device_class='running',
|
||||
value_fn=lambda v: _SAMSUNG_STATE_TO_OCF.get(v) == 'active'),
|
||||
SensorDesc(key='progress', field='x.com.samsung.da.progress',
|
||||
name='Progress', icon='mdi:progress-wrench', value_fn=_progress),
|
||||
# Samsung firmware keeps state='Run' after progress reaches 'Finish',
|
||||
# so we also gate on progress to avoid a stuck 'Running' indication.
|
||||
BinarySensorDesc(key='cycle_active', device_class='running',
|
||||
name='Cycle active',
|
||||
rep_fn=lambda rep: (
|
||||
_SAMSUNG_STATE_TO_OCF.get(rep.get('x.com.samsung.da.state')) == 'active'
|
||||
and rep.get('x.com.samsung.da.progress') != 'Finish'
|
||||
)),
|
||||
SensorDesc(key='progress', name='Progress', icon='mdi:progress-wrench',
|
||||
rep_fn=lambda rep: (
|
||||
'Idle' if _SAMSUNG_STATE_TO_OCF.get(rep.get('x.com.samsung.da.state')) != 'active'
|
||||
else _progress(rep.get('x.com.samsung.da.progress'))
|
||||
)),
|
||||
SensorDesc(key='progress_percentage',
|
||||
field='x.com.samsung.da.progressPercentage',
|
||||
name='Progress percent', unit='%', state_class='measurement',
|
||||
value_fn=_int),
|
||||
SensorDesc(key='finish_time', field='x.com.samsung.da.remainingTime',
|
||||
name='Estimated finish', device_class='timestamp',
|
||||
value_fn=_finish_time),
|
||||
# Only show finish time when machine is actively running. Samsung
|
||||
# firmware leaves a stale remainingTime after a cycle ends, and
|
||||
# freezes it at '00:01:00' when progress reaches 'Finish'.
|
||||
SensorDesc(key='finish_time', device_class='timestamp',
|
||||
name='Estimated finish',
|
||||
rep_fn=lambda rep: (
|
||||
None if _SAMSUNG_STATE_TO_OCF.get(rep.get('x.com.samsung.da.state')) != 'active'
|
||||
or rep.get('x.com.samsung.da.progress') == 'Finish'
|
||||
else _finish_time(rep.get('x.com.samsung.da.remainingTime'))
|
||||
)),
|
||||
TimeDesc(key='delay_start_time', field='x.com.samsung.da.delayStartTime',
|
||||
name='Delay start',
|
||||
value_fn=_parse_hms,
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"sensor": {
|
||||
"ice_making_status": {
|
||||
"state": {
|
||||
"ICESTATUS_STOP": "Idle"
|
||||
"ICESTATUS_STOP": "Idle",
|
||||
"ICESTATUS_RUN": "Making ice"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"sensor": {
|
||||
"ice_making_status": {
|
||||
"state": {
|
||||
"ICESTATUS_STOP": "Idle"
|
||||
"ICESTATUS_STOP": "Idle",
|
||||
"ICESTATUS_RUN": "Making ice"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+20
-16
@@ -1,57 +1,61 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"auto_release_dry",
|
||||
"child_lock",
|
||||
"child_lock_binary",
|
||||
"completion_minutes",
|
||||
"completion_time",
|
||||
"cycle",
|
||||
"cycle_active",
|
||||
"delay_start_time",
|
||||
"energy_kwh",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"heated_dry",
|
||||
"led_brightness",
|
||||
"led_night_brightness",
|
||||
"led_night_end",
|
||||
"led_night_light",
|
||||
"led_night_start",
|
||||
"machine_state",
|
||||
"power_state",
|
||||
"power_state_binary",
|
||||
"power_switch",
|
||||
"power_watts",
|
||||
"progress",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"remote_control_binary",
|
||||
"sanitize",
|
||||
"sound_mode",
|
||||
"sound_volume",
|
||||
"storm_wash",
|
||||
"water_liters"
|
||||
],
|
||||
"discovery_unique_ids": [
|
||||
"samsung_dishwasher_alarm_code",
|
||||
"samsung_dishwasher_auto_release_dry",
|
||||
"samsung_dishwasher_child_lock",
|
||||
"samsung_dishwasher_child_lock_binary",
|
||||
"samsung_dishwasher_completion_minutes",
|
||||
"samsung_dishwasher_completion_time",
|
||||
"samsung_dishwasher_cycle",
|
||||
"samsung_dishwasher_cycle_active",
|
||||
"samsung_dishwasher_delay_start_time",
|
||||
"samsung_dishwasher_energy_kwh",
|
||||
"samsung_dishwasher_filter_status",
|
||||
"samsung_dishwasher_filter_usage",
|
||||
"samsung_dishwasher_finish_time",
|
||||
"samsung_dishwasher_firmware_update",
|
||||
"samsung_dishwasher_heated_dry",
|
||||
"samsung_dishwasher_led_brightness",
|
||||
"samsung_dishwasher_led_night_brightness",
|
||||
"samsung_dishwasher_led_night_end",
|
||||
"samsung_dishwasher_led_night_light",
|
||||
"samsung_dishwasher_led_night_start",
|
||||
"samsung_dishwasher_machine_state",
|
||||
"samsung_dishwasher_pause",
|
||||
"samsung_dishwasher_power_state",
|
||||
"samsung_dishwasher_power_state_binary",
|
||||
"samsung_dishwasher_power_switch",
|
||||
"samsung_dishwasher_power_watts",
|
||||
"samsung_dishwasher_progress",
|
||||
"samsung_dishwasher_progress_percentage",
|
||||
"samsung_dishwasher_remote_control",
|
||||
"samsung_dishwasher_remote_control_binary",
|
||||
"samsung_dishwasher_sanitize",
|
||||
"samsung_dishwasher_sound_mode",
|
||||
"samsung_dishwasher_sound_volume",
|
||||
"samsung_dishwasher_start",
|
||||
"samsung_dishwasher_stop",
|
||||
"samsung_dishwasher_storm_wash",
|
||||
"samsung_dishwasher_water_liters"
|
||||
]
|
||||
}
|
||||
|
||||
+30
-26
@@ -1,70 +1,74 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"any_door_open",
|
||||
"auto_door_opener",
|
||||
"autofill",
|
||||
"beverage_zone_mode",
|
||||
"cabinet_light",
|
||||
"cabinet_light_on",
|
||||
"brightness_level",
|
||||
"cabinet_light_dim",
|
||||
"cabinet_light_switch",
|
||||
"cooler_setpoint",
|
||||
"cooler_temperature",
|
||||
"day_brightness",
|
||||
"door_alert",
|
||||
"door_cooler_open",
|
||||
"door_freezer_open",
|
||||
"energy_kwh",
|
||||
"filter_status",
|
||||
"filter_usage",
|
||||
"firmware_update",
|
||||
"ice_maker_enabled",
|
||||
"flex_zone_mode",
|
||||
"freezer_setpoint",
|
||||
"freezer_temperature",
|
||||
"fridge_sound",
|
||||
"ice_night_mode",
|
||||
"icemaker_one_enabled",
|
||||
"icemaker_one_making_status",
|
||||
"icemaker_one_on",
|
||||
"icemaker_one_state",
|
||||
"icemaker_one_type",
|
||||
"icemaker_two_enabled",
|
||||
"icemaker_two_making_status",
|
||||
"icemaker_two_on",
|
||||
"icemaker_two_state",
|
||||
"icemaker_two_type",
|
||||
"night_end",
|
||||
"night_start",
|
||||
"power_watts",
|
||||
"rapid_freezing",
|
||||
"rapid_fridge",
|
||||
"sabbath_mode",
|
||||
"temperature_current_cooler_temp_f",
|
||||
"temperature_current_freezer_temp_f",
|
||||
"temperature_desired_cooler_temp_f",
|
||||
"temperature_desired_freezer_temp_f"
|
||||
"welcome_lighting"
|
||||
],
|
||||
"discovery_unique_ids": [
|
||||
"samsung_refrigerator_alarm_code",
|
||||
"samsung_refrigerator_any_door_open",
|
||||
"samsung_refrigerator_auto_door_opener",
|
||||
"samsung_refrigerator_autofill",
|
||||
"samsung_refrigerator_beverage_zone_mode",
|
||||
"samsung_refrigerator_cabinet_light",
|
||||
"samsung_refrigerator_cabinet_light_on",
|
||||
"samsung_refrigerator_brightness_level",
|
||||
"samsung_refrigerator_cabinet_light_dim",
|
||||
"samsung_refrigerator_cabinet_light_switch",
|
||||
"samsung_refrigerator_cooler_setpoint",
|
||||
"samsung_refrigerator_cooler_temperature",
|
||||
"samsung_refrigerator_day_brightness",
|
||||
"samsung_refrigerator_door_alert",
|
||||
"samsung_refrigerator_door_cooler_open",
|
||||
"samsung_refrigerator_door_freezer_open",
|
||||
"samsung_refrigerator_energy_kwh",
|
||||
"samsung_refrigerator_filter_status",
|
||||
"samsung_refrigerator_filter_usage",
|
||||
"samsung_refrigerator_firmware_update",
|
||||
"samsung_refrigerator_ice_maker_enabled",
|
||||
"samsung_refrigerator_flex_zone_mode",
|
||||
"samsung_refrigerator_freezer_setpoint",
|
||||
"samsung_refrigerator_freezer_temperature",
|
||||
"samsung_refrigerator_fridge_sound",
|
||||
"samsung_refrigerator_ice_night_mode",
|
||||
"samsung_refrigerator_icemaker_one_enabled",
|
||||
"samsung_refrigerator_icemaker_one_making_status",
|
||||
"samsung_refrigerator_icemaker_one_on",
|
||||
"samsung_refrigerator_icemaker_one_state",
|
||||
"samsung_refrigerator_icemaker_one_type",
|
||||
"samsung_refrigerator_icemaker_two_enabled",
|
||||
"samsung_refrigerator_icemaker_two_making_status",
|
||||
"samsung_refrigerator_icemaker_two_on",
|
||||
"samsung_refrigerator_icemaker_two_state",
|
||||
"samsung_refrigerator_icemaker_two_type",
|
||||
"samsung_refrigerator_night_end",
|
||||
"samsung_refrigerator_night_start",
|
||||
"samsung_refrigerator_power_watts",
|
||||
"samsung_refrigerator_rapid_freezing",
|
||||
"samsung_refrigerator_rapid_fridge",
|
||||
"samsung_refrigerator_sabbath_mode",
|
||||
"samsung_refrigerator_temperature_current_cooler_temp_f",
|
||||
"samsung_refrigerator_temperature_current_freezer_temp_f",
|
||||
"samsung_refrigerator_temperature_desired_cooler_temp_f",
|
||||
"samsung_refrigerator_temperature_desired_freezer_temp_f"
|
||||
"samsung_refrigerator_welcome_lighting"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import time
|
||||
from custom_components.localthings.ocf.registry.capabilities.operational import OPERATIONAL_STATE
|
||||
|
||||
|
||||
@@ -12,11 +11,3 @@ def test_machine_state_maps_samsung_to_ocf():
|
||||
def test_active_when_true_only_when_running():
|
||||
assert OPERATIONAL_STATE.active_when({'x.com.samsung.da.state': 'Run'}) is True
|
||||
assert OPERATIONAL_STATE.active_when({'x.com.samsung.da.state': 'Ready'}) is False
|
||||
|
||||
|
||||
def test_project_extrapolates_remaining_time():
|
||||
state = {}
|
||||
OPERATIONAL_STATE.on_observation(state, {'x.com.samsung.da.remainingTime': '1:00:00'})
|
||||
assert 'remaining_anchor' in state
|
||||
out = OPERATIONAL_STATE.project(state, {'machine_state': 'active', 'completion_minutes': 60})
|
||||
assert out['completion_minutes'] <= 60
|
||||
|
||||
Reference in New Issue
Block a user