11"""Plugwise USB-Stick API."""
22
3+ from collections .abc import Awaitable , Callable
34from dataclasses import dataclass
45from datetime import datetime
56from enum import Enum , auto
7+ import logging
8+ from typing import Any , Protocol
9+
10+ _LOGGER = logging .getLogger (__name__ )
611
712
813class StickEvent (Enum ):
@@ -32,6 +37,23 @@ class NodeEvent(Enum):
3237 JOIN = auto ()
3338
3439
40+ class NodeFeature (str , Enum ):
41+ """USB Stick Node feature."""
42+
43+ AVAILABLE = "available"
44+ BATTERY = "battery"
45+ ENERGY = "energy"
46+ HUMIDITY = "humidity"
47+ INFO = "info"
48+ MOTION = "motion"
49+ PING = "ping"
50+ POWER = "power"
51+ RELAY = "relay"
52+ RELAY_INIT = "relay_init"
53+ SWITCH = "switch"
54+ TEMPERATURE = "temperature"
55+
56+
3557class NodeType (Enum ):
3658 """USB Node types."""
3759
@@ -50,28 +72,11 @@ class NodeType(Enum):
5072# 11 AME_STAR
5173
5274
53- class NodeFeature (str , Enum ):
54- """USB Stick Node feature."""
55-
56- AVAILABLE = "available"
57- BATTERY = "battery"
58- ENERGY = "energy"
59- HUMIDITY = "humidity"
60- INFO = "info"
61- MOTION = "motion"
62- PING = "ping"
63- POWER = "power"
64- RELAY = "relay"
65- RELAY_INIT = "relay_init"
66- SWITCH = "switch"
67- TEMPERATURE = "temperature"
68-
69-
7075PUSHING_FEATURES = (
7176 NodeFeature .HUMIDITY ,
7277 NodeFeature .MOTION ,
7378 NodeFeature .TEMPERATURE ,
74- NodeFeature .SWITCH
79+ NodeFeature .SWITCH ,
7580)
7681
7782
@@ -103,13 +108,13 @@ class NodeInfo:
103108
104109 mac : str
105110 zigbee_address : int
106- battery_powered : bool = False
111+ is_battery_powered : bool = False
107112 features : tuple [NodeFeature , ...] = (NodeFeature .INFO ,)
108113 firmware : datetime | None = None
109114 name : str | None = None
110115 model : str | None = None
111116 model_type : str | None = None
112- type : NodeType | None = None
117+ node_type : NodeType | None = None
113118 timestamp : datetime | None = None
114119 version : str | None = None
115120
@@ -169,3 +174,260 @@ class EnergyStatistics:
169174 day_production_reset : datetime | None = None
170175 week_production : float | None = None
171176 week_production_reset : datetime | None = None
177+
178+
179+ class PlugwiseNode (Protocol ):
180+ """Protocol definition of a Plugwise device node."""
181+
182+ def __init__ (
183+ self ,
184+ mac : str ,
185+ address : int ,
186+ loaded_callback : Callable [[NodeEvent , str ], Awaitable [None ]],
187+ ) -> None :
188+ """Initialize plugwise node object."""
189+
190+ # region Generic node details
191+ @property
192+ def features (self ) -> tuple [NodeFeature , ...]:
193+ """Supported feature types of node."""
194+
195+ @property
196+ def is_battery_powered (self ) -> bool :
197+ """Indicate if node is power by battery."""
198+
199+ @property
200+ def is_loaded (self ) -> bool :
201+ """Indicate if node is loaded."""
202+
203+ @property
204+ def last_update (self ) -> datetime :
205+ """Timestamp of last update."""
206+
207+ @property
208+ def name (self ) -> str :
209+ """Return name of node."""
210+
211+ @property
212+ def node_info (self ) -> NodeInfo :
213+ """Node information."""
214+
215+ async def load (self ) -> bool :
216+ """Load configuration and activate node features."""
217+
218+ async def update_node_details (
219+ self ,
220+ firmware : datetime | None ,
221+ hardware : str | None ,
222+ node_type : NodeType | None ,
223+ timestamp : datetime | None ,
224+ relay_state : bool | None ,
225+ logaddress_pointer : int | None ,
226+ ) -> bool :
227+ """Update node information."""
228+
229+ async def unload (self ) -> None :
230+ """Load configuration and activate node features."""
231+
232+ # endregion
233+
234+ # region Network
235+ @property
236+ def available (self ) -> bool :
237+ """Last known network availability state."""
238+
239+ @property
240+ def mac (self ) -> str :
241+ """Zigbee mac address."""
242+
243+ @property
244+ def network_address (self ) -> int :
245+ """Zigbee network registration address."""
246+
247+ @property
248+ def ping_stats (self ) -> NetworkStatistics :
249+ """Ping statistics."""
250+
251+ async def is_online (self ) -> bool :
252+ """Check network status."""
253+
254+ def update_ping_stats (
255+ self , timestamp : datetime , rssi_in : int , rssi_out : int , rtt : int
256+ ) -> None :
257+ """Update ping statistics."""
258+
259+ # TODO: Move to node with subscription to stick event
260+ async def reconnect (self ) -> None :
261+ """Reconnect node to Plugwise Zigbee network."""
262+
263+ # TODO: Move to node with subscription to stick event
264+ async def disconnect (self ) -> None :
265+ """Disconnect from Plugwise Zigbee network."""
266+
267+ # endregion
268+
269+ # region cache
270+
271+ @property
272+ def cache_folder (self ) -> str :
273+ """Path to cache folder."""
274+
275+ @cache_folder .setter
276+ def cache_folder (self , cache_folder : str ) -> None :
277+ """Path to cache folder."""
278+
279+ @property
280+ def cache_folder_create (self ) -> bool :
281+ """Create cache folder when it does not exists."""
282+
283+ @cache_folder_create .setter
284+ def cache_folder_create (self , enable : bool = True ) -> None :
285+ """Create cache folder when it does not exists."""
286+
287+ @property
288+ def cache_enabled (self ) -> bool :
289+ """Activate caching of retrieved information."""
290+
291+ @cache_enabled .setter
292+ def cache_enabled (self , enable : bool ) -> None :
293+ """Activate caching of retrieved information."""
294+
295+ async def clear_cache (self ) -> None :
296+ """Clear currently cached information."""
297+
298+ async def save_cache (
299+ self , trigger_only : bool = True , full_write : bool = False
300+ ) -> None :
301+ """Write currently cached information to cache file."""
302+
303+ # endregion
304+
305+ # region sensors
306+ @property
307+ def energy (self ) -> EnergyStatistics | None :
308+ """Energy statistics.
309+
310+ Raises NodeError when energy feature is not present at device.
311+ """
312+
313+ @property
314+ def humidity (self ) -> float | None :
315+ """Last received humidity state.
316+
317+ Raises NodeError when humidity feature is not present at device.
318+ """
319+
320+ @property
321+ def motion (self ) -> bool | None :
322+ """Current state of motion detection.
323+
324+ Raises NodeError when motion feature is not present at device.
325+ """
326+
327+ @property
328+ def motion_state (self ) -> MotionState :
329+ """Last known motion state information.
330+
331+ Raises NodeError when motion feature is not present at device.
332+ """
333+
334+ @property
335+ def power (self ) -> PowerStatistics :
336+ """Current power statistics.
337+
338+ Raises NodeError when power feature is not present at device.
339+ """
340+
341+ @property
342+ def relay (self ) -> bool :
343+ """Current state of relay.
344+
345+ Raises NodeError when relay feature is not present at device.
346+ """
347+
348+ @property
349+ def relay_state (self ) -> RelayState :
350+ """Last known relay state information.
351+
352+ Raises NodeError when relay feature is not present at device.
353+ """
354+
355+ @property
356+ def switch (self ) -> bool | None :
357+ """Current state of the switch.
358+
359+ Raises NodeError when switch feature is not present at device.
360+ """
361+
362+ @property
363+ def temperature (self ) -> float | None :
364+ """Last received temperature state.
365+
366+ Raises NodeError when temperature feature is not present at device.
367+ """
368+
369+ async def get_state (self , features : tuple [NodeFeature ]) -> dict [NodeFeature , Any ]:
370+ """Request an updated state for given feature.
371+
372+ Returns the state or statistics for each requested feature.
373+ """
374+
375+ # endregion
376+
377+ # region control & configure
378+ @property
379+ def battery_config (self ) -> BatteryConfig :
380+ """Battery configuration settings.
381+
382+ Raises NodeError when battery configuration feature is not present at device.
383+ """
384+
385+ @property
386+ def relay_init (self ) -> bool | None :
387+ """Configured state at which the relay must be at initial power-up of device.
388+
389+ Raises NodeError when relay configuration feature is not present at device.
390+ """
391+
392+ async def switch_relay (self , state : bool ) -> bool | None :
393+ """Change the state of the relay and return the new state of relay.
394+
395+ Raises NodeError when relay feature is not present at device.
396+ """
397+
398+ async def switch_relay_init_off (self , state : bool ) -> bool | None :
399+ """Change the state of initial (power-up) state of the relay and return the new configured setting.
400+
401+ Raises NodeError when the initial (power-up) relay configure feature is not present at device.
402+ """
403+
404+ @property
405+ def energy_consumption_interval (self ) -> int | None : ... # noqa: D102
406+
407+ @property
408+ def energy_production_interval (self ) -> int | None : ... # noqa: D102
409+
410+ @property
411+ def maintenance_interval (self ) -> int | None : ... # noqa: D102
412+
413+ @property
414+ def motion_reset_timer (self ) -> int : ... # noqa: D102
415+
416+ @property
417+ def daylight_mode (self ) -> bool : ... # noqa: D102
418+
419+ @property
420+ def sensitivity_level (self ) -> MotionSensitivity : ... # noqa: D102
421+
422+ async def configure_motion_reset (self , delay : int ) -> bool : ... # noqa: D102
423+
424+ async def scan_calibrate_light (self ) -> bool : ... # noqa: D102
425+
426+ async def scan_configure ( # noqa: D102
427+ self ,
428+ motion_reset_timer : int ,
429+ sensitivity_level : MotionSensitivity ,
430+ daylight_mode : bool ,
431+ ) -> bool : ...
432+
433+ # endregion
0 commit comments