pysmarlaapi 0.9.3__tar.gz → 0.10.0__tar.gz

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.

Files changed (22) hide show
  1. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/PKG-INFO +1 -1
  2. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/__init__.py +1 -1
  3. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/connection_hub/__init__.py +39 -7
  4. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/__init__.py +3 -1
  5. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/services/__init__.py +1 -0
  6. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/services/analyser_service.py +19 -0
  7. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/services/info_service.py +18 -0
  8. pysmarlaapi-0.10.0/pysmarlaapi/federwiege/services/system_service.py +65 -0
  9. pysmarlaapi-0.10.0/pysmarlaapi/federwiege/types/__init__.py +3 -0
  10. pysmarlaapi-0.10.0/pysmarlaapi/federwiege/types/send_diag_status.py +8 -0
  11. pysmarlaapi-0.10.0/pysmarlaapi/federwiege/types/spring_status.py +10 -0
  12. pysmarlaapi-0.10.0/pysmarlaapi/federwiege/types/update_status.py +8 -0
  13. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/LICENSE +0 -0
  14. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/README.md +0 -0
  15. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pyproject.toml +0 -0
  16. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/classes/__init__.py +0 -0
  17. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/classes/auth_token.py +0 -0
  18. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/classes/connection.py +0 -0
  19. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/classes/__init__.py +0 -0
  20. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/classes/property.py +0 -0
  21. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/classes/service.py +0 -0
  22. {pysmarlaapi-0.9.3 → pysmarlaapi-0.10.0}/pysmarlaapi/federwiege/services/babywiege_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysmarlaapi
3
- Version: 0.9.3
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
@@ -1,4 +1,4 @@
1
- __version__ = "0.9.3"
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)
@@ -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,32 @@ class ConnectionHub:
125
135
  def wake_up(self):
126
136
  self._wake.set()
127
137
 
128
- def close_connection(self):
129
- 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:
130
159
  return
131
- 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()
132
164
 
133
165
  async def refresh_token(self):
134
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
File without changes
File without changes
File without changes