☰
50元自制AI桌面助手:ESP32-S3实现语音、鼠标与手机远程控制
2026/10/23 6:18:36 网站建设 项目流程

最近在折腾桌面自动化工具时,发现市面上的成品要么功能单一,要么价格昂贵。于是萌生了一个想法:能否用极低的成本,自己动手做一个功能全面的桌面机器人?它最好能听懂指令、能对话、能控制鼠标键盘,还能用手机远程操作。经过一番研究和实践,我发现用几十块钱的硬件加上开源软件,完全可以实现这个目标。本文将分享一套完整的制作方案,从硬件选型、固件烧录,到软件配置、功能集成,手把手带你打造一个集AI对话、AI控制、鼠标控制、手机控制、语音控制于一体的智能桌面助手。无论你是嵌入式新手,还是对桌面自动化感兴趣的开发者,都能跟着教程一步步实现。

1. 项目核心概念与硬件选型

1.1 什么是桌面机器人?

桌面机器人并非指一个在桌面上行走的实体机器人,而是一个运行在电脑上的智能代理程序。它通过监听用户的指令(语音、文本或手机指令),自动执行一系列桌面操作,例如打开应用、搜索信息、控制鼠标键盘、进行智能对话等。其核心价值在于将重复、繁琐的桌面操作自动化,并通过AI能力增强交互的智能性。

本项目实现的桌面机器人,其“大脑”是运行在电脑上的主控程序,“四肢”和“感官”则由一个廉价的微控制器(MCU)扩展实现。MCU负责模拟键盘鼠标(HID设备)、接收语音指令,并与电脑上的主控程序通信,共同完成复杂任务。

1.2 硬件清单与成本分析

实现本项目的核心是选择一个合适的MCU开发板。它需要具备USB HID(人机接口设备)功能,以模拟键盘鼠标;最好有麦克风接口或I2S接口,以支持语音输入;同时需要足够的GPIO和计算资源。

推荐方案:ESP32-S3 开发板

经过对比,ESP32-S3是一款性价比极高的选择。它双核处理器、Wi-Fi/蓝牙、USB OTG、丰富的IO口,并且原生支持USB HID和I2S。市面上常见的ESP32-S3开发板(如ESP32-S3-DevKitC-1)价格仅在30-50元人民币。

完整硬件清单:

组件型号/规格预估成本(元)作用
主控MCUESP32-S3 开发板35核心控制器,运行固件,模拟HID设备
麦克风模块INMP441 (I2S接口)8采集语音信号,用于语音控制
杜邦线母对母2连接开发板与麦克风模块
USB数据线Type-C5供电及与电脑通信
合计约 50

为什么选择ESP32-S3?

  1. 成本极低:核心板仅需几十元。
  2. 功能强大:集成USB OTG,可轻松模拟键盘、鼠标、游戏手柄等HID设备,无需额外芯片。
  3. 生态完善:Arduino、ESP-IDF等开发框架支持良好,有丰富的库和社区资源。
  4. 扩展性强:双核240MHz,支持Wi-Fi和蓝牙,为未来增加网络控制、蓝牙遥控等功能留有余地。

如果手头有ESP32-S2、RP2040(如Raspberry Pi Pico)等同样支持USB HID的开发板,也可以作为备选,但ESP32-S3在性能和接口上更为均衡。

2. 开发环境搭建与固件准备

2.1 软件环境准备

在开始编写和烧录固件前,需要在你的电脑上搭建开发环境。

1. 安装 Arduino IDEArduino IDE 是入门最简单的方式,它集成了代码编辑、编译和烧录功能。

  • 访问 Arduino 官网下载并安装最新版 Arduino IDE。
  • 安装后,打开文件->首选项,在“附加开发板管理器网址”中添加以下网址:
    https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  • 然后打开工具->开发板->开发板管理器,搜索esp32,找到并安装Espressif Systems提供的esp32开发板包。

2. 安装 CP210x/USB转串口驱动ESP32开发板通常通过USB转串口芯片(如CP2102、CH340)与电脑通信。请根据你的开发板型号,安装对应的驱动程序,以便电脑识别出串口。

3. 安装必要的库本项目需要用到以下Arduino库,可通过项目->加载库->管理库进行搜索安装:

  • USBHID(通常已内置)
  • WiFi(用于后续网络功能)
  • HTTPClient(用于后续网络功能)
  • ArduinoJson(用于处理JSON数据,与AI服务通信时使用)

2.2 固件源码结构与获取

本项目的固件主要包含以下几个核心模块:

  1. HID设备模拟:让电脑将ESP32识别为一个键盘和鼠标。
  2. 语音采集与识别:通过I2S麦克风采集音频,并发送到电脑端或云端进行识别。
  3. 串口通信协议:定义ESP32与电脑主控程序之间的指令格式。
  4. 网络服务(可选):提供HTTP API,供手机App或网页控制。

由于固件代码较长,我将分模块讲解核心逻辑。你可以通过文末提示的方式获取完整的、可编译的工程源码。

核心固件功能预览:

// 示例:模拟鼠标移动的核心函数 #include <USBHID.h> USBHID HID; void moveMouseRelative(int8_t x, int8_t y) { uint8_t report[4] = {0}; report[0] = 0; // 按钮状态 report[1] = constrain(x, -127, 127); // X轴位移 report[2] = constrain(y, -127, 127); // Y轴位移 report[3] = 0; // 滚轮 HID.SendReport(2, report, 4); // 发送鼠标报告 } // 示例:模拟键盘按键 void pressKey(uint8_t keycode) { uint8_t report[8] = {0}; report[2] = keycode; // 将按键码放入报告的第3个字节(标准HID键盘报告描述符) HID.SendReport(1, report, 8); delay(50); report[2] = 0; // 释放按键 HID.SendReport(1, report, 8); }

3. 固件开发详解:从HID模拟到语音采集

3.1 USB HID设备模拟实现

要让ESP32-S3被电脑识别为键盘鼠标,我们需要在固件中正确配置USB HID报告描述符。这是最核心的一步。

1. 配置HID报告描述符在Arduino项目中,我们通常通过USBHID库的配置来实现。以下是一个简化的键盘和鼠标复合设备的描述符配置示例:

#include <USBHID.h> static const uint8_t report_descriptor[] = { // 鼠标报告描述符 (用法页:通用桌面,用法:鼠标) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) // ... 省略详细字节,定义X,Y位移和按钮 // 键盘报告描述符 (用法页:键盘/键区) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) // ... 省略详细字节,定义按键码 }; USBHID HID(report_descriptor, sizeof(report_descriptor), 1, 100, false); // 创建HID实例 void setup() { HID.begin(); Serial.begin(115200); }

关键点:完整的报告描述符比较复杂,它定义了设备类型、数据格式和长度。在提供的完整源码中,会包含一个经过测试的、完整的描述符,确保能被Windows、macOS、Linux系统正确识别。

2. 实现基础控制函数基于配置好的HID实例,我们可以封装常用的控制函数。

// 鼠标控制类 class DesktopMouse { public: void move(int16_t dx, int16_t dy) { // 处理超出单次报告范围(-127~127)的移动 while(dx != 0 || dy != 0) { int8_t reportX = (dx > 127) ? 127 : (dx < -127) ? -127 : dx; int8_t reportY = (dy > 127) ? 127 : (dy < -127) ? -127 : dy; sendMouseReport(0, reportX, reportY, 0); dx -= reportX; dy -= reportY; } } void click(uint8_t button = MOUSE_LEFT) { sendMouseReport(button, 0, 0, 0); delay(10); sendMouseReport(0, 0, 0, 0); } void scroll(int8_t value) { sendMouseReport(0, 0, 0, value); } private: void sendMouseReport(uint8_t buttons, int8_t x, int8_t y, int8_t wheel) { uint8_t report[4] = {buttons, (uint8_t)x, (uint8_t)y, (uint8_t)wheel}; HID.SendReport(2, report, 4); // 假设鼠标报告ID是2 } }; // 键盘控制类 class DesktopKeyboard { public: void press(String keys) { // 简化示例:实际需要将字符串映射为HID键码并处理组合键 // 例如,实现 press(KEY_LEFT_CTRL, 'c') 进行复制 } void write(String text) { // 模拟逐字输入 for (char c : text) { pressKey(charToHidKey(c)); } } };

3.2 I2S麦克风语音采集

语音控制是本项目的亮点之一。我们使用INMP441这类数字麦克风,通过I2S接口将音频数据实时发送到电脑,由电脑上性能更强的AI服务进行语音识别(ASR)。

1. 硬件连接将INMP441模块与ESP32-S3连接:

  • INMP441 L/R->GND(选择左声道)
  • INMP441 DOUT->ESP32-S3 GPIO33(I2S数据线)
  • INMP441 BCLK->ESP32-S3 GPIO17(位时钟)
  • INMP441 LRCL->ESP32-S3 GPIO16(左右声道时钟)
  • INMP441 VDD->3.3V
  • INMP441 GND->GND

2. 固件中的音频采集代码

#include <driver/i2s.h> #define I2S_SAMPLE_RATE 16000 // 16kHz采样率,满足语音识别需求 #define I2S_BUFFER_SIZE 1024 void setupI2S() { i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = I2S_SAMPLE_RATE, .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = I2S_BUFFER_SIZE, .use_apll = false, .tx_desc_auto_clear = false, .fixed_mclk = 0 }; i2s_pin_config_t pin_config = { .bck_io_num = 17, .ws_io_num = 16, .data_out_num = I2S_PIN_NO_CHANGE, .data_in_num = 33 }; i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); i2s_set_pin(I2S_NUM_0, &pin_config); } void audioTask(void *parameter) { int32_t audio_buffer[I2S_BUFFER_SIZE]; size_t bytes_read; while(1) { // 从I2S读取音频数据 i2s_read(I2S_NUM_0, audio_buffer, sizeof(audio_buffer), &bytes_read, portMAX_DELAY); // 此处可以将audio_buffer通过串口或Wi-Fi发送给电脑 // 例如:Serial.write((uint8_t*)audio_buffer, bytes_read); // 注意:直接串口发送原始音频数据量很大,实际项目中需要压缩或分帧。 } } void setup() { Serial.begin(115200); setupI2S(); xTaskCreatePinnedToCore(audioTask, "Audio Task", 4096, NULL, 1, NULL, 1); }

重要提示:在原型阶段,可以通过串口将音频原始数据发送到电脑,但速率可能成为瓶颈。更成熟的方案是在ESP32上先进行压缩(如ADPCM),或通过Wi-Fi UDP流式传输。

3.3 串口通信协议设计

ESP32与电脑主控程序之间需要通过串口进行双向通信。我们需要定义一个简单高效的文本协议。

协议格式(示例):

  • 电脑 -> ESP32 (控制指令)CMD:MOVE, X:100, Y:-50\n// 移动鼠标CMD:CLICK, BTN:LEFT\n// 点击左键CMD:TYPE, TEXT:Hello World\n// 输入文本

  • ESP32 -> 电脑 (状态/数据上报)EVENT:VOICE_START\n// 开始拾音DATA:AUDIO, LEN:1024\n// 音频数据头(后跟二进制数据)STATUS:READY\n// 设备就绪

固件端协议解析示例:

String serialBuffer = ""; void loop() { // 读取串口指令 while (Serial.available()) { char c = Serial.read(); if (c == '\n') { parseCommand(serialBuffer); serialBuffer = ""; } else { serialBuffer += c; } } // ... 其他任务 } void parseCommand(String cmd) { cmd.trim(); if (cmd.startsWith("CMD:MOVE")) { // 解析参数,例如使用字符串查找“X:”和“Y:” int xStart = cmd.indexOf("X:") + 2; int xEnd = cmd.indexOf(',', xStart); int yStart = cmd.indexOf("Y:") + 2; int yEnd = cmd.length(); int x = cmd.substring(xStart, xEnd).toInt(); int y = cmd.substring(yStart).toInt(); mouse.move(x, y); Serial.println("ACK:MOVE_OK"); } else if (cmd.startsWith("CMD:CLICK")) { // 解析点击类型 mouse.click(); Serial.println("ACK:CLICK_OK"); } // ... 解析其他指令 }

4. 电脑端主控程序开发(Python示例)

桌面机器人的“大脑”是运行在电脑上的主控程序。它负责接收来自各方的指令(串口、网络、语音),调用AI服务,并最终通过串口向ESP32发送HID控制命令。这里以Python为例,因为它跨平台且开发效率高。

4.1 环境准备与项目结构

安装必要的Python库:

pip install pyserial # 串口通信 pip install requests # 网络请求,用于调用AI API pip install pyautogui # 备用桌面控制(可选) pip install SpeechRecognition pyaudio # 语音识别(本地备用)

项目目录结构:

desktop_robot/ ├── main.py # 主程序入口 ├── serial_controller.py # 串口通信封装 ├── ai_client.py # AI对话与控制逻辑 ├── voice_engine.py # 语音识别引擎 ├── web_api.py # 手机控制HTTP API └── config.yaml # 配置文件

4.2 串口通信模块

这个模块负责与ESP32固件通信。

# serial_controller.py import serial import serial.tools.list_ports import threading import time import logging class RobotSerialController: def __init__(self, port=None, baudrate=115200): self.ser = None self.port = port self.baudrate = baudrate self.is_connected = False self._find_and_connect() def _find_and_connect(self): """自动查找并连接ESP32设备""" if self.port: ports = [self.port] else: ports = [p.device for p in serial.tools.list_ports.comports()] # 可以加入更智能的筛选,例如通过USB PID/VID ports = [p for p in ports if 'USB' in p or 'ACM' in p] for port in ports: try: self.ser = serial.Serial(port, self.baudrate, timeout=1) time.sleep(2) # 等待设备初始化 self.ser.write(b'\n') # 发送测试指令 response = self.ser.readline().decode().strip() if 'READY' in response or response == '': self.is_connected = True self.port = port logging.info(f"Connected to robot at {port}") # 启动接收线程 threading.Thread(target=self._read_loop, daemon=True).start() return else: self.ser.close() except Exception as e: logging.warning(f"Failed to connect {port}: {e}") logging.error("Could not find or connect to robot.") def _read_loop(self): """持续读取串口数据(如音频流、状态上报)""" while self.is_connected and self.ser: try: if self.ser.in_waiting: line = self.ser.readline().decode().strip() if line: self._handle_incoming_data(line) except Exception as e: logging.error(f"Error reading serial: {e}") break def _handle_incoming_data(self, data): """处理从设备上报的数据""" if data.startswith('DATA:AUDIO'): # 处理音频数据头,准备接收二进制音频帧 pass elif data.startswith('EVENT:'): event = data.split(':')[1] logging.info(f"Device event: {event}") else: logging.debug(f"From device: {data}") def send_command(self, cmd_type, **kwargs): """发送指令到设备""" if not self.is_connected: logging.error("Not connected to device.") return False # 构建协议字符串,例如 CMD:MOVE, X:100, Y:50 if cmd_type == 'MOVE': cmd_str = f"CMD:MOVE, X:{kwargs['x']}, Y:{kwargs['y']}\n" elif cmd_type == 'CLICK': btn = kwargs.get('button', 'LEFT') cmd_str = f"CMD:CLICK, BTN:{btn}\n" elif cmd_type == 'TYPE': text = kwargs['text'].replace('\n', '\\n') cmd_str = f"CMD:TYPE, TEXT:{text}\n" else: logging.error(f"Unknown command type: {cmd_type}") return False try: self.ser.write(cmd_str.encode()) return True except Exception as e: logging.error(f"Failed to send command: {e}") self.is_connected = False return False def disconnect(self): self.is_connected = False if self.ser: self.ser.close()

4.3 AI对话与控制逻辑集成

这是项目的智能核心。我们可以接入大语言模型(LLM)的API,让机器人不仅能对话,还能理解自然语言指令并转化为桌面操作。

1. 调用AI API(以 OpenAI ChatGPT 格式为例)

# ai_client.py import requests import json import logging from typing import List, Dict, Any class AIClient: def __init__(self, api_key: str, base_url: str = "https://api.openai.com/v1"): self.api_key = api_key self.base_url = base_url self.headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } # 定义系统提示词,告诉AI它的角色和能力 self.system_prompt = """你是一个桌面助手机器人,可以控制鼠标、键盘,并执行电脑操作。 用户可能会用自然语言描述任务,你需要将其解析为明确的指令。 可用的指令类型有: - MOVE [x] [y]: 将鼠标移动到相对当前位置的(x, y)像素点。x, y为整数。 - CLICK [button]: 点击鼠标按钮。button可以是 LEFT, RIGHT, MIDDLE。 - DOUBLE_CLICK: 双击左键。 - SCROLL [amount]: 滚动滚轮。amount为正数向上,负数向下。 - TYPE [text]: 输入文本。 - PRESS [key1, key2...]: 按下组合键,如 ['ctrl', 'c']。 - OPEN [app_name]: 打开应用程序(需要预设映射)。 - SEARCH [query]: 在浏览器中搜索。 请根据用户请求,只输出一个JSON数组,每个元素是一个指令对象。例如: 用户说:“选中所有文字并复制” 你输出:[{"action": "PRESS", "args": ["ctrl", "a"]}, {"action": "PRESS", "args": ["ctrl", "c"]}] 用户说:“把鼠标往右移动100像素然后点击” 你输出:[{"action": "MOVE", "args": [100, 0]}, {"action": "CLICK", "args": ["LEFT"]}] 如果只是普通对话,不涉及控制,则输出空数组 []。 """ def chat_with_instruction(self, user_message: str) -> (str, List[Dict]): """ 与AI对话,并尝试解析出控制指令。 返回:(AI的回复文本, 指令列表) """ messages = [ {"role": "system", "content": self.system_prompt}, {"role": "user", "content": user_message} ] try: resp = requests.post( f"{self.base_url}/chat/completions", headers=self.headers, json={ "model": "gpt-3.5-turbo", "messages": messages, "temperature": 0.2, "max_tokens": 500 }, timeout=30 ) resp.raise_for_status() result = resp.json() ai_response_text = result['choices'][0]['message']['content'].strip() # 尝试从回复中提取JSON数组 instructions = [] # 简单查找JSON数组的开始和结束 start = ai_response_text.find('[') end = ai_response_text.rfind(']') + 1 if start != -1 and end != 0: try: instructions = json.loads(ai_response_text[start:end]) except json.JSONDecodeError: logging.warning("Failed to parse instructions from AI response.") # 返回纯文本回复和指令列表 return ai_response_text, instructions except requests.exceptions.RequestException as e: logging.error(f"AI API request failed: {e}") return "抱歉,AI服务暂时不可用。", []

2. 指令执行器

# command_executor.py import logging from serial_controller import RobotSerialController class CommandExecutor: def __init__(self, serial_controller: RobotSerialController): self.sc = serial_controller def execute_instructions(self, instructions: List[Dict]): """执行AI解析出来的指令列表""" for instr in instructions: action = instr.get('action') args = instr.get('args', []) try: if action == 'MOVE': self.sc.send_command('MOVE', x=args[0], y=args[1]) elif action == 'CLICK': self.sc.send_command('CLICK', button=args[0].upper()) elif action == 'TYPE': self.sc.send_command('TYPE', text=' '.join(args)) elif action == 'PRESS': # 将组合键转换为一系列按键操作(需在固件端实现组合键逻辑) # 简化处理:假设固件支持组合键指令 CMD:PRESS, KEYS:ctrl,c key_str = ','.join(args).lower() self.sc.send_command('PRESS', keys=key_str) # ... 处理其他指令类型 logging.info(f"Executed: {action} {args}") except Exception as e: logging.error(f"Failed to execute {action} {args}: {e}")

4.4 语音识别集成

我们可以使用离线的SpeechRecognition库(配合pyaudio)或在线API(如百度、科大讯飞)来实现语音转文本。

使用离线识别(需安装pyaudio和SpeechRecognition):

# voice_engine.py import speech_recognition as sr import logging import threading class VoiceEngine: def __init__(self, serial_controller, ai_client, command_executor): self.recognizer = sr.Recognizer() self.microphone = sr.Microphone() self.sc = serial_controller self.ai_client = ai_client self.executor = command_executor self.is_listening = False def start_listening(self): """开始后台语音监听线程""" self.is_listening = True thread = threading.Thread(target=self._listen_loop, daemon=True) thread.start() logging.info("Voice engine started.") def _listen_loop(self): with self.microphone as source: self.recognizer.adjust_for_ambient_noise(source, duration=1) while self.is_listening: try: logging.debug("Listening...") audio = self.recognizer.listen(source, timeout=3, phrase_time_limit=5) text = self.recognizer.recognize_google(audio, language='zh-CN') logging.info(f"Recognized: {text}") # 将识别文本交给AI处理 ai_response, instructions = self.ai_client.chat_with_instruction(text) logging.info(f"AI says: {ai_response}") if instructions: self.executor.execute_instructions(instructions) except sr.WaitTimeoutError: continue except sr.UnknownValueError: logging.debug("Could not understand audio.") except sr.RequestError as e: logging.error(f"Speech recognition error: {e}") except Exception as e: logging.error(f"Unexpected error in listen loop: {e}") def stop_listening(self): self.is_listening = False

4.5 手机控制HTTP API

通过一个简单的HTTP服务器,我们可以用手机浏览器或专用App发送控制指令。

# web_api.py from flask import Flask, request, jsonify import threading import logging class WebControlAPI: def __init__(self, serial_controller, command_executor, host='0.0.0.0', port=5000): self.sc = serial_controller self.executor = command_executor self.app = Flask(__name__) self.host = host self.port = port self._setup_routes() def _setup_routes(self): @self.app.route('/api/control', methods=['POST']) def control(): data = request.json action = data.get('action') args = data.get('args', []) if action == 'move': self.sc.send_command('MOVE', x=args[0], y=args[1]) elif action == 'click': self.sc.send_command('CLICK', button=args[0]) elif action == 'type': self.sc.send_command('TYPE', text=args[0]) # ... 其他指令映射 return jsonify({'status': 'ok'}) @self.app.route('/api/ai_chat', methods=['POST']) def ai_chat(): text = request.json.get('text') # 这里需要接入ai_client,为简化示例直接返回 return jsonify({'reply': f'Received: {text}'}) @self.app.route('/') def index(): return ''' <html><body> <h1>Desktop Robot Control</h1> <button onclick="move(0, -50)">Up</button><br> <button onclick="move(-50,0)">Left</button> <button onclick="click()">Click</button> <button onclick="move(50,0)">Right</button><br> <button onclick="move(0,50)">Down</button> <script> function move(x,y){fetch('/api/control',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'move',args:[x,y]})});} function click(){fetch('/api/control',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'click',args:['LEFT']})});} </script> </body></html> ''' def run(self): """在独立线程中运行Flask服务器""" thread = threading.Thread(target=self.app.run, kwargs={'host': self.host, 'port': self.port, 'debug': False, 'use_reloader': False}) thread.daemon = True thread.start() logging.info(f"Web control API started at http://{self.host}:{self.port}")

4.6 主程序整合

最后,我们将所有模块整合到一个主程序中。

# main.py import logging import time from serial_controller import RobotSerialController from ai_client import AIClient from command_executor import CommandExecutor from voice_engine import VoiceEngine from web_api import WebControlAPI logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def main(): # 1. 初始化串口控制器(连接ESP32) sc = RobotSerialController() # 自动查找端口 if not sc.is_connected: logging.error("Failed to connect to robot hardware. Exiting.") return # 2. 初始化AI客户端(需要配置你的API Key) ai = AIClient(api_key="your_openai_api_key_here") # 请替换为实际Key # 3. 初始化指令执行器 executor = CommandExecutor(sc) # 4. 初始化语音引擎(可选) voice_engine = VoiceEngine(sc, ai, executor) # voice_engine.start_listening() # 如需语音控制,取消注释 # 5. 初始化Web API(手机控制) web_api = WebControlAPI(sc, executor, host='0.0.0.0', port=5000) web_api.run() logging.info("Desktop Robot started successfully!") logging.info("Control via: 1. Serial Command 2. Voice 3. Web UI http://<your_ip>:5000") # 主循环,保持程序运行 try: while True: # 这里可以添加其他轮询任务,例如从GUI读取指令 time.sleep(1) except KeyboardInterrupt: logging.info("Shutting down...") sc.disconnect() if __name__ == "__main__": main()

5. 完整部署与使用流程

5.1 硬件组装与固件烧录

  1. 硬件连接:使用杜邦线将INMP441麦克风模块按照前述引脚定义连接到ESP32-S3开发板。
  2. 固件烧录:
    • 在Arduino IDE中选择开发板:ESP32S3 Dev Module。
    • 选择正确的端口(Port)。
    • 将完整的固件代码(包含HID描述符、I2S配置、串口协议解析)上传到开发板。
    • 上传成功后,打开串口监视器,波特率设置为115200,应看到[INFO] Robot Ready之类的启动日志。

5.2 电脑端环境配置与运行

  1. 安装依赖:在电脑上安装Python及上述列出的库 (pyserial,requests等)。
  2. 配置参数:
    • 在main.py中,将AIClient的api_key替换为你自己的OpenAI API Key(或其他兼容API的Key和URL)。
    • 检查serial_controller.py中的波特率是否与固件设置一致(默认115200)。
  3. 运行主程序:在项目根目录下执行python main.py。
  4. 验证连接:程序应自动找到ESP32设备并连接,打印成功日志。

5.3 功能测试

  1. 基础控制测试:在程序运行后,你可以在Python交互环境或修改main.py临时添加测试代码,发送指令:

    # 临时测试 sc.send_command('MOVE', x=100, y=100) sc.send_command('CLICK', button='LEFT')

    观察鼠标是否相应移动和点击。

  2. AI对话测试:修改main.py,在初始化后直接调用AI:

    reply, instr = ai.chat_with_instruction("帮我打开记事本") print(f"AI回复: {reply}") print(f"解析指令: {instr}") executor.execute_instructions(instr)

    观察AI是否解析出正确的操作指令(如打开记事本可能需要预设应用映射)。

  3. 语音控制测试:取消main.py中voice_engine.start_listening()的注释,对着麦克风说“向上移动鼠标”,观察鼠标是否移动。

  4. 手机控制测试:确保电脑和手机在同一局域网。在电脑上运行ipconfig(Windows) 或ifconfig(Linux/macOS) 查看本机IP。在手机浏览器输入http://<电脑IP>:5000,打开控制页面,点击按钮测试控制是否生效。

6. 常见问题与排查思路

在制作和运行过程中,你可能会遇到以下问题:

问题现象可能原因排查思路与解决方案
电脑无法识别ESP32为HID设备1. 固件中HID报告描述符错误。
2. USB数据线仅供电,不支持数据传输。
3. 驱动问题。
1. 检查并确保使用了正确的、完整的HID报告描述符。
2. 更换一根已知良好的数据线。
3. 在设备管理器中查看是否有未知设备,尝试重新安装ESP32-S3的USB驱动。
串口连接失败1. 端口被占用。
2. 波特率不匹配。
3. 驱动未安装。
1. 关闭其他可能占用串口的软件(如Arduino IDE串口监视器)。
2. 确认固件和电脑端程序使用相同的波特率(如115200)。
3. 安装CP210x或CH340驱动。
语音识别无反应或错误率高1. 麦克风连接错误或损坏。
2. 环境噪音太大。
3. 离线识别库不支持中文或精度低。
1. 用万用表检查麦克风供电,或用示波器/逻辑分析仪检查I2S信号。
2. 在安静环境下测试,或增加软件降噪。
3. 考虑换用在线语音识别API(如百度、讯飞),精度更高。
AI指令解析不准确1. 系统提示词(System Prompt)描述不清晰。
2. AI模型理解偏差。
3. 指令映射规则不完善。
1. 优化系统提示词,更详细地定义指令格式和示例。
2. 尝试让AI在回复中先复述理解的任务,再输出指令。
3. 在command_executor.py中增加更多指令类型和容错处理。
手机网页无法访问控制界面1. 电脑防火墙阻止了5000端口。
2. 手机和电脑不在同一网络。
3. Flask服务未成功启动。
1. 在电脑防火墙设置中允许5000端口的入站连接。
2. 确保手机连接的是同一个Wi-Fi。
3. 检查程序日志,确认Web control API started日志已打印。
鼠标移动不流畅或跳跃1. 串口指令发送频率过高或过低。
2. 单次移动位移过大,超出HID报告范围。
1. 在固件和电脑端代码中加入适当的延时(如time.sleep(0.01))。
2. 确保move函数正确处理了超出-127~127范围的移动,将其拆分为多次小移动。
程序占用CPU或内存过高1. 语音识别或AI API调用循环阻塞。
2. 线程未正确管理。
1. 为语音识别和网络请求等IO密集型操作使用异步(asyncio)或线程池。
2. 检查是否有死循环或资源未释放。使用logging监控各线程状态。

7. 优化方向与进阶玩法

完成基础版本后,你可以从以下几个方面进行优化和扩展,让你的桌面机器人更强大、更实用:

  1. 固件优化:

    • 低功耗模式:当无操作时,让ESP32进入轻睡眠,通过USB或特定串口指令唤醒。
    • 本地简单指令识别:在ESP32上集成TinyML框架(如TensorFlow Lite Micro),实现本地关键词唤醒(如“你好机器人”),减少对电脑的依赖。
    • 增加状态反馈:添加RGB LED或蜂鸣器,用不同灯光或声音表示“正在聆听”、“执行成功”、“错误”等状态。
  2. 电脑端功能增强:

    • 图形化界面(GUI):使用PyQt或Tkinter制作一个控制面板,集成语音开关、指令录制回放、自定义宏等功能。
    • 指令录制与宏:实现“录制”功能,记录一系列鼠标键盘操作,并保存为可重复执行的脚本。
    • 应用场景化:预设针对不同应用(如Photoshop、代码编辑器、游戏)的专用指令集和AI提示词。
    • 多AI模型支持:除了OpenAI,可以接入本地部署的Ollama、通义千问、文心一言等API,提供更多选择。
  3. 网络与远程控制强化:

    • 内网穿透:使用frp或ngrok将本地Web API暴露到公网,实现真正的远程控制。
    • 安全加固:为Web API添加简单的Token认证,防止被他人随意访问。
    • 开发手机App:用Flutter或React Native开发一个专用App,提供比网页更流畅的控制体验和手势操作。
  4. AI能力深化:

    • 视觉能力集成:使用电脑摄像头或额外USB摄像头,通过OpenCV和视觉AI模型,让机器人具备“看到”屏幕并理解内容的能力(如自动识别按钮位置)。
    • 上下文记忆:让AI记住之前的对话和操作,实现多轮交互和更复杂的任务规划。

这个项目最大的乐趣在于其可扩展性。几十元的硬件成本只是一个起点,通过软件和AI的赋能,它可以演化成高度个性化的生产力工具。你可以根据自己的需求,不断为其添加新的技能模块。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询