reachy-mini 1.2.5rc1__py3-none-any.whl → 1.2.11__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.
Files changed (65) hide show
  1. reachy_mini/apps/app.py +24 -21
  2. reachy_mini/apps/manager.py +17 -3
  3. reachy_mini/apps/sources/hf_auth.py +92 -0
  4. reachy_mini/apps/sources/hf_space.py +1 -1
  5. reachy_mini/apps/sources/local_common_venv.py +199 -24
  6. reachy_mini/apps/templates/main.py.j2 +4 -3
  7. reachy_mini/daemon/app/dashboard/static/js/apps.js +9 -1
  8. reachy_mini/daemon/app/dashboard/static/js/appstore.js +228 -0
  9. reachy_mini/daemon/app/dashboard/static/js/logs.js +148 -0
  10. reachy_mini/daemon/app/dashboard/templates/logs.html +37 -0
  11. reachy_mini/daemon/app/dashboard/templates/sections/appstore.html +92 -0
  12. reachy_mini/daemon/app/dashboard/templates/sections/cache.html +82 -0
  13. reachy_mini/daemon/app/dashboard/templates/sections/daemon.html +5 -0
  14. reachy_mini/daemon/app/dashboard/templates/settings.html +1 -0
  15. reachy_mini/daemon/app/main.py +172 -7
  16. reachy_mini/daemon/app/models.py +8 -0
  17. reachy_mini/daemon/app/routers/apps.py +56 -0
  18. reachy_mini/daemon/app/routers/cache.py +58 -0
  19. reachy_mini/daemon/app/routers/hf_auth.py +57 -0
  20. reachy_mini/daemon/app/routers/logs.py +124 -0
  21. reachy_mini/daemon/app/routers/state.py +25 -1
  22. reachy_mini/daemon/app/routers/wifi_config.py +75 -0
  23. reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py +1 -1
  24. reachy_mini/daemon/app/services/bluetooth/commands/WIFI_RESET.sh +8 -0
  25. reachy_mini/daemon/app/services/wireless/launcher.sh +8 -2
  26. reachy_mini/daemon/app/services/wireless/reachy-mini-daemon.service +13 -0
  27. reachy_mini/daemon/backend/abstract.py +29 -9
  28. reachy_mini/daemon/backend/mockup_sim/__init__.py +12 -0
  29. reachy_mini/daemon/backend/mockup_sim/backend.py +176 -0
  30. reachy_mini/daemon/backend/mujoco/backend.py +0 -5
  31. reachy_mini/daemon/backend/robot/backend.py +78 -5
  32. reachy_mini/daemon/daemon.py +46 -7
  33. reachy_mini/daemon/utils.py +71 -15
  34. reachy_mini/io/zenoh_client.py +26 -0
  35. reachy_mini/io/zenoh_server.py +10 -6
  36. reachy_mini/kinematics/nn_kinematics.py +2 -2
  37. reachy_mini/kinematics/placo_kinematics.py +15 -15
  38. reachy_mini/media/__init__.py +55 -1
  39. reachy_mini/media/audio_base.py +185 -13
  40. reachy_mini/media/audio_control_utils.py +60 -5
  41. reachy_mini/media/audio_gstreamer.py +97 -16
  42. reachy_mini/media/audio_sounddevice.py +120 -19
  43. reachy_mini/media/audio_utils.py +110 -5
  44. reachy_mini/media/camera_base.py +182 -11
  45. reachy_mini/media/camera_constants.py +132 -4
  46. reachy_mini/media/camera_gstreamer.py +42 -2
  47. reachy_mini/media/camera_opencv.py +83 -5
  48. reachy_mini/media/camera_utils.py +95 -7
  49. reachy_mini/media/media_manager.py +139 -6
  50. reachy_mini/media/webrtc_client_gstreamer.py +142 -13
  51. reachy_mini/media/webrtc_daemon.py +72 -7
  52. reachy_mini/motion/recorded_move.py +76 -2
  53. reachy_mini/reachy_mini.py +196 -40
  54. reachy_mini/tools/reflash_motors.py +1 -1
  55. reachy_mini/tools/scan_motors.py +86 -0
  56. reachy_mini/tools/setup_motor.py +49 -31
  57. reachy_mini/utils/interpolation.py +1 -1
  58. reachy_mini/utils/wireless_version/startup_check.py +278 -21
  59. reachy_mini/utils/wireless_version/update.py +44 -1
  60. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/METADATA +7 -6
  61. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/RECORD +65 -53
  62. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/WHEEL +0 -0
  63. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/entry_points.txt +0 -0
  64. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/licenses/LICENSE +0 -0
  65. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,6 @@ Also checks and updates the bluetooth service if needed.
7
7
 
8
8
  import filecmp
9
9
  import logging
10
- import pwd
11
10
  import subprocess
12
11
  from pathlib import Path
13
12
 
@@ -18,13 +17,15 @@ USER = "pollen"
18
17
  def check_and_fix_venvs_ownership(
19
18
  venvs_path: str = "/venvs", custom_logger: logging.Logger | None = None
20
19
  ) -> None:
21
- """Check if files under venvs_path are owned by user pollen and fix if needed.
20
+ """For wireless units, check if files under venvs_path are owned by user pollen and fix if needed.
22
21
 
23
22
  Args:
24
23
  venvs_path: Path to the virtual environments directory (default: /venvs)
25
24
  custom_logger: Optional logger to use instead of the module logger
26
25
 
27
26
  """
27
+ import pwd
28
+
28
29
  try:
29
30
  # Get pollen user's UID
30
31
  pollen_uid = pwd.getpwnam(USER).pw_uid
@@ -81,39 +82,71 @@ def check_and_update_bluetooth_service() -> None:
81
82
 
82
83
  Compares the source bluetooth_service.py with the installed version at
83
84
  /bluetooth/bluetooth_service.py. If they differ, copies the new version
84
- and restarts the bluetooth service.
85
+ and restarts the bluetooth service. Also syncs the commands/ folder.
85
86
  """
86
87
  # This file: src/reachy_mini/utils/wireless_version/startup_check.py
87
88
  # Target: src/reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py
88
89
  # From parent: ../../daemon/app/services/bluetooth/bluetooth_service.py
89
- source = Path(__file__).parent / ".." / ".." / "daemon" / "app" / "services" / "bluetooth" / "bluetooth_service.py"
90
- source = source.resolve()
90
+ bluetooth_dir = (
91
+ Path(__file__).parent
92
+ / ".."
93
+ / ".."
94
+ / "daemon"
95
+ / "app"
96
+ / "services"
97
+ / "bluetooth"
98
+ )
99
+ bluetooth_dir = bluetooth_dir.resolve()
100
+ source = bluetooth_dir / "bluetooth_service.py"
91
101
  target = Path("/bluetooth/bluetooth_service.py")
102
+ source_commands = bluetooth_dir / "commands"
103
+ target_commands = Path("/bluetooth/commands")
92
104
 
93
105
  if not source.exists():
94
106
  print(f"Source bluetooth service not found at {source}")
95
107
  return
96
108
 
97
- # Check if target exists
109
+ needs_update = False
110
+ needs_commands_update = False
111
+
112
+ # Check if bluetooth_service.py needs update
98
113
  if not target.exists():
99
114
  print(f"Bluetooth service not installed at {target}, copying...")
100
115
  needs_update = True
101
116
  else:
102
- # Compare files
103
117
  try:
104
- if filecmp.cmp(str(source), str(target), shallow=False):
105
- print("Bluetooth service is up to date")
106
- return
107
- else:
118
+ if not filecmp.cmp(str(source), str(target), shallow=False):
108
119
  print("Bluetooth service has changed, updating...")
109
120
  needs_update = True
110
121
  except Exception as e:
111
122
  print(f"Error comparing bluetooth service files: {e}")
112
- return
113
123
 
114
- if needs_update:
115
- try:
116
- # Copy the new version using sudo
124
+ # Check if commands folder needs update
125
+ if source_commands.exists():
126
+ if not target_commands.exists():
127
+ print("Commands folder not installed, copying...")
128
+ needs_commands_update = True
129
+ else:
130
+ # Compare each command file
131
+ for cmd_file in source_commands.glob("*.sh"):
132
+ target_cmd = target_commands / cmd_file.name
133
+ if not target_cmd.exists():
134
+ needs_commands_update = True
135
+ break
136
+ try:
137
+ if not filecmp.cmp(str(cmd_file), str(target_cmd), shallow=False):
138
+ needs_commands_update = True
139
+ break
140
+ except Exception:
141
+ needs_commands_update = True
142
+ break
143
+
144
+ if not needs_update and not needs_commands_update:
145
+ print("Bluetooth service and commands are up to date")
146
+ return
147
+
148
+ try:
149
+ if needs_update:
117
150
  print(f"Copying {source} to {target}")
118
151
  subprocess.run(
119
152
  ["sudo", "cp", str(source), str(target)],
@@ -123,22 +156,246 @@ def check_and_update_bluetooth_service() -> None:
123
156
  )
124
157
  print("Successfully copied bluetooth service")
125
158
 
126
- # Restart the bluetooth service
127
- print("Restarting bluetooth service...")
159
+ if needs_commands_update:
160
+ print(f"Syncing commands folder to {target_commands}")
161
+ subprocess.run(
162
+ ["sudo", "mkdir", "-p", str(target_commands)],
163
+ check=True,
164
+ capture_output=True,
165
+ text=True,
166
+ )
167
+ subprocess.run(
168
+ ["sudo", "cp", "-r", f"{source_commands}/.", str(target_commands)],
169
+ check=True,
170
+ capture_output=True,
171
+ text=True,
172
+ )
173
+ print("Successfully synced commands folder")
174
+
175
+ # Restart the bluetooth service
176
+ print("Restarting bluetooth service...")
177
+ subprocess.run(
178
+ ["sudo", "systemctl", "restart", "reachy-mini-bluetooth"],
179
+ check=True,
180
+ capture_output=True,
181
+ text=True,
182
+ )
183
+ print("Successfully restarted bluetooth service")
184
+ except subprocess.CalledProcessError as e:
185
+ print(f"Failed to update bluetooth service: {e.stderr}")
186
+ except Exception as e:
187
+ print(f"Unexpected error while updating bluetooth service: {e}")
188
+
189
+
190
+ def check_and_update_wireless_launcher() -> None:
191
+ """Check if wireless daemon service needs updating and update if different.
192
+
193
+ Compares the source reachy-mini-daemon.service with the installed version.
194
+ If they differ, copies the new version and reloads systemd.
195
+ """
196
+ source = (
197
+ Path(__file__).parent
198
+ / ".."
199
+ / ".."
200
+ / "daemon"
201
+ / "app"
202
+ / "services"
203
+ / "wireless"
204
+ / "reachy-mini-daemon.service"
205
+ )
206
+ source = source.resolve()
207
+ target = Path("/etc/systemd/system/reachy-mini-daemon.service")
208
+
209
+ if not source.exists():
210
+ print(f"Source service file not found at {source}")
211
+ return
212
+
213
+ # Check if target exists
214
+ if not target.exists():
215
+ print(f"Wireless daemon service not installed at {target}")
216
+ return
217
+
218
+ # Compare files
219
+ try:
220
+ if filecmp.cmp(str(source), str(target), shallow=False):
221
+ print("Wireless daemon service is up to date")
222
+ return
223
+ else:
224
+ print("Wireless daemon service has changed, updating...")
225
+ except Exception as e:
226
+ print(f"Error comparing service files: {e}")
227
+ return
228
+
229
+ # Update service file
230
+ try:
231
+ print(f"Copying {source} to {target}")
232
+ subprocess.run(
233
+ ["sudo", "cp", str(source), str(target)],
234
+ check=True,
235
+ capture_output=True,
236
+ text=True,
237
+ )
238
+ print("Successfully copied service file")
239
+
240
+ # Reload systemd daemon
241
+ print("Reloading systemd daemon...")
242
+ subprocess.run(
243
+ ["sudo", "systemctl", "daemon-reload"],
244
+ check=True,
245
+ capture_output=True,
246
+ text=True,
247
+ )
248
+ print("Successfully reloaded systemd")
249
+ except subprocess.CalledProcessError as e:
250
+ print(f"Failed to update service: {e.stderr}")
251
+ except Exception as e:
252
+ print(f"Unexpected error while updating service: {e}")
253
+
254
+
255
+ def check_and_sync_apps_venv_sdk() -> None:
256
+ """Check if the apps_venv SDK version matches daemon and update if needed.
257
+
258
+ This ensures that when the daemon is updated, the SDK in the apps_venv
259
+ is also updated to match, so apps get the latest fixes automatically.
260
+ """
261
+ from importlib.metadata import version
262
+
263
+ # Get daemon venv SDK version
264
+ try:
265
+ daemon_version = version("reachy_mini")
266
+ except Exception as e:
267
+ print(f"Could not get daemon SDK version: {e}")
268
+ return
269
+
270
+ # Path to apps_venv python
271
+ apps_venv_python = Path("/venvs/apps_venv/bin/python")
272
+ if not apps_venv_python.exists():
273
+ print("apps_venv not found, skipping SDK sync")
274
+ return
275
+
276
+ # Get apps_venv SDK version
277
+ try:
278
+ result = subprocess.run(
279
+ [
280
+ str(apps_venv_python),
281
+ "-c",
282
+ "from importlib.metadata import version; print(version('reachy_mini'))",
283
+ ],
284
+ capture_output=True,
285
+ text=True,
286
+ timeout=10,
287
+ )
288
+ if result.returncode != 0:
289
+ print(f"Could not get apps_venv SDK version: {result.stderr}")
290
+ return
291
+ apps_version = result.stdout.strip()
292
+ except subprocess.TimeoutExpired:
293
+ print("Timeout getting apps_venv SDK version")
294
+ return
295
+ except Exception as e:
296
+ print(f"Error getting apps_venv SDK version: {e}")
297
+ return
298
+
299
+ print(f"Daemon SDK version: {daemon_version}")
300
+ print(f"Apps venv SDK version: {apps_version}")
301
+
302
+ if daemon_version == apps_version:
303
+ print("Apps venv SDK is up to date")
304
+ return
305
+
306
+ # Versions differ, update apps_venv
307
+ print(f"Updating apps_venv SDK from {apps_version} to {daemon_version}...")
308
+ try:
309
+ # Use uv if available, otherwise pip
310
+ import shutil
311
+
312
+ apps_venv_pip = Path("/venvs/apps_venv/bin/pip")
313
+ use_uv = shutil.which("uv") is not None
314
+
315
+ if use_uv:
316
+ install_cmd = [
317
+ "uv",
318
+ "pip",
319
+ "install",
320
+ "--python",
321
+ str(apps_venv_python),
322
+ f"reachy-mini[gstreamer]=={daemon_version}",
323
+ ]
324
+ else:
325
+ install_cmd = [
326
+ str(apps_venv_pip),
327
+ "install",
328
+ f"reachy-mini[gstreamer]=={daemon_version}",
329
+ ]
330
+
331
+ result = subprocess.run(
332
+ install_cmd,
333
+ capture_output=True,
334
+ text=True,
335
+ timeout=300, # 5 minute timeout for install
336
+ )
337
+
338
+ if result.returncode == 0:
339
+ print(f"Successfully updated apps_venv SDK to {daemon_version}")
340
+ else:
341
+ print(f"Failed to update apps_venv SDK: {result.stderr}")
342
+ except subprocess.TimeoutExpired:
343
+ print("Timeout updating apps_venv SDK")
344
+ except Exception as e:
345
+ print(f"Error updating apps_venv SDK: {e}")
346
+
347
+
348
+ def check_and_fix_restore_venv() -> None:
349
+ """Check if restore venv has editable install and fix if needed.
350
+
351
+ The restore partition at /restore/venvs should have a proper PyPI install,
352
+ not an editable install. If an editable install is detected, reinstall
353
+ from PyPI with a known good version.
354
+ """
355
+ restore_python = Path("/restore/venvs/mini_daemon/bin/python")
356
+
357
+ if not restore_python.exists():
358
+ print("Restore venv not found, skipping")
359
+ return
360
+
361
+ # Check if editable install
362
+ try:
363
+ result = subprocess.run(
364
+ [str(restore_python), "-m", "pip", "show", "reachy-mini"],
365
+ capture_output=True,
366
+ text=True,
367
+ timeout=30,
368
+ )
369
+ except subprocess.TimeoutExpired:
370
+ print("Timeout checking restore venv")
371
+ return
372
+ except Exception as e:
373
+ print(f"Error checking restore venv: {e}")
374
+ return
375
+
376
+ if "Editable project location" in result.stdout:
377
+ print("Legacy editable install detected in restore venv, reinstalling...")
378
+ try:
128
379
  subprocess.run(
129
- ["sudo", "systemctl", "restart", "reachy-mini-bluetooth"],
380
+ [str(restore_python), "-m", "pip", "install", "reachy-mini==1.2.8"],
130
381
  check=True,
131
382
  capture_output=True,
132
383
  text=True,
384
+ timeout=300,
133
385
  )
134
- print("Successfully restarted bluetooth service")
386
+ print("Successfully reinstalled reachy-mini in restore venv")
135
387
  except subprocess.CalledProcessError as e:
136
- print(f"Failed to update bluetooth service: {e.stderr}")
388
+ print(f"Failed to reinstall in restore venv: {e.stderr}")
389
+ except subprocess.TimeoutExpired:
390
+ print("Timeout reinstalling in restore venv")
137
391
  except Exception as e:
138
- print(f"Unexpected error while updating bluetooth service: {e}")
392
+ print(f"Error reinstalling in restore venv: {e}")
393
+ else:
394
+ print("Restore venv install is correct")
139
395
 
140
396
 
141
397
  if __name__ == "__main__":
142
398
  logging.basicConfig(level=logging.INFO)
143
399
  check_and_fix_venvs_ownership()
144
400
  check_and_update_bluetooth_service()
401
+ check_and_sync_apps_venv_sdk()
@@ -1,20 +1,63 @@
1
1
  """Module to handle software updates for the Reachy Mini wireless."""
2
2
 
3
3
  import logging
4
+ import shutil
5
+ from pathlib import Path
4
6
 
5
7
  from .utils import call_logger_wrapper
6
8
 
7
9
 
8
10
  async def update_reachy_mini(pre_release: bool, logger: logging.Logger) -> None:
9
- """Perform a software update by upgrading the reachy_mini package and restarting the daemon."""
11
+ """Perform a software update by upgrading the reachy_mini package and restarting the daemon.
12
+
13
+ This updates both:
14
+ - The daemon venv (where the daemon runs)
15
+ - The apps_venv (where apps run) - to keep SDK in sync
16
+ """
10
17
  extra_args = []
11
18
  if pre_release:
12
19
  extra_args.append("--pre")
13
20
 
21
+ # Update daemon venv
22
+ logger.info("Updating daemon venv...")
14
23
  await call_logger_wrapper(
15
24
  ["pip", "install", "--upgrade", "reachy_mini[wireless-version]"] + extra_args,
16
25
  logger,
17
26
  )
27
+
28
+ # Update apps_venv if it exists
29
+ apps_venv_python = Path("/venvs/apps_venv/bin/python")
30
+ if apps_venv_python.exists():
31
+ logger.info("Updating apps_venv SDK...")
32
+
33
+ # Use uv if available for faster installs
34
+ use_uv = shutil.which("uv") is not None
35
+
36
+ if use_uv:
37
+ install_cmd = [
38
+ "uv",
39
+ "pip",
40
+ "install",
41
+ "--python",
42
+ str(apps_venv_python),
43
+ "--upgrade",
44
+ "reachy-mini[gstreamer]",
45
+ ] + extra_args
46
+ else:
47
+ apps_venv_pip = Path("/venvs/apps_venv/bin/pip")
48
+ install_cmd = [
49
+ str(apps_venv_pip),
50
+ "install",
51
+ "--upgrade",
52
+ "reachy-mini[gstreamer]",
53
+ ] + extra_args
54
+
55
+ await call_logger_wrapper(install_cmd, logger)
56
+ logger.info("Apps venv SDK updated successfully")
57
+ else:
58
+ logger.info("apps_venv not found, skipping apps venv update")
59
+
60
+ # Restart daemon to apply updates
18
61
  await call_logger_wrapper(
19
62
  ["sudo", "systemctl", "restart", "reachy-mini-daemon"], logger
20
63
  )
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reachy_mini
3
- Version: 1.2.5rc1
3
+ Version: 1.2.11
4
4
  Author-email: Pollen Robotics <contact@pollen-robotics.com>
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: numpy>=2.2.5
9
9
  Requires-Dist: scipy<2.0.0,>=1.15.3
10
- Requires-Dist: reachy_mini_motor_controller>=1.4.2
10
+ Requires-Dist: reachy_mini_motor_controller>=1.5.3
11
11
  Requires-Dist: eclipse-zenoh~=1.7.0
12
12
  Requires-Dist: opencv-python<=5.0
13
13
  Requires-Dist: cv2_enumerate_cameras>=1.2.1
@@ -16,7 +16,7 @@ Requires-Dist: jinja2
16
16
  Requires-Dist: uvicorn[standard]
17
17
  Requires-Dist: fastapi
18
18
  Requires-Dist: pyserial
19
- Requires-Dist: huggingface-hub==0.34.4
19
+ Requires-Dist: huggingface-hub==1.3.0
20
20
  Requires-Dist: sounddevice==0.5.1
21
21
  Requires-Dist: soundfile==0.13.1
22
22
  Requires-Dist: reachy-mini-rust-kinematics>=1.0.3
@@ -30,7 +30,9 @@ Requires-Dist: rich
30
30
  Requires-Dist: questionary
31
31
  Requires-Dist: websockets<16,>=12
32
32
  Requires-Dist: toml
33
- Requires-Dist: rustypot
33
+ Requires-Dist: rustypot>=1.4.2
34
+ Requires-Dist: pyyaml>=6.0
35
+ Requires-Dist: requests>=2.28.0
34
36
  Provides-Extra: dev
35
37
  Requires-Dist: pytest; extra == "dev"
36
38
  Requires-Dist: pytest-asyncio; extra == "dev"
@@ -40,7 +42,6 @@ Requires-Dist: mujoco==3.3.0; extra == "dev"
40
42
  Requires-Dist: placo==0.9.14; extra == "dev"
41
43
  Requires-Dist: pre-commit; extra == "dev"
42
44
  Requires-Dist: mypy==1.18.2; extra == "dev"
43
- Requires-Dist: rustypot>=1.4.0; extra == "dev"
44
45
  Requires-Dist: types-requests; extra == "dev"
45
46
  Requires-Dist: types-toml; extra == "dev"
46
47
  Requires-Dist: rerun-sdk>=0.27.2; extra == "dev"
@@ -98,7 +99,7 @@ Dynamic: license-file
98
99
  Reachy Mini comes with an app store powered by Hugging Face Spaces. You can install these apps directly from your robot's dashboard with one click!
99
100
 
100
101
  * **🗣️ [Conversation App](https://huggingface.co/spaces/pollen-robotics/reachy_mini_conversation_app):** Talk naturally with Reachy Mini (powered by LLMs).
101
- * **📻 [Radio](https://huggingface.co/spaces/pollen-robotics/reachy_mini_radio):** Listen to the radio with Reachy Mini !
102
+ * **📻 [Radio](https://huggingface.co/spaces/pollen-robotics/reachy_mini_radio):** Listen to the radio with Reachy Mini!
102
103
  * **👋 [Hand Tracker](https://huggingface.co/spaces/pollen-robotics/hand_tracker_v2):** The robot follows your hand movements in real-time.
103
104
 
104
105
  👉 [**Browse all apps on Hugging Face**](https://hf.co/reachy-mini/#/apps)