diff --git a/k230/agent_client_http.py b/k230/agent_client_http.py new file mode 100644 index 0000000..77c21e8 --- /dev/null +++ b/k230/agent_client_http.py @@ -0,0 +1,246 @@ +import socket +import json +import time +import os +import network +import struct +import gc +from audio_module import AudioPlayer + +# Try importing wave, handle different environments +try: + import media.wave as wave +except ImportError: + try: + import wave + except ImportError: + wave = None + + +class AgentClientHTTP: + """ + K230 Agent Client (HTTP) + """ + + def __init__(self, server_host, server_port=8000): + self._server_host = server_host + self._server_port = server_port + self._sta = None + self._is_connected = False + + def connect_wifi(self, ssid, password=None, timeout=15): + """ + Connect to WiFi + """ + self._sta = network.WLAN(network.STA_IF) + self._sta.active(True) + + if password: + self._sta.connect(ssid, password) + else: + self._sta.connect(ssid) + + print("正在连接WiFi: " + ssid + "...") + + start_time = time.time() + while not self._sta.isconnected(): + if time.time() - start_time > timeout: + raise RuntimeError("WiFi连接超时: " + ssid) + time.sleep(1) + try: + os.exitpoint() + except BaseException: + pass + + ip = self._sta.ifconfig()[0] + print("WiFi连接成功! IP: " + ip) + self._is_connected = True + return ip + + def disconnect(self): + """断开网络连接""" + if self._sta: + if self._sta.isconnected(): + self._sta.disconnect() + self._sta.active(False) + print("WiFi已断开,网卡已关闭。") + self._is_connected = False + + def trigger_abnormal_state(self, reason, context_data=None): + """ + Trigger abnormal state and save response as WAV. + """ + url_path = "/abnormal_trigger" + + payload = { + "type": "abnormal_trigger", + "trigger_reason": reason, + "enable_streaming": True, + "context_data": context_data or {} + } + + body_json = json.dumps(payload) + print(f"[AgentHTTP] 请求服务器: {self._server_host}:{self._server_port}...") + + try: + # Send Request + response_data = self._send_http_request(url_path, body_json) + + # Parse Header/Body + header_end = response_data.find(b"\r\n\r\n") + if header_end == -1: + print("[AgentHTTP] 响应格式错误") + return None + + body_bytes = response_data[header_end + 4:] + + if len(body_bytes) == 0: + print("[AgentHTTP] 响应正文为空") + return None + + print(f"[AgentHTTP] 成功接收音频数据: {len(body_bytes)} 字节") + + # Save as WAV file + # TTS server returns 24000Hz, 1 channel, 16bit (2 bytes) PCM + wav_file = "/sdcard/agent_response.wav" + self._save_wav(wav_file, body_bytes, rate=24000, channels=1, sampwidth=2) + + return wav_file + + except Exception as e: + print(f"[AgentHTTP] 请求失败: {e}") + return None + + def _save_wav(self, filename, pcm_data, rate=24000, channels=1, sampwidth=2): + """ + Save PCM data with WAV header + """ + print(f"[AgentHTTP] 保存 WAV 文件: {filename} (Rate: {rate}, Ch: {channels})") + try: + if wave: + # Use wave module if available + wf = wave.open(filename, 'wb') + wf.set_channels(channels) + wf.set_sampwidth(sampwidth) + wf.set_framerate(rate) + wf.write_frames(pcm_data) + wf.close() + else: + # Manual WAV header creation + total_len = len(pcm_data) + 36 + header = struct.pack( + '<4sI4s4sIHHIIHH4sI', + b'RIFF', + total_len, + b'WAVE', + b'fmt ', + 16, + 1, + channels, + rate, + rate * channels * sampwidth, + channels * sampwidth, + sampwidth * 8, + b'data', + len(pcm_data)) + with open(filename, 'wb') as f: + f.write(header) + f.write(pcm_data) + + except Exception as e: + print(f"[AgentHTTP] 保存 WAV 失败: {e}") + + def play_audio_file(self, file_path): + """ + Play audio file using AudioPlayer.play_file (handles WAV) + """ + if not file_path: + return + + print(f"[AgentHTTP] 正在播放: {file_path}...") + + player = None + try: + player = AudioPlayer() + # play_file will init and start stream, write frames, and then deinit in + # finally block + player.play_file(file_path) + + print("[AgentHTTP] 播放完成。") + + except Exception as e: + print(f"[AgentHTTP] 播放出错: {e}") + finally: + # Double check deinit to prevent noise/resource leak + if player: + try: + if player.is_running: # Check if flag exists + player.deinit() + except BaseException: + pass + + def _send_http_request(self, path, body_json): + """ + Send HTTP POST + """ + addr_info = socket.getaddrinfo(self._server_host, self._server_port) + addr = addr_info[0][-1] + + s = socket.socket() + s.settimeout(10) + + try: + s.connect(addr) + body_bytes = body_json.encode('utf-8') + + req = f"POST {path} HTTP/1.1\r\n" + req += f"Host: {self._server_host}:{self._server_port}\r\n" + req += "Content-Type: application/json\r\n" + req += f"Content-Length: {len(body_bytes)}\r\n" + req += "Connection: close\r\n\r\n" + + s.send(req.encode('utf-8')) + s.send(body_bytes) + + response = bytearray() + while True: + chunk = s.recv(4096) + if not chunk: + break + response.extend(chunk) + + return response + + finally: + s.close() + + +if __name__ == "__main__": + # ========== CONFIG ========== + WIFI_SSID = "dongshengwu" + WIFI_PASSWORD = "wds666666" + SERVER_HOST = "172.20.10.2" + SERVER_PORT = 8000 + # ============================ + + client = AgentClientHTTP(server_host=SERVER_HOST, server_port=SERVER_PORT) + + try: + # 1. Connect + client.connect_wifi(WIFI_SSID, WIFI_PASSWORD) + + # 2. Trigger & Save + print("\n[测试] 触发皮肤状态异常提示...") + wav_file = client.trigger_abnormal_state("poor_skin") + + # 3. Play + if wav_file: + client.play_audio_file(wav_file) + + except Exception as e: + print("程序异常: " + str(e)) + finally: + # 4. Clean up resources + print("\n[资源清理] 断开 WiFi 并回收资源...") + gc.collect() + print("测试结束,已安全退出。")