python-swidget 1.4.6__tar.gz → 1.4.8__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.
- {python_swidget-1.4.6 → python_swidget-1.4.8}/PKG-INFO +1 -1
- {python_swidget-1.4.6 → python_swidget-1.4.8}/pyproject.toml +1 -1
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/swidgetdevice.py +9 -2
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/websocket.py +28 -23
- {python_swidget-1.4.6 → python_swidget-1.4.8}/README.md +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/__init__.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/cli.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/discovery.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/exceptions.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/provision.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/py.typed +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/swidgetdimmer.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/swidgetoutlet.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/swidgetswitch.py +0 -0
- {python_swidget-1.4.6 → python_swidget-1.4.8}/swidget/swidgettimerswitch.py +0 -0
|
@@ -413,8 +413,15 @@ class SwidgetDevice:
|
|
|
413
413
|
for id, component in self.assemblies[assembly].components.items():
|
|
414
414
|
try:
|
|
415
415
|
component.functions.update(state[assembly]["components"][id])
|
|
416
|
-
except Exception:
|
|
417
|
-
|
|
416
|
+
except Exception as exc:
|
|
417
|
+
# Don't fail the whole state-process loop on one bad
|
|
418
|
+
# component, but DO surface what was skipped — silent
|
|
419
|
+
# failures here are how is_on ends up reading from a
|
|
420
|
+
# never-populated None placeholder.
|
|
421
|
+
_LOGGER.debug(
|
|
422
|
+
f"process_state: skipped {assembly}/{id} "
|
|
423
|
+
f"({type(exc).__name__}: {exc})"
|
|
424
|
+
)
|
|
418
425
|
self._last_update = int(time.time())
|
|
419
426
|
|
|
420
427
|
async def update(self) -> None:
|
|
@@ -27,7 +27,7 @@ class SwidgetWebsocket:
|
|
|
27
27
|
session: aiohttp.ClientSession | None = None,
|
|
28
28
|
use_security: bool = True,
|
|
29
29
|
verify_ssl: bool = False,
|
|
30
|
-
retry_interval: int =
|
|
30
|
+
retry_interval: int = 5, # Initial retry interval in seconds
|
|
31
31
|
max_retries: int | None = None, # Maximum number of reconnection attempts
|
|
32
32
|
):
|
|
33
33
|
"""Initialize the SwidgetWebsocket.
|
|
@@ -106,27 +106,31 @@ class SwidgetWebsocket:
|
|
|
106
106
|
self._client = None
|
|
107
107
|
|
|
108
108
|
async def send_str(self, message: str) -> None:
|
|
109
|
-
"""Send a string message through the websocket
|
|
109
|
+
"""Send a string message through the websocket.
|
|
110
|
+
|
|
111
|
+
Drops the message (no retry) if the connection is missing or in
|
|
112
|
+
a closing state. Retrying against a closing transport just wastes
|
|
113
|
+
time — the right recovery is to invalidate the client so the
|
|
114
|
+
run() loop's reconnect kicks in on the next iteration.
|
|
115
|
+
"""
|
|
110
116
|
_LOGGER.debug(f"[{self.host}] websocket.send_str: {message}")
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
f"Failed to send message after {max_send_retries} attempts."
|
|
129
|
-
)
|
|
117
|
+
if self._client is None or self._client.closed:
|
|
118
|
+
if self._client is not None:
|
|
119
|
+
# aiohttp says it's closed but we still hold a reference;
|
|
120
|
+
# clear it so run() reconnects on the next loop.
|
|
121
|
+
self._client = None
|
|
122
|
+
_LOGGER.warning(
|
|
123
|
+
f"[{self.host}] websocket not connected, dropping message"
|
|
124
|
+
)
|
|
125
|
+
return
|
|
126
|
+
try:
|
|
127
|
+
await self._client.send_str(message)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
_LOGGER.warning(
|
|
130
|
+
f"[{self.host}] websocket send failed ({e}); invalidating client"
|
|
131
|
+
)
|
|
132
|
+
# Force the run() loop's reconnect path on the next iteration.
|
|
133
|
+
self._client = None
|
|
130
134
|
|
|
131
135
|
async def receive(self) -> Any | None:
|
|
132
136
|
"""Receive a message from the WebSocket server."""
|
|
@@ -180,8 +184,9 @@ class SwidgetWebsocket:
|
|
|
180
184
|
)
|
|
181
185
|
|
|
182
186
|
self.retry_count += 1
|
|
183
|
-
#
|
|
184
|
-
|
|
187
|
+
# Exponential backoff capped at 60s so a long outage doesn't push
|
|
188
|
+
# the next attempt out by hours.
|
|
189
|
+
delay = min(self.retry_interval * (2 ** (self.retry_count - 1)), 60)
|
|
185
190
|
_LOGGER.warning(
|
|
186
191
|
f"Reconnecting to Swidget device: {self.host} in {delay} seconds (attempt {self.retry_count})..."
|
|
187
192
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|