pysmarlaapi 0.9.3__py3-none-any.whl → 0.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pysmarlaapi might be problematic. Click here for more details.

pysmarlaapi/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.9.3"
1
+ __version__ = "0.10.1"
2
2
 
3
3
  from .classes import Connection
4
4
  from .federwiege import Federwiege
@@ -9,11 +9,12 @@ from pysignalr.transport.abstract import ConnectionState
9
9
  from ..classes import Connection
10
10
 
11
11
 
12
- async def event_wait(event, timeout):
12
+ async def event_wait(event, timeout) -> bool:
13
13
  try:
14
14
  await asyncio.wait_for(event.wait(), timeout)
15
15
  except asyncio.TimeoutError:
16
- return
16
+ return False
17
+ return True
17
18
 
18
19
 
19
20
  # suppress warnings from pysignalr (to avoid missing client method warnings)
@@ -38,11 +39,13 @@ class ConnectionHub:
38
39
  event_loop: asyncio.AbstractEventLoop,
39
40
  connection: Connection,
40
41
  max_delay: int = 256,
42
+ forced_reconnect_interval: int = 86400,
41
43
  ):
42
44
  self.connection: Connection = connection
43
45
  self._loop = event_loop
44
46
  self._retry_delay = 1 # Initial connection retry delay
45
47
  self._max_delay = max_delay
48
+ self._forced_reconnect_interval = forced_reconnect_interval
46
49
 
47
50
  self.logger = logging.getLogger(f"{__package__}[{self.connection.token.serialNumber}]")
48
51
 
@@ -51,6 +54,10 @@ class ConnectionHub:
51
54
  self._running = False
52
55
  self._wake = asyncio.Event()
53
56
 
57
+ self._reconnect_future: asyncio.Future = None
58
+ self._reconnect_lock = asyncio.Lock()
59
+ self._reconnect_cancel_event = asyncio.Event()
60
+
54
61
  self.client = None
55
62
  self.setup()
56
63
 
@@ -85,9 +92,11 @@ class ConnectionHub:
85
92
  async def on_open_function(self):
86
93
  self._retry_delay = 1
87
94
  self.logger.info("Connection to server established")
95
+ await self.start_reconnect_job()
88
96
 
89
97
  async def on_close_function(self):
90
98
  self.logger.info("Connection to server closed")
99
+ await self.cancel_reconnect_job()
91
100
 
92
101
  async def on_error(self, message):
93
102
  self.logger.error("Connection error occurred: %s", str(message))
@@ -102,8 +111,8 @@ class ConnectionHub:
102
111
  if not self.running:
103
112
  return
104
113
  self._running = False
105
- self.close_connection()
106
114
  self.wake_up()
115
+ asyncio.run_coroutine_threadsafe(self.close_connection(), self._loop)
107
116
 
108
117
  async def connection_watcher(self):
109
118
  while self.running:
@@ -115,7 +124,8 @@ class ConnectionHub:
115
124
 
116
125
  # Random backoff to avoid simultaneous connection attempts
117
126
  jitter = random.uniform(0, 0.5) * self._retry_delay
118
- await event_wait(self._wake, self._retry_delay + jitter)
127
+ delay = self._retry_delay + jitter
128
+ await event_wait(self._wake, delay)
119
129
  self._wake.clear()
120
130
 
121
131
  # Double the delay for the next attempt
@@ -125,10 +135,34 @@ class ConnectionHub:
125
135
  def wake_up(self):
126
136
  self._wake.set()
127
137
 
128
- def close_connection(self):
138
+ async def close_connection(self):
129
139
  if not self.connected:
130
140
  return
131
- asyncio.run_coroutine_threadsafe(self.client._transport._ws.close(), self._loop)
141
+ await self.cancel_reconnect_job()
142
+ await self.client._transport._ws.close()
143
+
144
+ async def start_reconnect_job(self):
145
+ async with self._reconnect_lock:
146
+ if self._reconnect_future and not self._reconnect_future.done():
147
+ return
148
+ self._reconnect_cancel_event.clear()
149
+ self._reconnect_future = asyncio.create_task(self.reconnect_job())
150
+
151
+ async def cancel_reconnect_job(self):
152
+ async with self._reconnect_lock:
153
+ if not self._reconnect_future or self._reconnect_future.done():
154
+ return
155
+ self._reconnect_cancel_event.set()
156
+ await self._reconnect_future
157
+
158
+ async def reconnect_job(self):
159
+ cancelled = await event_wait(self._reconnect_cancel_event, self._forced_reconnect_interval)
160
+ if cancelled:
161
+ return
162
+
163
+ # Close connection to trigger a reconnection
164
+ # Make sure that connection stays healthy
165
+ await self.client._transport._ws.close()
132
166
 
133
167
  async def refresh_token(self):
134
168
  await self.connection.refresh_token()
@@ -4,7 +4,8 @@ import threading
4
4
  from ..classes import Connection
5
5
  from ..connection_hub import ConnectionHub
6
6
  from .classes import Service
7
- from .services import AnalyserService, BabywiegeService, InfoService
7
+ from .services import (AnalyserService, BabywiegeService, InfoService,
8
+ SystemService)
8
9
 
9
10
 
10
11
  class Federwiege:
@@ -29,6 +30,7 @@ class Federwiege:
29
30
  "babywiege": BabywiegeService(self.hub),
30
31
  "analyser": AnalyserService(self.hub),
31
32
  "info": InfoService(self.hub),
33
+ "system": SystemService(self.hub),
32
34
  }
33
35
 
34
36
  self.registered = False
@@ -1,3 +1,4 @@
1
1
  from .analyser_service import AnalyserService
2
2
  from .babywiege_service import BabywiegeService
3
3
  from .info_service import InfoService
4
+ from .system_service import SystemService
@@ -1,5 +1,6 @@
1
1
  from ...connection_hub import ConnectionHub
2
2
  from ..classes import Property, Service
3
+ from ..types import SpringStatus
3
4
 
4
5
 
5
6
  class AnalyserService(Service):
@@ -9,6 +10,7 @@ class AnalyserService(Service):
9
10
  self.add_property("oscillation", OscillationProperty(hub))
10
11
  self.add_property("activity", ActivityProperty(hub))
11
12
  self.add_property("swing_count", SwingCountProperty(hub))
13
+ self.add_property("spring_status", SpringStatusProperty(hub))
12
14
 
13
15
 
14
16
  class OscillationProperty(Property[list[int]]):
@@ -60,3 +62,20 @@ class SwingCountProperty(Property[int]):
60
62
 
61
63
  def register(self):
62
64
  self.hub.client.on("GetSwingCountCallback", self.on_callback)
65
+
66
+
67
+ class SpringStatusProperty(Property[SpringStatus]):
68
+
69
+ async def on_callback(self, args):
70
+ value = args[0]["value"]
71
+ self.set(value, push=False)
72
+ await self.notify_listeners()
73
+
74
+ def __init__(self, hub: ConnectionHub):
75
+ super().__init__(hub)
76
+
77
+ def pull(self):
78
+ self.hub.send_serialized_data("GetSpringStatus")
79
+
80
+ def register(self):
81
+ self.hub.client.on("GetSpringStatusCallback", self.on_callback)
@@ -8,6 +8,7 @@ class InfoService(Service):
8
8
  super().__init__()
9
9
  self.add_property("display_name", DisplayNameProperty(hub))
10
10
  self.add_property("version", VersionProperty(hub))
11
+ self.add_property("total_swing_time", TotalSwingTimeProperty(hub))
11
12
 
12
13
 
13
14
  class DisplayNameProperty(Property[str]):
@@ -42,3 +43,20 @@ class VersionProperty(Property[str]):
42
43
 
43
44
  def register(self):
44
45
  self.hub.client.on("GetVersionCallback", self.on_callback)
46
+
47
+
48
+ class TotalSwingTimeProperty(Property[int]):
49
+
50
+ async def on_callback(self, args):
51
+ value = args[0]["value"]
52
+ self.set(value, push=False)
53
+ await self.notify_listeners()
54
+
55
+ def __init__(self, hub: ConnectionHub):
56
+ super().__init__(hub)
57
+
58
+ def pull(self):
59
+ self.hub.send_serialized_data("GetTotalSwingTime")
60
+
61
+ def register(self):
62
+ self.hub.client.on("GetTotalSwingTimeCallback", self.on_callback)
@@ -0,0 +1,65 @@
1
+ from ...connection_hub import ConnectionHub
2
+ from ..classes import Property, Service
3
+ from ..types import SendDiagStatus, UpdateStatus
4
+
5
+
6
+ class SystemService(Service):
7
+
8
+ def __init__(self, hub: ConnectionHub):
9
+ super().__init__()
10
+ self.add_property("firmware_update", FirmwareUpdateProperty(hub))
11
+ self.add_property("firmware_update_status", FirmwareUpdateStatusProperty(hub))
12
+ self.add_property("send_diagnostic_data", SendDiagnosticDataProperty(hub))
13
+ self.add_property("send_diagnostic_data_status", SendDiagnosticDataStatusProperty(hub))
14
+
15
+
16
+ class FirmwareUpdateProperty(Property[int]):
17
+
18
+ def __init__(self, hub: ConnectionHub):
19
+ super().__init__(hub)
20
+
21
+ def push(self, value: int):
22
+ self.hub.send_serialized_data("SetFirmwareUpdate", value)
23
+
24
+
25
+ class FirmwareUpdateStatusProperty(Property[UpdateStatus]):
26
+
27
+ async def on_callback(self, args):
28
+ value = args[0]["value"]
29
+ self.set(value, push=False)
30
+ await self.notify_listeners()
31
+
32
+ def __init__(self, hub: ConnectionHub):
33
+ super().__init__(hub)
34
+
35
+ def pull(self):
36
+ self.hub.send_serialized_data("GetFirmwareUpdate")
37
+
38
+ def register(self):
39
+ self.hub.client.on("GetFirmwareUpdateCallback", self.on_callback)
40
+
41
+
42
+ class SendDiagnosticDataProperty(Property[str]):
43
+
44
+ def __init__(self, hub: ConnectionHub):
45
+ super().__init__(hub)
46
+
47
+ def push(self, value: str):
48
+ self.hub.send_serialized_data("SetSendDiagnosticData", value)
49
+
50
+
51
+ class SendDiagnosticDataStatusProperty(Property[SendDiagStatus]):
52
+
53
+ async def on_callback(self, args):
54
+ value = args[0]["value"]
55
+ self.set(value, push=False)
56
+ await self.notify_listeners()
57
+
58
+ def __init__(self, hub: ConnectionHub):
59
+ super().__init__(hub)
60
+
61
+ def pull(self):
62
+ self.hub.send_serialized_data("GetSendDiagnosticData")
63
+
64
+ def register(self):
65
+ self.hub.client.on("GetSendDiagnosticDataCallback", self.on_callback)
@@ -0,0 +1,3 @@
1
+ from .send_diag_status import SendDiagStatus
2
+ from .spring_status import SpringStatus
3
+ from .update_status import UpdateStatus
@@ -0,0 +1,8 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class SendDiagStatus(IntEnum):
5
+ IDLE = 0
6
+ GATHERING = 1
7
+ SENDING = 2
8
+ FAILED = 3
@@ -0,0 +1,10 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class SpringStatus(IntEnum):
5
+ UNKNOWN = 0
6
+ NORMAL = 1
7
+ CONSTELLATION_TOO_HIGH = 2
8
+ CONSTELLATION_TOO_LOW = 3
9
+ CONSTELLATION_CRITICAL_TOO_HIGH = 4
10
+ CONSTELLATION_CRITICAL_TOO_LOW = 5
@@ -0,0 +1,8 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class UpdateStatus(IntEnum):
5
+ IDLE = 0
6
+ DOWNLOADING = 1
7
+ INSTALLING = 2
8
+ FAILED = 3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysmarlaapi
3
- Version: 0.9.3
3
+ Version: 0.10.1
4
4
  Summary: Swing2Sleep Smarla API
5
5
  Author-email: Robin Lintermann <robin.lintermann@explicatis.com>
6
6
  Requires-Python: >=3.11
@@ -0,0 +1,22 @@
1
+ pysmarlaapi/__init__.py,sha256=1goHranjjV8lIuKigPYHQQMfvZxAQ_pPurcDaLQKYL4,91
2
+ pysmarlaapi/classes/__init__.py,sha256=N-ZV3Id_t5ciovUlPUGCk5SLLiMUonRoQZWpfOU4ZsM,69
3
+ pysmarlaapi/classes/auth_token.py,sha256=dpo0lBT9Advm3Iyxu-fT9sq078U2OxgXXBuF5gbBZkM,942
4
+ pysmarlaapi/classes/connection.py,sha256=vVC0Ur0KQUb4pNDVuCYnfk-JXs-O-FwYVO-2y1E2g6g,1551
5
+ pysmarlaapi/connection_hub/__init__.py,sha256=4cTBxvDPz0mOC8wICnFZikYp0RhFHcl06JVssZ48YDY,6153
6
+ pysmarlaapi/federwiege/__init__.py,sha256=P3hpgf-okQDEpwCUiLHn1acnenURUorEDVFya9Dvk6M,2020
7
+ pysmarlaapi/federwiege/classes/__init__.py,sha256=DFJJVOKpra40S4ZX_oq2RyMazg6Nx0c8hoPggmj1bXA,60
8
+ pysmarlaapi/federwiege/classes/property.py,sha256=S5Zzo9cXDJMFcQG1vCw1eO-oqeSpQs2YjxWouN_KRkU,1032
9
+ pysmarlaapi/federwiege/classes/service.py,sha256=Y0OQxtQLaHp4qY8Vthj0zRroPoYTiD2WA9ZR96teZTo,632
10
+ pysmarlaapi/federwiege/services/__init__.py,sha256=qhsnybmBF7K1sR2IdfL4FAmAtJGla66NqrMiJrDlCvU,174
11
+ pysmarlaapi/federwiege/services/analyser_service.py,sha256=Fzc4zK-IIVkIllAq32tzf1QkkeYc4SGSU_8YISOW2C0,2249
12
+ pysmarlaapi/federwiege/services/babywiege_service.py,sha256=6u0fSsRoWfv6A0Qad1WLD2yeRHMGnwbKVOJLxEqwzGE,1979
13
+ pysmarlaapi/federwiege/services/info_service.py,sha256=DJNzz7Z6vx2VXhr7CkCuB2CbCY3YCnTy21PvfjGf-bU,1702
14
+ pysmarlaapi/federwiege/services/system_service.py,sha256=88MGFfdyXny8D8lWKMtcu96UX0ZAcC9QER000WC8cgY,1967
15
+ pysmarlaapi/federwiege/types/__init__.py,sha256=XWB_IrNYbIyQjyR_oKAOvHAjGGRLSQqZka0t_oc60ts,125
16
+ pysmarlaapi/federwiege/types/send_diag_status.py,sha256=hxpfNQmZs1YOztxLVnN3Bu50fXwf-hfV6K8GiQlBvkk,120
17
+ pysmarlaapi/federwiege/types/spring_status.py,sha256=cJsWFf2ZG-NvnT3l_CxVJfMFB3Ruv_juTy7Lpb-YEsA,227
18
+ pysmarlaapi/federwiege/types/update_status.py,sha256=YkajOEJ09pY9u4EBxw8ePEjPjYOLdK0i2l41cXbjb8Y,123
19
+ pysmarlaapi-0.10.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
20
+ pysmarlaapi-0.10.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
21
+ pysmarlaapi-0.10.1.dist-info/METADATA,sha256=9jlUhgfqTbyQWYfaOfIzKjO4ITrMiF8tfw3B5hvwC2c,1104
22
+ pysmarlaapi-0.10.1.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- pysmarlaapi/__init__.py,sha256=xSrA3ZAl-0UUKk_KVble3F2UoRkGUAanIJvs0ZO7EXI,90
2
- pysmarlaapi/classes/__init__.py,sha256=N-ZV3Id_t5ciovUlPUGCk5SLLiMUonRoQZWpfOU4ZsM,69
3
- pysmarlaapi/classes/auth_token.py,sha256=dpo0lBT9Advm3Iyxu-fT9sq078U2OxgXXBuF5gbBZkM,942
4
- pysmarlaapi/classes/connection.py,sha256=vVC0Ur0KQUb4pNDVuCYnfk-JXs-O-FwYVO-2y1E2g6g,1551
5
- pysmarlaapi/connection_hub/__init__.py,sha256=k8s4VNO0QeINLycN94Fl5YXr1PcHkfvEI8GWimqTD_A,4776
6
- pysmarlaapi/federwiege/__init__.py,sha256=7Fl_GnwN6GbS8fhkHdfMhuYFospKnap40pvUtSGIeoE,1933
7
- pysmarlaapi/federwiege/classes/__init__.py,sha256=DFJJVOKpra40S4ZX_oq2RyMazg6Nx0c8hoPggmj1bXA,60
8
- pysmarlaapi/federwiege/classes/property.py,sha256=S5Zzo9cXDJMFcQG1vCw1eO-oqeSpQs2YjxWouN_KRkU,1032
9
- pysmarlaapi/federwiege/classes/service.py,sha256=Y0OQxtQLaHp4qY8Vthj0zRroPoYTiD2WA9ZR96teZTo,632
10
- pysmarlaapi/federwiege/services/__init__.py,sha256=bJuOsk0lw9WWxB1NZodRTL8Pm_Ra8CuTCxNh33WzxkQ,132
11
- pysmarlaapi/federwiege/services/analyser_service.py,sha256=xRPvSO5FdwX-gqWLWYQq8zvQSPnkE5Y3d-kG7N3awgg,1695
12
- pysmarlaapi/federwiege/services/babywiege_service.py,sha256=6u0fSsRoWfv6A0Qad1WLD2yeRHMGnwbKVOJLxEqwzGE,1979
13
- pysmarlaapi/federwiege/services/info_service.py,sha256=XqMSMU3BusB85V0O93rzliuJCK_H-PE4kXqJX-v7Kc4,1179
14
- pysmarlaapi-0.9.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
- pysmarlaapi-0.9.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
16
- pysmarlaapi-0.9.3.dist-info/METADATA,sha256=3iezX1ozOGN0Q9g2Wnff0-w_lJmZlAfYS6avnhjOTmI,1103
17
- pysmarlaapi-0.9.3.dist-info/RECORD,,