forked from xiaozhi/xiaozhi-esp32
Add wechat-mini-program support for otto-robot (#1444)
* otto v1.4.0 MCP 1.使用MCP协议控制机器人 2.gif继承lcdDisplay,避免修改lcdDisplay * otto v1.4.1 gif as components gif as components * electronBot v1.1.0 mcp 1.增加electronBot支持 2.mcp协议 3.gif 作为组件 4.display子类 * 规范代码 1.规范代码 2.修复切换主题死机bug * fix(ota): 修复 ottoRobot和electronBot OTA 升级崩溃问题 bug * 1.增加robot舵机初始位置校准 2.fix(mcp_sever) 超出范围异常捕获类型 bug * refactor: Update Electron and Otto emoji display implementations - Removed GIF selection from Kconfig for Electron and Otto boards. - Updated Electron and Otto bot versions to 2.0.4 in their respective config files. - Refactored emoji display classes to utilize EmojiCollection for managing emojis. - Enhanced chat label setup and status display functionality in both classes. - Cleaned up unused code and improved initialization logging for emoji displays. * Rename OTTO_ICON_FONT.c to otto_icon_font.c * Rename OTTO_ICON_FONT.c to otto_icon_font.c * refactor: Update Otto emoji display configurations and functionalities - Changed chat label text mode to circular scrolling for both Otto and Electron emoji displays. - Bumped Otto robot version to 2.0.5 in the configuration file. - Added new actions for Otto robot including Sit, WhirlwindLeg, Fitness, Greeting, Shy, RadioCalisthenics, MagicCircle, and Showcase. - Enhanced servo sequence handling and added support for executing custom servo sequences. - Improved logging and error handling for servo sequence execution. * refactor: Update chat label long mode for Electron and Otto emoji displays - Changed chat label text mode from wrap to circular scrolling for both Electron and Otto emoji displays. - Improved consistency in chat label setup across both implementations. * Update Otto robot README with new actions and parameters * Update Otto controller parameters for oscillation settings - Changed default oscillation period from 500ms to 300ms. - Increased default steps from 5.0 to 8.0. - Updated default amplitude from 20 degrees to 0 degrees. - Enhanced documentation with new examples for oscillation modes and sequences. * Fix default amplitude initialization in Otto controller to use a single zero instead of two digits. * chore: update txp666/otto-emoji-gif-component version to 1.0.3 in idf_component.yml * Refactor Otto controller - Consolidated movement actions into a unified tool for the Otto robot, allowing for a single action command with various parameters. - Removed individual movement tools (walk, turn, jump, etc.) and replaced them with a more flexible action system. * Enhance Otto robot functionality by adding WebSocket control server and IP address retrieval feature. Updated config to support WebSocket, and revised README to include new control options and usage examples.
This commit is contained in:
@@ -11,6 +11,14 @@ otto 机器人是一个开源的人形机器人平台,具有多种动作能力
|
||||
|
||||
- <a href="www.ottodiy.tech" target="_blank" title="otto官网">复刻教程</a>
|
||||
|
||||
### 微信小程序控制
|
||||
|
||||
<p align="center">
|
||||
<img width="300" src="https://youke1.picui.cn/s1/2025/11/17/691abaa8278eb.jpg" alt="微信小程序二维码">
|
||||
</p>
|
||||
|
||||
扫描上方二维码,使用微信小程序控制 Otto 机器人。
|
||||
|
||||
## 硬件
|
||||
- <a href="https://oshwhub.com/txp666/ottorobot" target="_blank" title="立创开源">立创开源</a>
|
||||
|
||||
@@ -49,29 +57,43 @@ otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇
|
||||
|
||||
### 动作
|
||||
|
||||
| MCP工具名称 | 描述 | 参数说明 |
|
||||
|-------------------|-----------------|---------------------------------------------------|
|
||||
| self.otto.walk_forward | 行走 | **steps**: 行走步数(1-100,默认3)<br>**speed**: 行走速度(500-1500,数值越小越快,默认1000)<br>**direction**: 行走方向(-1=后退, 1=前进,默认1)<br>**arm_swing**: 手臂摆动幅度(0-170度,默认50) |
|
||||
| self.otto.turn_left | 转身 | **steps**: 转身步数(1-100,默认3)<br>**speed**: 转身速度(500-1500,数值越小越快,默认1000)<br>**direction**: 转身方向(1=左转, -1=右转,默认1)<br>**arm_swing**: 手臂摆动幅度(0-170度,默认50) |
|
||||
| self.otto.jump | 跳跃 | **steps**: 跳跃次数(1-100,默认1)<br>**speed**: 跳跃速度(500-1500,数值越小越快,默认1000) |
|
||||
| self.otto.swing | 左右摇摆 | **steps**: 摇摆次数(1-100,默认3)<br>**speed**: 摇摆速度(500-1500,数值越小越快,默认1000)<br>**amount**: 摇摆幅度(0-170度,默认30) |
|
||||
| self.otto.moonwalk | 太空步 | **steps**: 太空步步数(1-100,默认3)<br>**speed**: 速度(500-1500,数值越小越快,默认1000)<br>**direction**: 方向(1=左, -1=右,默认1)<br>**amount**: 幅度(0-170度,默认25) |
|
||||
| self.otto.bend | 弯曲身体 | **steps**: 弯曲次数(1-100,默认1)<br>**speed**: 弯曲速度(500-1500,数值越小越快,默认1000)<br>**direction**: 弯曲方向(1=左, -1=右,默认1) |
|
||||
| self.otto.shake_leg | 摇腿 | **steps**: 摇腿次数(1-100,默认1)<br>**speed**: 摇腿速度(500-1500,数值越小越快,默认1000)<br>**direction**: 腿部选择(1=左腿, -1=右腿,默认1) |
|
||||
| self.otto.sit | 坐下 | 不需要参数 |
|
||||
| self.otto.showcase | 展示动作 | 不需要参数。串联执行多个动作:往前走3步、挥挥手、跳舞(广播体操)、太空步、摇摆、起飞、健身、往后走3步 |
|
||||
| self.otto.updown | 上下运动 | **steps**: 上下运动次数(1-100,默认3)<br>**speed**: 运动速度(500-1500,数值越小越快,默认1000)<br>**amount**: 运动幅度(0-170度,默认20) |
|
||||
| self.otto.whirlwind_leg | 旋风腿 | **steps**: 动作次数(3-100,默认3)<br>**speed**: 动作速度(100-1000,数值越小越快,建议300)<br>**amplitude**: 踢腿幅度(20-40度,默认30) |
|
||||
| self.otto.hands_up | 举手 * | **speed**: 举手速度(500-1500,数值越小越快,默认1000)<br>**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) |
|
||||
| self.otto.hands_down | 放手 * | **speed**: 放手速度(500-1500,数值越小越快,默认1000)<br>**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) |
|
||||
| self.otto.hand_wave | 挥手 * | **direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) |
|
||||
| self.otto.windmill | 大风车 * | **steps**: 动作次数(3-100,默认6)<br>**speed**: 动作周期(300-2000毫秒,数值越小越快,默认500)<br>**amplitude**: 振荡幅度(50-90度,默认70) |
|
||||
| self.otto.takeoff | 起飞 * | **steps**: 动作次数(5-100,默认5)<br>**speed**: 动作周期(200-600毫秒,数值越小越快,建议300)<br>**amplitude**: 振荡幅度(20-60度,默认40) |
|
||||
| self.otto.fitness | 健身 * | **steps**: 动作次数(3-100,默认5)<br>**speed**: 动作速度(500-2000毫秒,数值越小越快,默认1000)<br>**amplitude**: 振荡幅度(10-50度,默认25) |
|
||||
| self.otto.greeting | 打招呼 * | **direction**: 手部选择(1=左手, -1=右手,默认1)<br>**steps**: 动作次数(3-100,默认5) |
|
||||
| self.otto.shy | 害羞 * | **direction**: 方向(1=左, -1=右,默认1)<br>**steps**: 动作次数(3-100,默认5) |
|
||||
| self.otto.radio_calisthenics | 广播体操 * | 不需要参数 |
|
||||
| self.otto.magic_circle | 爱的魔力转圈圈 * | 不需要参数 |
|
||||
所有动作通过统一的 `self.otto.action` 工具调用,通过 `action` 参数指定动作名称。
|
||||
|
||||
| MCP工具名称 | 描述 | 参数说明 |
|
||||
|-----------|------|---------|
|
||||
| self.otto.action | 执行机器人动作 | **action**: 动作名称(必填)<br>**steps**: 动作步数(1-100,默认3)<br>**speed**: 动作速度(100-3000,数值越小越快,默认700)<br>**direction**: 方向参数(1/-1/0,默认1,根据动作类型不同含义不同)<br>**amount**: 动作幅度(0-170,默认30)<br>**arm_swing**: 手臂摆动幅度(0-170,默认50) |
|
||||
|
||||
#### 支持的动作列表
|
||||
|
||||
**基础移动动作**:
|
||||
- `walk` - 行走(需 steps/speed/direction/arm_swing)
|
||||
- `turn` - 转身(需 steps/speed/direction/arm_swing)
|
||||
- `jump` - 跳跃(需 steps/speed)
|
||||
|
||||
**特殊动作**:
|
||||
- `swing` - 左右摇摆(需 steps/speed/amount)
|
||||
- `moonwalk` - 太空步(需 steps/speed/direction/amount)
|
||||
- `bend` - 弯曲身体(需 steps/speed/direction)
|
||||
- `shake_leg` - 摇腿(需 steps/speed/direction)
|
||||
- `updown` - 上下运动(需 steps/speed/amount)
|
||||
- `whirlwind_leg` - 旋风腿(需 steps/speed/amount)
|
||||
|
||||
**固定动作**:
|
||||
- `sit` - 坐下(无需参数)
|
||||
- `showcase` - 展示动作(无需参数,串联执行多个动作)
|
||||
- `home` - 复位到初始位置(无需参数)
|
||||
|
||||
**手部动作**(需手部舵机支持,标记 *):
|
||||
- `hands_up` - 举手(需 speed/direction)*
|
||||
- `hands_down` - 放手(需 speed/direction)*
|
||||
- `hand_wave` - 挥手(需 direction)*
|
||||
- `windmill` - 大风车(需 steps/speed/amount)*
|
||||
- `takeoff` - 起飞(需 steps/speed/amount)*
|
||||
- `fitness` - 健身(需 steps/speed/amount)*
|
||||
- `greeting` - 打招呼(需 direction/steps)*
|
||||
- `shy` - 害羞(需 direction/steps)*
|
||||
- `radio_calisthenics` - 广播体操(无需参数)*
|
||||
- `magic_circle` - 爱的魔力转圈圈(无需参数)*
|
||||
|
||||
**注**: 标记 * 的手部动作仅在配置了手部舵机时可用。
|
||||
|
||||
@@ -79,28 +101,31 @@ otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇
|
||||
|
||||
| MCP工具名称 | 描述 | 返回值/说明 |
|
||||
|-------------------|-----------------|---------------------------------------------------|
|
||||
| self.otto.home | 复位机器人到初始位置 | 不需要参数 |
|
||||
| self.otto.stop | 立即停止所有动作并复位 | 停止当前动作并回到初始位置 |
|
||||
| self.otto.get_status | 获取机器人状态 | 返回 "moving" 或 "idle" |
|
||||
| self.otto.set_trim | 校准单个舵机位置 | **servo_type**: 舵机类型(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand)<br>**trim_value**: 微调值(-50到50度) |
|
||||
| self.otto.get_trims | 获取当前的舵机微调设置 | 返回所有舵机微调值的JSON格式 |
|
||||
| self.otto.get_ip | 获取机器人WiFi IP地址 | 返回IP地址和连接状态的JSON格式:`{"ip":"192.168.x.x","connected":true}` 或 `{"ip":"","connected":false}` |
|
||||
| self.battery.get_level | 获取电池状态 | 返回电量百分比和充电状态的JSON格式 |
|
||||
| self.otto.servo_sequences | 舵机序列自编程 | 支持分段发送序列,支持普通移动和振荡器两种模式。详见代码注释中的详细说明 |
|
||||
|
||||
**注**: `home`(复位)动作通过 `self.otto.action` 工具调用,参数为 `{"action": "home"}`。
|
||||
|
||||
### 参数说明
|
||||
|
||||
1. **steps**: 动作执行的步数/次数,数值越大动作持续时间越长
|
||||
2. **speed**: 动作执行速度/周期,**数值越小越快**
|
||||
`self.otto.action` 工具的参数说明:
|
||||
|
||||
1. **action** (必填): 动作名称,支持的动作见上方"支持的动作列表"
|
||||
2. **steps**: 动作执行的步数/次数(1-100,默认3),数值越大动作持续时间越长
|
||||
3. **speed**: 动作执行速度/周期(100-3000,默认700),**数值越小越快**
|
||||
- 大多数动作: 500-1500毫秒
|
||||
- 特殊动作可能有所不同(如旋风腿: 100-1000,起飞: 200-600等)
|
||||
- 具体范围请参考各动作的说明
|
||||
3. **direction**: 方向参数
|
||||
- 移动动作: 1=左/前进, -1=右/后退
|
||||
- 手部动作: 1=左手, -1=右手, 0=双手
|
||||
4. **amount/amplitude/arm_swing**: 动作幅度,范围根据动作而定(通常0-170度)
|
||||
- 0表示不摆动(适用于手臂摆动)
|
||||
- 数值越大幅度越大
|
||||
- 不同动作可能有不同的幅度范围限制
|
||||
4. **direction**: 方向参数(-1/0/1,默认1),根据动作类型不同含义不同:
|
||||
- **移动动作** (walk/turn): 1=前进/左转, -1=后退/右转
|
||||
- **方向动作** (bend/shake_leg/moonwalk): 1=左, -1=右
|
||||
- **手部动作** (hands_up/hands_down/hand_wave/greeting/shy): 1=左手, -1=右手, 0=双手(仅hands_up/hands_down支持0)
|
||||
5. **amount**: 动作幅度(0-170,默认30),数值越大幅度越大
|
||||
6. **arm_swing**: 手臂摆动幅度(0-170,默认50),仅用于 walk/turn 动作,0表示不摆动
|
||||
|
||||
### 动作控制
|
||||
- 每个动作执行完成后,机器人会自动回到初始位置(home),以便于执行下一个动作
|
||||
@@ -112,35 +137,50 @@ otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇
|
||||
|
||||
### MCP工具调用示例
|
||||
```json
|
||||
// 向前走3步
|
||||
{"name": "self.otto.walk_forward", "arguments": {}}
|
||||
// 向前走3步(使用默认参数)
|
||||
{"name": "self.otto.action", "arguments": {"action": "walk"}}
|
||||
|
||||
// 向前走5步,稍快一些
|
||||
{"name": "self.otto.walk_forward", "arguments": {"steps": 5, "speed": 800}}
|
||||
{"name": "self.otto.action", "arguments": {"action": "walk", "steps": 5, "speed": 800}}
|
||||
|
||||
// 左转2步,大幅度摆动手臂
|
||||
{"name": "self.otto.turn_left", "arguments": {"steps": 2, "arm_swing": 100}}
|
||||
// 左转2步,大幅度摆动手臂
|
||||
{"name": "self.otto.action", "arguments": {"action": "turn", "steps": 2, "arm_swing": 100}}
|
||||
|
||||
// 摇摆舞蹈,中等幅度
|
||||
{"name": "self.otto.swing", "arguments": {"steps": 5, "amount": 50}}
|
||||
{"name": "self.otto.action", "arguments": {"action": "swing", "steps": 5, "amount": 50}}
|
||||
|
||||
// 跳跃
|
||||
{"name": "self.otto.action", "arguments": {"action": "jump", "steps": 1, "speed": 1000}}
|
||||
|
||||
// 太空步
|
||||
{"name": "self.otto.action", "arguments": {"action": "moonwalk", "steps": 3, "speed": 800, "direction": 1, "amount": 30}}
|
||||
|
||||
// 挥左手打招呼
|
||||
{"name": "self.otto.hand_wave", "arguments": {"direction": 1}}
|
||||
{"name": "self.otto.action", "arguments": {"action": "hand_wave", "direction": 1}}
|
||||
|
||||
// 展示动作(串联多个动作)
|
||||
{"name": "self.otto.showcase", "arguments": {}}
|
||||
{"name": "self.otto.action", "arguments": {"action": "showcase"}}
|
||||
|
||||
// 坐下
|
||||
{"name": "self.otto.action", "arguments": {"action": "sit"}}
|
||||
|
||||
// 大风车动作
|
||||
{"name": "self.otto.windmill", "arguments": {"steps": 10, "amplitude": 80}}
|
||||
{"name": "self.otto.action", "arguments": {"action": "windmill", "steps": 10, "speed": 500, "amount": 80}}
|
||||
|
||||
// 起飞动作
|
||||
{"name": "self.otto.takeoff", "arguments": {"steps": 5, "speed": 300}}
|
||||
{"name": "self.otto.action", "arguments": {"action": "takeoff", "steps": 5, "speed": 300, "amount": 40}}
|
||||
|
||||
// 广播体操
|
||||
{"name": "self.otto.radio_calisthenics", "arguments": {}}
|
||||
{"name": "self.otto.action", "arguments": {"action": "radio_calisthenics"}}
|
||||
|
||||
// 立即停止
|
||||
// 复位到初始位置
|
||||
{"name": "self.otto.action", "arguments": {"action": "home"}}
|
||||
|
||||
// 立即停止所有动作并复位
|
||||
{"name": "self.otto.stop", "arguments": {}}
|
||||
|
||||
// 获取机器人IP地址
|
||||
{"name": "self.otto.get_ip", "arguments": {}}
|
||||
```
|
||||
|
||||
### 语音指令示例
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
"builds": [
|
||||
{
|
||||
"name": "otto-robot",
|
||||
"sdkconfig_append": []
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_HTTPD_WS_SUPPORT=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "otto_movements.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "settings.h"
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "OttoController"
|
||||
|
||||
@@ -818,6 +819,17 @@ public:
|
||||
",\"charging\":" + (charging ? "true" : "false") + "}";
|
||||
return status;
|
||||
});
|
||||
|
||||
mcp_server.AddTool("self.otto.get_ip", "获取机器人WiFi IP地址", PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
std::string ip = wifi_station.GetIpAddress();
|
||||
if (ip.empty()) {
|
||||
return "{\"ip\":\"\",\"connected\":false}";
|
||||
}
|
||||
std::string status = "{\"ip\":\"" + ip + "\",\"connected\":true}";
|
||||
return status;
|
||||
});
|
||||
|
||||
ESP_LOGI(TAG, "MCP工具注册完成");
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "power_manager.h"
|
||||
#include "system_reset.h"
|
||||
#include "wifi_board.h"
|
||||
#include "websocket_control_server.h"
|
||||
|
||||
#define TAG "OttoRobot"
|
||||
|
||||
@@ -28,6 +29,7 @@ private:
|
||||
LcdDisplay* display_;
|
||||
PowerManager* power_manager_;
|
||||
Button boot_button_;
|
||||
WebSocketControlServer* ws_control_server_;
|
||||
void InitializePowerManager() {
|
||||
power_manager_ =
|
||||
new PowerManager(POWER_CHARGE_DETECT_PIN, POWER_ADC_UNIT, POWER_ADC_CHANNEL);
|
||||
@@ -94,6 +96,25 @@ private:
|
||||
::InitializeOttoController();
|
||||
}
|
||||
|
||||
void InitializeWebSocketControlServer() {
|
||||
ESP_LOGI(TAG, "初始化WebSocket控制服务器");
|
||||
ws_control_server_ = new WebSocketControlServer();
|
||||
if (!ws_control_server_->Start(8080)) {
|
||||
ESP_LOGE(TAG, "Failed to start WebSocket control server");
|
||||
delete ws_control_server_;
|
||||
ws_control_server_ = nullptr;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "WebSocket control server started on port 8080");
|
||||
}
|
||||
}
|
||||
|
||||
void StartNetwork() override {
|
||||
WifiBoard::StartNetwork();
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
InitializeWebSocketControlServer();
|
||||
}
|
||||
|
||||
public:
|
||||
OttoRobot() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeSpi();
|
||||
@@ -101,6 +122,7 @@ public:
|
||||
InitializeButtons();
|
||||
InitializePowerManager();
|
||||
InitializeOttoController();
|
||||
ws_control_server_ = nullptr;
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
|
||||
191
main/boards/otto-robot/websocket_control_server.cc
Normal file
191
main/boards/otto-robot/websocket_control_server.cc
Normal file
@@ -0,0 +1,191 @@
|
||||
#include "websocket_control_server.h"
|
||||
#include "mcp_server.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_http_server.h>
|
||||
#include <sys/param.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
|
||||
static const char* TAG = "WSControl";
|
||||
|
||||
WebSocketControlServer* WebSocketControlServer::instance_ = nullptr;
|
||||
|
||||
WebSocketControlServer::WebSocketControlServer() : server_handle_(nullptr) {
|
||||
instance_ = this;
|
||||
}
|
||||
|
||||
WebSocketControlServer::~WebSocketControlServer() {
|
||||
Stop();
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
esp_err_t WebSocketControlServer::ws_handler(httpd_req_t *req) {
|
||||
if (instance_ == nullptr) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (req->method == HTTP_GET) {
|
||||
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
|
||||
instance_->AddClient(req);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
uint8_t *buf = NULL;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
|
||||
/* Set max_len = 0 to get the frame len */
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
|
||||
|
||||
if (ws_pkt.len) {
|
||||
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
|
||||
buf = (uint8_t*)calloc(1, ws_pkt.len + 1);
|
||||
if (buf == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to calloc memory for buf");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ws_pkt.payload = buf;
|
||||
/* Set max_len = ws_pkt.len to get the frame payload */
|
||||
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
|
||||
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE) {
|
||||
ESP_LOGI(TAG, "WebSocket close frame received");
|
||||
instance_->RemoveClient(req);
|
||||
free(buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
|
||||
if (ws_pkt.len > 0 && buf != nullptr) {
|
||||
buf[ws_pkt.len] = '\0';
|
||||
instance_->HandleMessage(req, (const char*)buf, ws_pkt.len);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported frame type: %d", ws_pkt.type);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool WebSocketControlServer::Start(int port) {
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = port;
|
||||
config.max_open_sockets = 7;
|
||||
|
||||
httpd_uri_t ws_uri = {
|
||||
.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = ws_handler,
|
||||
.user_ctx = nullptr,
|
||||
.is_websocket = true
|
||||
};
|
||||
|
||||
if (httpd_start(&server_handle_, &config) == ESP_OK) {
|
||||
httpd_register_uri_handler(server_handle_, &ws_uri);
|
||||
ESP_LOGI(TAG, "WebSocket server started on port %d", port);
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "Failed to start WebSocket server");
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebSocketControlServer::Stop() {
|
||||
if (server_handle_) {
|
||||
httpd_stop(server_handle_);
|
||||
server_handle_ = nullptr;
|
||||
clients_.clear();
|
||||
ESP_LOGI(TAG, "WebSocket server stopped");
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketControlServer::HandleMessage(httpd_req_t *req, const char* data, size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
ESP_LOGE(TAG, "Invalid message: data is null or len is 0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 4096) {
|
||||
ESP_LOGE(TAG, "Message too long: %zu bytes", len);
|
||||
return;
|
||||
}
|
||||
|
||||
char* temp_buf = (char*)malloc(len + 1);
|
||||
if (temp_buf == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory");
|
||||
return;
|
||||
}
|
||||
memcpy(temp_buf, data, len);
|
||||
temp_buf[len] = '\0';
|
||||
|
||||
cJSON* root = cJSON_Parse(temp_buf);
|
||||
free(temp_buf);
|
||||
|
||||
if (root == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to parse JSON");
|
||||
return;
|
||||
}
|
||||
|
||||
// 支持两种格式:
|
||||
// 1. 完整格式:{"type":"mcp","payload":{...}}
|
||||
// 2. 简化格式:直接是MCP payload对象
|
||||
|
||||
cJSON* payload = nullptr;
|
||||
cJSON* type = cJSON_GetObjectItem(root, "type");
|
||||
|
||||
if (type && cJSON_IsString(type) && strcmp(type->valuestring, "mcp") == 0) {
|
||||
payload = cJSON_GetObjectItem(root, "payload");
|
||||
if (payload != nullptr) {
|
||||
cJSON_DetachItemViaPointer(root, payload);
|
||||
McpServer::GetInstance().ParseMessage(payload);
|
||||
cJSON_Delete(payload);
|
||||
}
|
||||
} else {
|
||||
payload = cJSON_Duplicate(root, 1);
|
||||
if (payload != nullptr) {
|
||||
McpServer::GetInstance().ParseMessage(payload);
|
||||
cJSON_Delete(payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (payload == nullptr) {
|
||||
ESP_LOGE(TAG, "Invalid message format or failed to parse");
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
void WebSocketControlServer::AddClient(httpd_req_t *req) {
|
||||
int sock_fd = httpd_req_to_sockfd(req);
|
||||
if (clients_.find(sock_fd) == clients_.end()) {
|
||||
clients_[sock_fd] = req;
|
||||
ESP_LOGI(TAG, "Client connected: %d (total: %zu)", sock_fd, clients_.size());
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketControlServer::RemoveClient(httpd_req_t *req) {
|
||||
int sock_fd = httpd_req_to_sockfd(req);
|
||||
clients_.erase(sock_fd);
|
||||
ESP_LOGI(TAG, "Client disconnected: %d (total: %zu)", sock_fd, clients_.size());
|
||||
}
|
||||
|
||||
size_t WebSocketControlServer::GetClientCount() const {
|
||||
return clients_.size();
|
||||
}
|
||||
33
main/boards/otto-robot/websocket_control_server.h
Normal file
33
main/boards/otto-robot/websocket_control_server.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef WEBSOCKET_CONTROL_SERVER_H
|
||||
#define WEBSOCKET_CONTROL_SERVER_H
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <cJSON.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class WebSocketControlServer {
|
||||
public:
|
||||
WebSocketControlServer();
|
||||
~WebSocketControlServer();
|
||||
|
||||
bool Start(int port = 8080);
|
||||
|
||||
void Stop();
|
||||
|
||||
size_t GetClientCount() const;
|
||||
|
||||
private:
|
||||
httpd_handle_t server_handle_;
|
||||
std::map<int, httpd_req_t*> clients_;
|
||||
|
||||
static esp_err_t ws_handler(httpd_req_t *req);
|
||||
|
||||
void HandleMessage(httpd_req_t *req, const char* data, size_t len);
|
||||
void AddClient(httpd_req_t *req);
|
||||
void RemoveClient(httpd_req_t *req);
|
||||
static WebSocketControlServer* instance_;
|
||||
};
|
||||
|
||||
#endif // WEBSOCKET_CONTROL_SERVER_H
|
||||
|
||||
Reference in New Issue
Block a user