Skip to content

Commit 0ace7cd

Browse files
author
Frank
committed
Introduce protocol for plugwise node
1 parent 1c9cff9 commit 0ace7cd

File tree

12 files changed

+1167
-766
lines changed

12 files changed

+1167
-766
lines changed

plugwise_usb/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@
1212
import logging
1313
from typing import Any, TypeVar, cast
1414

15-
from .api import NodeEvent, StickEvent
15+
from .api import NodeEvent, PlugwiseNode, StickEvent
1616
from .connection import StickController
1717
from .exceptions import StickError, SubscriptionError
1818
from .network import StickNetwork
19-
from .nodes import PlugwiseNode
2019

2120
FuncT = TypeVar("FuncT", bound=Callable[..., Any])
2221

plugwise_usb/api.py

Lines changed: 282 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
"""Plugwise USB-Stick API."""
22

3+
from collections.abc import Awaitable, Callable
34
from dataclasses import dataclass
45
from datetime import datetime
56
from enum import Enum, auto
7+
import logging
8+
from typing import Any, Protocol
9+
10+
_LOGGER = logging.getLogger(__name__)
611

712

813
class 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+
3557
class 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-
7075
PUSHING_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

Comments
 (0)