这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【涂鸦T5】使用MCPServer结合大模型控制LED灯

共1条 1/1 1 跳转至

【涂鸦T5】使用MCPServer结合大模型控制LED灯

工程师
2025-09-03 20:27:04     打赏

简介

最近半年来,相信热度很高的一个话题就是MCP,即模型上下文通信协议。现在很多AI 的客户端都支持MCP服务的直接调用。网上也有很多MCP server可以用。即可以使用大模型来控制本地的某些服务。那么在这篇文章中我将会带着大家手动来创建自己的MCP Server 并且通过Cherry Studio进行服务的调用使其控制T5 AI开发板上的LED灯进行闪烁。


MCP服务搭建

首先在使用MCPServer之前需要配置环境这里使用的是UV的包管理方式。

image.png

使用下述命令进行UV的安装

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

然后重启PowerShell并且检查UV的版本

image.png

至此UV安装完毕。然后我们来创建一个文件夹,并且初始化UV的环境变量并且增加MCP的依赖。

uv init --python 3.13 my-mcp-project

上述命令即使用UV来创建一个Py版本为3.13的MCP project。然后在项目中执行下面的命令

uv add "mcp[client]"

至此MCP的依赖已经安装完成。

然后将下面的代码拷贝到main中

from mcp.server.fastmcp import FastMCP
import paho.mqtt.client as mqtt

# 创建 MCP server
mcp = FastMCP("Demo")

# 配置 MQTT
BROKER = "broker.emqx.io"
PORT = 1883
TOPIC = "tuya/tos-test"

client = mqtt.Client()

def connect_mqtt():
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("已连接到 MQTT Broker!")
        else:
            print(f"连接失败, code={rc}")

    client.on_connect = on_connect
    client.connect(BROKER, PORT, 60)
    client.loop_start()

# 初始化时连接一次
connect_mqtt()

# 定义开关灯工具
@mcp.tool()
def switch_led(state: str) -> str:
    """
    开关LED灯
    参数:
        state: "ON" 打开LED灯, "OFF" 关闭LED灯
    """
    if state.upper() not in ["ON", "OFF"]:
        return "参数错误: 只能是 'ON' 或 'OFF'"

    client.publish(TOPIC, state.upper(), qos=0)
    print(f"已发送消息: {state.upper()} 到 {TOPIC}")
    return f"LED灯已切换为 {state.upper()}"

# 动态资源示例(保持不变)
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

其代码的主要行为为,定义了一个MCP的服务,即开灯或者关灯,通讯的方式采用的是流式的HTTP服务。其中连接到了公共的MQTT服务器来进行消息的发布。当MCP server收到大模型的命令的时候,其对应的switch_led就会向对应的主题发送消息。那么我们只需要在T5的开发板中订阅这个MQTT的消息即可完成整个MCP服务的调用。从而来控制LED灯的闪烁。

image.png

首先启动这个MCP Server,然后打开Cherry studio。进行MCP服务的配置。

image.png

类型选择HTTP,然后填上本机的MCP服务地址。之后点击更新即可。 然后我们尝试使用大模型来控制LED灯的闪烁。可以通过MQTT消息的发布状态来确定是否调用成功。或者增加状态日志的打印。

image.png可以看到现在的时间,已经成功的调用了MCP的服务,同时向MQTT发送了数据。

image.png

然后关闭LED。

image.png

至此MCP的服务搭建完毕。


涂鸦T5AI开发板 连接MQTT和GPIO控制

image.png

涂鸦在SDK的协议example下的MQTTdemo下已经有了完整的实现。我们这里只需要做一点小小的修改。 首先修改WIFI的账号和密码。然后引入下述的两个头文件。

#include "tkl_gpio.h"
#include "tal_api.h"

在MQTT消息接收回调函数中增加收到MQTT消息时对GPIO的控制。

static void mqtt_client_message_cb(void *client, uint16_t msgid, const mqtt_client_message_t *msg, void *userdata)
{
    PR_DEBUG("recv message TopicName:%s, payload len:%d", msg->topic, msg->length);

    // 检查是否是目标主题的消息
    if (strcmp(msg->topic, "tuya/tos-test") == 0) {
        // 将payload转换为字符串进行比较
        char *payload = (char *)msg->payload;

        // 检查消息内容是否为"ON"
        if (strncmp(payload, "ON", msg->length) == 0 || strncmp(payload, "on", msg->length) == 0) {
            // 打开LED灯
            tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_HIGH);
            PR_DEBUG("LED turned ON");
        }
        // 检查消息内容是否为"OFF"
        else if (strncmp(payload, "OFF", msg->length) == 0 || strncmp(payload, "off", msg->length) == 0) {
            // 关闭LED灯
            tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_LOW);
            PR_DEBUG("LED turned OFF");
        }
    }
}

然后在pub回调函数中移除对主题的取消订阅代码(否则的话,等任务运行完毕订阅会被自动取消)

static void mqtt_client_puback_cb(void *client, uint16_t msgid, void *userdata)
{
    PR_DEBUG("PUBACK successed ID:%d", msgid);
    // PR_DEBUG("UnSubscribe topic tuya/tos-test");
    // mqtt_client_unsubscribe(client, "tuya/tos-test", MQTT_QOS_0);

    // PR_DEBUG("MQTT Client Disconnect");
    // mqtt_client_disconnect(client);
}


然后在主程序中增加对GPIO 1 的初始化代码

    TUYA_GPIO_BASE_CFG_T out_pin_cfg = {
        .mode = TUYA_GPIO_PUSH_PULL, .direct = TUYA_GPIO_OUTPUT, .level = TUYA_GPIO_LEVEL_LOW};
    TUYA_CALL_ERR_LOG(tkl_gpio_init(TUYA_GPIO_NUM_1, &out_pin_cfg));
    int i = 10;
    for (int index = 0; index < i; index++) {
        tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_HIGH);
        tal_system_sleep(100);
        tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_LOW);
        tal_system_sleep(100);
    }

对应的GPIO pin可以查看下面的原理图

image.png

然后在主程序中移除或者注释掉主程序task删除任务的代码

static void tuya_app_thread(void *arg)
{
    user_main();

    while (1) {
        tal_system_sleep(1000); // 每秒睡眠 1s,降低 CPU 占用
    }
    // tal_thread_delete(ty_app_thread);
    // ty_app_thread = NULL;
}


完整的代码如下所示。

#include "tuya_cloud_types.h"
#include "mqtt_client_interface.h"
#include "tuya_config_defaults.h"
#include "core_mqtt_config.h"
#include "core_mqtt.h"
#include "tuya_transporter.h"
#include "backoff_algorithm.h"
#include "tal_api.h"
#include "tkl_output.h"
#include "lwip_init.h"
#include "netmgr.h"
#if defined(ENABLE_WIFI) && (ENABLE_WIFI == 1)
#include "netconn_wifi.h"
#include "tkl_gpio.h"
#endif
#if defined(ENABLE_WIRED) && (ENABLE_WIRED == 1)
#include "netconn_wired.h"
#endif

/***********************************************************
*********************** macro define ***********************
***********************************************************/
#ifdef ENABLE_WIFI
#define DEFAULT_WIFI_SSID "ImmortalWrt"
#define DEFAULT_WIFI_PSWD "mazha1997"
#endif
/***********************************************************
********************** typedef define **********************
***********************************************************/
typedef struct {
    mqtt_client_config_t config;
    MQTTContext_t mqclient;
    tuya_transporter_t network;
    uint8_t mqttbuffer[CORE_MQTT_BUFFER_SIZE];
} mqtt_client_context_t;

/***********************************************************
********************** variable define *********************
***********************************************************/
static netmgr_status_e netmgr_status = NETMGR_LINK_DOWN;

/***********************************************************
********************** function define *********************
***********************************************************/
static void mqtt_client_connected_cb(void *client, void *userdata)
{
    PR_INFO("mqtt client connected! try to subscribe tuya/tos-test");
    uint16_t msgid = mqtt_client_subscribe(client, "tuya/tos-test", MQTT_QOS_0);
    if (msgid <= 0) {
        PR_ERR("Subscribe failed!");
    }
    PR_DEBUG("Subscribe topic tuya/tos-test ID:%d", msgid);
}

static void mqtt_client_disconnected_cb(void *client, void *userdata)
{
    PR_INFO("mqtt client disconnected!");

    // PR_DEBUG("MQTT Client Deinit");
    // mqtt_client_deinit(client);
}

static void mqtt_client_message_cb(void *client, uint16_t msgid, const mqtt_client_message_t *msg, void *userdata)
{
    PR_DEBUG("recv message TopicName:%s, payload len:%d", msg->topic, msg->length);

    // 检查是否是目标主题的消息
    if (strcmp(msg->topic, "tuya/tos-test") == 0) {
        // 将payload转换为字符串进行比较
        char *payload = (char *)msg->payload;

        // 检查消息内容是否为"ON"
        if (strncmp(payload, "ON", msg->length) == 0 || strncmp(payload, "on", msg->length) == 0) {
            // 打开LED灯
            tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_HIGH);
            PR_DEBUG("LED turned ON");
        }
        // 检查消息内容是否为"OFF"
        else if (strncmp(payload, "OFF", msg->length) == 0 || strncmp(payload, "off", msg->length) == 0) {
            // 关闭LED灯
            tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_LOW);
            PR_DEBUG("LED turned OFF");
        }
    }
}

static void mqtt_client_subscribed_cb(void *client, uint16_t msgid, void *userdata)
{
    PR_DEBUG("Subscribe successed ID:%d", msgid);
    uint16_t new_msgid = mqtt_client_publish(client, "tuya/tos-test",
                                             (const uint8_t *)"hello, tuya-open-sdk-for-device", // 类型转换
                                             strlen("hello, tuya-open-sdk-for-device") + 1, MQTT_QOS_1);
    if (new_msgid <= 0) {
        PR_ERR("Publish failed!");
    }
    PR_DEBUG("Publish msg ID:%d", new_msgid);
}

static void mqtt_client_puback_cb(void *client, uint16_t msgid, void *userdata)
{
    PR_DEBUG("PUBACK successed ID:%d", msgid);
    // PR_DEBUG("UnSubscribe topic tuya/tos-test");
    // mqtt_client_unsubscribe(client, "tuya/tos-test", MQTT_QOS_0);

    // PR_DEBUG("MQTT Client Disconnect");
    // mqtt_client_disconnect(client);
}

static void mqtt_client_example(void)
{
    PR_DEBUG("start mqtt client to broker.emqx.io");

    /* MQTT Client init */
    mqtt_client_context_t mqtt_client = {0};
    mqtt_client_status_t mqtt_status;
    const mqtt_client_config_t mqtt_config = {.cacert = NULL,
                                              .cacert_len = 0,
                                              .host = "broker.emqx.io",
                                              .port = 1883,
                                              .keepalive = MQTT_KEEPALIVE_INTERVALIN,
                                              .timeout_ms = MATOP_TIMEOUT_MS_DEFAULT,
                                              .clientid = "tuya-open-sdk-for-device-01",
                                              .username = "emqx",
                                              .password = "public",
                                              .on_connected = mqtt_client_connected_cb,
                                              .on_disconnected = mqtt_client_disconnected_cb,
                                              .on_message = mqtt_client_message_cb,
                                              .on_subscribed = mqtt_client_subscribed_cb,
                                              .on_published = mqtt_client_puback_cb,
                                              .userdata = NULL};
    mqtt_status = mqtt_client_init(&mqtt_client, &mqtt_config);
    if (mqtt_status != MQTT_STATUS_SUCCESS) {
        PR_ERR("MQTT init failed: Status = %d.", mqtt_status);
        return;
    }

    mqtt_status = mqtt_client_connect(&mqtt_client);
    if (MQTT_STATUS_NOT_AUTHORIZED == mqtt_status) {
        PR_ERR("MQTT connect fail:%d", mqtt_status);
        return;
    }

    mqtt_client_yield(&mqtt_client);
}

/**
 * @brief  __link_status_cb
 *
 * @param[in] param:Task parameters
 * @return none
 */
OPERATE_RET __link_status_cb(void *data)
{
    PR_DEBUG("link status changed: %d", (netmgr_status_e)data);
    if (netmgr_status == (netmgr_status_e)data && NETMGR_LINK_UP == (netmgr_status_e)data)
        return OPRT_OK;

    netmgr_status = (netmgr_status_e)data;

    return OPRT_OK;
}

/**
 * @brief user_main
 *
 * @return void
 */
void user_main(void)
{
    OPERATE_RET rt = OPRT_OK; // 添加这一行

    /* basic init */
    tal_log_init(TAL_LOG_LEVEL_DEBUG, 1024, (TAL_LOG_OUTPUT_CB)tkl_log_output);

    PR_NOTICE("Application information:");
    PR_NOTICE("Project name:        %s", PROJECT_NAME);
    PR_NOTICE("App version:         %s", PROJECT_VERSION);
    PR_NOTICE("Compile time:        %s", __DATE__);
    PR_NOTICE("TuyaOpen version:    %s", OPEN_VERSION);
    PR_NOTICE("TuyaOpen commit-id:  %s", OPEN_COMMIT);
    PR_NOTICE("Platform chip:       %s", PLATFORM_CHIP);
    PR_NOTICE("Platform board:      %s", PLATFORM_BOARD);
    PR_NOTICE("Platform commit-id:  %s", PLATFORM_COMMIT);

    TUYA_GPIO_BASE_CFG_T out_pin_cfg = {
        .mode = TUYA_GPIO_PUSH_PULL, .direct = TUYA_GPIO_OUTPUT, .level = TUYA_GPIO_LEVEL_LOW};
    TUYA_CALL_ERR_LOG(tkl_gpio_init(TUYA_GPIO_NUM_1, &out_pin_cfg));
    int i = 10;
    for (int index = 0; index < i; index++) {
        tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_HIGH);
        tal_system_sleep(100);
        tkl_gpio_write(TUYA_GPIO_NUM_1, TUYA_GPIO_LEVEL_LOW);
        tal_system_sleep(100);
    }
    tal_kv_init(&(tal_kv_cfg_t){
        .seed = "vmlkasdh93dlvlcy",
        .key = "dflfuap134ddlduq",
    });
    tal_sw_timer_init();
    tal_workq_init();
    tal_event_subscribe(EVENT_LINK_STATUS_CHG, "mqtt_client", __link_status_cb, SUBSCRIBE_TYPE_NORMAL);

#if defined(ENABLE_LIBLWIP) && (ENABLE_LIBLWIP == 1)
    TUYA_LwIP_Init();
#endif

    // network init
    netmgr_type_e type = 0;
#if defined(ENABLE_WIFI) && (ENABLE_WIFI == 1)
    type |= NETCONN_WIFI;
#endif
#if defined(ENABLE_WIRED) && (ENABLE_WIRED == 1)
    type |= NETCONN_WIRED;
#endif
    netmgr_init(type);

#if defined(ENABLE_WIFI) && (ENABLE_WIFI == 1)
    netconn_wifi_info_t wifi_info = {0};
    // connect wifi
    strcpy(wifi_info.ssid, DEFAULT_WIFI_SSID);
    strcpy(wifi_info.pswd, DEFAULT_WIFI_PSWD);
    netmgr_conn_set(NETCONN_WIFI, NETCONN_CMD_SSID_PSWD, &wifi_info);
#endif

    while (1) {
        if (netmgr_status == NETMGR_LINK_UP) {
            mqtt_client_example();
            break;
        } else {
            tal_system_sleep(50);
        }
    }

    return;
}

/**
 * @brief main
 *
 * @param argc
 * @param argv
 * @return void
 */
#if OPERATING_SYSTEM == SYSTEM_LINUX
void main(int argc, char *argv[])
{
    user_main();
    while (1) {
        tal_system_sleep(500);
    }
}
#else

/* Tuya thread handle */
static THREAD_HANDLE ty_app_thread = NULL;

/**
 * @brief  task thread
 *
 * @param[in] arg:Parameters when creating a task
 * @return none
 */
static void tuya_app_thread(void *arg)
{
    user_main();

    while (1) {
        tal_system_sleep(1000); // 每秒睡眠 1s,降低 CPU 占用
    }
    // tal_thread_delete(ty_app_thread);
    // ty_app_thread = NULL;
}

void tuya_app_main(void)
{
    THREAD_CFG_T thrd_param = {4096, 4, "tuya_app_main"};
    tal_thread_create_and_start(&ty_app_thread, NULL, NULL, tuya_app_thread, NULL, &thrd_param);
}
#endif

然后将程序烧录到单片机中,并且使用tos.py monitor的方式监控日志。image.png

可以看到成功的订阅了主题 tuya/tos-test。 然后我们在CherryStudio中告诉GPT,让他帮我们打开LED灯。

image.png

LED已经被正确的打开。然后我们让他关闭LED

image.png


总结

从上述的效果看来搭建MCP的服务还是比较轻松的,这样如果本地要是有ASR或者TTS服务的话可以非常方便的将家中的智能设备都通过MCP进行集成,从而使用大模型进行控制。目前的效果上看来,不知道是因为FreeRTOS的原因还是因为免费的MQTT服务的原因。T5开发板在订阅几次消息之后便会出现无响应的情况。暂时没有发现时什么原因。这两天抽空再用ESP32来试一下。




关键词: 涂鸦     MCP     MQTT     T5     AI    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]