From ab67b128c142ba1cdf88ad233da741d2d85c1625 Mon Sep 17 00:00:00 2001 From: Lucca Pirovano Date: Thu, 16 Jul 2026 14:16:25 -0400 Subject: [PATCH] added naomusic.py and moved all music stuff to it --- __pycache__/naomusic.cpython-311.pyc | Bin 0 -> 8986 bytes naowalk.py | 267 ++++++++++++++++++++++++++- 2 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 __pycache__/naomusic.cpython-311.pyc diff --git a/__pycache__/naomusic.cpython-311.pyc b/__pycache__/naomusic.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80091de9624f666560869a9b145c3678b47f69ec GIT binary patch literal 8986 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 F{BM)(4od(4 literal 0 HcmV?d00001 diff --git a/naowalk.py b/naowalk.py index 6b8b0f0..5daae3a 100644 --- a/naowalk.py +++ b/naowalk.py @@ -270,4 +270,269 @@ class NaoTeleop: def update_title(self): mode = "[TYPING] " if self.typing_mode else "" - pygame.display.set_caption(f"NAO Teleop | {mode}Battery: {self.battery_level}% | Vol + pygame.display.set_caption(f"NAO Teleop | {mode}Battery: {self.battery_level}% | Vol: {self.volume}% | Speed: {self.walk_speed:.2f}") + + def change_volume(self, delta): + self.volume = int(min(100, max(0, self.volume + delta))) + if self.audio_device: + self._safe("Volume set", lambda: self.audio_device.setOutputVolume(self.volume)) + print(f"šŸ”Š NAO volume: {self.volume}%") + self.update_title() + + def check_battery(self): + if not self.battery: return + now = time.time() + if now - self.last_batt_check > 10: + try: + self.battery_level = self.battery.getBatteryCharge() + except: pass + self.update_title() + self.last_batt_check = now + + def run(self): + self.motion.wakeUp() + self.posture.goToPosture("StandInit", 0.5) + time.sleep(1.0) + self.motion.setMoveArmsEnabled(True, True) + + print("🦶 Initializing walk engine...") + self._safe("moveInit", self.motion.moveInit) + print("🦶 Walk engine ready") + + print("\nšŸŽ® Controls: WASD/Arrows=Walk | [/]=Speed | IJKL=Head | SPACE=Reset head | 1=Wave | M=Music search (↑↓ select, Enter confirm) | X=Stop | -/==Volume | T=TTS | ESC=Quit") + + try: + while self.running: + try: + self.check_battery() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + elif event.type == pygame.KEYDOWN: + if self.music_search_mode: + if event.key == pygame.K_RETURN: + if self.music_results: + entry = self.music_results[self.selected_result] + threading.Thread( + target=self.music.play, + args=(entry['url'], entry.get('title', 'Unknown')), + daemon=True + ).start() + self.music_search_mode = False + elif self.music_search_text.strip() and not self.music_search_pending: + query = self.music_search_text + self.music_search_pending = True + def do_search(q=query): + results = self.music.search(q) + self.music_results = results + self.selected_result = 0 + self.music_search_pending = False + if not results: + self.music_search_mode = False + threading.Thread(target=do_search, daemon=True).start() + else: + self.music_search_mode = False + elif event.key == pygame.K_ESCAPE: + self.music_search_mode = False + self.music_search_text = "" + self.music_results = [] + elif event.key == pygame.K_UP: + if self.music_results: + self.selected_result = (self.selected_result - 1) % len(self.music_results[:5]) + elif event.key == pygame.K_DOWN: + if self.music_results: + self.selected_result = (self.selected_result + 1) % len(self.music_results[:5]) + elif event.key == pygame.K_BACKSPACE: + self.music_search_text = self.music_search_text[:-1] + self.music_results = [] + elif event.key in (pygame.K_1, pygame.K_2, pygame.K_3, pygame.K_4, pygame.K_5): + idx = int(event.unicode) - 1 + if idx < len(self.music_results): + entry = self.music_results[idx] + threading.Thread( + target=self.music.play, + args=(entry['url'], entry.get('title', 'Unknown')), + daemon=True + ).start() + self.music_search_mode = False + elif event.unicode.isprintable(): + self.music_search_text += event.unicode + self.music_results = [] + elif self.typing_mode: + if event.key == pygame.K_RETURN: + if self.chat_message.strip(): + threading.Thread(target=self.tts.say, args=(self.chat_message,), daemon=True).start() + self.chat_message = "" + self.typing_mode = False + elif event.key == pygame.K_ESCAPE: + self.chat_message = "" + self.typing_mode = False + elif event.key == pygame.K_BACKSPACE: + self.chat_message = self.chat_message[:-1] + else: + self.chat_message += event.unicode + else: + if event.key == pygame.K_ESCAPE: + self.running = False + elif event.key == pygame.K_1: + self.wave_gesture() + elif event.key == pygame.K_SPACE: + self.reset_head() + elif event.key == pygame.K_m: + self.music_search_mode = True + self.music_search_text = "" + self.music_results = [] + elif event.key == pygame.K_x: + self.music.stop() + elif event.key in (pygame.K_MINUS, pygame.K_KP_MINUS): + self.change_volume(-5) + elif event.key in (pygame.K_EQUALS, pygame.K_KP_PLUS): + self.change_volume(5) + elif event.key == pygame.K_t: + self.typing_mode = True + self._safe("stopMove", self.motion.stopMove) + + if not self.typing_mode and not self.music_search_mode: + keys = pygame.key.get_pressed() + + speed_changed = False + if keys[pygame.K_LEFTBRACKET]: + self.walk_speed = max(0.1, self.walk_speed - 0.01) + speed_changed = True + if keys[pygame.K_RIGHTBRACKET]: + self.walk_speed = min(1.0, self.walk_speed + 0.01) + speed_changed = True + if speed_changed: + self.update_title() + + x = y = theta = 0.0 + if keys[pygame.K_w] or keys[pygame.K_UP]: x = self.walk_speed + if keys[pygame.K_s] or keys[pygame.K_DOWN]: x = -self.walk_speed + if keys[pygame.K_a] or keys[pygame.K_LEFT]: theta = self.walk_speed * 0.8 + if keys[pygame.K_d] or keys[pygame.K_RIGHT]: theta = -self.walk_speed * 0.8 + + try: + if abs(x) > 0.05 or abs(theta) > 0.05: + stable_config = [ + ["StepHeight", 0.02], + ["MaxStepFrequency", 0.5], + ["TorsoWy", 0.08], + ["MaxStepX", 0.03] + ] + self.motion.moveToward(x, y, theta, stable_config) + else: + self.motion.stopMove() + except Exception as e: + print(f"Move error: {e}") + self.handle_head_movement() + + frame = self.get_image() + if frame is not None: + up = cv2.resize(frame, (self.display_width, self.display_height), interpolation=cv2.INTER_CUBIC) + rgb = cv2.cvtColor(up, cv2.COLOR_BGR2RGB) + surf = pygame.surfarray.make_surface(rgb.swapaxes(0,1)) + self.screen.blit(surf, (0, 0)) + else: + self.screen.fill((10, 10, 30)) + + # Music status + if self.music.is_loading: + text = self.font.render(f"♪ {self.music.status}", True, (255, 200, 0)) + self.screen.blit(text, (10, 10)) + if self.music.progress > 0: + bar_w = 220 + pct = min(self.music.progress, 100) / 100.0 + pygame.draw.rect(self.screen, (70, 70, 70), (10, 32, bar_w, 8)) + pygame.draw.rect(self.screen, (255, 200, 0), (10, 32, int(bar_w * pct), 8)) + elif self.music.load_error: + text = self.font.render(f"♪ Error: {self.music.load_error}", True, (255, 80, 80)) + self.screen.blit(text, (10, 10)) + elif self.music.current_title: + text = self.font.render(f"♪ {self.music.current_title[:45]}", True, (0, 255, 100)) + self.screen.blit(text, (10, 10)) + + # Music search interface + if self.music_search_mode: + box_h = min(self.display_height - 80, 260) + s = pygame.Surface((self.display_width, box_h)) + s.set_alpha(220) + s.fill((0, 0, 40)) + self.screen.blit(s, (0, 80)) + + text = self.font.render(f"Search: {self.music_search_text}", True, (255, 255, 255)) + self.screen.blit(text, (20, 100)) + + if self.music_results: + shown = self.music_results[:5] + for i, entry in enumerate(shown): + row_y = 140 + i * 30 + if i == self.selected_result: + pygame.draw.rect(self.screen, (90, 90, 0), (16, row_y - 3, self.display_width - 32, 26)) + color = (255, 255, 0) if i == self.selected_result else (200, 200, 200) + title = entry.get('title', 'No title')[:60] + line = self.font.render(f"{i+1}. {title}", True, color) + self.screen.blit(line, (20, row_y)) + hint_y = 140 + len(shown) * 30 + 6 + hint = self.font.render("\u2191\u2193 Select Enter Confirm 1-5 Quick pick Esc Cancel", True, (150, 150, 150)) + self.screen.blit(hint, (20, hint_y)) + elif self.music_search_pending: + text = self.font.render("Searching...", True, (200, 200, 200)) + self.screen.blit(text, (20, 140)) + else: + text = self.font.render("No results. Type to search again, Enter to search", True, (200, 200, 200)) + self.screen.blit(text, (20, 140)) + + # TTS box + if self.typing_mode: + s = pygame.Surface((self.display_width, 40)) + s.set_alpha(180) + s.fill((0,0,0)) + self.screen.blit(s, (0, self.display_height-40)) + text = self.font.render(f"Say: {self.chat_message}", True, (255,255,255)) + self.screen.blit(text, (10, self.display_height-30)) + + pygame.display.flip() + self.clock.tick(0) + except Exception as e: + print(f"\u26a0\ufe0f Frame error (continuing): {e}") + finally: + print("šŸ›‘ Shutting down...") + self._safe("stopMove", self.motion.stopMove) + self._safe("music stop", self.music.stop) + if self.audio_device: + self._safe("audio unsubscribe", lambda: self.audio_device.unsubscribe(self.audio_service_name)) + if getattr(self, "sound_receiver", None): + self._safe("sound receiver close", self.sound_receiver.close) + if hasattr(self, 'video') and self.video_client: + self._safe("video unsubscribe", lambda: self.video.unsubscribe(self.video_client)) + self._safe("rest", self.motion.rest) + pygame.quit() + + +if __name__ == "__main__": + MIC_CHANNELS = {"left": 1, "right": 2, "front": 3, "rear": 4} + + parser = argparse.ArgumentParser() + parser.add_argument("--ip", type=str, default="127.0.0.1") + parser.add_argument("--port", type=int, default=9559) + parser.add_argument("--audio-device-index", type=int, default=None) + parser.add_argument("--mic-channel", choices=MIC_CHANNELS.keys(), default="left") + parser.add_argument("--mic-gain", type=float, default=8.0) + parser.add_argument("--video-scale", type=float, default=2.0) + args = parser.parse_args() + + session = qi.Session() + try: + session.connect(f"tcp://{args.ip}:{args.port}") + print(f"āœ… Connected to {args.ip}") + except Exception as e: + print(f"āŒ Connection failed: {e}") + sys.exit(1) + + NaoTeleop(session, nao_ip=args.ip, + audio_device_index=args.audio_device_index, + mic_channel=MIC_CHANNELS[args.mic_channel], + mic_gain=args.mic_gain, + video_scale=args.video_scale).run() +