小智AI二次开发:用MCP协议让AI语音助手控制GPIO外设

上一篇《环境搭建与烧录实操》把小智AI完整跑起来了,喊"你好小智"已经能对话。本篇进入二次开发:让小智AI真正"动手"——用 MCP 协议控制 GPIO,接一个 LED、一个舵机,说一句话就能开关。所有代码均来自小智AI开源固件(MIT协议),可在 xiaozhi-esp32 仓库对应文件逐行找到。

一、MCP 是什么:AI 控制硬件的"通用接口"

MCP(Model Context Protocol)是连接 AI 大模型与外部能力的开放协议,小智AI 采用它作为设备控制协议。简单说:后台大模型通过 MCP 发现设备有什么功能、并能调用这些功能。

完整控制链路:

你说话 → 小智AI 后台(LLM 决策)→ MCP 协议(WebSocket)→ ESP32 设备端 MCP Server → 工具回调 → GPIO 电平变化 → 外设动作

为什么选 MCP 而不是给每块板子写死接口:

  1. 工具自描述:设备注册"工具"(名称 + 描述 + 参数),大模型自动学会什么时候调用
  2. 标准协议:基于 JSON-RPC 2.0,后台不用为每个设备定制接口
  3. 跨设备通用:换开发板、换外设,后台侧零改动

二、动手前准备

项目要求说明
固件小智AI v2.x(ESP-IDF 5.4+)05 篇装好的环境直接用
开发板ESP32-S3(面包板 WiFi 版)或任何已跑通小智AI 的板子
外设LED + 限流电阻,或舵机官方演示灯位是 GPIO18
接线LED 正极→GPIO18,负极串电阻→GND面包板方案 LAMP_GPIO = GPIO18
控制面板xiaozhi.me 已绑定设备05 篇配网激活已完成

三、设备端:注册你的第一个 GPIO 工具

小智AI 面包板固件自带一个现成的灯控示例(LampController),注册了三个工具:self.lamp.get_state、self.lamp.turn_on、self.lamp.turn_off。完整代码在 boards/common/lamp_controller.h:

class LampController {
private:
    bool power_ = false;
    gpio_num_t gpio_num_;

public:
    LampController(gpio_num_t gpio_num) : gpio_num_(gpio_num) {
        // 1. 把 GPIO 配置成输出模式
        gpio_config_t config = {
            .pin_bit_mask = (1ULL << gpio_num_),
            .mode = GPIO_MODE_OUTPUT,
            .pull_up_en = GPIO_PULLUP_DISABLE,
            .pull_down_en = GPIO_PULLDOWN_DISABLE,
            .intr_type = GPIO_INTR_DISABLE,
        };
        ESP_ERROR_CHECK(gpio_config(&config));
        gpio_set_level(gpio_num_, 0);

        // 2. 注册 MCP 工具:查询灯状态
        mcp_server.AddTool("self.lamp.get_state", "Get the power state of the lamp",
            PropertyList(),
            [this](const PropertyList& properties) -> ReturnValue {
                return power_ ? "{\"power\": true}" : "{\"power\": false}";
            });

        // 3. 注册 MCP 工具:开灯
        mcp_server.AddTool("self.lamp.turn_on", "Turn on the lamp",
            PropertyList(),
            [this](const PropertyList& properties) -> ReturnValue {
                power_ = true;
                gpio_set_level(gpio_num_, 1);
                return true;
            });
    }
};

拆解要点:

  • GPIO 配置:gpio_config 把引脚设为推挽输出,gpio_set_level(引脚, 1/0) 拉高/拉低电平——这就是"控制外设"的全部本质
  • AddTool 签名:工具名(self.lamp.turn_on)+ 描述(写给大模型看的说明书)+ 参数列表 + 回调函数
  • 命名规范:用"模块.动作"分层命名,后台大模型靠名字和描述决定何时调用

注册一个带参数的工具(控制亮度 0~100):

mcp_server.AddTool("self.lamp.set_brightness", "Set the brightness of the lamp (0-100)",
    PropertyList({ Property("brightness", kPropertyTypeInteger, 0, 100) }),
    [this](const PropertyList& properties) -> ReturnValue {
        int brightness = properties["brightness"].value<int>();
        // 你的 PWM 控制代码
        return true;
    });

参数支持三种类型:kPropertyTypeBoolean(布尔)、kPropertyTypeInteger(整数,可设 min/max)、kPropertyTypeString(字符串)。

最后在板级初始化里注册(面包板 WiFi 版 compact_wifi_board.cc 的 InitializeTools):

void InitializeTools() {
    static LampController lamp(LAMP_GPIO);   // LAMP_GPIO = GPIO18
}

四、后台侧:工具是怎么被发现和调用的

设备连上后台后,后台通过三条 JSON-RPC 消息完成"认识设备 → 调用能力":

  1. initialize:握手,设备返回协议版本和设备信息
  2. tools/list:后台拉取设备支持的全部工具清单(名称 + 描述 + 参数 schema)
  3. tools/call:后台调用某个工具,传入参数,设备执行并返回结果

后台调用"开灯"的消息(后台 → 设备):

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.lamp.turn_on",
    "arguments": {}
  },
  "id": 3
}

设备执行 gpio_set_level(18, 1) 后返回:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [ { "type": "text", "text": "true" } ],
    "isError": false
  }
}

五、完整链路:对小智说"开灯"发生了什么

  1. 你说:"小智,把灯打开"
  2. 音频经麦克风上传到小智AI 后台
  3. 后台大模型看到工具清单里有 self.lamp.turn_on(描述"Turn on the lamp"),判定该调用它
  4. 后台通过 WebSocket 发 tools/call 到设备
  5. ESP32 设备端 MCP Server 找到工具回调,执行 gpio_set_level(GPIO18, 1)
  6. 灯亮,设备返回 true → 后台回复你"灯已经打开了"

整个链路的关键:大模型不需要知道 GPIO18 是什么,它只需要根据工具名和描述决定"该调用哪个工具"。这就是 MCP 让 AI 控制硬件的精髓——硬件能力被描述成大模型能理解的"工具"。

六、避坑清单

  1. 工具描述要写人话:描述是大模型决定何时调用的依据,"Turn on the lamp" 比 "Set GPIO high" 好——大模型不懂引脚,懂灯
  2. 工具名用"模块.动作":self.lamp.turn_on 这种命名让大模型一眼看懂归属
  3. GPIO 别接错:面包板方案的演示灯在 GPIO18,换引脚要改 LAMP_GPIO 宏
  4. 整数参数记得限范围:用 min/max 约束(如 0~100),防止大模型传越界值
  5. 回调返回要明确:返回 true/false 或 JSON,后台靠它判断"到底执行成功没有"
  6. 带电流的外设加驱动:LED 串限流电阻;舵机、电机别直接怼 GPIO,要用驱动板(GPIO 只能输出毫安级电流)
  7. 改完代码重新烧录:idf.py build && idf.py flash,重新配网激活(05 篇完整流程)

七、写在最后

从选型、接线、烧录到今天的 MCP 控制 GPIO,小智AI 已经从"能聊天"变成了"能动手"。下一篇预告:小智AI 控制舵机做实物(机械臂/小车方向)——把 MCP 工具接到舵机控制上,语音指挥实体设备动起来。

你在小智AI 上接了什么外设?GPIO 控制踩过什么坑?评论区聊聊。

本文代码与流程基于小智AI开源固件(MIT协议)与乐鑫 ESP-IDF 官方文档整理。本人嵌入式工程师(蓝牙音频/STM32/ESP32方向,3年原厂量产经验),本系列为个人技术记录,后续将陆续更新小智AI 二次开发系列教程。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐