Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions plugwise_usb/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class NodeFeature(str, Enum):
RELAY_INIT = "relay_init"
RELAY_LOCK = "relay_lock"
SWITCH = "switch"
SENSE = "sense"
TEMPERATURE = "temperature"


Expand Down Expand Up @@ -80,6 +81,7 @@ class NodeType(Enum):
NodeFeature.MOTION,
NodeFeature.MOTION_CONFIG,
NodeFeature.TEMPERATURE,
NodeFeature.SENSE,
NodeFeature.SWITCH,
)

Expand Down Expand Up @@ -229,6 +231,12 @@ class EnergyStatistics:
day_production: float | None = None
day_production_reset: datetime | None = None

@dataclass
class SenseStatistics:
"""Sense statistics collection."""

temperature: float | None = None
humidity: float | None = None

class PlugwiseNode(Protocol):
"""Protocol definition of a Plugwise device node."""
Expand Down
18 changes: 5 additions & 13 deletions plugwise_usb/nodes/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
RelayConfig,
RelayLock,
RelayState,
SenseStatistics,
)
from ..connection import StickController
from ..constants import SUPPRESS_INITIALIZATION_WARNINGS, TYPE_MODEL, UTF8
Expand Down Expand Up @@ -192,14 +193,6 @@ def features(self) -> tuple[NodeFeature, ...]:
"""Supported feature types of node."""
return self._features

@property
@raise_not_loaded
def humidity(self) -> float:
"""Humidity state."""
if NodeFeature.HUMIDITY not in self._features:
raise FeatureError(f"Humidity state is not supported for node {self.mac}")
raise NotImplementedError()

@property
def is_battery_powered(self) -> bool:
"""Return if node is battery powered."""
Expand Down Expand Up @@ -320,13 +313,12 @@ def switch(self) -> bool:

@property
@raise_not_loaded
def temperature(self) -> float:
"""Temperature value."""
if NodeFeature.TEMPERATURE not in self._features:
def sense(self) -> SenseStatistics:
"""Sense statistics."""
if NodeFeature.SENSE not in self._features:
raise FeatureError(
f"Temperature state is not supported for node {self.mac}"
f"Sense statistics is not supported for node {self.mac}"
)
raise NotImplementedError()

# endregion

Expand Down
77 changes: 45 additions & 32 deletions plugwise_usb/nodes/sense.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import logging
from typing import Any, Final

from ..api import NodeEvent, NodeFeature
from ..api import NodeEvent, NodeFeature, SenseStatistics
from ..connection import StickController
from ..exceptions import MessageError, NodeError
from ..messages.responses import SENSE_REPORT_ID, PlugwiseResponse, SenseReportResponse
Expand All @@ -25,8 +25,7 @@

SENSE_FEATURES: Final = (
NodeFeature.INFO,
NodeFeature.TEMPERATURE,
NodeFeature.HUMIDITY,
NodeFeature.SENSE,
)


Expand All @@ -43,8 +42,7 @@
"""Initialize Scan Device."""
super().__init__(mac, address, controller, loaded_callback)

self._humidity: float | None = None
self._temperature: float | None = None
self._sense_statistics = SenseStatistics()

Check warning on line 45 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L45

Added line #L45 was not covered by tests

self._sense_subscription: Callable[[], None] | None = None

Expand All @@ -56,16 +54,17 @@
self._node_info.is_battery_powered = True
if self._cache_enabled:
_LOGGER.debug("Loading Sense node %s from cache", self._node_info.mac)
if await self._load_from_cache():
self._loaded = True
self._setup_protocol(
SENSE_FIRMWARE_SUPPORT,
(NodeFeature.INFO, NodeFeature.TEMPERATURE, NodeFeature.HUMIDITY),
)
if await self.initialize():
await self._loaded_callback(NodeEvent.LOADED, self.mac)
return True

await self._load_from_cache()

Check warning on line 57 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L57

Added line #L57 was not covered by tests
else:
self._load_defaults()
self._setup_protocol(

Check warning on line 60 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L59-L60

Added lines #L59 - L60 were not covered by tests
SENSE_FIRMWARE_SUPPORT,
(NodeFeature.INFO, NodeFeature.SENSE),
)
if await self.initialize():
await self._loaded_callback(NodeEvent.LOADED, self.mac)
self._loaded = True
return True

Check warning on line 67 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L64-L67

Added lines #L64 - L67 were not covered by tests
_LOGGER.debug("Loading of Sense node %s failed", self._node_info.mac)
return False

Expand All @@ -90,35 +89,51 @@
self._sense_subscription()
await super().unload()

def _load_defaults(self) -> None:
"""Load default configuration settings."""
super()._load_defaults()
self._sense_statistics = SenseStatistics(

Check warning on line 95 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L94-L95

Added lines #L94 - L95 were not covered by tests
temperature=0.0,
humidity=0.0,
)

# region properties

@property
@raise_not_loaded
def sense_statistics(self) -> SenseStatistics:
"""Sense Statistics."""
return self._sense_statistics

Check warning on line 106 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L106

Added line #L106 was not covered by tests

# end region

async def _sense_report(self, response: PlugwiseResponse) -> bool:
"""Process sense report message to extract current temperature and humidity values."""
if not isinstance(response, SenseReportResponse):
raise MessageError(
f"Invalid response message type ({response.__class__.__name__}) received, expected SenseReportResponse"
)
report_received = False
report_received_1 = report_received_2 = False

Check warning on line 116 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L116

Added line #L116 was not covered by tests
await self._available_update_state(True, response.timestamp)
if response.temperature.value != 65535:
self._temperature = int(
self._sense_statistics.temperature = float(

Check warning on line 119 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L119

Added line #L119 was not covered by tests
SENSE_TEMPERATURE_MULTIPLIER * (response.temperature.value / 65536)
- SENSE_TEMPERATURE_OFFSET
)
await self.publish_feature_update_to_subscribers(
NodeFeature.TEMPERATURE, self._temperature
)
report_received = True
report_received_1 = True

Check warning on line 123 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L123

Added line #L123 was not covered by tests

if response.humidity.value != 65535:
self._humidity = int(
self._sense_statistics.humidity = float(

Check warning on line 126 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L126

Added line #L126 was not covered by tests
SENSE_HUMIDITY_MULTIPLIER * (response.humidity.value / 65536)
- SENSE_HUMIDITY_OFFSET
)
await self.publish_feature_update_to_subscribers(
NodeFeature.HUMIDITY, self._humidity
)
report_received = True

return report_received
report_received_2 = True

Check warning on line 130 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L130

Added line #L130 was not covered by tests

await self.publish_feature_update_to_subscribers(

Check warning on line 132 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L132

Added line #L132 was not covered by tests
NodeFeature.SENSE, self._sense_statistics
)

return report_received_1 and report_received_2

Check warning on line 136 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L136

Added line #L136 was not covered by tests

@raise_not_loaded
async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any]:
Expand All @@ -136,12 +151,10 @@
)

match feature:
case NodeFeature.TEMPERATURE:
states[NodeFeature.TEMPERATURE] = self._temperature
case NodeFeature.HUMIDITY:
states[NodeFeature.HUMIDITY] = self._humidity
case NodeFeature.PING:
states[NodeFeature.PING] = await self.ping_update()
case NodeFeature.SENSE:
states[NodeFeature.SENSE] = self._sense_statistics

Check warning on line 157 in plugwise_usb/nodes/sense.py

View check run for this annotation

Codecov / codecov/patch

plugwise_usb/nodes/sense.py#L156-L157

Added lines #L156 - L157 were not covered by tests
case _:
state_result = await super().get_state((feature,))
states[feature] = state_result[feature]
Expand Down
8 changes: 2 additions & 6 deletions tests/test_usb.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,9 +614,7 @@ async def test_stick_node_discovered_subscription(
with pytest.raises(pw_exceptions.FeatureError):
assert stick.nodes["5555555555555555"].power
with pytest.raises(pw_exceptions.FeatureError):
assert stick.nodes["5555555555555555"].humidity
with pytest.raises(pw_exceptions.FeatureError):
assert stick.nodes["5555555555555555"].temperature
assert stick.nodes["5555555555555555"].sense
with pytest.raises(pw_exceptions.FeatureError):
assert stick.nodes["5555555555555555"].energy

Expand Down Expand Up @@ -847,9 +845,7 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No
with pytest.raises(pw_exceptions.FeatureError):
assert stick.nodes["0098765432101234"].switch
with pytest.raises(pw_exceptions.FeatureError):
assert stick.nodes["0098765432101234"].humidity
with pytest.raises(pw_exceptions.FeatureError):
assert stick.nodes["0098765432101234"].temperature
assert stick.nodes["0098765432101234"].sense

# Test relay init
# load node 2222222222222222 which has
Expand Down
Loading