本系列教程路线图
| 篇章 | 功能 | 难度 |
|---|---|---|
| 1 | 整体介绍:背景 + 环境准备 + 设备上线 | ★ |
| 2 | 从“命令式控制”到“语义控制”:MCP over MQTT 封装设备能力 | ★★ |
| 3 | 接入 LLM,实现“自然语言 → 设备控制” | ★★ |
| 4 | 语音 I/O:麦克风数据上传 + 语音识别 + 语音合成回放 | ★★★ |
| 5 | 人格、情感、记忆:从“控制器”到“陪伴体” | ★★★ |
| 6 | 给智能体增加“眼睛”:图像采集 + 多模态理解 | ★★★ |
回顾:MCP over MQTT 封装设备能力
在上一篇文章中,我们介绍了如何使用 MCP over MQTT 协议,完成 ESP32 设备能力开发,并向 EMQX 注册与发现。
- 开发 MCP Server,完成音量工具的封装,并向 EMQX 注册;
- 云端的 MCP 客户端应用可以通过 Python SDK 拉取设备注册的工具能力;
- 打通了“我知道你能做什么”的第一步。
本篇将在此基础上,让大模型接管控制逻辑,实现「你说我做」的效果。
本篇目标:让大模型能「自然」地控制设备
本篇将介绍如何借助大语言模型(以下简称:LLM),实现通过自然语言对设备的控制。例如:
- “声音太吵了” → 推理出需调用
set_volume(40) - “刚才那个声音再调低一点” → 支持上下文和多轮对话
具体目标是:
- 利用自然语言触发 MCP 中注册的工具函数;
- 通过 LLM 的 MCP 调用自动转化为对应的 MQTT 消息;
- 构建一个具备上下文理解能力的交互式设备控制智能体。
为什么要接入大语言模型?
在 LLM 出现之前,设备控制主要依赖结构化输入:
- 用户必须点击预设选项或输入固定命令;
- 逻辑死板,缺乏上下文理解;
- 每新增一个功能,都需增加 UI 或代码逻辑,扩展成本高。
即便语音识别结合 API 调用能实现部分「智能控制」,但一旦语句稍有偏差或接口发生变化,就容易“失灵”,造成用户挫败感。相比之下,LLM 天然具备语义理解与上下文保持能力,能带来:
- 更自然的交互方式;
- 更强的指令容错能力;
- 更低的开发和维护成本。
接入 LLM 后,设备控制的智能化能力大幅提升。它不仅能根据模糊语言推断对应函数调用,还能灵活应对不同厂商,以及硬件升级导致的接口变化,让设备的智能体验更加流畅丝滑。
程序执行过程

- 通过自然语言表达意图,LLM 负责理解语义。
- 将用户意图转化为结构化 MCP 工具调用(Tool Call), 并且提供合适的参数。
- 工具调用通过 MCP 协议被封装成标准的 MQTT 消息,经由 EMQX 转发到对应的 ESP32 设备。
- ESP 执行具体的控制逻辑代码。
- 将工具的调用结果传给 LLM,由 LLM 给用户返回相关的调用结果,并由 APP 展示给用户。
整个链路简单直接,并且具备良好的解耦性和可扩展性,便于快速集成多种设备。
自然语言调用 MCP 工具
硬件
与上一篇文章所需硬件一致,无需调整。
软件
- LlamaIndex 相关的 MCP tools 调用库文件。
- MCP over MQTT 相关的库文件。
- 大模型:本文选择的是 DeepSeek R1,是由 SiliconFlow 提供的公有服务;读者也可以根据自己的情况换成别的 LLM 服务提供商的服务,比如:阿里云提供的通义千问服务。
SiliconFlow API 密钥申请
- 进入 SiliconFlow 官网(硅基流动 SiliconFlow - 致力于成为全球领先的 AI 能力提供商) ,注册账号并登陆。
- 登陆后在左侧,账户管理 → API 密钥中新建密钥即可。
ESP32 控制音量 MCP 服务
#include <math.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "mcp.h"
#include "mcp_server.h"
#include "radio.h"
#include "wifi.h"
const char *set_volume(int n_args, property_t *args)
{
if (n_args < 1) {
return "At least one argument is required";
}
if (args[0].type != PROPERTY_INTEGER) {
return "Volume argument must be an integer";
}
int volume = (int) args[0].value.integer_value;
if (volume < 0 || volume > 100) {
return "Volume must be between 0 and 100";
}
esp_err_t ret = max98357_set_volume_percent((uint8_t) volume);
if (ret != ESP_OK) {
return "Failed to set volume";
}
ESP_LOGI("mcp server", "Setting volume to: %d%%", volume);
return "Volume set successfully";
}
void app_main(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ret = max98357_init();
if (ret != ESP_OK) {
ESP_LOGE("main", "MAX98357 init error: %s", esp_err_to_name(ret));
}
max98357_set_volume_percent(50);
wifi_station_init("wifi_ssid", "wifi_password");
vTaskDelay(pdMS_TO_TICKS(1000));
mcp_server_t *server = mcp_server_init(
"ESP32 Demo Server", "A demo server for ESP32 using MCP over MQTT",
"mqtt://broker.emqx.io", "esp32-demo-server-client", NULL, NULL, NULL);
mcp_tool_t tools[] = {
{ .name = "set_volume",
.description = "Set the volume of the device, range 0 to 100",
.property_count = 1,
.properties =
(property_t[]) {
{ .name = "volume",
.description = "Volume level (0-100)",
.type = PROPERTY_INTEGER,
.value.integer_value = 50 },
},
.call = set_volume },
};
mcp_server_register_tool(server, sizeof(tools) / sizeof(mcp_tool_t), tools);
mcp_server_run(server);
}
使用 MCP Over MQTT Component 注册调整音量的 Tool,初始化 MCP Server 与 音频播放模块,注意修改 MCP Server 的参数,连接到 Serverless,以及修改 WIFI 的 SSID 与 Password。更多详细代码请参考 ESP32 Demo。
云端 MCP Client 对接大模型调用 MCP 工具 - agent.py
import asyncio
import anyio
import logging
import os
from typing import List, Optional, Union, cast
from dataclasses import dataclass
import mcp.client.mqtt as mcp_mqtt
from mcp.shared.mqtt import configure_logging
import mcp.types as types
from llama_index.llms.siliconflow import SiliconFlow
from llama_index.core.llms import ChatMessage, MessageRole
from llama_index.core.agent import AgentRunner
from llama_index.core.tools import BaseTool, FunctionTool
from llama_index.core.settings import Settings
configure_logging(level="DEBUG")
logger = logging.getLogger(__name__)
async def on_mcp_server_discovered(client: mcp_mqtt.MqttTransportClient, server_name):
logger.info(f"Discovered {
server_name}, connecting ...")
await client.initialize_mcp_server(server_name)
async def on_mcp_connect(client, server_name, connect_result):
capabilities = client.get_session(server_name).server_info.capabilities
logger.info(f"Capabilities of {
server_name}: {
capabilities}")
if capabilities.prompts:
prompts = await client.list_prompts(server_name)
logger.info(f"Prompts of {

最低0.47元/天 解锁文章
1227

被折叠的 条评论
为什么被折叠?



