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:
kkqq9320
2026-08-04 14:44:54 +09:00
parent a5484f746a
commit 96d06369bc
2 changed files with 41 additions and 11 deletions
@@ -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",
+16 -4
View File
@@ -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 -------------------------------------------------------------