回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

xiaozhi webrtc+FireBeetle 2 esp32c5小智点灯 简单

头像 云天 2025.10.17 34 0

c5d1fe3093d8e15858b1a701530d20e2.png

80b0a1098ab941e7da73d169de697cfd.jpg

项目背景

本项目旨在构建一个基于WebRTC的AI互动智能照明控制系统,通过网页端的小智AI卡通少女形象与用户进行实时语音对话,并借助MCP(Model Context Protocol)服务实现对FireBeetle 2 ESP32-C5开发板连接LED灯带的智能控制。该系统融合了人工智能、物联网、实时音视频通信等前沿技术,为用户提供一种全新的、拟人化的智能家居交互体验。

技术实现

1. 小智WebRTC AI互动系统 小智WebRTC是一个开源的AI实时音视频互动项目,专注于打造具有情感温度的AI伴侣。该系统具备以下核心能力:

基于WebRTC的实时音视频通信,提供超低延迟的互动体验

Live2D动态卡通形象呈现,增强用户的亲和感和沉浸感

融合视觉多模态理解、智能问答与MCP控制能力

支持网页端直接访问,无需安装额外软件

2. MCP(Model Context Protocol)服务 MCP是一种新兴的标准化协议,专门用于AI模型与物联网设备之间的高效通信。通过MCP服务,可以实现:

标准化的设备能力描述和发现机制

自然语言到设备控制命令的转换

实时的设备状态监控和数据获取

低延迟、高可靠性的设备控制

3. FireBeetle 2 ESP32-C5开发板 FireBeetle 2 ESP32-C5是DFRobot推出的高性能物联网开发板,具备:

ESP32-C5芯片:240MHz RISC-V 32位单核处理器

双频Wi-Fi 6(2.4GHz和5GHz)和蓝牙5.0连接

19个可编程GPIO引脚,支持多种外设接口

丰富的PWM控制器,特别适合LED灯带控制

支持MicroPython开发,降低开发门槛

【配置XiaoZhi WebRTC】

一个基于 **WebRTC** 的 AI 实时音视频互动项目,致力于打造你的专属、贴心且充满温度的情感伴侣。

---

## ✨ 功能特性
- **XiaoZhi 核心能力**:融合视觉多模态理解、智能问答与 MCP 控制,带来更强大的交互与处理能力。
- **实时音视频沟通**:超低延迟与高清体验,让交流顺畅自然。
- **Live2D 动态呈现**:拟真互动与沉浸式表现,提升亲和力与互动感。
---

## 🎯 在线体验

[https://xiaozhi.dairoot.cn](https://xiaozhi.dairoot.cn)

> 💡 **提示**: 由于部署在海外服务器,访问会稍微卡顿(仅体验)

https://github.com/user-attachments/assets/d985aacc-b07d-4874-a10a-c2139bd6c4bf

---

## 🚀 快速开始

```bash
# 克隆项目
git clone https://github.com/dairoot/xiaozhi-webrtc.git
cd xiaozhi-webrtc
```

#### 方法一:使用 uv(推荐)

```bash
# 安装 uv
#powershell
#临时用(一次性)
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple uv
#或者给当前用户永久换源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install uv

pip install uv

# 安装项目依赖
uv sync

# 运行项目
uv run main.py
```

#### 方法二:使用 Docker

```bash
# 使用 Docker Compose 运行
docker compose up
```

#### 方法三:传统 pip 安装

```bash
# 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# 或 venv\Scripts\activate # Windows

#(如出现如下错误——source : 无法将“source”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径
正确,然后再试一次。
所在位置 行:1 字符: 1
+ source venv/bin/activate
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (source:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

解决办法:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\venv\Scripts\Activate.ps1
# 安装依赖
pip install -e .

# 运行项目
python main.py
```
---

## ⚙️ 部署要求

### 端口要求

WebRTC 需要以下端口用于实时音视频通信:

| 端口 | 协议 | 用途 |
|------|------|------|
| 3478 | UDP | STUN 服务 |
| 49152–65535 | UDP | WebRTC 媒体流端口(默认) |

**注意:** 确保防火墙允许这些端口的通信,特别是在生产环境中部署时。


### HTTPS 要求

**线上环境必须使用 HTTPS**:WebRTC 需要访问摄像头和麦克风,现代浏览器出于安全考虑只允许在 HTTPS 环境下使用这些功能。

## 📖 使用说明

1. **启动服务**: 运行项目后,服务将在 `http://localhost:51000` 或者 `https://yourdomain.com` 启动
2. **访问页面**: 在浏览器中打开上述地址
3. **授权权限**: 允许浏览器访问摄像头和麦克风
4. **开始通信**: 点击开始按钮建立 WebRTC 连接

**注意**: 生产环境必须使用 HTTPS,否则 WebRTC 功能将无法正常工作。

4.在xiaozhi控制台注册,并获取mcp接入点

【FireBeetle 2 esp32c5程序】

#include

#include

#include

#include


#define PIN 3

#define NUMPIXELS 5

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);


// WiFi配置

const char* ssid = "************";

const char* password = "**************";


// MCP服务器配置

const char* mcpEndpoint = "你的mcp接入点";


// 创建MCP客户端实例

WebSocketMCP mcpClient;


// 效果控制变量

int currentEffect = 0;

unsigned long previousMillis = 0;

int effectPosition = 0;

String currentColor = "0,255,0"; // 默认绿色


// 连接状态回调函数

void onConnectionStatus(bool connected) {

if (connected) {

Serial.println("[MCP] 已连接到服务器");

registerMcpTools(); // 连接成功后注册工具

} else {

Serial.println("[MCP] 与服务器断开连接");

}

}


void setup() {

Serial.begin(115200);

pixels.begin();

pixels.show(); // 初始化时关闭所有灯

// 连接WiFi

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

Serial.print(".");

}

Serial.println("WiFi已连接");


// 初始化MCP客户端

mcpClient.begin(mcpEndpoint, onConnectionStatus);

}


void loop() {

mcpClient.loop(); // 处理MCP客户端事件

// 根据当前效果执行动画

switch(currentEffect) {

case 0: // 无效果 - 静态显示

break;

case 1: // 流水灯效果

runningLights();

break;

case 2: // 呼吸灯效果

breathingEffect();

break;

case 3: // 彩虹效果

rainbowCycle();

break;

case 4: // 警车灯效果

policeLights();

break;

case 5: // 颜色渐变效果

colorFade();

break;

}

delay(10);

}


// 注册MCP工具

void registerMcpTools() {

// 工具1: 控制单个灯

mcpClient.registerTool(

"neopixel_single",

"控制单个LED灯,参数:灯号(0-4),红色(0-255),绿色(0-255),蓝色(0-255),例如:设置第2个灯为红色",

"{\"type\":\"object\",\"properties\":{\"pixel\":{\"type\":\"number\",\"minimum\":0,\"maximum\":4},\"red\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255},\"green\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255},\"blue\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255}},\"required\":[\"pixel\",\"red\",\"green\",\"blue\"]}",

[](const String& args) {

DynamicJsonDocument doc(256);

deserializeJson(doc, args);

int pixel = doc["pixel"];

int red = doc["red"];

int green = doc["green"];

int blue = doc["blue"];

if (pixel >= 0 && pixel < NUMPIXELS) {

pixels.setPixelColor(pixel, pixels.Color(red, green, blue));

pixels.show();

currentEffect = 0; // 切换到静态模式

String response = "设置灯" + String(pixel) + "为颜色(R:" + String(red) + ",G:" + String(green) + ",B:" + String(blue) + ")";

return WebSocketMCP::ToolResponse("{\"success\":true,\"message\":\"" + response + "\"}");

} else {

return WebSocketMCP::ToolResponse("{\"success\":false,\"message\":\"灯号无效,请输入0-4\"}");

}

}

);


// 工具2: 控制所有灯

mcpClient.registerTool(

"neopixel_all",

"控制所有LED灯,参数:红色(0-255),绿色(0-255),蓝色(0-255),例如:设置所有灯为蓝色",

"{\"type\":\"object\",\"properties\":{\"red\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255},\"green\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255},\"blue\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255}},\"required\":[\"red\",\"green\",\"blue\"]}",

[](const String& args) {

DynamicJsonDocument doc(256);

deserializeJson(doc, args);

int red = doc["red"];

int green = doc["green"];

int blue = doc["blue"];

for(int i = 0; i < NUMPIXELS; i++) {

pixels.setPixelColor(i, pixels.Color(red, green, blue));

}

pixels.show();

currentEffect = 0; // 切换到静态模式

currentColor = String(red) + "," + String(green) + "," + String(blue);

String response = "设置所有灯为颜色(R:" + String(red) + ",G:" + String(green) + ",B:" + String(blue) + ")";

return WebSocketMCP::ToolResponse("{\"success\":true,\"message\":\"" + response + "\"}");

}

);


// 工具3: 开关控制

mcpClient.registerTool(

"neopixel_switch",

"开关所有LED灯,参数:状态(开灯、关灯)",

"{\"type\":\"object\",\"properties\":{\"state\":{\"type\":\"string\",\"enum\":[\"开灯\",\"关灯\"]}},\"required\":[\"state\"]}",

[](const String& args) {

DynamicJsonDocument doc(256);

deserializeJson(doc, args);

String state = doc["state"];

if (state == "开灯") {

// 使用当前颜色或默认绿色开灯

int red, green, blue;

sscanf(currentColor.c_str(), "%d,%d,%d", &red, &green, &blue);

for(int i = 0; i < NUMPIXELS; i++) {

pixels.setPixelColor(i, pixels.Color(red, green, blue));

}

pixels.show();

currentEffect = 0;

return WebSocketMCP::ToolResponse("{\"success\":true,\"message\":\"已开灯\"}");

} else if (state == "关灯") {

pixels.clear();

pixels.show();

currentEffect = 0;

return WebSocketMCP::ToolResponse("{\"success\":true,\"message\":\"已关灯\"}");

}

return WebSocketMCP::ToolResponse("{\"success\":false,\"message\":\"参数错误\"}");

}

);


// 工具4: 灯光效果

mcpClient.registerTool(

"neopixel_effect",

"设置LED灯光效果,参数:效果类型(流水灯、呼吸灯、彩虹灯、警车灯、颜色渐变、关闭效果)",

"{\"type\":\"object\",\"properties\":{\"effect\":{\"type\":\"string\",\"enum\":[\"流水灯\",\"呼吸灯\",\"彩虹灯\",\"警车灯\",\"颜色渐变\",\"关闭效果\"]}},\"required\":[\"effect\"]}",

[](const String& args) {

DynamicJsonDocument doc(256);

deserializeJson(doc, args);

String effect = doc["effect"];

if (effect == "流水灯") {

currentEffect = 1;

effectPosition = 0;

} else if (effect == "呼吸灯") {

currentEffect = 2;

} else if (effect == "彩虹灯") {

currentEffect = 3;

effectPosition = 0;

} else if (effect == "警车灯") {

currentEffect = 4;

effectPosition = 0;

} else if (effect == "颜色渐变") {

currentEffect = 5;

effectPosition = 0;

} else if (effect == "关闭效果") {

currentEffect = 0;

}

return WebSocketMCP::ToolResponse("{\"success\":true,\"message\":\"已切换到效果:" + effect + "\"}");

}

);


Serial.println("[MCP] NeoPixel控制工具已注册");

}


// ========== 灯光效果函数 ==========


// 流水灯效果

void runningLights() {

unsigned long currentMillis = millis();

if (currentMillis - previousMillis >= 150) {

previousMillis = currentMillis;

// 清除所有灯

for(int i = 0; i < NUMPIXELS; i++) {

pixels.setPixelColor(i, pixels.Color(0, 0, 0));

}

// 设置当前灯和下一个灯

int current = effectPosition % NUMPIXELS;

int next = (effectPosition + 1) % NUMPIXELS;

pixels.setPixelColor(current, pixels.Color(0, 150, 0)); // 主灯绿色

pixels.setPixelColor(next, pixels.Color(0, 50, 0)); // 下一个灯暗绿色

pixels.show();

effectPosition = (effectPosition + 1) % NUMPIXELS;

}

}


// 呼吸灯效果

void breathingEffect() {

unsigned long currentMillis = millis();

if (currentMillis - previousMillis >= 20) {

previousMillis = currentMillis;

// 使用正弦波计算亮度

float breath = (exp(sin(millis()/2000.0*PI)) - 0.36787944)*108.0;

uint32_t color = pixels.Color(0, breath, 0); // 绿色呼吸

for(int i = 0; i < NUMPIXELS; i++) {

pixels.setPixelColor(i, color);

}

pixels.show();

}

}


// 彩虹效果

void rainbowCycle() {

unsigned long currentMillis = millis();

if (currentMillis - previousMillis >= 100) {

previousMillis = currentMillis;

for(int i = 0; i < NUMPIXELS; i++) {

// 计算每个灯的颜色

int hue = (effectPosition * 256 / NUMPIXELS) + (i * 256 / NUMPIXELS);

uint32_t color = wheel(hue & 255);

pixels.setPixelColor(i, color);

}

pixels.show();

effectPosition = (effectPosition + 1) % 256;

}

}


// 警车灯效果

void policeLights() {

unsigned long currentMillis = millis();

if (currentMillis - previousMillis >= 300) {

previousMillis = currentMillis;

if (effectPosition == 0) {

// 红色闪烁

for(int i = 0; i < NUMPIXELS; i++) {

pixels.setPixelColor(i, pixels.Color(255, 0, 0));

}

effectPosition = 1;

} else {

// 蓝色闪烁

for(int i = 0; i < NUMPIXELS; i++) {

pixels.setPixelColor(i, pixels.Color(0, 0, 255));

}

effectPosition = 0;

}

pixels.show();

}

}


// 颜色渐变效果

void colorFade() {

unsigned long currentMillis = millis();

if (currentMillis - previousMillis >= 100) {

previousMillis = currentMillis;

// 在多个颜色间渐变

uint32_t colors[] = {

pixels.Color(255, 0, 0), // 红

pixels.Color(255, 255, 0), // 黄

pixels.Color(0, 255, 0), // 绿

pixels.Color(0, 255, 255), // 青

pixels.Color(0, 0, 255), // 蓝

pixels.Color(255, 0, 255) // 紫

};

int numColors = 6;

int currentColor = (effectPosition / 50) % numColors;

int nextColor = (currentColor + 1) % numColors;

float blendRatio = (effectPosition % 50) / 50.0;

uint32_t blended = colorBlend(colors[currentColor], colors[nextColor], blendRatio);

for(int i = 0; i < NUMPIXELS; i++) {

pixels.setPixelColor(i, blended);

}

pixels.show();

effectPosition = (effectPosition + 1) % (50 * numColors);

}

}


// ========== 辅助函数 ==========


// 生成彩虹色轮

uint32_t wheel(byte WheelPos) {

WheelPos = 255 - WheelPos;

if(WheelPos < 85) {

return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);

}

if(WheelPos < 170) {

WheelPos -= 85;

return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);

}

WheelPos -= 170;

return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);

}


// 颜色混合函数

uint32_t colorBlend(uint32_t color1, uint32_t color2, float ratio) {

int r1 = (color1 >> 16) & 0xFF;

int g1 = (color1 >> 8) & 0xFF;

int b1 = color1 & 0xFF;

int r2 = (color2 >> 16) & 0xFF;

int g2 = (color2 >> 8) & 0xFF;

int b2 = color2 & 0xFF;

int r = r1 + (r2 - r1) * ratio;

int g = g1 + (g2 - g1) * ratio;

int b = b1 + (b2 - b1) * ratio;

return pixels.Color(r, g, b);

}

42adf37b6f3ff735b521fa1b13d4fcc6.jpg
9b0aab9f4642a7e5e51a4a06bc8d6d0f.jpg
30d1700258d315c62302b6b5d6ecc408.jpg

项目创新点

1. 拟人化AI交互 通过小智AI卡通少女形象,为用户提供具有情感温度的交互体验。用户可以通过自然语言与AI对话,实现LED灯带的控制,如"小智,把灯光调成温暖的黄色"、"让灯带闪烁出彩虹效果"等。

2. 多模态交互融合 结合语音识别、自然语言处理、视觉呈现等多种技术手段,实现用户与AI之间的多维度交互。AI不仅能理解用户的语音指令,还能通过表情、动作等方式给予反馈。

3. 低延迟实时控制 利用WebRTC技术实现网页端与AI的实时音视频通信,通过MCP服务将AI的决策快速转化为设备控制指令,确保LED灯带能够实时响应用户的指令。

4. 标准化物联网架构 采用MCP协议作为AI与物联网设备之间的通信桥梁,实现设备能力的标准化描述和调用,为未来的功能扩展和设备接入奠定基础。

【演示视频】

评论

user-avatar