Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 numberDiff line numberDiff 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 DownExpand Up@@ -80,6 +81,7 @@ class NodeType(Enum):
NodeFeature.MOTION,
NodeFeature.MOTION_CONFIG,
NodeFeature.TEMPERATURE,
NodeFeature.SENSE,
NodeFeature.SWITCH,
)

Expand DownExpand 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
Comment thread
dirixmjm marked this conversation as resolved.
humidity: float | None = None
Comment thread
dirixmjm marked this conversation as resolved.

class PlugwiseNode(Protocol):
"""Protocol definition of a Plugwise device node."""
Expand Down
1 change: 1 addition & 0 deletions plugwise_usb/nodes/helpers/firmware.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -156,6 +156,7 @@ class SupportedVersions(NamedTuple):
FEATURE_SUPPORTED_AT_FIRMWARE: Final = {
NodeFeature.BATTERY: 2.0,
NodeFeature.INFO: 2.0,
NodeFeature.SENSE: 2.0,
NodeFeature.TEMPERATURE: 2.0,
NodeFeature.HUMIDITY: 2.0,
NodeFeature.ENERGY: 2.0,
Expand Down
18 changes: 5 additions & 13 deletions plugwise_usb/nodes/node.py
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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
68 changes: 41 additions & 27 deletions plugwise_usb/nodes/sense.py
Original file line numberDiff line numberDiff 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 @@ def __init__(
"""Initialize Scan Device."""
super().__init__(mac, address, controller, loaded_callback)

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

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

Expand All@@ -56,16 +54,17 @@ async def load(self) -> bool:
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()
else:
self._load_defaults()
self._loaded = True
self._setup_protocol(
SENSE_FIRMWARE_SUPPORT,
(NodeFeature.INFO, NodeFeature.SENSE),
)
if await self.initialize():
await self._loaded_callback(NodeEvent.LOADED, self.mac)
return True
Comment thread
coderabbitai[bot] marked this conversation as resolved.
_LOGGER.debug("Loading of Sense node %s failed", self._node_info.mac)
return False

Expand All@@ -90,6 +89,24 @@ async def unload(self) -> None:
self._sense_subscription()
await super().unload()

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

# region properties

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

# 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):
Expand All@@ -99,25 +116,24 @@ async def _sense_report(self, response: PlugwiseResponse) -> bool:
report_received = False
await self._available_update_state(True, response.timestamp)
if response.temperature.value != 65535:
self._temperature = int(
self._sense_statistics.temperature = float(
SENSE_TEMPERATURE_MULTIPLIER * (response.temperature.value / 65536)
- SENSE_TEMPERATURE_OFFSET
)
await self.publish_feature_update_to_subscribers(
NodeFeature.TEMPERATURE, self._temperature
)
report_received = True

if response.humidity.value != 65535:
self._humidity = int(
self._sense_statistics.humidity = float(
SENSE_HUMIDITY_MULTIPLIER * (response.humidity.value / 65536)
- SENSE_HUMIDITY_OFFSET
)
report_received = True

if report_received:
await self.publish_feature_update_to_subscribers(
NodeFeature.HUMIDITY, self._humidity
NodeFeature.SENSE, self._sense_statistics
)
report_received = True


return report_received

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

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
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 numberDiff line numberDiff 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 DownExpand 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