找回密码
 立即注册
首页 业界区 业界 将SignalR移植到Esp32—让小智设备无缝连接.NET功能拓展 ...

将SignalR移植到Esp32—让小智设备无缝连接.NET功能拓展MCP服务

闻成 昨天 17:00
前言

这段时间迷上了手搓Esp32的小智聊天机器人,也用.NET为小智AI开发了一些MCP转接平台和MCP服务。小智ESP32本身就具备MCP能力,可以调用本地MCP工具和服务端MCP工具,并将结果返回给设备,这个功能一直都有。
如果你有手搓Esp32的硬件玩具打算,可以关注我的B站账号(绿荫阿广)https://space.bilibili.com/25228512
带你手搓玩具。
小智原本这套架构有个局限性:MCP工具执行完之后,只能同步返回结果或者通过异步邮件通知,设备无法被动接收服务端的消息。比如我想让服务端主动给设备推送一张图片、播放一段语音、或者发送一个文本通知,在之前的架构下是做不到的。
所以我就决定改造小智客户端,集成SignalR实时通信框架。这次改造的核心价值是:通过SignalR消息通道,让设备可以接收各种类型的消息(声音、图片、文本通知),服务端的MCP工具执行成功后,可以根据用户ID推送数据到对应的用户通道
整个改造涉及SignalR C++客户端的集成、JWT Token认证、扫码登录(基于ESP32本地MCP工具实现)、以及服务端消息推送逻辑。客户端代码都是C++实现的,不过现在AI辅助编程很强大,帮我节省了大量时间。
1.png

问题解答

Q: 为什么选择SignalR而不是直接用WebSocket?
A: 起初我确实考虑过直接用WebSocket,但SignalR提供了很多开箱即用的功能:

  • Hub抽象:服务端可以轻松实现群组管理,按用户ID推送消息,比如Clients.Group($"Users:{userId}").SendAsync("Notification", message)
  • 消息路由:不需要自己写消息分发逻辑,SignalR的Hub方法调用和事件推送已经很完善了
  • 类型化调用:相比原始WebSocket的字符串消息,SignalR提供了类似RPC的调用体验,代码更清晰
虽然ESP32没有现成的SignalR库,但我找到了微软官方的C++ SignalR客户端(半成品),将它与ESP32的WebSocket组件整合后,就能用上SignalR的这些特性了。至于SignalR自带的重连机制,我没用,小智有自己的循环重连逻辑,更可控一些。
Q: 改造的核心价值是什么?解决了什么问题?
A: 改造前,ESP32的MCP工具调用完成后,只能通过两种方式通知:

  • 同步返回:工具执行结果直接返回给调用方
  • 异步邮件:通过邮件发送执行结果
这两种方式都无法满足实时推送的需求。比如我想让服务端在生图完成后立即推送图片给设备显示,或者播放一段语音提示,之前的架构做不到。
改造后,通过SignalR建立了一条服务端到设备的实时消息通道

  • 服务端的MCP工具执行成功后,可以调用_hubContext.Clients.Group($"Users:{userId}").SendAsync("ShowImage", imageData)将图片推送给设备
  • 设备通过SignalR的事件监听接收消息:connection->on("ShowImage", [](const std::vector& args) { ... })
  • 支持推送任意类型的数据:文本、图片(Base64)、语音URL、JSON通知等
这才是这次改造的核心价值:让设备具备被动接收服务端消息的能力,而不仅仅是主动调用和同步返回。
Q: 扫码登录是怎么实现的?
A: 扫码登录功能是基于ESP32本地MCP工具实现的,这是小智的固有功能,我只是进行了拓展:

  • 设备启动时检查是否有JWT Token
  • 如果没有Token,调用本地MCP工具display_qrcode在屏幕上显示二维码
  • 二维码内容包含设备ID和服务端地址:https://mcp-server.com/device-login?deviceId=xxx
  • 用户用手机扫码,完成授权。
  • 设备获取Token后保存到NVS(Non-Volatile Storage),下次启动直接使用
这样就实现了设备的快速认证,用户体验很好。扫码认证的服务端是使用开源的keycloak做的,对接了设备认证类型。
2.png

名词解释

核心概念


  • SignalR:微软提供的实时通信框架,封装了WebSocket、Server‑Sent Events和长轮询等传输方式,支持Hub模型、自动重连与消息序列化。适合实现双向、低延迟的实时消息系统。将它移植到嵌入式设备时需考虑客户端实现的体积、内存消耗与线程模型。
  • Hub(集线器):SignalR的核心抽象,类似于MVC中的Controller。服务端通过Hub定义方法供客户端调用,客户端也可以注册事件监听服务端推送。例如ChatHub.SendMessage(user, message)就是一个典型的Hub方法。
  • MCP(Model Context Protocol):一种基于JSON-RPC 2.0的协议,用于定义客户端和服务端之间的工具调用规范。在IoT场景中,设备可以作为MCP Server暴露能力(如重启、显示图片),而云端服务作为MCP Client调用这些能力。
  • JSON-RPC 2.0:一种轻量级的远程过程调用协议,使用JSON编码。MCP协议基于此标准,定义了initialize、tools/list、tools/call等方法。每个请求必须包含jsonrpc: "2.0"、method、id字段。
ESP32相关


  • FreeRTOS:一个开源、轻量级的实时操作系统内核,常用于微控制器平台(如ESP32)。提供任务调度、优先级、互斥锁、信号量、队列、软件定时器等实时特性,便于在资源受限设备上实现并发与确定性行为。使用时需注意堆栈大小、中断安全和任务优先级设计。
  • ESP32 PSRAM:ESP32可选的外部伪静态RAM(Pseudo-SRAM),用于扩展设备可用内存(常见4MB/8MB/16MB)。适合存放大对象、图像缓存、网络缓冲和动态分配数据。在ESP-IDF中需启用并正确配置,分配时也可使用不同的堆区域(如heap_caps_malloc(size, MALLOC_CAP_SPIRAM))来控制放置与性能/DMA限制。
  • WebSocket:一种基于TCP的全双工通信协议,通过HTTP握手升级建立连接。SignalR默认优先使用WebSocket作为传输层,在ESP32上通过esp_websocket_client组件实现。需要注意的是ESP32的WebSocket客户端不支持自动重连,需要在应用层实现。
认证相关


  • Bearer Token:一种HTTP认证方案,将Token放在Authorization头中:Authorization: Bearer 。在SignalR中,通常将Token作为查询参数传递:/hub?access_token=YOUR_TOKEN
  • JWT(JSON Web Token):一种开放标准(RFC 7512),用于在各方之间安全地传输信息。在Verdure MCP中,使用Keycloak签发的JWT进行用户认证,Token中包含用户ID、角色、过期时间等Claim信息。
  • API Token:一种简单的认证方式,后续连接时携带此Token验证身份。Verdure MCP同时支持API Token和JWT两种方式。
3.png

核心技术架构

整个改造的架构可以用一张图说明:
  1. ┌──────────────────────┐                          ┌──────────────────────┐
  2. │   .NET MCP Service   │                          │   ESP32 Device       │
  3. │   (Verdure MCP)      │◄─────SignalR Hub────────►│   (小智客户端)       │
  4. │                      │                          │                      │
  5. │  ┌────────────────┐  │  ① JWT Token认证         │  ┌────────────────┐  │
  6. │  │  DeviceHub.cs  │  │◄─────────────────────────│  │  扫码登录      │  │
  7. │  │                │  │                          │  │  (本地MCP工具) │  │
  8. │  │ OnConnected    │  │                          │  └────────────────┘  │
  9. │  │ (验证Token)    │  │                          │          ↓           │
  10. │  └────────────────┘  │  ② 建立连接               │  ┌────────────────┐  │
  11. │          ↓           │◄─────────────────────────│  │ SignalR Client │  │
  12. │  ┌────────────────┐  │                          │  │ - connection   │  │
  13. │  │  群组管理      │  │                          │  │ - on() events  │  │
  14. │  │ Users:{userId} │  │                          │  └────────────────┘  │
  15. │  └────────────────┘  │                          │                      │
  16. │          ↓           │                          │                      │
  17. │  ┌────────────────┐  │  ③ MCP工具执行后推送     │  ┌────────────────┐  │
  18. │  │  消息推送      │  │─────────────────────────►│  │ 消息接收处理   │  │
  19. │  │ SendAsync()    │  │  ShowImage(imageData)    │  │ - 显示图片     │  │
  20. │  │                │  │  PlayAudio(audioUrl)     │  │ - 播放语音     │  │
  21. │  │                │  │  Notification(text)      │  │ - 显示通知     │  │
  22. │  └────────────────┘  │                          │  └────────────────┘  │
  23. └──────────────────────┘                          └──────────────────────┘
复制代码
关键流程:

  • 扫码登录:设备启动后,如果没有Token,调用本地MCP工具显示二维码,用户扫码后获取JWT Token
  • 建立连接:携带JWT Token连接SignalR Hub,服务端验证后加入用户群组Users:{userId}
  • 消息推送:服务端MCP工具执行完成后,通过SignalR将结果推送给设备

    • _hubContext.Clients.Group($"Users:{userId}").SendAsync("ShowImage", imageData)
    • 设备监听事件并处理:connection->on("ShowImage", handler)

这套架构的核心价值就是让服务端可以主动推送消息给设备,而不仅仅是等待设备轮询或同步返回。
开发环境准备

ESP32开发环境(VS Code方式)

最简单的方式是使用VS Code的ESP-IDF插件:

  • 安装VS Code和插件

    • 下载安装 Visual Studio Code
    • 安装扩展:Espressif IDF (搜索 esp-idf)

  • 配置ESP-IDF

    • 按F1打开命令面板,输入 ESP-IDF: Configure ESP-IDF Extension
    • 选择 Express 快速配置
    • 选择ESP-IDF版本(推荐v5.1或更高)
    • 等待安装完成(会自动下载工具链、Python环境等)

  • 创建/打开项目

    • F1 → ESP-IDF: Show Examples Projects
    • 或直接打开 esp-signalr-example 项目文件夹

  • 编译和烧录

    • 点击底部状态栏的 Build、Flash、Monitor 按钮
    • 或按快捷键:Ctrl+E B(编译)、Ctrl+E F(烧录)

这种方式比命令行简单很多,适合.NET开发者快速上手ESP32开发。
4.png

.NET开发环境

服务端使用.NET 10开发:
  1. # Windows: 下载安装器 https://dotnet.microsoft.com/download/dotnet/10.0
  2. # 验证安装
  3. dotnet --version  # 应该输出 10.0.x
复制代码
核心代码实现

本章节将代码分为示例代码实际整合代码两个部分进行讲解:

  • 示例代码:用于理解核心概念的简化版本,便于学习和快速上手
  • 实际整合代码:生产环境中的完整实现,包含完善的错误处理、状态管理等
关于示例仓库

为了帮助开发者快速上手ESP32的SignalR集成,我创建了一个完整的示例仓库:

<strong>
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册