Add exponential backoff to all WebSocket clients; update setup step to mention dashboard login
Browse files- index.html +1 -1
- src/reachy_mini_remote_control_app/websocket_clients/audio_stream.py +5 -2
- src/reachy_mini_remote_control_app/websocket_clients/robot_control.py +5 -3
- src/reachy_mini_remote_control_app/websocket_clients/robot_state.py +5 -12
- src/reachy_mini_remote_control_app/websocket_clients/video_stream.py +5 -13
index.html
CHANGED
|
@@ -70,7 +70,7 @@
|
|
| 70 |
<li><strong>Create a Hugging Face account</strong> (free) at <a href="https://huggingface.co/join" target="_blank" style="color: #10b981; text-decoration: underline;">huggingface.co/join</a></li>
|
| 71 |
<li><strong>Create an access token</strong> at <a href="https://huggingface.co/settings/tokens" target="_blank" style="color: #10b981; text-decoration: underline;">huggingface.co/settings/tokens</a> with 'read' permissions</li>
|
| 72 |
<li><strong>Install this app</strong> on your Reachy Mini robot</li>
|
| 73 |
-
<li><strong>
|
| 74 |
<li><strong>Open the control interface</strong> in your browser and sign in with your Hugging Face account: <a href="https://huggingfacem4-reachy-mini-remote-control.hf.space" target="_blank" style="color: #10b981; text-decoration: underline;">huggingfacem4-reachy-mini-remote-control.hf.space</a></li>
|
| 75 |
<li><strong>Start controlling!</strong> Your robot should appear connected in the interface</li>
|
| 76 |
</ol>
|
|
|
|
| 70 |
<li><strong>Create a Hugging Face account</strong> (free) at <a href="https://huggingface.co/join" target="_blank" style="color: #10b981; text-decoration: underline;">huggingface.co/join</a></li>
|
| 71 |
<li><strong>Create an access token</strong> at <a href="https://huggingface.co/settings/tokens" target="_blank" style="color: #10b981; text-decoration: underline;">huggingface.co/settings/tokens</a> with 'read' permissions</li>
|
| 72 |
<li><strong>Install this app</strong> on your Reachy Mini robot</li>
|
| 73 |
+
<li><strong>Log in to HuggingFace:</strong> Open the robot dashboard or desktop app and sign in with your HuggingFace account — the token is picked up automatically</li>
|
| 74 |
<li><strong>Open the control interface</strong> in your browser and sign in with your Hugging Face account: <a href="https://huggingfacem4-reachy-mini-remote-control.hf.space" target="_blank" style="color: #10b981; text-decoration: underline;">huggingfacem4-reachy-mini-remote-control.hf.space</a></li>
|
| 75 |
<li><strong>Start controlling!</strong> Your robot should appear connected in the interface</li>
|
| 76 |
</ol>
|
src/reachy_mini_remote_control_app/websocket_clients/audio_stream.py
CHANGED
|
@@ -67,6 +67,7 @@ class AsyncWebSocketAudioStreamer:
|
|
| 67 |
|
| 68 |
async def _run(self) -> None:
|
| 69 |
"""Run the main reconnect loop."""
|
|
|
|
| 70 |
while not self.stop_flag:
|
| 71 |
try:
|
| 72 |
# Add authentication header if token is provided
|
|
@@ -80,6 +81,7 @@ class AsyncWebSocketAudioStreamer:
|
|
| 80 |
) as ws:
|
| 81 |
logger.info("[Audio Stream] Connected to remote server")
|
| 82 |
self.connected.set()
|
|
|
|
| 83 |
|
| 84 |
# Update connection status
|
| 85 |
if self.monitor:
|
|
@@ -103,13 +105,14 @@ class AsyncWebSocketAudioStreamer:
|
|
| 103 |
pass
|
| 104 |
|
| 105 |
except Exception as e:
|
| 106 |
-
logger.info(f"[Audio Stream] Connection failed: {e}")
|
| 107 |
|
| 108 |
# Update connection status
|
| 109 |
if self.monitor:
|
| 110 |
self.monitor.update_audio_metrics(connected=False)
|
| 111 |
|
| 112 |
-
await asyncio.sleep(
|
|
|
|
| 113 |
|
| 114 |
self.connected.clear()
|
| 115 |
|
|
|
|
| 67 |
|
| 68 |
async def _run(self) -> None:
|
| 69 |
"""Run the main reconnect loop."""
|
| 70 |
+
retry_delay = 1.0
|
| 71 |
while not self.stop_flag:
|
| 72 |
try:
|
| 73 |
# Add authentication header if token is provided
|
|
|
|
| 81 |
) as ws:
|
| 82 |
logger.info("[Audio Stream] Connected to remote server")
|
| 83 |
self.connected.set()
|
| 84 |
+
retry_delay = 1.0 # Reset backoff on successful connection
|
| 85 |
|
| 86 |
# Update connection status
|
| 87 |
if self.monitor:
|
|
|
|
| 105 |
pass
|
| 106 |
|
| 107 |
except Exception as e:
|
| 108 |
+
logger.info(f"[Audio Stream] Connection failed: {e} (retrying in {retry_delay:.0f}s)")
|
| 109 |
|
| 110 |
# Update connection status
|
| 111 |
if self.monitor:
|
| 112 |
self.monitor.update_audio_metrics(connected=False)
|
| 113 |
|
| 114 |
+
await asyncio.sleep(retry_delay)
|
| 115 |
+
retry_delay = min(retry_delay * 2, 60.0)
|
| 116 |
|
| 117 |
self.connected.clear()
|
| 118 |
|
src/reachy_mini_remote_control_app/websocket_clients/robot_control.py
CHANGED
|
@@ -101,6 +101,7 @@ class AsyncWebSocketController:
|
|
| 101 |
|
| 102 |
async def _run(self) -> None:
|
| 103 |
"""Run the WebSocket controller loop with automatic reconnection."""
|
|
|
|
| 104 |
while not self.stop_flag:
|
| 105 |
try:
|
| 106 |
ws: ClientConnection
|
|
@@ -116,6 +117,7 @@ class AsyncWebSocketController:
|
|
| 116 |
additional_headers=extra_headers if extra_headers else None,
|
| 117 |
) as ws:
|
| 118 |
logger.info("[Robot Control] Connected to remote server")
|
|
|
|
| 119 |
|
| 120 |
# Update connection status
|
| 121 |
if self.monitor:
|
|
@@ -133,14 +135,14 @@ class AsyncWebSocketController:
|
|
| 133 |
await self.on_command(data)
|
| 134 |
|
| 135 |
except Exception as e:
|
| 136 |
-
logger.info("[Robot Control] Connection failed: %s", e)
|
| 137 |
|
| 138 |
# Update connection status
|
| 139 |
if self.monitor:
|
| 140 |
self.monitor.update_control_metrics(connected=False)
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
| 144 |
|
| 145 |
def stop(self) -> None:
|
| 146 |
"""Stop the WebSocket controller."""
|
|
|
|
| 101 |
|
| 102 |
async def _run(self) -> None:
|
| 103 |
"""Run the WebSocket controller loop with automatic reconnection."""
|
| 104 |
+
retry_delay = 1.0
|
| 105 |
while not self.stop_flag:
|
| 106 |
try:
|
| 107 |
ws: ClientConnection
|
|
|
|
| 117 |
additional_headers=extra_headers if extra_headers else None,
|
| 118 |
) as ws:
|
| 119 |
logger.info("[Robot Control] Connected to remote server")
|
| 120 |
+
retry_delay = 1.0 # Reset backoff on successful connection
|
| 121 |
|
| 122 |
# Update connection status
|
| 123 |
if self.monitor:
|
|
|
|
| 135 |
await self.on_command(data)
|
| 136 |
|
| 137 |
except Exception as e:
|
| 138 |
+
logger.info("[Robot Control] Connection failed: %s (retrying in %.0fs)", e, retry_delay)
|
| 139 |
|
| 140 |
# Update connection status
|
| 141 |
if self.monitor:
|
| 142 |
self.monitor.update_control_metrics(connected=False)
|
| 143 |
|
| 144 |
+
await asyncio.sleep(retry_delay)
|
| 145 |
+
retry_delay = min(retry_delay * 2, 60.0)
|
| 146 |
|
| 147 |
def stop(self) -> None:
|
| 148 |
"""Stop the WebSocket controller."""
|
src/reachy_mini_remote_control_app/websocket_clients/robot_state.py
CHANGED
|
@@ -80,6 +80,7 @@ class AsyncWebSocketRobotStatePublisher:
|
|
| 80 |
async def _run(self) -> None:
|
| 81 |
"""Run the WebSocket robot state publisher loop with automatic reconnection."""
|
| 82 |
interval = 1.0 / self.update_rate
|
|
|
|
| 83 |
|
| 84 |
while not self.stop_flag:
|
| 85 |
try:
|
|
@@ -100,6 +101,7 @@ class AsyncWebSocketRobotStatePublisher:
|
|
| 100 |
f"[Robot State] Connected to remote server, publishing at {self.update_rate} Hz"
|
| 101 |
)
|
| 102 |
self.connected.set()
|
|
|
|
| 103 |
|
| 104 |
# Update connection status
|
| 105 |
if self.monitor:
|
|
@@ -123,24 +125,15 @@ class AsyncWebSocketRobotStatePublisher:
|
|
| 123 |
logger.error(f"[Robot State] Send error: {e}")
|
| 124 |
break
|
| 125 |
|
| 126 |
-
except ConnectionClosed as e:
|
| 127 |
-
logger.error(f"[Robot State] Connection lost ({type(e).__name__}). Retrying...")
|
| 128 |
-
self.connected.clear()
|
| 129 |
-
|
| 130 |
-
# Update connection status
|
| 131 |
-
if self.monitor:
|
| 132 |
-
self.monitor.update_control_metrics(connected=False)
|
| 133 |
-
|
| 134 |
-
await asyncio.sleep(0.5)
|
| 135 |
-
|
| 136 |
except Exception as e:
|
| 137 |
-
logger.error(f"[Robot State]
|
| 138 |
|
| 139 |
# Update connection status
|
| 140 |
if self.monitor:
|
| 141 |
self.monitor.update_control_metrics(connected=False)
|
| 142 |
|
| 143 |
-
await asyncio.sleep(
|
|
|
|
| 144 |
|
| 145 |
self.connected.clear()
|
| 146 |
|
|
|
|
| 80 |
async def _run(self) -> None:
|
| 81 |
"""Run the WebSocket robot state publisher loop with automatic reconnection."""
|
| 82 |
interval = 1.0 / self.update_rate
|
| 83 |
+
retry_delay = 1.0
|
| 84 |
|
| 85 |
while not self.stop_flag:
|
| 86 |
try:
|
|
|
|
| 101 |
f"[Robot State] Connected to remote server, publishing at {self.update_rate} Hz"
|
| 102 |
)
|
| 103 |
self.connected.set()
|
| 104 |
+
retry_delay = 1.0 # Reset backoff on successful connection
|
| 105 |
|
| 106 |
# Update connection status
|
| 107 |
if self.monitor:
|
|
|
|
| 125 |
logger.error(f"[Robot State] Send error: {e}")
|
| 126 |
break
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
except Exception as e:
|
| 129 |
+
logger.error(f"[Robot State] Connection error: {e} (retrying in {retry_delay:.0f}s)")
|
| 130 |
|
| 131 |
# Update connection status
|
| 132 |
if self.monitor:
|
| 133 |
self.monitor.update_control_metrics(connected=False)
|
| 134 |
|
| 135 |
+
await asyncio.sleep(retry_delay)
|
| 136 |
+
retry_delay = min(retry_delay * 2, 60.0)
|
| 137 |
|
| 138 |
self.connected.clear()
|
| 139 |
|
src/reachy_mini_remote_control_app/websocket_clients/video_stream.py
CHANGED
|
@@ -70,6 +70,7 @@ class AsyncWebSocketFrameSender:
|
|
| 70 |
|
| 71 |
async def _run(self) -> None:
|
| 72 |
"""Run the WebSocket frame sender loop with automatic reconnection."""
|
|
|
|
| 73 |
while not self.stop_flag:
|
| 74 |
try:
|
| 75 |
ws: ClientConnection
|
|
@@ -88,6 +89,7 @@ class AsyncWebSocketFrameSender:
|
|
| 88 |
logger.info("[Video Stream] Connected to remote server")
|
| 89 |
self.connected.set()
|
| 90 |
self._clear_queue()
|
|
|
|
| 91 |
|
| 92 |
# Update connection status
|
| 93 |
if self.monitor:
|
|
@@ -113,25 +115,15 @@ class AsyncWebSocketFrameSender:
|
|
| 113 |
logger.error(f"[Video Stream] Send error: {e}")
|
| 114 |
break
|
| 115 |
|
| 116 |
-
except ConnectionClosed as e:
|
| 117 |
-
logger.error(f"[Video Stream] Connection lost ({type(e).__name__}). Retrying...")
|
| 118 |
-
self.connected.clear()
|
| 119 |
-
self._last_frame = None
|
| 120 |
-
|
| 121 |
-
# Update connection status
|
| 122 |
-
if self.monitor:
|
| 123 |
-
self.monitor.update_video_metrics(connected=False)
|
| 124 |
-
|
| 125 |
-
await asyncio.sleep(0.5)
|
| 126 |
-
|
| 127 |
except Exception as e:
|
| 128 |
-
logger.error(f"[Video Stream]
|
| 129 |
|
| 130 |
# Update connection status
|
| 131 |
if self.monitor:
|
| 132 |
self.monitor.update_video_metrics(connected=False)
|
| 133 |
|
| 134 |
-
await asyncio.sleep(
|
|
|
|
| 135 |
|
| 136 |
self.connected.clear()
|
| 137 |
self._last_frame = None
|
|
|
|
| 70 |
|
| 71 |
async def _run(self) -> None:
|
| 72 |
"""Run the WebSocket frame sender loop with automatic reconnection."""
|
| 73 |
+
retry_delay = 1.0
|
| 74 |
while not self.stop_flag:
|
| 75 |
try:
|
| 76 |
ws: ClientConnection
|
|
|
|
| 89 |
logger.info("[Video Stream] Connected to remote server")
|
| 90 |
self.connected.set()
|
| 91 |
self._clear_queue()
|
| 92 |
+
retry_delay = 1.0 # Reset backoff on successful connection
|
| 93 |
|
| 94 |
# Update connection status
|
| 95 |
if self.monitor:
|
|
|
|
| 115 |
logger.error(f"[Video Stream] Send error: {e}")
|
| 116 |
break
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
except Exception as e:
|
| 119 |
+
logger.error(f"[Video Stream] Connection error: {e} (retrying in {retry_delay:.0f}s)")
|
| 120 |
|
| 121 |
# Update connection status
|
| 122 |
if self.monitor:
|
| 123 |
self.monitor.update_video_metrics(connected=False)
|
| 124 |
|
| 125 |
+
await asyncio.sleep(retry_delay)
|
| 126 |
+
retry_delay = min(retry_delay * 2, 60.0)
|
| 127 |
|
| 128 |
self.connected.clear()
|
| 129 |
self._last_frame = None
|