RemiFabre commited on
Commit
a007414
Β·
1 Parent(s): 214a331

Smoother first movement, more robustness to port collision

Browse files
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__(self, host: str = "0.0.0.0", port: int = DASHBOARD_PORT) -> None:
 
 
 
 
 
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
- with http.server.ThreadingHTTPServer((self.host, self.port), handler) as httpd:
138
- httpd.timeout = 0.5
139
- while not self._stop.is_set():
140
- httpd.handle_request()
 
 
 
 
 
 
 
 
 
 
 
 
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=DASHBOARD_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