otto新增动作和AI自定义编程动作MCP工具 (#1365)

* 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
This commit is contained in:
小鹏 2025-11-02 18:04:06 +08:00 committed by GitHub
parent 1f602fa3a0
commit e39a46c1a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 939 additions and 183 deletions

View File

@ -89,7 +89,7 @@ void ElectronEmojiDisplay::SetupChatLabel() {
chat_message_label_ = lv_label_create(container_);
lv_label_set_text(chat_message_label_, "");
lv_obj_set_width(chat_message_label_, width_ * 0.9); // 限制宽度为屏幕宽度的 90%
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); // 设置为自动换行模式
lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐
lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0);
SetTheme(LvglThemeManager::GetInstance().GetTheme("dark"));

View File

@ -21,8 +21,8 @@ otto 机器人是一个开源的人形机器人平台,具有多种动作能力
>
> **我的动作能力**
> - **基础移动**: 行走(前后), 转向(左右), 跳跃
> - **特殊动作**: 摇摆, 太空步, 弯曲身体, 摇腿, 上下运动
> - **手部动作**: 举手, 放手, 挥手 (仅在配置手部舵机时可用)
> - **特殊动作**: 摇摆, 太空步, 弯曲身体, 摇腿, 上下运动, 旋风腿, 坐下, 展示动作
> - **手部动作**: 举手, 放手, 挥手, 大风车, 起飞, 健身, 打招呼, 害羞, 广播体操, 爱的魔力转圈圈 (仅在配置手部舵机时可用)
>
> **我的个性特点**
> - 我有强迫症,每次说话都要根据我的心情随机做一个动作(先发送动作指令再说话)
@ -58,37 +58,57 @@ otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇
| 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 | 挥手 * | **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 | 爱的魔力转圈圈 * | 不需要参数 |
**注**: 标记 * 的手部动作仅在配置了手部舵机时可用。
### 系统工具
| MCP工具名称 | 描述 | 返回值 |
| MCP工具名称 | 描述 | 返回值/说明 |
|-------------------|-----------------|---------------------------------------------------|
| self.otto.stop | 立即停止 | 停止当前动作并回到初始位置 |
| 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.battery.get_level | 获取电池状态 | 返回电量百分比和充电状态的JSON格式 |
| self.otto.servo_sequences | 舵机序列自编程 | 支持分段发送序列,支持普通移动和振荡器两种模式。详见代码注释中的详细说明 |
### 参数说明
1. **steps**: 动作执行的步数/次数,数值越大动作持续时间越长
2. **speed**: 动作执行速度数值范围500-1500**数值越小越快**
2. **speed**: 动作执行速度/周期,**数值越小越快**
- 大多数动作: 500-1500毫秒
- 特殊动作可能有所不同(如旋风腿: 100-1000起飞: 200-600等
- 具体范围请参考各动作的说明
3. **direction**: 方向参数
- 移动动作: 1=左/前进, -1=右/后退
- 手部动作: 1=左手, -1=右手, 0=双手
4. **amount/arm_swing**: 动作幅度范围0-170度
4. **amount/amplitude/arm_swing**: 动作幅度,范围根据动作而定(通常0-170度
- 0表示不摆动适用于手臂摆动
- 数值越大幅度越大
- 不同动作可能有不同的幅度范围限制
### 动作控制
- 每个动作执行完成后,机器人会自动回到初始位置(home),以便于执行下一个动作
- **例外**: `sit`(坐下)和 `showcase`(展示动作)执行后不会自动复位
- 所有参数都有合理的默认值,可以省略不需要自定义的参数
- 动作在后台任务中执行,不会阻塞主程序
- 支持动作队列,可以连续执行多个动作
- 手部动作需要配置手部舵机才能使用,如果没有配置手部舵机,相关动作将被跳过
### MCP工具调用示例
```json
@ -107,6 +127,18 @@ otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇
// 挥左手打招呼
{"name": "self.otto.hand_wave", "arguments": {"direction": 1}}
// 展示动作(串联多个动作)
{"name": "self.otto.showcase", "arguments": {}}
// 大风车动作
{"name": "self.otto.windmill", "arguments": {"steps": 10, "amplitude": 80}}
// 起飞动作
{"name": "self.otto.takeoff", "arguments": {"steps": 5, "speed": 300}}
// 广播体操
{"name": "self.otto.radio_calisthenics", "arguments": {}}
// 立即停止
{"name": "self.otto.stop", "arguments": {}}
```
@ -115,9 +147,20 @@ otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇
- "向前走" / "向前走5步" / "快速向前"
- "左转" / "右转" / "转身"
- "跳跃" / "跳一下"
- "摇摆" / "跳舞"
- "摇摆" / "摇摆舞" / "跳舞"
- "太空步" / "月球漫步"
- "挥手" / "举手" / "放手"
- "旋风腿" / "旋风腿动作"
- "坐下" / "坐下休息"
- "展示动作" / "表演一下"
- "挥手" / "挥手打招呼"
- "举手" / "双手举起" / "放手"
- "大风车" / "做大风车"
- "起飞" / "准备起飞"
- "健身" / "做健身动作"
- "打招呼" / "打招呼动作"
- "害羞" / "害羞动作"
- "广播体操" / "做广播体操"
- "爱的魔力转圈圈" / "转圈圈"
- "停止" / "停下"
**说明**: 小智控制机器人动作是创建新的任务在后台控制,动作执行期间仍可接受新的语音指令。可以通过"停止"语音指令立即停下Otto。

View File

@ -47,6 +47,6 @@
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define OTTO_ROBOT_VERSION "2.0.4"
#define OTTO_ROBOT_VERSION "2.0.5"
#endif // _BOARD_CONFIG_H_

View File

@ -1,3 +1,11 @@
//--------------------------------------------------------------
//-- Oscillator.pde
//-- Generate sinusoidal oscillations in the servos
//--------------------------------------------------------------
//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
//-- (c) txp666 for esp32, 202503
//-- GPL license
//--------------------------------------------------------------
#include "oscillator.h"
#include <driver/ledc.h>

View File

@ -1,3 +1,11 @@
//--------------------------------------------------------------
//-- Oscillator.pde
//-- Generate sinusoidal oscillations in the servos
//--------------------------------------------------------------
//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
//-- (c) txp666 for esp32, 202503
//-- GPL license
//--------------------------------------------------------------
#ifndef __OSCILLATOR_H__
#define __OSCILLATOR_H__

View File

@ -5,6 +5,7 @@
#include <cJSON.h>
#include <esp_log.h>
#include <cstdlib>
#include <cstring>
#include "application.h"
@ -31,6 +32,7 @@ private:
int speed;
int direction;
int amount;
char servo_sequence_json[512]; // 用于存储舵机序列的JSON字符串
};
enum ActionType {
@ -41,6 +43,9 @@ private:
ACTION_MOONWALK = 5,
ACTION_BEND = 6,
ACTION_SHAKE_LEG = 7,
ACTION_SIT = 25, // 坐下
ACTION_RADIO_CALISTHENICS = 26, // 广播体操
ACTION_MAGIC_CIRCLE = 27, // 爱的魔力转圈圈
ACTION_UPDOWN = 8,
ACTION_TIPTOE_SWING = 9,
ACTION_JITTER = 10,
@ -50,7 +55,15 @@ private:
ACTION_HANDS_UP = 14,
ACTION_HANDS_DOWN = 15,
ACTION_HAND_WAVE = 16,
ACTION_HOME = 17
ACTION_WINDMILL = 20, // 大风车
ACTION_TAKEOFF = 21, // 起飞
ACTION_FITNESS = 22, // 健身
ACTION_GREETING = 23, // 打招呼
ACTION_SHY = 24, // 害羞
ACTION_SHOWCASE = 28, // 展示动作
ACTION_HOME = 17,
ACTION_SERVO_SEQUENCE = 18, // 舵机序列(自编程)
ACTION_WHIRLWIND_LEG = 19 // 旋风腿
};
static void ActionTask(void* arg) {
@ -62,73 +75,355 @@ private:
if (xQueueReceive(controller->action_queue_, &params, pdMS_TO_TICKS(1000)) == pdTRUE) {
ESP_LOGI(TAG, "执行动作: %d", params.action_type);
controller->is_action_in_progress_ = true;
switch (params.action_type) {
case ACTION_WALK:
controller->otto_.Walk(params.steps, params.speed, params.direction,
params.amount);
break;
case ACTION_TURN:
controller->otto_.Turn(params.steps, params.speed, params.direction,
params.amount);
break;
case ACTION_JUMP:
controller->otto_.Jump(params.steps, params.speed);
break;
case ACTION_SWING:
controller->otto_.Swing(params.steps, params.speed, params.amount);
break;
case ACTION_MOONWALK:
controller->otto_.Moonwalker(params.steps, params.speed, params.amount,
params.direction);
break;
case ACTION_BEND:
controller->otto_.Bend(params.steps, params.speed, params.direction);
break;
case ACTION_SHAKE_LEG:
controller->otto_.ShakeLeg(params.steps, params.speed, params.direction);
break;
case ACTION_UPDOWN:
controller->otto_.UpDown(params.steps, params.speed, params.amount);
break;
case ACTION_TIPTOE_SWING:
controller->otto_.TiptoeSwing(params.steps, params.speed, params.amount);
break;
case ACTION_JITTER:
controller->otto_.Jitter(params.steps, params.speed, params.amount);
break;
case ACTION_ASCENDING_TURN:
controller->otto_.AscendingTurn(params.steps, params.speed, params.amount);
break;
case ACTION_CRUSAITO:
controller->otto_.Crusaito(params.steps, params.speed, params.amount,
params.direction);
break;
case ACTION_FLAPPING:
controller->otto_.Flapping(params.steps, params.speed, params.amount,
params.direction);
break;
case ACTION_HANDS_UP:
if (controller->has_hands_) {
controller->otto_.HandsUp(params.speed, params.direction);
if (params.action_type == ACTION_SERVO_SEQUENCE) {
// 执行舵机序列(自编程)- 仅支持短键名格式
cJSON* json = cJSON_Parse(params.servo_sequence_json);
if (json != nullptr) {
ESP_LOGD(TAG, "JSON解析成功长度=%d", strlen(params.servo_sequence_json));
// 使用短键名 "a" 表示动作数组
cJSON* actions = cJSON_GetObjectItem(json, "a");
if (cJSON_IsArray(actions)) {
int array_size = cJSON_GetArraySize(actions);
ESP_LOGI(TAG, "执行舵机序列,共%d个动作", array_size);
// 获取序列执行完成后的延迟(短键名 "d",顶层参数)
int sequence_delay = 0;
cJSON* delay_item = cJSON_GetObjectItem(json, "d");
if (cJSON_IsNumber(delay_item)) {
sequence_delay = delay_item->valueint;
if (sequence_delay < 0) sequence_delay = 0;
}
// 初始化当前舵机位置(用于保持未指定的舵机位置)
int current_positions[SERVO_COUNT];
for (int j = 0; j < SERVO_COUNT; j++) {
current_positions[j] = 90; // 默认中间位置
}
// 手部舵机默认位置
current_positions[LEFT_HAND] = 45;
current_positions[RIGHT_HAND] = 180 - 45;
for (int i = 0; i < array_size; i++) {
cJSON* action_item = cJSON_GetArrayItem(actions, i);
if (cJSON_IsObject(action_item)) {
// 检查是否为振荡器模式(短键名 "osc"
cJSON* osc_item = cJSON_GetObjectItem(action_item, "osc");
if (cJSON_IsObject(osc_item)) {
// 振荡器模式 - 使用Execute2以绝对角度为中心振荡
int amplitude[SERVO_COUNT] = {0};
int center_angle[SERVO_COUNT] = {0};
double phase_diff[SERVO_COUNT] = {0};
int period = 500; // 默认周期500毫秒
float steps = 5.0; // 默认步数5.0
const char* servo_names[] = {"ll", "rl", "lf", "rf", "lh", "rh"};
// 读取振幅(短键名 "a"默认20度
for (int j = 0; j < SERVO_COUNT; j++) {
amplitude[j] = 20; // 默认振幅20度
}
cJSON* amp_item = cJSON_GetObjectItem(osc_item, "a");
if (cJSON_IsObject(amp_item)) {
for (int j = 0; j < SERVO_COUNT; j++) {
cJSON* amp_value = cJSON_GetObjectItem(amp_item, servo_names[j]);
if (cJSON_IsNumber(amp_value)) {
int amp = amp_value->valueint;
if (amp >= 10 && amp <= 90) {
amplitude[j] = amp;
}
}
}
}
// 读取中心角度(短键名 "o"默认90度绝对角度0-180度
for (int j = 0; j < SERVO_COUNT; j++) {
center_angle[j] = 90; // 默认中心角度90度中间位置
}
cJSON* center_item = cJSON_GetObjectItem(osc_item, "o");
if (cJSON_IsObject(center_item)) {
for (int j = 0; j < SERVO_COUNT; j++) {
cJSON* center_value = cJSON_GetObjectItem(center_item, servo_names[j]);
if (cJSON_IsNumber(center_value)) {
int center = center_value->valueint;
if (center >= 0 && center <= 180) {
center_angle[j] = center;
}
}
}
}
// 安全检查:防止左右腿脚同时做大幅度振荡(振幅检查)
const int LARGE_AMPLITUDE_THRESHOLD = 40; // 大幅度振幅阈值40度
bool left_leg_large = amplitude[LEFT_LEG] >= LARGE_AMPLITUDE_THRESHOLD;
bool right_leg_large = amplitude[RIGHT_LEG] >= LARGE_AMPLITUDE_THRESHOLD;
bool left_foot_large = amplitude[LEFT_FOOT] >= LARGE_AMPLITUDE_THRESHOLD;
bool right_foot_large = amplitude[RIGHT_FOOT] >= LARGE_AMPLITUDE_THRESHOLD;
if (left_leg_large && right_leg_large) {
ESP_LOGW(TAG, "检测到左右腿同时大幅度振荡,限制右腿振幅");
amplitude[RIGHT_LEG] = 0; // 禁止右腿振荡
}
if (left_foot_large && right_foot_large) {
ESP_LOGW(TAG, "检测到左右脚同时大幅度振荡,限制右脚振幅");
amplitude[RIGHT_FOOT] = 0; // 禁止右脚振荡
}
// 读取相位差(短键名 "ph",单位为度,转换为弧度)
cJSON* phase_item = cJSON_GetObjectItem(osc_item, "ph");
if (cJSON_IsObject(phase_item)) {
for (int j = 0; j < SERVO_COUNT; j++) {
cJSON* phase_value = cJSON_GetObjectItem(phase_item, servo_names[j]);
if (cJSON_IsNumber(phase_value)) {
// 将度数转换为弧度
phase_diff[j] = phase_value->valuedouble * 3.141592653589793 / 180.0;
}
}
}
// 读取周期(短键名 "p"范围100-3000毫秒
cJSON* period_item = cJSON_GetObjectItem(osc_item, "p");
if (cJSON_IsNumber(period_item)) {
period = period_item->valueint;
if (period < 100) period = 100;
if (period > 3000) period = 3000; // 与描述一致限制3000毫秒
}
// 读取周期数(短键名 "c"范围0.1-20.0
cJSON* steps_item = cJSON_GetObjectItem(osc_item, "c");
if (cJSON_IsNumber(steps_item)) {
steps = (float)steps_item->valuedouble;
if (steps < 0.1) steps = 0.1;
if (steps > 20.0) steps = 20.0; // 与描述一致限制20.0
}
// 执行振荡 - 使用Execute2以绝对角度为中心
ESP_LOGI(TAG, "执行振荡动作%d: period=%d, steps=%.1f", i, period, steps);
controller->otto_.Execute2(amplitude, center_angle, period, phase_diff, steps);
// 振荡后更新位置使用center_angle作为最终位置
for (int j = 0; j < SERVO_COUNT; j++) {
current_positions[j] = center_angle[j];
}
} else {
// 普通移动模式
// 从当前位置数组复制,保持未指定的舵机位置
int servo_target[SERVO_COUNT];
for (int j = 0; j < SERVO_COUNT; j++) {
servo_target[j] = current_positions[j];
}
// 从JSON中读取舵机位置短键名 "s"
cJSON* servos_item = cJSON_GetObjectItem(action_item, "s");
if (cJSON_IsObject(servos_item)) {
// 短键名ll/rl/lf/rf/lh/rh
const char* servo_names[] = {"ll", "rl", "lf", "rf", "lh", "rh"};
for (int j = 0; j < SERVO_COUNT; j++) {
cJSON* servo_value = cJSON_GetObjectItem(servos_item, servo_names[j]);
if (cJSON_IsNumber(servo_value)) {
int position = servo_value->valueint;
// 限制位置范围在0-180度
if (position >= 0 && position <= 180) {
servo_target[j] = position;
}
}
}
}
// 安全检查:防止左右腿脚同时做大幅度动作
const int LARGE_MOVEMENT_THRESHOLD = 40; // 大幅度动作阈值40度
bool left_leg_large = abs(servo_target[LEFT_LEG] - current_positions[LEFT_LEG]) >= LARGE_MOVEMENT_THRESHOLD;
bool right_leg_large = abs(servo_target[RIGHT_LEG] - current_positions[RIGHT_LEG]) >= LARGE_MOVEMENT_THRESHOLD;
bool left_foot_large = abs(servo_target[LEFT_FOOT] - current_positions[LEFT_FOOT]) >= LARGE_MOVEMENT_THRESHOLD;
bool right_foot_large = abs(servo_target[RIGHT_FOOT] - current_positions[RIGHT_FOOT]) >= LARGE_MOVEMENT_THRESHOLD;
if (left_leg_large && right_leg_large) {
ESP_LOGW(TAG, "检测到左右腿同时大幅度动作,限制右腿动作");
// 保持右腿在原位置
servo_target[RIGHT_LEG] = current_positions[RIGHT_LEG];
}
if (left_foot_large && right_foot_large) {
ESP_LOGW(TAG, "检测到左右脚同时大幅度动作,限制右脚动作");
// 保持右脚在原位置
servo_target[RIGHT_FOOT] = current_positions[RIGHT_FOOT];
}
// 获取移动速度(短键名 "v"默认1000毫秒
int speed = 1000;
cJSON* speed_item = cJSON_GetObjectItem(action_item, "v");
if (cJSON_IsNumber(speed_item)) {
speed = speed_item->valueint;
if (speed < 100) speed = 100; // 最小100毫秒
if (speed > 3000) speed = 3000; // 最大3000毫秒
}
// 执行舵机移动
ESP_LOGI(TAG, "执行动作%d: ll=%d, rl=%d, lf=%d, rf=%d, v=%d",
i, servo_target[LEFT_LEG], servo_target[RIGHT_LEG],
servo_target[LEFT_FOOT], servo_target[RIGHT_FOOT], speed);
controller->otto_.MoveServos(speed, servo_target);
// 更新当前位置数组,用于下一个动作
for (int j = 0; j < SERVO_COUNT; j++) {
current_positions[j] = servo_target[j];
}
}
// 获取动作后的延迟时间(短键名 "d"
int delay_after = 0;
cJSON* delay_item = cJSON_GetObjectItem(action_item, "d");
if (cJSON_IsNumber(delay_item)) {
delay_after = delay_item->valueint;
if (delay_after < 0) delay_after = 0;
}
// 动作后的延迟(最后一个动作后不延迟)
if (delay_after > 0 && i < array_size - 1) {
ESP_LOGI(TAG, "动作%d执行完成延迟%d毫秒", i, delay_after);
vTaskDelay(pdMS_TO_TICKS(delay_after));
}
}
}
// 序列执行完成后的延迟(用于序列之间的停顿)
if (sequence_delay > 0) {
// 检查队列中是否还有待执行的序列
UBaseType_t queue_count = uxQueueMessagesWaiting(controller->action_queue_);
if (queue_count > 0) {
ESP_LOGI(TAG, "序列执行完成,延迟%d毫秒后执行下一个序列队列中还有%d个序列",
sequence_delay, queue_count);
vTaskDelay(pdMS_TO_TICKS(sequence_delay));
}
}
// 释放JSON内存
cJSON_Delete(json);
} else {
ESP_LOGE(TAG, "舵机序列格式错误: 'a'不是数组");
cJSON_Delete(json);
}
break;
case ACTION_HANDS_DOWN:
if (controller->has_hands_) {
controller->otto_.HandsDown(params.speed, params.direction);
} else {
// 获取cJSON的错误信息
const char* error_ptr = cJSON_GetErrorPtr();
int json_len = strlen(params.servo_sequence_json);
ESP_LOGE(TAG, "解析舵机序列JSON失败长度=%d错误位置: %s", json_len,
error_ptr ? error_ptr : "未知");
ESP_LOGE(TAG, "JSON内容: %s", params.servo_sequence_json);
}
} else {
// 执行预定义动作
switch (params.action_type) {
case ACTION_WALK:
controller->otto_.Walk(params.steps, params.speed, params.direction,
params.amount);
break;
case ACTION_TURN:
controller->otto_.Turn(params.steps, params.speed, params.direction,
params.amount);
break;
case ACTION_JUMP:
controller->otto_.Jump(params.steps, params.speed);
break;
case ACTION_SWING:
controller->otto_.Swing(params.steps, params.speed, params.amount);
break;
case ACTION_MOONWALK:
controller->otto_.Moonwalker(params.steps, params.speed, params.amount,
params.direction);
break;
case ACTION_BEND:
controller->otto_.Bend(params.steps, params.speed, params.direction);
break;
case ACTION_SHAKE_LEG:
controller->otto_.ShakeLeg(params.steps, params.speed, params.direction);
break;
case ACTION_SIT:
controller->otto_.Sit();
break;
case ACTION_RADIO_CALISTHENICS:
if (controller->has_hands_) {
controller->otto_.RadioCalisthenics();
}
break;
case ACTION_MAGIC_CIRCLE:
if (controller->has_hands_) {
controller->otto_.MagicCircle();
}
break;
case ACTION_SHOWCASE:
controller->otto_.Showcase();
break;
case ACTION_UPDOWN:
controller->otto_.UpDown(params.steps, params.speed, params.amount);
break;
case ACTION_TIPTOE_SWING:
controller->otto_.TiptoeSwing(params.steps, params.speed, params.amount);
break;
case ACTION_JITTER:
controller->otto_.Jitter(params.steps, params.speed, params.amount);
break;
case ACTION_ASCENDING_TURN:
controller->otto_.AscendingTurn(params.steps, params.speed, params.amount);
break;
case ACTION_CRUSAITO:
controller->otto_.Crusaito(params.steps, params.speed, params.amount,
params.direction);
break;
case ACTION_FLAPPING:
controller->otto_.Flapping(params.steps, params.speed, params.amount,
params.direction);
break;
case ACTION_WHIRLWIND_LEG:
controller->otto_.WhirlwindLeg(params.steps, params.speed, params.amount);
break;
case ACTION_HANDS_UP:
if (controller->has_hands_) {
controller->otto_.HandsUp(params.speed, params.direction);
}
break;
case ACTION_HANDS_DOWN:
if (controller->has_hands_) {
controller->otto_.HandsDown(params.speed, params.direction);
}
break;
case ACTION_HAND_WAVE:
if (controller->has_hands_) {
controller->otto_.HandWave( params.direction);
}
break;
case ACTION_WINDMILL:
if (controller->has_hands_) {
controller->otto_.Windmill(params.steps, params.speed, params.amount);
}
break;
case ACTION_TAKEOFF:
if (controller->has_hands_) {
controller->otto_.Takeoff(params.steps, params.speed, params.amount);
}
break;
case ACTION_FITNESS:
if (controller->has_hands_) {
controller->otto_.Fitness(params.steps, params.speed, params.amount);
}
break;
case ACTION_GREETING:
if (controller->has_hands_) {
controller->otto_.Greeting(params.direction, params.steps);
}
break;
case ACTION_SHY:
if (controller->has_hands_) {
controller->otto_.Shy(params.direction, params.steps);
}
break;
case ACTION_HOME:
controller->otto_.Home(true);
break;
}
if(params.action_type != ACTION_SIT){
if (params.action_type != ACTION_HOME && params.action_type != ACTION_SERVO_SEQUENCE) {
controller->otto_.Home(params.action_type != ACTION_HANDS_UP);
}
break;
case ACTION_HAND_WAVE:
if (controller->has_hands_) {
controller->otto_.HandWave(params.speed, params.direction);
}
break;
case ACTION_HOME:
controller->otto_.Home(params.direction == 1);
break;
}
if (params.action_type != ACTION_HOME) {
controller->otto_.Home(params.action_type < ACTION_HANDS_UP);
}
}
controller->is_action_in_progress_ = false;
vTaskDelay(pdMS_TO_TICKS(20));
@ -145,15 +440,52 @@ private:
void QueueAction(int action_type, int steps, int speed, int direction, int amount) {
// 检查手部动作
if ((action_type >= ACTION_HANDS_UP && action_type <= ACTION_HAND_WAVE) && !has_hands_) {
ESP_LOGW(TAG, "尝试执行手部动作,但机器人没有配置手部舵机");
return;
if ((action_type >= ACTION_HANDS_UP && action_type <= ACTION_HAND_WAVE) ||
(action_type == ACTION_WINDMILL) || (action_type == ACTION_TAKEOFF) ||
(action_type == ACTION_FITNESS) || (action_type == ACTION_GREETING) ||
(action_type == ACTION_SHY) || (action_type == ACTION_RADIO_CALISTHENICS) ||
(action_type == ACTION_MAGIC_CIRCLE)) {
if (!has_hands_) {
ESP_LOGW(TAG, "尝试执行手部动作,但机器人没有配置手部舵机");
return;
}
}
ESP_LOGI(TAG, "动作控制: 类型=%d, 步数=%d, 速度=%d, 方向=%d, 幅度=%d", action_type, steps,
speed, direction, amount);
OttoActionParams params = {action_type, steps, speed, direction, amount};
OttoActionParams params = {action_type, steps, speed, direction, amount, ""};
xQueueSend(action_queue_, &params, portMAX_DELAY);
StartActionTaskIfNeeded();
}
void QueueServoSequence(const char* servo_sequence_json) {
if (servo_sequence_json == nullptr) {
ESP_LOGE(TAG, "序列JSON为空");
return;
}
int input_len = strlen(servo_sequence_json);
const int buffer_size = 512; // servo_sequence_json数组大小
ESP_LOGI(TAG, "队列舵机序列,输入长度=%d缓冲区大小=%d", input_len, buffer_size);
if (input_len >= buffer_size) {
ESP_LOGE(TAG, "JSON字符串太长输入长度=%d最大允许=%d", input_len, buffer_size - 1);
return;
}
if (input_len == 0) {
ESP_LOGW(TAG, "序列JSON为空字符串");
return;
}
OttoActionParams params = {ACTION_SERVO_SEQUENCE, 0, 0, 0, 0, ""};
// 复制JSON字符串到结构体中限制长度
strncpy(params.servo_sequence_json, servo_sequence_json, sizeof(params.servo_sequence_json) - 1);
params.servo_sequence_json[sizeof(params.servo_sequence_json) - 1] = '\0';
ESP_LOGD(TAG, "序列已加入队列: %s", params.servo_sequence_json);
xQueueSend(action_queue_, &params, portMAX_DELAY);
StartActionTaskIfNeeded();
}
@ -299,6 +631,22 @@ public:
return true;
});
mcp_server.AddTool("self.otto.sit",
"坐下。不需要参数",
PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
QueueAction(ACTION_SIT, 1, 0, 0, 0);
return true;
});
mcp_server.AddTool("self.otto.showcase",
"展示动作。串联执行多个动作往前走3步、挥挥手、跳舞广播体操、太空步、摇摆、起飞、健身、往后走3步。不需要参数",
PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
QueueAction(ACTION_SHOWCASE, 1, 0, 0, 0);
return true;
});
mcp_server.AddTool("self.otto.updown",
"上下运动。steps: 上下运动次数(1-100); speed: "
"运动速度(500-1500数值越小越快); amount: 运动幅度(0-170度)",
@ -313,6 +661,21 @@ public:
return true;
});
mcp_server.AddTool("self.otto.whirlwind_leg",
"旋风腿。"
"steps: 动作次数(3-100); speed: 动作速度(100-1000数值越小越快建议300); "
"amplitude: 踢腿幅度(20-40度)",
PropertyList({Property("steps", kPropertyTypeInteger, 3, 3, 100),
Property("speed", kPropertyTypeInteger, 300, 100, 1000),
Property("amplitude", kPropertyTypeInteger, 30, 20, 40)}),
[this](const PropertyList& properties) -> ReturnValue {
int steps = properties["steps"].value<int>();
int speed = properties["speed"].value<int>();
int amplitude = properties["amplitude"].value<int>();
QueueAction(ACTION_WHIRLWIND_LEG, steps, speed, 0, amplitude);
return true;
});
// 手部动作(仅在有手部舵机时可用)
if (has_hands_) {
mcp_server.AddTool(
@ -343,20 +706,141 @@ public:
mcp_server.AddTool(
"self.otto.hand_wave",
"挥手。speed: 挥手速度(500-1500数值越小越快); direction: 手部选择(1=左手, "
"-1=右手, 0=双手)",
PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
"挥手。direction: 手部选择(1=左手,-1=右手,0=双手)",
PropertyList({Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
[this](const PropertyList& properties) -> ReturnValue {
int direction = properties["direction"].value<int>();
QueueAction(ACTION_HAND_WAVE, 1, 0, 0, direction);
return true;
});
mcp_server.AddTool(
"self.otto.windmill",
"大风车。steps: 动作次数(3-100); "
"speed: 动作周期(300-2000毫秒数值越小越快); amplitude: 振荡幅度(50-90度)",
PropertyList({Property("steps", kPropertyTypeInteger, 6, 3, 100),
Property("speed", kPropertyTypeInteger, 500, 300, 2000),
Property("amplitude", kPropertyTypeInteger, 70, 50, 90)}),
[this](const PropertyList& properties) -> ReturnValue {
int steps = properties["steps"].value<int>();
int speed = properties["speed"].value<int>();
int direction = properties["direction"].value<int>();
QueueAction(ACTION_HAND_WAVE, 1, speed, direction, 0);
int amplitude = properties["amplitude"].value<int>();
QueueAction(ACTION_WINDMILL, steps, speed, 0, amplitude);
return true;
});
mcp_server.AddTool(
"self.otto.takeoff",
"起飞。双手在90度位置同相快速振荡模拟起飞动作。steps: 动作次数(5-100); "
"speed: 动作周期(200-600毫秒数值越小越快建议300); amplitude: 振荡幅度(20-60度)",
PropertyList({Property("steps", kPropertyTypeInteger, 5, 5, 100),
Property("speed", kPropertyTypeInteger, 300, 200, 600),
Property("amplitude", kPropertyTypeInteger, 40, 20, 60)}),
[this](const PropertyList& properties) -> ReturnValue {
int steps = properties["steps"].value<int>();
int speed = properties["speed"].value<int>();
int amplitude = properties["amplitude"].value<int>();
QueueAction(ACTION_TAKEOFF, steps, speed, 0, amplitude);
return true;
});
mcp_server.AddTool(
"self.otto.fitness",
"健身。steps: 动作次数(3-100); speed: 动作速度(500-2000毫秒数值越小越快); amplitude: 振荡幅度(10-50度)",
PropertyList({Property("steps", kPropertyTypeInteger, 5, 3, 100),
Property("speed", kPropertyTypeInteger, 1000, 500, 2000),
Property("amplitude", kPropertyTypeInteger, 25, 10, 50)}),
[this](const PropertyList& properties) -> ReturnValue {
int steps = properties["steps"].value<int>();
int speed = properties["speed"].value<int>();
int amplitude = properties["amplitude"].value<int>();
QueueAction(ACTION_FITNESS, steps, speed, 0, amplitude);
return true;
});
mcp_server.AddTool(
"self.otto.greeting",
"打招呼。direction: 手部选择(1=左手, -1=右手); steps: 动作次数(3-100)",
PropertyList({Property("direction", kPropertyTypeInteger, 1, -1, 1),
Property("steps", kPropertyTypeInteger, 5, 3, 100)}),
[this](const PropertyList& properties) -> ReturnValue {
int direction = properties["direction"].value<int>();
int steps = properties["steps"].value<int>();
QueueAction(ACTION_GREETING, steps, 0, direction, 0);
return true;
});
mcp_server.AddTool(
"self.otto.shy",
"害羞。direction: 方向(1=左, -1=右); steps: 动作次数(3-100)",
PropertyList({Property("direction", kPropertyTypeInteger, 1, -1, 1),
Property("steps", kPropertyTypeInteger, 5, 3, 100)}),
[this](const PropertyList& properties) -> ReturnValue {
int direction = properties["direction"].value<int>();
int steps = properties["steps"].value<int>();
QueueAction(ACTION_SHY, steps, 0, direction, 0);
return true;
});
mcp_server.AddTool("self.otto.radio_calisthenics",
"广播体操。不需要参数",
PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
QueueAction(ACTION_RADIO_CALISTHENICS, 1, 0, 0, 0);
return true;
});
mcp_server.AddTool("self.otto.magic_circle",
"爱的魔力转圈圈。不需要参数",
PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
QueueAction(ACTION_MAGIC_CIRCLE, 1, 0, 0, 0);
return true;
});
}
// 舵机序列工具(支持分段发送,每次发送一个序列,自动排队执行)
mcp_server.AddTool(
"self.otto.servo_sequences",
"控制每个舵机实现自主动作编程。支持分段发送序列AI可以连续多次调用此工具每次发送一个短序列系统会自动排队按顺序执行。支持普通移动和振荡器两种模式。"
"机器人结构:双手可上下摆动,双腿可内收外展,双脚可上下翻转。"
"舵机说明:"
"ll(左腿)内收外展0度=完全外展90度=中立180度=完全内收;"
"rl(右腿)内收外展0度=完全内收90度=中立180度=完全外展;"
"lf(左脚)上下翻转0度=完全向上90度=水平180度=完全向下;"
"rf(右脚)上下翻转0度=完全向下90度=水平180度=完全向上;"
"lh(左手)上下摆动0度=完全向下90度=水平180度=完全向上;"
"rh(右手)上下摆动0度=完全向上90度=水平180度=完全向下;"
"sequence: 单个序列对象,包含'a'动作数组,顶层可选参数:"
"'d'(序列执行完成后延迟毫秒数,用于序列之间的停顿)。"
"每个动作对象包含:"
"普通模式:'s'舵机位置对象(键名ll/rl/lf/rf/lh/rh0-180度)'v'移动速度100-3000毫秒(默认1000)'d'动作后延迟毫秒数(默认0)"
"振荡模式:'osc'振荡器对象,包含'a'振幅对象(各舵机振幅10-90度默认20度)'o'中心角度对象(各舵机振荡中心绝对角度0-180度默认90度)'ph'相位差对象(各舵机相位差0-360度默认0度)'p'周期100-3000毫秒(默认500)'c'周期数0.1-20.0(默认5.0)"
"使用方式AI可以连续多次调用此工具每次发送一个序列系统会自动排队按顺序执行。"
"重要说明左右腿脚震荡的时候有一只脚必须在90度否则会损坏机器人如果发送多个序列序列数>1完成所有序列后需要复位时AI应该最后单独调用self.otto.home工具进行复位不要在序列中设置复位参数。"
"示例发送3个序列最后调用复位"
"第1次调用{\"sequence\":\"{\\\"a\\\":[{\\\"s\\\":{\\\"ll\\\":100},\\\"v\\\":1000}],\\\"d\\\":500}\"}"
"第2次调用{\"sequence\":\"{\\\"a\\\":[{\\\"s\\\":{\\\"ll\\\":90},\\\"v\\\":800}],\\\"d\\\":500}\"}"
"第3次调用{\"sequence\":\"{\\\"a\\\":[{\\\"s\\\":{\\\"ll\\\":80},\\\"v\\\":800}]}\"}"
"最后调用self.otto.home工具进行复位。",
PropertyList({Property("sequence", kPropertyTypeString,
"{\"a\":[{\"s\":{\"ll\":90,\"rl\":90},\"v\":1000}]}")}),
[this](const PropertyList& properties) -> ReturnValue {
std::string sequence = properties["sequence"].value<std::string>();
// 检查是否是JSON对象可能是字符串格式或已解析的对象
// 如果sequence是JSON字符串直接使用如果是对象字符串也需要使用
QueueServoSequence(sequence.c_str());
return true;
});
// 系统工具
mcp_server.AddTool("self.otto.stop", "立即停止", PropertyList(),
mcp_server.AddTool("self.otto.home", "复位机器人到初始位置", PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
QueueAction(ACTION_HOME, 1, 1000, 1, 0);
return true;
});
mcp_server.AddTool("self.otto.stop", "立即停止所有动作并复位", PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
if (action_task_handle_ != nullptr) {
vTaskDelete(action_task_handle_);

View File

@ -88,7 +88,7 @@ void OttoEmojiDisplay::SetupChatLabel() {
chat_message_label_ = lv_label_create(container_);
lv_label_set_text(chat_message_label_, "");
lv_obj_set_width(chat_message_label_, width_ * 0.9); // 限制宽度为屏幕宽度的 90%
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐
lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0);
SetTheme(LvglThemeManager::GetInstance().GetTheme("dark"));

View File

@ -2,6 +2,7 @@
#include <algorithm>
#include "freertos/idf_additions.h"
#include "oscillator.h"
static const char* TAG = "OttoMovements";
@ -198,6 +199,39 @@ void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int peri
vTaskDelay(pdMS_TO_TICKS(10));
}
//---------------------------------------------------------
//-- Execute2: 使用绝对角度作为振荡中心
//-- Parameters:
//-- amplitude: 振幅数组(每个舵机的振荡幅度)
//-- center_angle: 绝对角度数组0-180度作为振荡中心位置
//-- period: 周期(毫秒)
//-- phase_diff: 相位差数组(弧度)
//-- steps: 步数/周期数(可为小数)
//---------------------------------------------------------
void Otto::Execute2(int amplitude[SERVO_COUNT], int center_angle[SERVO_COUNT], int period,
double phase_diff[SERVO_COUNT], float steps = 1.0) {
if (GetRestState() == true) {
SetRestState(false);
}
// 将绝对角度转换为offsetoffset = center_angle - 90
int offset[SERVO_COUNT];
for (int i = 0; i < SERVO_COUNT; i++) {
offset[i] = center_angle[i] - 90;
}
int cycles = (int)steps;
//-- Execute complete cycles
if (cycles >= 1)
for (int i = 0; i < cycles; i++)
OscillateServos(amplitude, offset, period, phase_diff);
//-- Execute the final not complete cycle
OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles);
vTaskDelay(pdMS_TO_TICKS(10));
}
///////////////////////////////////////////////////////////////////
//-- HOME = Otto at rest position -------------------------------//
///////////////////////////////////////////////////////////////////
@ -224,7 +258,7 @@ void Otto::Home(bool hands_down) {
}
}
MoveServos(500, homes);
MoveServos(700, homes);
is_otto_resting_ = true;
}
@ -389,7 +423,7 @@ void Otto::ShakeLeg(int steps, int period, int dir) {
int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
// Changes in the parameters if left leg is chosen
if (dir == 1) {
if (dir == LEFT) {
shake_leg1[2] = 180 - 35;
shake_leg1[3] = 180 - 58;
shake_leg2[2] = 180 - 120;
@ -420,6 +454,14 @@ void Otto::ShakeLeg(int steps, int period, int dir) {
vTaskDelay(pdMS_TO_TICKS(period));
}
//---------------------------------------------------------
//-- Otto movement: Sit (坐下)
//---------------------------------------------------------
void Otto::Sit() {
int target[SERVO_COUNT] = {120, 60, 0, 180, 45, 135};
MoveServos(600, target);
}
//---------------------------------------------------------
//-- Otto movement: up & down
//-- Parameters:
@ -588,6 +630,29 @@ void Otto::Flapping(float steps, int period, int height, int dir) {
Execute(A, O, period, phase_diff, steps);
}
//---------------------------------------------------------
//-- Otto gait: WhirlwindLeg (旋风腿)
//-- Parameters:
//-- steps: Number of steps
//-- period: Period (建议100-800毫秒)
//-- amplitude: amplitude (Values between 20 - 40)
//---------------------------------------------------------
void Otto::WhirlwindLeg(float steps, int period, int amplitude) {
int target[SERVO_COUNT] = {90, 90, 180, 90, 45, 20};
MoveServos(100, target);
target[RIGHT_FOOT] = 160;
MoveServos(500, target);
vTaskDelay(pdMS_TO_TICKS(1000));
int C[SERVO_COUNT] = {90, 90, 180, 160, 45, 20};
int A[SERVO_COUNT] = {amplitude, 0, 0, 0, amplitude, 0};
double phase_diff[SERVO_COUNT] = {DEG2RAD(20), 0, 0, 0, DEG2RAD(20), 0};
Execute2(A, C, period, phase_diff, steps);
}
//---------------------------------------------------------
//-- 手部动作: 举手
//-- Parameters:
@ -599,16 +664,15 @@ void Otto::HandsUp(int period, int dir) {
return;
}
int initial[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
if (dir == 0) {
target[LEFT_HAND] = 170;
target[RIGHT_HAND] = 10;
} else if (dir == 1) {
} else if (dir == LEFT) {
target[LEFT_HAND] = 170;
target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition();
} else if (dir == -1) {
} else if (dir == RIGHT) {
target[RIGHT_HAND] = 10;
target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition();
}
@ -629,9 +693,9 @@ void Otto::HandsDown(int period, int dir) {
int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
if (dir == 1) {
if (dir == LEFT) {
target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition();
} else if (dir == -1) {
} else if (dir == RIGHT) {
target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition();
}
@ -639,111 +703,248 @@ void Otto::HandsDown(int period, int dir) {
}
//---------------------------------------------------------
//-- 手部动作: 挥手
//-- 手部动作: 挥手
//-- Parameters:
//-- period: 动作周期
//-- dir: 方向 LEFT/RIGHT/BOTH
//-- dir: 方向 LEFT/RIGHT/BOTH
//---------------------------------------------------------
void Otto::HandWave(int period, int dir) {
void Otto::HandWave(int dir) {
if (!has_hands_) {
return;
}
if (dir == LEFT) {
int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 160, 135};
int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 0};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), 0};
Execute2(A, center_angle, 300, phase_diff, 5);
}
else if (dir == RIGHT) {
int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 45, 20};
int A[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, DEG2RAD(90)};
Execute2(A, center_angle, 300, phase_diff, 5);
}
else {
int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 160, 20};
int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 20};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(90)};
Execute2(A, center_angle, 300, phase_diff, 5);
}
}
//---------------------------------------------------------
//-- 手部动作: 大风车
//-- Parameters:
//-- steps: 动作次数
//-- period: 动作周期(毫秒)
//-- amplitude: 振荡幅度(度)
//---------------------------------------------------------
void Otto::Windmill(float steps, int period, int amplitude) {
if (!has_hands_) {
return;
}
if (dir == BOTH) {
HandWaveBoth(period);
return;
}
int servo_index = (dir == LEFT) ? LEFT_HAND : RIGHT_HAND;
int current_positions[SERVO_COUNT];
for (int i = 0; i < SERVO_COUNT; i++) {
if (servo_pins_[i] != -1) {
current_positions[i] = servo_[i].GetPosition();
} else {
current_positions[i] = 90;
}
}
int position;
if (servo_index == LEFT_HAND) {
position = 170;
} else {
position = 10;
}
current_positions[servo_index] = position;
MoveServos(300, current_positions);
vTaskDelay(pdMS_TO_TICKS(300));
// 左右摆动5次
for (int i = 0; i < 5; i++) {
if (servo_index == LEFT_HAND) {
current_positions[servo_index] = position - 30;
MoveServos(period / 10, current_positions);
vTaskDelay(pdMS_TO_TICKS(period / 10));
current_positions[servo_index] = position + 30;
MoveServos(period / 10, current_positions);
} else {
current_positions[servo_index] = position + 30;
MoveServos(period / 10, current_positions);
vTaskDelay(pdMS_TO_TICKS(period / 10));
current_positions[servo_index] = position - 30;
MoveServos(period / 10, current_positions);
}
vTaskDelay(pdMS_TO_TICKS(period / 10));
}
if (servo_index == LEFT_HAND) {
current_positions[servo_index] = HAND_HOME_POSITION;
} else {
current_positions[servo_index] = 180 - HAND_HOME_POSITION;
}
MoveServos(300, current_positions);
int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 90, 90};
int A[SERVO_COUNT] = {0, 0, 0, 0, amplitude, amplitude};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(90)};
Execute2(A, center_angle, period, phase_diff, steps);
}
//---------------------------------------------------------
//-- 手部动作: 双手同时挥手
//-- 手部动作: 起飞
//-- Parameters:
//-- period: 动作周期
//-- steps: 动作次数
//-- period: 动作周期(毫秒),数值越小速度越快
//-- amplitude: 振荡幅度(度)
//---------------------------------------------------------
void Otto::HandWaveBoth(int period) {
void Otto::Takeoff(float steps, int period, int amplitude) {
if (!has_hands_) {
return;
}
int current_positions[SERVO_COUNT];
for (int i = 0; i < SERVO_COUNT; i++) {
if (servo_pins_[i] != -1) {
current_positions[i] = servo_[i].GetPosition();
} else {
current_positions[i] = 90;
}
Home(true);
int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 90, 90};
int A[SERVO_COUNT] = {0, 0, 0, 0, amplitude, amplitude};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
Execute2(A, center_angle, period, phase_diff, steps);
}
//---------------------------------------------------------
//-- 手部动作: 健身
//-- Parameters:
//-- steps: 动作次数
//-- period: 动作周期(毫秒)
//-- amplitude: 振荡幅度(度)
//---------------------------------------------------------
void Otto::Fitness(float steps, int period, int amplitude) {
if (!has_hands_) {
return;
}
int target[SERVO_COUNT] = {90, 90, 90, 0, 160, 135};
MoveServos(100, target);
target[LEFT_FOOT] = 20;
MoveServos(400, target);
vTaskDelay(pdMS_TO_TICKS(2000));
int C[SERVO_COUNT] = {90, 90, 20, 90, 160, 135};
int A[SERVO_COUNT] = {0, 0, 0, 0, 0, amplitude};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
Execute2(A, C, period, phase_diff, steps);
}
//---------------------------------------------------------
//-- 手部动作: 打招呼
//-- Parameters:
//-- dir: 方向 LEFT=左手, RIGHT=右手
//-- steps: 动作次数
//---------------------------------------------------------
void Otto::Greeting(int dir, float steps) {
if (!has_hands_) {
return;
}
if (dir == LEFT) {
int target[SERVO_COUNT] = {90, 90, 150, 150, 45, 135};
MoveServos(400, target);
int C[SERVO_COUNT] = {90, 90, 150, 150, 160, 135};
int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 0};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
Execute2(A, C, 300, phase_diff, steps);
}
else if (dir == RIGHT) {
int target[SERVO_COUNT] = {90, 90, 30, 30, 45, 135};
MoveServos(400, target);
int C[SERVO_COUNT] = {90, 90, 30, 30, 45, 20};
int A[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
Execute2(A, C, 300, phase_diff, steps);
}
int left_position = 170;
int right_position = 10;
}
current_positions[LEFT_HAND] = left_position;
current_positions[RIGHT_HAND] = right_position;
MoveServos(300, current_positions);
// 左右摆动5次
for (int i = 0; i < 5; i++) {
// 波浪向左
current_positions[LEFT_HAND] = left_position - 30;
current_positions[RIGHT_HAND] = right_position + 30;
MoveServos(period / 10, current_positions);
// 波浪向右
current_positions[LEFT_HAND] = left_position + 30;
current_positions[RIGHT_HAND] = right_position - 30;
MoveServos(period / 10, current_positions);
//---------------------------------------------------------
//-- 手部动作: 害羞
//-- Parameters:
//-- dir: 方向 LEFT=左手, RIGHT=右手
//-- steps: 动作次数
//---------------------------------------------------------
void Otto::Shy(int dir, float steps) {
if (!has_hands_) {
return;
}
current_positions[LEFT_HAND] = HAND_HOME_POSITION;
current_positions[RIGHT_HAND] = 180 - HAND_HOME_POSITION;
MoveServos(300, current_positions);
if (dir == LEFT) {
int target[SERVO_COUNT] = {90, 90, 150, 150, 45, 135};
MoveServos(400, target);
int C[SERVO_COUNT] = {90, 90, 150, 150, 45, 135};
int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 20};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
Execute2(A, C, 300, phase_diff, steps);
}
else if (dir == RIGHT) {
int target[SERVO_COUNT] = {90, 90, 30, 30, 45, 135};
MoveServos(400, target);
int C[SERVO_COUNT] = {90, 90, 30, 30, 45, 135};
int A[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
Execute2(A, C, 300, phase_diff, steps);
}
}
//---------------------------------------------------------
//-- 手部动作: 广播体操
//---------------------------------------------------------
void Otto::RadioCalisthenics() {
if (!has_hands_) {
return;
}
const int period = 1000;
const float steps = 8.0;
int C1[SERVO_COUNT] = {90, 90, 90, 90, 145, 45};
int A1[SERVO_COUNT] = {0, 0, 0, 0, 45, 45};
double phase_diff1[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
Execute2(A1, C1, period, phase_diff1, steps);
int C2[SERVO_COUNT] = {90, 90, 115, 65, 90, 90};
int A2[SERVO_COUNT] = {0, 0, 25, 25, 0, 0};
double phase_diff2[SERVO_COUNT] = {0, 0, DEG2RAD(90), DEG2RAD(-90), 0, 0};
Execute2(A2, C2, period, phase_diff2, steps);
int C3[SERVO_COUNT] = {90, 90, 130, 130, 90, 90};
int A3[SERVO_COUNT] = {0, 0, 0, 0, 20, 0};
double phase_diff3[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
Execute2(A3, C3, period, phase_diff3, steps);
int C4[SERVO_COUNT] = {90, 90, 50, 50, 90, 90};
int A4[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
double phase_diff4[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
Execute2(A4, C4, period, phase_diff4, steps);
}
//---------------------------------------------------------
//-- 手部动作: 爱的魔力转圈圈
//---------------------------------------------------------
void Otto::MagicCircle() {
if (!has_hands_) {
return;
}
int A[SERVO_COUNT] = {30, 30, 30, 30, 50, 50};
int O[SERVO_COUNT] = {0, 0, 5, -5, 0, 0};
double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(-90), DEG2RAD(-90) , DEG2RAD(90)};
Execute(A, O, 700, phase_diff, 40);
}
//---------------------------------------------------------
//-- 展示动作:串联多个动作展示
//---------------------------------------------------------
void Otto::Showcase() {
if (GetRestState() == true) {
SetRestState(false);
}
// 1. 往前走3步
Walk(3, 1000, FORWARD, 50);
vTaskDelay(pdMS_TO_TICKS(500));
// 2. 挥挥手
if (has_hands_) {
HandWave(LEFT);
vTaskDelay(pdMS_TO_TICKS(500));
}
// 3. 跳舞(使用广播体操)
if (has_hands_) {
RadioCalisthenics();
vTaskDelay(pdMS_TO_TICKS(500));
}
// 4. 太空步
Moonwalker(3, 900, 25, LEFT);
vTaskDelay(pdMS_TO_TICKS(500));
// 5. 摇摆
Swing(3, 1000, 30);
vTaskDelay(pdMS_TO_TICKS(500));
// 6. 起飞
if (has_hands_) {
Takeoff(5, 300, 40);
vTaskDelay(pdMS_TO_TICKS(500));
}
// 7. 健身
if (has_hands_) {
Fitness(5, 1000, 25);
vTaskDelay(pdMS_TO_TICKS(500));
}
// 8. 往后走3步
Walk(3, 1000, BACKWARD, 50);
}
void Otto::EnableServoLimit(int diff_limit) {

View File

@ -51,6 +51,8 @@ public:
void MoveSingle(int position, int servo_number);
void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
double phase_diff[SERVO_COUNT], float cycle);
void Execute2(int amplitude[SERVO_COUNT], int center_angle[SERVO_COUNT], int period,
double phase_diff[SERVO_COUNT], float steps);
//-- HOME = Otto at rest position
void Home(bool hands_down = true);
@ -64,6 +66,7 @@ public:
void Turn(float steps = 4, int period = 2000, int dir = LEFT, int amount = 0);
void Bend(int steps = 1, int period = 1400, int dir = LEFT);
void ShakeLeg(int steps = 1, int period = 2000, int dir = RIGHT);
void Sit(); // 坐下
void UpDown(float steps = 1, int period = 1000, int height = 20);
void Swing(float steps = 1, int period = 1000, int height = 20);
@ -74,12 +77,20 @@ public:
void Moonwalker(float steps = 1, int period = 900, int height = 20, int dir = LEFT);
void Crusaito(float steps = 1, int period = 900, int height = 20, int dir = FORWARD);
void Flapping(float steps = 1, int period = 1000, int height = 20, int dir = FORWARD);
void WhirlwindLeg(float steps = 1, int period = 300, int amplitude = 30);
// -- 手部动作
void HandsUp(int period = 1000, int dir = 0); // 双手举起
void HandsDown(int period = 1000, int dir = 0); // 双手放下
void HandWave(int period = 1000, int dir = LEFT); // 挥手
void HandWaveBoth(int period = 1000); // 双手同时挥手
void HandWave(int dir = LEFT); // 挥手
void Windmill(float steps = 10, int period = 500, int amplitude = 90); // 大风车
void Takeoff(float steps = 5, int period = 300, int amplitude = 40); // 起飞
void Fitness(float steps = 5, int period = 1000, int amplitude = 25); // 健身
void Greeting(int dir = LEFT, float steps = 5); // 打招呼
void Shy(int dir = LEFT, float steps = 5); // 害羞
void RadioCalisthenics(); // 广播体操
void MagicCircle(); // 爱的魔力转圈圈
void Showcase(); // 展示动作(串联多个动作)
// -- Servo limiter
void EnableServoLimit(int speed_limit_degree_per_sec = SERVO_LIMIT_DEFAULT);
@ -100,6 +111,7 @@ private:
void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
double phase_diff[SERVO_COUNT], float steps);
};
#endif // __OTTO_MOVEMENTS_H__