Skip to content

Commit a3178a4

Browse files
committed
Append report interval setting to Sense node configuration.
Extend MockStickController with a response queue to send allow sending different responses sequentially
1 parent 1ea9c34 commit a3178a4

File tree

4 files changed

+145
-66
lines changed

4 files changed

+145
-66
lines changed

plugwise_usb/api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ class SenseHysteresisConfig:
278278
temperature_upper_bound: float | None: upper temperature switching threshold (°C)
279279
temperature_lower_bound: float | None: lower temperature switching threshold (°C)
280280
temperature_direction: bool | None: True = switch ON when temperature rises; False = switch OFF when temperature rises
281+
report_interval: int | None = None: Interval time at which the temperature and humidity are reported
281282
dirty: bool: Settings changed, device update pending
282283
283284
Notes:
@@ -293,6 +294,7 @@ class SenseHysteresisConfig:
293294
temperature_upper_bound: float | None = None
294295
temperature_lower_bound: float | None = None
295296
temperature_direction: bool | None = None
297+
report_interval: int | None = None
296298
dirty: bool = False
297299

298300

plugwise_usb/messages/requests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,7 @@ def __init__(
14931493
mac: bytes,
14941494
interval: int,
14951495
):
1496-
"""Initialize ScanLightCalibrateRequest message object."""
1496+
"""Initialize SenseReportIntervalRequest message object."""
14971497
super().__init__(send_fn, mac)
14981498
self._args.append(Int(interval, length=2))
14991499

plugwise_usb/nodes/sense.py

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
)
1919
from ..connection import StickController
2020
from ..exceptions import MessageError, NodeError
21-
from ..messages.requests import SenseConfigureHysteresisRequest
21+
from ..messages.requests import (
22+
SenseConfigureHysteresisRequest,
23+
SenseReportIntervalRequest,
24+
)
2225
from ..messages.responses import (
2326
NODE_SWITCH_GROUP_ID,
2427
SENSE_REPORT_ID,
@@ -59,6 +62,7 @@
5962
CACHE_SENSE_HYSTERESIS_TEMPERATURE_UPPER_BOUND = "temperature_upper_bound"
6063
CACHE_SENSE_HYSTERESIS_TEMPERATURE_LOWER_BOUND = "temperature_lower_bound"
6164
CACHE_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION = "temperature_direction"
65+
CACHE_SENSE_HYSTERESIS_REPORT_INTERVAL = "report_interval"
6266
CACHE_SENSE_HYSTERESIS_CONFIG_DIRTY = "sense_hysteresis_config_dirty"
6367

6468
DEFAULT_SENSE_HYSTERESIS_HUMIDITY_ENABLED: Final = False
@@ -69,6 +73,7 @@
6973
DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_UPPER_BOUND: Final = 50.0
7074
DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_LOWER_BOUND: Final = 50.0
7175
DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION: Final = True
76+
DEFAULT_SENSE_HYSTERESIS_REPORT_INTERVAL: Final = 15
7277

7378

7479
class PlugwiseSense(NodeSED):
@@ -175,6 +180,9 @@ async def _load_from_cache(self) -> bool:
175180
if (temperature_direction := self._temperature_direction_from_cache()) is None:
176181
dirty = True
177182
temperature_direction = DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION
183+
if (report_interval := self._report_interval_from_cache()) is None:
184+
dirty = True
185+
report_interval = DEFAULT_SENSE_HYSTERESIS_REPORT_INTERVAL
178186
dirty |= self._sense_hysteresis_config_dirty_from_cache()
179187

180188
self._hysteresis_config = SenseHysteresisConfig(
@@ -186,6 +194,7 @@ async def _load_from_cache(self) -> bool:
186194
temperature_upper_bound=temperature_upper_bound,
187195
temperature_lower_bound=temperature_lower_bound,
188196
temperature_direction=temperature_direction,
197+
report_interval=report_interval,
189198
dirty=dirty,
190199
)
191200
if dirty:
@@ -248,6 +257,14 @@ def _temperature_direction_from_cache(self) -> bool | None:
248257
"""Load Temperature hysteresis switch direction from cache."""
249258
return self._get_cache_as_bool(CACHE_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION)
250259

260+
def _report_interval_from_cache(self) -> float | None:
261+
"""Report interval from cache."""
262+
if (
263+
report_interval := self._get_cache(CACHE_SENSE_HYSTERESIS_REPORT_INTERVAL)
264+
) is not None:
265+
return int(report_interval)
266+
return None
267+
251268
def _sense_hysteresis_config_dirty_from_cache(self) -> bool:
252269
"""Load sense hysteresis dirty from cache."""
253270
if (
@@ -278,6 +295,7 @@ def hysteresis_config(self) -> SenseHysteresisConfig:
278295
temperature_upper_bound=self.temperature_upper_bound,
279296
temperature_lower_bound=self.temperature_lower_bound,
280297
temperature_direction=self.temperature_direction,
298+
report_interval=self.report_interval,
281299
dirty=self.hysteresis_config_dirty,
282300
)
283301

@@ -337,6 +355,13 @@ def temperature_direction(self) -> bool:
337355
return self._hysteresis_config.temperature_direction
338356
return DEFAULT_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION
339357

358+
@property
359+
def report_interval(self) -> float:
360+
"""Temperature lower bound value."""
361+
if self._hysteresis_config.report_interval is not None:
362+
return self._hysteresis_config.report_interval
363+
return DEFAULT_SENSE_HYSTERESIS_REPORT_INTERVAL
364+
340365
@property
341366
def hysteresis_config_dirty(self) -> bool:
342367
"""Sense hysteresis configuration dirty flag."""
@@ -537,6 +562,31 @@ async def set_hysteresis_temperature_lower_bound(self, lower_bound: float) -> bo
537562
await self._sense_configure_update()
538563
return True
539564

565+
async def set_report_interval(self, report_interval: int) -> bool:
566+
"""Configure Sense measurement interval.
567+
568+
Configuration request will be queued and will be applied the next time when node is awake for maintenance.
569+
"""
570+
_LOGGER.debug(
571+
"set_report_interval | Device %s | %s -> %s",
572+
self.name,
573+
self._hysteresis_config.report_interval,
574+
report_interval,
575+
)
576+
if report_interval < 1 or report_interval > 60:
577+
raise ValueError(
578+
f"Invalid measurement interval {report_interval}. It must be between 1 and 60 minutes"
579+
)
580+
if self._hysteresis_config.report_interval == report_interval:
581+
return False
582+
self._hysteresis_config = replace(
583+
self._hysteresis_config,
584+
report_interval=report_interval,
585+
dirty=True,
586+
)
587+
await self._sense_configure_update()
588+
return True
589+
540590
async def set_hysteresis_temperature_direction(self, state: bool) -> bool:
541591
"""Configure temperature hysteresis to switch on or off on increasing or decreasing direction.
542592
@@ -637,6 +687,7 @@ async def _run_awake_tasks(self) -> None:
637687
configure_result = await gather(
638688
self._configure_sense_humidity_task(),
639689
self._configure_sense_temperature_task(),
690+
self._configure_sense_report_interval_task(),
640691
)
641692
if all(configure_result):
642693
self._hysteresis_config = replace(self._hysteresis_config, dirty=False)
@@ -645,10 +696,11 @@ async def _run_awake_tasks(self) -> None:
645696
else:
646697
_LOGGER.warning(
647698
"Sense hysteresis configuration partially failed for %s "
648-
"(humidity=%s, temperature=%s); will retry on next wake.",
699+
"(humidity=%s, temperature=%s, report_interval=%s); will retry on next wake.",
649700
self.name,
650701
configure_result[0],
651702
configure_result[1],
703+
configure_result[2],
652704
)
653705
await self.publish_feature_update_to_subscribers(
654706
NodeFeature.SENSE_HYSTERESIS,
@@ -763,6 +815,35 @@ async def _configure_sense_temperature_task(self) -> bool:
763815
)
764816
return False
765817

818+
async def _configure_sense_report_interval_task(self) -> bool:
819+
"""Configure Sense report interval setting. Returns True if successful."""
820+
if not self._hysteresis_config.dirty:
821+
return True
822+
request = SenseReportIntervalRequest(
823+
self._send,
824+
self._mac_in_bytes,
825+
self.report_interval,
826+
)
827+
if (response := await request.send()) is None:
828+
_LOGGER.warning(
829+
"No response from %s to configure report interval.",
830+
self.name,
831+
)
832+
return False
833+
if response.node_ack_type == NodeAckResponseType.SENSE_INTERVAL_FAILED:
834+
_LOGGER.warning("Failed to configure report interval for %s", self.name)
835+
return False
836+
if response.node_ack_type == NodeAckResponseType.SENSE_INTERVAL_ACCEPTED:
837+
_LOGGER.debug("Successful configure report interval for %s", self.name)
838+
return True
839+
840+
_LOGGER.warning(
841+
"Unexpected response ack type %s for %s",
842+
response.node_ack_type,
843+
self.name,
844+
)
845+
return False
846+
766847
async def _sense_configure_update(self) -> None:
767848
"""Push sense configuration update to cache."""
768849
self._set_cache(CACHE_SENSE_HYSTERESIS_HUMIDITY_ENABLED, self.humidity_enabled)
@@ -787,6 +868,7 @@ async def _sense_configure_update(self) -> None:
787868
self._set_cache(
788869
CACHE_SENSE_HYSTERESIS_TEMPERATURE_DIRECTION, self.temperature_direction
789870
)
871+
self._set_cache(CACHE_SENSE_HYSTERESIS_REPORT_INTERVAL, self.report_interval)
790872
self._set_cache(
791873
CACHE_SENSE_HYSTERESIS_CONFIG_DIRTY, self.hysteresis_config_dirty
792874
)

0 commit comments

Comments
 (0)