review: floor the sensing interval at one minute
Dropping native_min to 0 fixed the read range and quietly opened a write:
native_min governs what the user can enter, not just what renders, so 0
became enterable and would have gone out as periodicSensingInterval "0".
Nothing establishes what that does to this board -- both fixtures report 600,
the app's smallest choice is 10 min, and 60 s is the lowest value confirmed
accepted. The two precedents leaned on differ in exactly the way that matters:
oven.cook_time and operational.delay_start_hours sit at a zero floor under a
value where 0 is a real setting ("no timer", "no delay").
One minute is also the resolution this board reports results at.
lastSensingTime lands on an exact minute on every sample from the AVT-WW-TP1
and A-VTWW-TP2 boards -- both fixtures, plus eleven consecutive live readings
-- where the TP1X/AC/hood boards report arbitrary seconds. A sub-minute
interval is unobservable here whether or not the board honours it.
So native_min goes to 1 rather than 0, and the read rounds up instead of to
nearest so a sub-minute reading renders as 1 rather than falling below the
entity's own floor. The write still refuses anything under a minute -- a None
return, the silent no-op range_hood._lamp_level_write uses for a level the
device didn't advertise -- since native_min only guards the UI path, not a
service call.
This commit is contained in:
@@ -575,13 +575,15 @@ SOUND_VOLUME = Capability(
|
||||
def _interval_minutes(seconds):
|
||||
"""Device stores the interval in seconds; the entity is in minutes.
|
||||
|
||||
`is None` rather than a falsy check so a reported 0 stays 0 instead of
|
||||
reading as unknown. Sub-30s values round to 0, which is why native_min is
|
||||
0 rather than 1 -- matching oven.cook_time and operational's delay hours,
|
||||
both of which convert a device time value and floor at zero.
|
||||
`is None` rather than a falsy check so a reported 0 is distinguishable
|
||||
from a missing one. Anything else nonzero rounds up rather than to
|
||||
nearest, so a sub-minute value can't render as 0 and fall below the
|
||||
entity's own floor.
|
||||
"""
|
||||
secs = int_or_none(seconds)
|
||||
return round(secs / 60) if secs is not None else None
|
||||
if secs is None:
|
||||
return None
|
||||
return -(-secs // 60) if secs > 0 else 0
|
||||
|
||||
|
||||
def _interval_write(payload, rep, href=None):
|
||||
@@ -592,8 +594,24 @@ def _interval_write(payload, rep, href=None):
|
||||
# does advertise constraints where it has them -- and it accepts values the
|
||||
# app never offers. Writing 60 s, six times finer than the app's smallest
|
||||
# choice, drove an observed ~60 s sensing cycle on hardware.
|
||||
#
|
||||
# One minute is the floor because that's the resolution this board reports
|
||||
# results at: lastSensingTime lands on an exact minute on every sample from
|
||||
# the AVT-WW-TP1 and A-VTWW-TP2 boards (both fixtures, and eleven
|
||||
# consecutive live readings), where the TP1X/AC/hood boards report arbitrary
|
||||
# seconds. A sub-minute interval is therefore unobservable here whether or
|
||||
# not the board honours it. Zero is refused for a separate reason: unlike
|
||||
# oven.cook_time or operational's delay hours, where 0 is a real setting
|
||||
# ("no timer", "no delay"), nothing establishes what a 0 interval does to
|
||||
# this board -- so native_min stops the UI offering it, and this guard
|
||||
# covers the service-call path. Silent no-op via a None return, the same
|
||||
# shape range_hood._lamp_level_write uses for a level the device didn't
|
||||
# advertise.
|
||||
minutes = round(float(payload))
|
||||
if minutes < 1:
|
||||
return None
|
||||
return ["airlevelcheck", "vs", "0"], {
|
||||
"x.com.samsung.da.periodicSensingInterval": str(round(float(payload) * 60))
|
||||
"x.com.samsung.da.periodicSensingInterval": str(minutes * 60)
|
||||
}
|
||||
|
||||
|
||||
@@ -694,7 +712,7 @@ AIR_LEVEL_CHECK = Capability(
|
||||
field="x.com.samsung.da.periodicSensingInterval",
|
||||
icon="mdi:timer-cog",
|
||||
entity_category="config",
|
||||
native_min=0,
|
||||
native_min=1,
|
||||
native_max=60,
|
||||
step=1,
|
||||
unit="min",
|
||||
|
||||
@@ -145,13 +145,25 @@ def test_interval_is_minutes_in_the_ui_and_seconds_on_the_wire():
|
||||
|
||||
|
||||
def test_interval_keeps_zero_distinct_from_unknown():
|
||||
"""`if secs` would fold a reported 0 into None; sub-30s values round to 0,
|
||||
which is why native_min is 0 rather than 1."""
|
||||
"""`if secs` would fold a reported 0 into None. Anything else nonzero
|
||||
rounds up, so a sub-minute reading can't render as 0 and fall below the
|
||||
entity's own floor."""
|
||||
desc = _desc("sensing_interval")
|
||||
assert desc.value_fn("0") == 0
|
||||
assert desc.value_fn("20") == 0
|
||||
assert desc.value_fn("20") == 1
|
||||
assert desc.value_fn("61") == 2
|
||||
assert desc.value_fn(None) is None
|
||||
assert desc.native_min == 0
|
||||
|
||||
|
||||
def test_interval_floor_is_one_minute():
|
||||
"""lastSensingTime lands on an exact minute on this board family, so a
|
||||
sub-minute interval is unobservable; and 0 has no established meaning
|
||||
here, unlike the zero floors on oven.cook_time / delay_start_hours."""
|
||||
desc = _desc("sensing_interval")
|
||||
assert desc.native_min == 1
|
||||
assert desc.write_fn(0, {}) is None
|
||||
assert desc.write_fn(0.4, {}) is None
|
||||
assert desc.write_fn(1, {})[1] == {"x.com.samsung.da.periodicSensingInterval": "60"}
|
||||
|
||||
|
||||
# --- skip window -------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user