Skip to content

Commit 1f8d91d

Browse files
committed
Propagate NodeType to node on init
Unify loading of SED nodes Setup proper defaults if cache is missing or lacking and requests updates when nodes are awake Fix testing.
1 parent b275689 commit 1f8d91d

File tree

8 files changed

+155
-88
lines changed

8 files changed

+155
-88
lines changed

plugwise_usb/nodes/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,41 +26,47 @@ def get_plugwise_node( # noqa: PLR0911
2626
return PlugwiseCirclePlus(
2727
mac,
2828
address,
29+
node_type,
2930
controller,
3031
loaded_callback,
3132
)
3233
if node_type == NodeType.CIRCLE:
3334
return PlugwiseCircle(
3435
mac,
3536
address,
37+
node_type,
3638
controller,
3739
loaded_callback,
3840
)
3941
if node_type == NodeType.SWITCH:
4042
return PlugwiseSwitch(
4143
mac,
4244
address,
45+
node_type,
4346
controller,
4447
loaded_callback,
4548
)
4649
if node_type == NodeType.SENSE:
4750
return PlugwiseSense(
4851
mac,
4952
address,
53+
node_type,
5054
controller,
5155
loaded_callback,
5256
)
5357
if node_type == NodeType.SCAN:
5458
return PlugwiseScan(
5559
mac,
5660
address,
61+
node_type,
5762
controller,
5863
loaded_callback,
5964
)
6065
if node_type == NodeType.STEALTH:
6166
return PlugwiseStealth(
6267
mac,
6368
address,
69+
node_type,
6470
controller,
6571
loaded_callback,
6672
)

plugwise_usb/nodes/circle.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
NodeFeature,
1717
NodeInfo,
1818
NodeInfoMessage,
19+
NodeType,
1920
PowerStatistics,
2021
RelayConfig,
2122
RelayLock,
@@ -81,11 +82,12 @@ def __init__(
8182
self,
8283
mac: str,
8384
address: int,
85+
node_type: NodeType,
8486
controller: StickController,
8587
loaded_callback: Callable[[NodeEvent, str], Awaitable[None]],
8688
):
8789
"""Initialize base class for Sleeping End Device."""
88-
super().__init__(mac, address, controller, loaded_callback)
90+
super().__init__(mac, address, node_type, controller, loaded_callback)
8991

9092
# Relay
9193
self._relay_lock: RelayLock = RelayLock()

plugwise_usb/nodes/node.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
)
4949

5050
CACHE_FIRMWARE = "firmware"
51-
CACHE_NODE_TYPE = "node_type"
5251
CACHE_HARDWARE = "hardware"
5352
CACHE_NODE_INFO_TIMESTAMP = "node_info_timestamp"
5453
CACHE_RELAY = "relay"
@@ -61,16 +60,22 @@ def __init__(
6160
self,
6261
mac: str,
6362
address: int,
63+
node_type: NodeType,
6464
controller: StickController,
6565
loaded_callback: Callable[[NodeEvent, str], Awaitable[None]],
6666
):
6767
"""Initialize Plugwise base node class."""
6868
super().__init__()
69+
self.node_type = node_type
6970
self._loaded_callback = loaded_callback
7071
self._message_subscribe = controller.subscribe_to_messages
7172
self._features: tuple[NodeFeature, ...] = NODE_FEATURES
7273
self._last_seen = datetime.now(tz=UTC)
73-
self._node_info = NodeInfo(mac, address)
74+
self._node_info = NodeInfo(
75+
mac=mac,
76+
zigbee_address=address,
77+
node_type=self.node_type,
78+
)
7479
self._ping = NetworkStatistics()
7580
self._mac_in_bytes = bytes(mac, encoding=UTF8)
7681
self._mac_in_str = mac
@@ -462,7 +467,6 @@ async def node_info_update(
462467
_LOGGER.debug("No response for node_info_update() for %s", self.mac)
463468
await self._available_update_state(False)
464469
return self._node_info
465-
466470
await self._available_update_state(True, node_info.timestamp)
467471
await self.update_node_details(node_info)
468472
return self._node_info
@@ -475,16 +479,13 @@ async def _node_info_load_from_cache(self) -> bool:
475479

476480
firmware = self._get_cache_as_datetime(CACHE_FIRMWARE)
477481
hardware = self._get_cache(CACHE_HARDWARE)
478-
node_type: NodeType | None = None
479-
if (node_type_str := self._get_cache(CACHE_NODE_TYPE)) is not None:
480-
node_type = NodeType(int(node_type_str))
481482
relay_state = self._get_cache(CACHE_RELAY) == "True"
482483
timestamp = self._get_cache_as_datetime(CACHE_NODE_INFO_TIMESTAMP)
483484
node_info = NodeInfoMessage(
484485
current_logaddress_pointer=None,
485486
firmware=firmware,
486487
hardware=hardware,
487-
node_type=node_type,
488+
node_type=self.node_type,
488489
relay_state=relay_state,
489490
timestamp=timestamp,
490491
)
@@ -509,9 +510,6 @@ async def update_node_details(
509510
complete = True
510511
if node_info.node_type is None:
511512
complete = False
512-
else:
513-
self._node_info.node_type = NodeType(node_info.node_type)
514-
self._set_cache(CACHE_NODE_TYPE, self._node_info.node_type.value)
515513

516514
if node_info.firmware is None:
517515
complete = False

plugwise_usb/nodes/scan.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
import logging
1010
from typing import Any, Final
1111

12-
from ..api import MotionConfig, MotionSensitivity, MotionState, NodeEvent, NodeFeature
12+
from ..api import (
13+
MotionConfig,
14+
MotionSensitivity,
15+
MotionState,
16+
NodeEvent,
17+
NodeFeature,
18+
NodeType,
19+
)
1320
from ..connection import StickController
1421
from ..constants import MAX_UINT_2
1522
from ..exceptions import MessageError, NodeError, NodeTimeout
@@ -48,11 +55,20 @@
4855
# Light override
4956
SCAN_DEFAULT_DAYLIGHT_MODE: Final = False
5057

58+
# Default firmware if not known
59+
DEFAULT_FIRMWARE: Final = datetime(2010, 11, 4, 16, 58, 46, tzinfo=UTC)
60+
5161
# Sensitivity values for motion sensor configuration
5262
SENSITIVITY_HIGH_VALUE = 20 # 0x14
5363
SENSITIVITY_MEDIUM_VALUE = 30 # 0x1E
5464
SENSITIVITY_OFF_VALUE = 255 # 0xFF
5565

66+
# Scan Features
67+
SCAN_FEATURES: Final = (
68+
NodeFeature.MOTION,
69+
NodeFeature.MOTION_CONFIG,
70+
)
71+
5672
# endregion
5773

5874

@@ -63,11 +79,12 @@ def __init__(
6379
self,
6480
mac: str,
6581
address: int,
82+
node_type: NodeType,
6683
controller: StickController,
6784
loaded_callback: Callable[[NodeEvent, str], Awaitable[None]],
6885
):
6986
"""Initialize Scan Device."""
70-
super().__init__(mac, address, controller, loaded_callback)
87+
super().__init__(mac, address, node_type, controller, loaded_callback)
7188
self._unsubscribe_switch_group: Callable[[], None] | None = None
7289
self._reset_timer_motion_on: datetime | None = None
7390
self._scan_subscription: Callable[[], None] | None = None
@@ -89,26 +106,17 @@ async def load(self) -> bool:
89106
"""Load and activate Scan node features."""
90107
if self._loaded:
91108
return True
92-
if self._cache_enabled:
93-
_LOGGER.debug("Load Scan node %s from cache", self._node_info.mac)
94-
await self._load_from_cache()
95-
else:
96-
self._load_defaults()
97-
self._loaded = True
98-
self._setup_protocol(
99-
SCAN_FIRMWARE_SUPPORT,
100-
(
101-
NodeFeature.BATTERY,
102-
NodeFeature.INFO,
103-
NodeFeature.PING,
104-
NodeFeature.MOTION,
105-
NodeFeature.MOTION_CONFIG,
106-
),
107-
)
109+
110+
_LOGGER.debug("Loading Scan node %s", self._node_info.mac)
111+
if not await super().load():
112+
_LOGGER.warning("Load Scan base node failed")
113+
return False
114+
115+
self._setup_protocol(SCAN_FIRMWARE_SUPPORT, SCAN_FEATURES)
108116
if await self.initialize():
109117
await self._loaded_callback(NodeEvent.LOADED, self.mac)
110118
return True
111-
_LOGGER.debug("Load of Scan node %s failed", self._node_info.mac)
119+
_LOGGER.warning("Load Scan node %s failed", self._node_info.mac)
112120
return False
113121

114122
@raise_not_loaded
@@ -132,9 +140,9 @@ async def unload(self) -> None:
132140
await super().unload()
133141

134142
# region Caching
135-
def _load_defaults(self) -> None:
143+
async def _load_defaults(self) -> None:
136144
"""Load default configuration settings."""
137-
super()._load_defaults()
145+
await super()._load_defaults()
138146
self._motion_state = MotionState(
139147
state=SCAN_DEFAULT_MOTION_STATE,
140148
timestamp=None,
@@ -144,11 +152,28 @@ def _load_defaults(self) -> None:
144152
daylight_mode=SCAN_DEFAULT_DAYLIGHT_MODE,
145153
sensitivity_level=SCAN_DEFAULT_SENSITIVITY,
146154
)
155+
if self._node_info.model is None:
156+
self._node_info.model = "Scan"
157+
self.node_info_default = True
158+
if self._node_info.name is None:
159+
self._node_info.name = f"Scan {self._node_info.mac[-5:]}"
160+
self.node_info_default = True
161+
if self._node_info.name is None:
162+
self._node_info.name = f"Scan {self._node_info.mac[-5:]}"
163+
self.node_info_default = True
164+
if self._node_info.firmware is None:
165+
self._node_info.firmware = DEFAULT_FIRMWARE
166+
self.node_info_default = True
167+
self._new_reset_timer = SCAN_DEFAULT_MOTION_RESET_TIMER
168+
self._new_daylight_mode = SCAN_DEFAULT_DAYLIGHT_MODE
169+
self._new_sensitivity_level = SCAN_DEFAULT_SENSITIVITY
170+
await self.schedule_task_when_awake(self._configure_scan_task())
171+
self._scan_config_task_scheduled = True
147172

148173
async def _load_from_cache(self) -> bool:
149174
"""Load states from previous cached information. Returns True if successful."""
150175
if not await super()._load_from_cache():
151-
self._load_defaults()
176+
await self._load_defaults()
152177
return False
153178
self._motion_state = MotionState(
154179
state=self._motion_from_cache(),

plugwise_usb/nodes/sed.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import logging
1818
from typing import Any, Final
1919

20-
from ..api import BatteryConfig, NodeEvent, NodeFeature, NodeInfo
20+
from ..api import BatteryConfig, NodeEvent, NodeFeature, NodeInfo, NodeType
2121
from ..connection import StickController
2222
from ..constants import MAX_UINT_2, MAX_UINT_4
2323
from ..exceptions import MessageError, NodeError
@@ -64,6 +64,12 @@
6464
# Time in minutes the SED will sleep
6565
SED_DEFAULT_SLEEP_DURATION: Final = 60
6666

67+
# Default firmware if not known
68+
DEFAULT_FIRMWARE: Final = None
69+
70+
# SED BaseNode Features
71+
SED_FEATURES: Final = (NodeFeature.BATTERY,)
72+
6773
# Value limits
6874
MAX_MINUTE_INTERVAL: Final = 1440
6975

@@ -83,11 +89,12 @@ def __init__(
8389
self,
8490
mac: str,
8591
address: int,
92+
node_type: NodeType,
8693
controller: StickController,
8794
loaded_callback: Callable[[NodeEvent, str], Awaitable[None]],
8895
):
8996
"""Initialize base class for Sleeping End Device."""
90-
super().__init__(mac, address, controller, loaded_callback)
97+
super().__init__(mac, address, node_type, controller, loaded_callback)
9198
self._loop = get_running_loop()
9299
self._node_info.is_battery_powered = True
93100

@@ -117,12 +124,8 @@ async def load(self) -> bool:
117124
else:
118125
self._load_defaults()
119126
self._loaded = True
120-
self._features += (NodeFeature.BATTERY,)
121-
if await self.initialize():
122-
await self._loaded_callback(NodeEvent.LOADED, self.mac)
123-
return True
124-
_LOGGER.debug("Load of SED node %s failed", self._node_info.mac)
125-
return False
127+
self._features += SED_FEATURES
128+
return True
126129

127130
async def unload(self) -> None:
128131
"""Deactivate and unload node features."""
@@ -156,7 +159,7 @@ async def initialize(self) -> bool:
156159
await super().initialize()
157160
return True
158161

159-
def _load_defaults(self) -> None:
162+
async def _load_defaults(self) -> None:
160163
"""Load default configuration settings."""
161164
self._battery_config = BatteryConfig(
162165
awake_duration=SED_DEFAULT_AWAKE_DURATION,
@@ -165,11 +168,15 @@ def _load_defaults(self) -> None:
165168
maintenance_interval=SED_DEFAULT_MAINTENANCE_INTERVAL,
166169
sleep_duration=SED_DEFAULT_SLEEP_DURATION,
167170
)
171+
await self.schedule_task_when_awake(self.node_info_update(None))
172+
self._sed_config_task_scheduled = True
173+
self._new_battery_config = self._battery_config
174+
await self.schedule_task_when_awake(self._configure_sed_task())
168175

169176
async def _load_from_cache(self) -> bool:
170177
"""Load states from previous cached information. Returns True if successful."""
171178
if not await super()._load_from_cache():
172-
self._load_defaults()
179+
await self._load_defaults()
173180
return False
174181
self._battery_config = BatteryConfig(
175182
awake_duration=self._awake_duration_from_cache(),
@@ -240,9 +247,6 @@ async def set_awake_duration(self, seconds: int) -> bool:
240247
f"Invalid awake duration ({seconds}). It must be between 1 and 255 seconds."
241248
)
242249

243-
if self._battery_config.awake_duration == seconds:
244-
return False
245-
246250
self._new_battery_config = replace(
247251
self._new_battery_config, awake_duration=seconds
248252
)
@@ -491,7 +495,7 @@ async def node_info_update(
491495
self, node_info: NodeInfoResponse | None = None
492496
) -> NodeInfo | None:
493497
"""Update Node (hardware) information."""
494-
if node_info is None and self.skip_update(self._node_info, 86400):
498+
if node_info is not None and self.skip_update(self._node_info, 86400):
495499
return self._node_info
496500
return await super().node_info_update(node_info)
497501

@@ -643,7 +647,6 @@ async def _send_tasks(self) -> None:
643647
"""Send all tasks in queue."""
644648
if len(self._send_task_queue) == 0:
645649
return
646-
647650
async with self._send_task_lock:
648651
task_result = await gather(*self._send_task_queue)
649652
if not all(task_result):

0 commit comments

Comments
 (0)