pysmarlaapi 0.9.2__py3-none-any.whl → 0.10.0__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.2"
1
+ __version__ = "0.10.0"
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)
@@ -37,13 +38,14 @@ class ConnectionHub:
37
38
  self,
38
39
  event_loop: asyncio.AbstractEventLoop,
39
40
  connection: Connection,
40
- interval: int = 60,
41
- backoff: int = 300,
41
+ max_delay: int = 256,
42
+ forced_reconnect_interval: int = 86400,
42
43
  ):
43
44
  self.connection: Connection = connection
44
45
  self._loop = event_loop
45
- self._interval = interval
46
- self._backoff = backoff
46
+ self._retry_delay = 1 # Initial connection retry delay
47
+ self._max_delay = max_delay
48
+ self._forced_reconnect_interval = forced_reconnect_interval
47
49
 
48
50
  self.logger = logging.getLogger(f"{__package__}[{self.connection.token.serialNumber}]")
49
51
 
@@ -52,6 +54,10 @@ class ConnectionHub:
52
54
  self._running = False
53
55
  self._wake = asyncio.Event()
54
56
 
57
+ self._reconnect_future: asyncio.Future = None
58
+ self._reconnect_lock = asyncio.Lock()
59
+ self._reconnect_cancel_event = asyncio.Event()
60
+
55
61
  self.client = None
56
62
  self.setup()
57
63
 
@@ -84,10 +90,13 @@ class ConnectionHub:
84
90
  await listener(value)
85
91
 
86
92
  async def on_open_function(self):
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:
@@ -114,17 +123,44 @@ class ConnectionHub:
114
123
  self.logger.warning("Error during connection: %s: %s", type(e).__name__, str(e))
115
124
 
116
125
  # Random backoff to avoid simultaneous connection attempts
117
- backoff = random.randint(0, self._backoff)
118
- await event_wait(self._wake, self._interval + backoff)
126
+ jitter = random.uniform(0, 0.5) * self._retry_delay
127
+ delay = self._retry_delay + jitter
128
+ await event_wait(self._wake, delay)
119
129
  self._wake.clear()
120
130
 
131
+ # Double the delay for the next attempt
132
+ if self._retry_delay < self._max_delay:
133
+ self._retry_delay *= 2
134
+
121
135
  def wake_up(self):
122
136
  self._wake.set()
123
137
 
124
- def close_connection(self):
125
- if not self.connected:
138
+ async def close_connection(self):
139
+ await self.cancel_reconnect_job()
140
+ await self.client._transport._ws.close()
141
+
142
+ async def start_reconnect_job(self):
143
+ async with self._reconnect_lock:
144
+ if self._reconnect_future and not self._reconnect_future.done():
145
+ return
146
+ self._reconnect_cancel_event.clear()
147
+ self._reconnect_future = asyncio.create_task(self.reconnect_job())
148
+
149
+ async def cancel_reconnect_job(self):
150
+ async with self._reconnect_lock:
151
+ if not self._reconnect_future or self._reconnect_future.done():
152
+ return
153
+ self._reconnect_cancel_event.set()
154
+ await self._reconnect_future
155
+
156
+ async def reconnect_job(self):
157
+ cancelled = await event_wait(self._reconnect_cancel_event, self._forced_reconnect_interval)
158
+ if cancelled:
126
159
  return
127
- asyncio.run_coroutine_threadsafe(self.client._transport._ws.close(), self._loop)
160
+
161
+ # Close connection to trigger a reconnection
162
+ # Make sure that connection stays healthy
163
+ await self.client._transport._ws.close()
128
164
 
129
165
  async def refresh_token(self):
130
166
  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.2
3
+ Version: 0.10.0
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=zAudM0e9KJckKmuJx89xr-nMMJj0gHfFJGPgdjOAIPA,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=Rk-1JOXQGWKuGR3lJbf_svJA7tXAgOn6Un62UvY9mxM,6103
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.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
20
+ pysmarlaapi-0.10.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
21
+ pysmarlaapi-0.10.0.dist-info/METADATA,sha256=x0F0aN-u_aKLLWPP7PF2GGWsr6jMFeLAyH-zL0Aiy9k,1104
22
+ pysmarlaapi-0.10.0.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- pysmarlaapi/__init__.py,sha256=B_DoIXRG3DXhi1WKt-nDtwdqSpNhPiZeUMqCF9rgPQA,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=ChI9If_a30UN7IH8vN1Bkcz2MKScS1_5W1VRGP1mFdc,4584
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.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
- pysmarlaapi-0.9.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
16
- pysmarlaapi-0.9.2.dist-info/METADATA,sha256=nmYVMHkKCvyQGVnLg_7tbI-5C39mbB956iD8LZE86OI,1103
17
- pysmarlaapi-0.9.2.dist-info/RECORD,,