feat: add completion time and minutes sensors with parsing logic
This commit is contained in:
@@ -70,6 +70,34 @@ def _finish_time(remaining_str):
|
||||
return None
|
||||
|
||||
|
||||
def _parse_remaining_time(rep):
|
||||
return (
|
||||
rep.get('x.com.samsung.da.remainingTime')
|
||||
or rep.get('remainingTime')
|
||||
)
|
||||
|
||||
|
||||
def _completion_time(rep):
|
||||
return _parse_remaining_time(rep)
|
||||
|
||||
|
||||
def _completion_minutes(rep):
|
||||
raw = _parse_remaining_time(rep)
|
||||
if not raw or not isinstance(raw, str):
|
||||
return None
|
||||
try:
|
||||
parts = [int(p) for p in raw.split(':')]
|
||||
if len(parts) == 3:
|
||||
h, m, s = parts
|
||||
return h * 60 + m + (1 if s > 0 else 0)
|
||||
elif len(parts) == 2:
|
||||
m, s = parts
|
||||
return m + (1 if s > 0 else 0)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
# Shared by dryer/dishwasher/oven/washer -- oven.py imports this directly
|
||||
# rather than keeping its own copy, since both wrote the identical
|
||||
# state='Ready' RMW.
|
||||
@@ -123,6 +151,15 @@ OPERATIONAL_STATE = Capability(
|
||||
or rep.get('x.com.samsung.da.progress') == 'Finish'
|
||||
else _finish_time(rep.get('x.com.samsung.da.remainingTime'))
|
||||
)),
|
||||
SensorDesc(key='completion_time', name='Completion time',
|
||||
icon='mdi:timer-sand',
|
||||
exists_fn=lambda rep, resources: _completion_time(rep) is not None,
|
||||
rep_fn=_completion_time),
|
||||
SensorDesc(key='completion_minutes', name='Remaining minutes',
|
||||
icon='mdi:clock-outline', unit='min',
|
||||
device_class='duration', state_class='measurement',
|
||||
exists_fn=lambda rep, resources: _completion_minutes(rep) is not None,
|
||||
rep_fn=_completion_minutes),
|
||||
NumberDesc(key='delay_start_hours', name='Delay start', icon='mdi:timer-plus-outline',
|
||||
device_class='duration', unit='h',
|
||||
native_min=0, native_max=24, step=1,
|
||||
@@ -144,4 +181,4 @@ OPERATIONAL_STATE = Capability(
|
||||
{'x.com.samsung.da.state': p})),
|
||||
STOP_BUTTON,
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -24,6 +24,39 @@ class TestProgressPercentage:
|
||||
assert desc.rep_fn(rep) == 42
|
||||
|
||||
|
||||
class TestCompletionTime:
|
||||
"""Unit tests for completion_time and completion_minutes entities."""
|
||||
|
||||
def test_completion_time_string(self):
|
||||
desc = next(e for e in OPERATIONAL_STATE.entities if e.key == 'completion_time')
|
||||
rep = {'x.com.samsung.da.remainingTime': '01:25:30'}
|
||||
assert desc.rep_fn(rep) == '01:25:30'
|
||||
|
||||
def test_completion_time_fallback(self):
|
||||
desc = next(e for e in OPERATIONAL_STATE.entities if e.key == 'completion_time')
|
||||
rep = {'remainingTime': '00:45:00'}
|
||||
assert desc.rep_fn(rep) == '00:45:00'
|
||||
|
||||
def test_completion_minutes_parsing(self):
|
||||
desc = next(e for e in OPERATIONAL_STATE.entities if e.key == 'completion_minutes')
|
||||
|
||||
# 1 hour 25 mins 30 secs -> 85 mins + 1 sec ceiling = 86 mins
|
||||
rep = {'x.com.samsung.da.remainingTime': '01:25:30'}
|
||||
assert desc.rep_fn(rep) == 86
|
||||
|
||||
# Exact minutes: 1 hour 30 mins 00 secs -> 90 mins
|
||||
rep_exact = {'x.com.samsung.da.remainingTime': '01:30:00'}
|
||||
assert desc.rep_fn(rep_exact) == 90
|
||||
|
||||
def test_completion_time_missing_or_invalid(self):
|
||||
desc_time = next(e for e in OPERATIONAL_STATE.entities if e.key == 'completion_time')
|
||||
desc_min = next(e for e in OPERATIONAL_STATE.entities if e.key == 'completion_minutes')
|
||||
|
||||
rep = {}
|
||||
assert desc_time.rep_fn(rep) is None
|
||||
assert desc_min.rep_fn(rep) is None
|
||||
|
||||
|
||||
class TestDelayFieldFallback:
|
||||
def test_reads_delay_end_time_when_delay_start_time_absent(self):
|
||||
from custom_components.localthings.registry.capabilities.operational import OPERATIONAL_STATE
|
||||
@@ -53,4 +86,4 @@ class TestDelayFieldFallback:
|
||||
desc = next(e for e in OPERATIONAL_STATE.entities if e.key == 'delay_start_hours')
|
||||
rep = {'x.com.samsung.da.delayStartTime': '00:00:00'}
|
||||
path, body = desc.write_fn(1.5, rep)
|
||||
assert body == {'x.com.samsung.da.delayStartTime': '1:30:00'}
|
||||
assert body == {'x.com.samsung.da.delayStartTime': '1:30:00'}
|
||||
Reference in New Issue
Block a user