ESP32 + MCP over MQTT:通过大模型控制智能硬件设备

本系列教程路线图

篇章 功能 难度
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 后,设备控制的智能化能力大幅提升。它不仅能根据模糊语言推断对应函数调用,还能灵活应对不同厂商,以及硬件升级导致的接口变化,让设备的智能体验更加流畅丝滑。

程序执行过程

image.png

  1. 通过自然语言表达意图,LLM 负责理解语义。
  2. 将用户意图转化为结构化 MCP 工具调用(Tool Call), 并且提供合适的参数。
  3. 工具调用通过 MCP 协议被封装成标准的 MQTT 消息,经由 EMQX 转发到对应的 ESP32 设备。
  4. ESP 执行具体的控制逻辑代码。
  5. 将工具的调用结果传给 LLM,由 LLM 给用户返回相关的调用结果,并由 APP 展示给用户。

整个链路简单直接,并且具备良好的解耦性和可扩展性,便于快速集成多种设备。

自然语言调用 MCP 工具

硬件

上一篇文章所需硬件一致,无需调整。

软件

  • LlamaIndex 相关的 MCP tools 调用库文件。
  • MCP over MQTT 相关的库文件。
  • 大模型:本文选择的是 DeepSeek R1,是由 SiliconFlow 提供的公有服务;读者也可以根据自己的情况换成别的 LLM 服务提供商的服务,比如:阿里云提供的通义千问服务。
SiliconFlow 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 {
     
     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值