Spaces:
Running
Running
RemiFabre
commited on
Commit
Β·
a007414
1
Parent(s):
214a331
Smoother first movement, more robustness to port collision
Browse files- Theremini/config.json +0 -1
- Theremini/dashboard.py +40 -5
- Theremini/main.py +20 -1
Theremini/config.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
| 2 |
"active_instruments": [
|
| 3 |
"choir_aahs",
|
| 4 |
"orchestra_hit",
|
| 5 |
-
"taiko_drum",
|
| 6 |
"trumpet",
|
| 7 |
"french_horn",
|
| 8 |
"soundtrack"
|
|
|
|
| 2 |
"active_instruments": [
|
| 3 |
"choir_aahs",
|
| 4 |
"orchestra_hit",
|
|
|
|
| 5 |
"trumpet",
|
| 6 |
"french_horn",
|
| 7 |
"soundtrack"
|
Theremini/dashboard.py
CHANGED
|
@@ -113,12 +113,25 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
|
| 113 |
self._send_json({"ok": True, "active_instruments": updated})
|
| 114 |
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
class DashboardServer:
|
| 117 |
-
def __init__(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
self.host = host
|
| 119 |
self.port = port
|
| 120 |
self._thread: threading.Thread | None = None
|
| 121 |
self._stop = threading.Event()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
def start(self) -> None:
|
| 124 |
if self._thread and self._thread.is_alive():
|
|
@@ -126,18 +139,40 @@ class DashboardServer:
|
|
| 126 |
self._stop.clear()
|
| 127 |
self._thread = threading.Thread(target=self._run, daemon=True)
|
| 128 |
self._thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
def stop(self) -> None:
|
| 131 |
self._stop.set()
|
| 132 |
if self._thread:
|
| 133 |
self._thread.join(timeout=1.5)
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
def _run(self) -> None:
|
| 136 |
handler = functools.partial(DashboardHandler)
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
|
| 143 |
__all__ = ["DashboardServer", "DASHBOARD_PORT", "set_status", "get_status"]
|
|
|
|
| 113 |
self._send_json({"ok": True, "active_instruments": updated})
|
| 114 |
|
| 115 |
|
| 116 |
+
class ReusableHTTPServer(http.server.ThreadingHTTPServer):
|
| 117 |
+
allow_reuse_address = True
|
| 118 |
+
|
| 119 |
+
|
| 120 |
class DashboardServer:
|
| 121 |
+
def __init__(
|
| 122 |
+
self,
|
| 123 |
+
host: str = "0.0.0.0",
|
| 124 |
+
port: int = DASHBOARD_PORT,
|
| 125 |
+
max_port_hops: int = 8,
|
| 126 |
+
) -> None:
|
| 127 |
self.host = host
|
| 128 |
self.port = port
|
| 129 |
self._thread: threading.Thread | None = None
|
| 130 |
self._stop = threading.Event()
|
| 131 |
+
self._ready = threading.Event()
|
| 132 |
+
self._error: OSError | None = None
|
| 133 |
+
self._actual_port: int | None = None
|
| 134 |
+
self._max_port_hops = max(1, max_port_hops)
|
| 135 |
|
| 136 |
def start(self) -> None:
|
| 137 |
if self._thread and self._thread.is_alive():
|
|
|
|
| 139 |
self._stop.clear()
|
| 140 |
self._thread = threading.Thread(target=self._run, daemon=True)
|
| 141 |
self._thread.start()
|
| 142 |
+
self._ready.wait(timeout=2.0)
|
| 143 |
+
if self._error:
|
| 144 |
+
print(
|
| 145 |
+
f"Dashboard server failed to bind on {self.host}:{self.port}"
|
| 146 |
+
f" after trying {self._max_port_hops} ports: {self._error}"
|
| 147 |
+
)
|
| 148 |
|
| 149 |
def stop(self) -> None:
|
| 150 |
self._stop.set()
|
| 151 |
if self._thread:
|
| 152 |
self._thread.join(timeout=1.5)
|
| 153 |
|
| 154 |
+
@property
|
| 155 |
+
def port_in_use(self) -> int:
|
| 156 |
+
return self._actual_port or self.port
|
| 157 |
+
|
| 158 |
def _run(self) -> None:
|
| 159 |
handler = functools.partial(DashboardHandler)
|
| 160 |
+
last_exc: OSError | None = None
|
| 161 |
+
for hop in range(self._max_port_hops):
|
| 162 |
+
candidate_port = self.port + hop
|
| 163 |
+
try:
|
| 164 |
+
with ReusableHTTPServer((self.host, candidate_port), handler) as httpd:
|
| 165 |
+
self._actual_port = httpd.server_address[1]
|
| 166 |
+
self._ready.set()
|
| 167 |
+
httpd.timeout = 0.5
|
| 168 |
+
while not self._stop.is_set():
|
| 169 |
+
httpd.handle_request()
|
| 170 |
+
return
|
| 171 |
+
except OSError as exc:
|
| 172 |
+
last_exc = exc
|
| 173 |
+
continue
|
| 174 |
+
self._error = last_exc or OSError("unable to bind dashboard server")
|
| 175 |
+
self._ready.set()
|
| 176 |
|
| 177 |
|
| 178 |
__all__ = ["DashboardServer", "DASHBOARD_PORT", "set_status", "get_status"]
|
Theremini/main.py
CHANGED
|
@@ -59,6 +59,8 @@ class Theremini(ReachyMiniApp):
|
|
| 59 |
self._note_handle: Any | None = None
|
| 60 |
self._current_pitch: int | None = None
|
| 61 |
self._current_prog: int | None = None
|
|
|
|
|
|
|
| 62 |
|
| 63 |
def _get_part_for_prog(self, prog: int):
|
| 64 |
name = self._active_parts[prog]
|
|
@@ -77,8 +79,14 @@ class Theremini(ReachyMiniApp):
|
|
| 77 |
def run(self, reachy_mini: ReachyMini, stop_event: threading.Event) -> None:
|
| 78 |
print("Roll β Pitch\nZ β Volume\nRight antenna β Instrument\n")
|
| 79 |
|
| 80 |
-
dashboard_server = DashboardServer(port=
|
| 81 |
dashboard_server.start()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
set_status(self._build_status_payload(0.0, 0.0, 0.0, 0.0, None, None, False))
|
| 83 |
|
| 84 |
reachy_mini.enable_motors()
|
|
@@ -240,6 +248,7 @@ class Theremini(ReachyMiniApp):
|
|
| 240 |
current_roll,
|
| 241 |
target_roll=-28.0,
|
| 242 |
duration=1.6,
|
|
|
|
| 243 |
)
|
| 244 |
self._demo_pause(reachy_mini, stop_event, 0.45)
|
| 245 |
|
|
@@ -337,8 +346,18 @@ class Theremini(ReachyMiniApp):
|
|
| 337 |
start_roll: float,
|
| 338 |
target_roll: float,
|
| 339 |
duration: float,
|
|
|
|
| 340 |
) -> float:
|
| 341 |
duration = max(0.3, duration)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
start_time = time.time()
|
| 343 |
while not stop_event.is_set():
|
| 344 |
elapsed = time.time() - start_time
|
|
|
|
| 59 |
self._note_handle: Any | None = None
|
| 60 |
self._current_pitch: int | None = None
|
| 61 |
self._current_prog: int | None = None
|
| 62 |
+
self._dashboard_port = DASHBOARD_PORT
|
| 63 |
+
self.custom_app_url = f"http://localhost:{self._dashboard_port}/index.html"
|
| 64 |
|
| 65 |
def _get_part_for_prog(self, prog: int):
|
| 66 |
name = self._active_parts[prog]
|
|
|
|
| 79 |
def run(self, reachy_mini: ReachyMini, stop_event: threading.Event) -> None:
|
| 80 |
print("Roll β Pitch\nZ β Volume\nRight antenna β Instrument\n")
|
| 81 |
|
| 82 |
+
dashboard_server = DashboardServer(port=self._dashboard_port)
|
| 83 |
dashboard_server.start()
|
| 84 |
+
self._dashboard_port = dashboard_server.port_in_use
|
| 85 |
+
self.custom_app_url = f"http://localhost:{self._dashboard_port}/index.html"
|
| 86 |
+
if self._dashboard_port != DASHBOARD_PORT:
|
| 87 |
+
print(
|
| 88 |
+
f"Dashboard port {DASHBOARD_PORT} busy, now serving on http://localhost:{self._dashboard_port}/index.html"
|
| 89 |
+
)
|
| 90 |
set_status(self._build_status_payload(0.0, 0.0, 0.0, 0.0, None, None, False))
|
| 91 |
|
| 92 |
reachy_mini.enable_motors()
|
|
|
|
| 248 |
current_roll,
|
| 249 |
target_roll=-28.0,
|
| 250 |
duration=1.6,
|
| 251 |
+
use_goto=True,
|
| 252 |
)
|
| 253 |
self._demo_pause(reachy_mini, stop_event, 0.45)
|
| 254 |
|
|
|
|
| 346 |
start_roll: float,
|
| 347 |
target_roll: float,
|
| 348 |
duration: float,
|
| 349 |
+
use_goto: bool = False,
|
| 350 |
) -> float:
|
| 351 |
duration = max(0.3, duration)
|
| 352 |
+
if use_goto:
|
| 353 |
+
pose = create_head_pose(z=0.0, roll=target_roll, degrees=True)
|
| 354 |
+
reachy_mini.goto_target(head=pose, duration=duration)
|
| 355 |
+
end = time.time() + duration
|
| 356 |
+
while not stop_event.is_set() and time.time() < end:
|
| 357 |
+
self._tick_from_robot(reachy_mini)
|
| 358 |
+
time.sleep(0.02)
|
| 359 |
+
return target_roll
|
| 360 |
+
|
| 361 |
start_time = time.time()
|
| 362 |
while not stop_event.is_set():
|
| 363 |
elapsed = time.time() - start_time
|