xiaozhi-esp32/main/boards/otto-robot/otto_controller.cc
Xiaoxia b7db68457c
Some checks failed
Build Boards / Determine variants to build (push) Has been cancelled
Build Boards / Build ${{ matrix.name }} (push) Has been cancelled
v2.1.0: Upgrade esp-wifi-connect to 3.0; New device state machine (#1528)
* Upgrade component version

* update fonts component version

* Handle OTA error code

* Update project version to 2.1.0 and add device state machine implementation

- Upgrade  esp-wifi-connect to 3.0.0, allowing reconfiguring wifi without rebooting
- Introduce device state machine with state change notification in new files
- Remove obsolete device state event files
- Update application logic to utilize new state machine
- Minor adjustments in various board implementations for state handling

* fix compile errors

* Refactor power saving mode implementation to use PowerSaveLevel enumeration

- Updated Application class to replace SetPowerSaveMode with SetPowerSaveLevel, allowing for LOW_POWER and PERFORMANCE settings.
- Modified various board implementations to align with the new power save level structure.
- Ensured consistent handling of power save levels across different board files, enhancing code maintainability and clarity.

* Refactor power save level checks across multiple board implementations

- Updated the condition for power save level checks in various board files to ensure that the power save timer only wakes up when the level is not set to LOW_POWER.
- Improved consistency in handling power save levels, enhancing code clarity and maintainability.

* Refactor EnterWifiConfigMode calls in board implementations

- Updated calls to EnterWifiConfigMode to use the appropriate instance reference (self or board) across multiple board files.
- Improved code consistency and clarity in handling device state during WiFi configuration mode entry.

* Add cellular modem event handling and improve network status updates

- Introduced new network events for cellular modem operations, including detecting, registration errors, and timeouts.
- Enhanced the Application class to handle different network states and update the display status accordingly.
- Refactored Ml307Board to implement a callback mechanism for network events, improving modularity and responsiveness.
- Updated dual_network_board and board headers to support new network event callbacks, ensuring consistent handling across board implementations.

* update esp-wifi-connect version

* Update WiFi configuration tool messages across multiple board implementations to clarify user actions
2025-12-09 09:24:56 +08:00

850 lines
50 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Otto机器人控制器 - MCP协议版本
*/
#include <cJSON.h>
#include <esp_log.h>
#include <cstdlib>
#include <cstring>
#include "application.h"
#include "board.h"
#include "config.h"
#include "mcp_server.h"
#include "otto_movements.h"
#include "power_manager.h"
#include "sdkconfig.h"
#include "settings.h"
#include <wifi_manager.h>
#define TAG "OttoController"
class OttoController {
private:
Otto otto_;
TaskHandle_t action_task_handle_ = nullptr;
QueueHandle_t action_queue_;
bool has_hands_ = false;
bool is_action_in_progress_ = false;
struct OttoActionParams {
int action_type;
int steps;
int speed;
int direction;
int amount;
char servo_sequence_json[512]; // 用于存储舵机序列的JSON字符串
};
enum ActionType {
ACTION_WALK = 1,
ACTION_TURN = 2,
ACTION_JUMP = 3,
ACTION_SWING = 4,
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,
ACTION_ASCENDING_TURN = 11,
ACTION_CRUSAITO = 12,
ACTION_FLAPPING = 13,
ACTION_HANDS_UP = 14,
ACTION_HANDS_DOWN = 15,
ACTION_HAND_WAVE = 16,
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) {
OttoController* controller = static_cast<OttoController*>(arg);
OttoActionParams params;
controller->otto_.AttachServos();
while (true) {
if (xQueueReceive(controller->action_queue_, &params, pdMS_TO_TICKS(1000)) == pdTRUE) {
ESP_LOGI(TAG, "执行动作: %d", params.action_type);
PowerManager::PauseBatteryUpdate(); // 动作开始时暂停电量更新
controller->is_action_in_progress_ = true;
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 = 300; // 默认周期300毫秒
float steps = 8.0; // 默认步数8.0
const char* servo_names[] = {"ll", "rl", "lf", "rf", "lh", "rh"};
// 读取振幅(短键名 "a"默认0度
for (int j = 0; j < SERVO_COUNT; j++) {
amplitude[j] = 0; // 默认振幅0度
}
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;
}
}
}
}
// 获取移动速度(短键名 "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);
}
} 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);
}
}
}
controller->is_action_in_progress_ = false;
PowerManager::ResumeBatteryUpdate(); // 动作结束时恢复电量更新
vTaskDelay(pdMS_TO_TICKS(20));
}
}
}
void StartActionTaskIfNeeded() {
if (action_task_handle_ == nullptr) {
xTaskCreate(ActionTask, "otto_action", 1024 * 3, this, configMAX_PRIORITIES - 1,
&action_task_handle_);
}
}
void QueueAction(int action_type, int steps, int speed, int direction, int amount) {
// 检查手部动作
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, ""};
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();
}
void LoadTrimsFromNVS() {
Settings settings("otto_trims", false);
int left_leg = settings.GetInt("left_leg", 0);
int right_leg = settings.GetInt("right_leg", 0);
int left_foot = settings.GetInt("left_foot", 0);
int right_foot = settings.GetInt("right_foot", 0);
int left_hand = settings.GetInt("left_hand", 0);
int right_hand = settings.GetInt("right_hand", 0);
ESP_LOGI(TAG, "从NVS加载微调设置: 左腿=%d, 右腿=%d, 左脚=%d, 右脚=%d, 左手=%d, 右手=%d",
left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
}
public:
OttoController(const HardwareConfig& hw_config) {
otto_.Init(
hw_config.left_leg_pin,
hw_config.right_leg_pin,
hw_config.left_foot_pin,
hw_config.right_foot_pin,
hw_config.left_hand_pin,
hw_config.right_hand_pin
);
has_hands_ = (hw_config.left_hand_pin != GPIO_NUM_NC && hw_config.right_hand_pin != GPIO_NUM_NC);
ESP_LOGI(TAG, "Otto机器人初始化%s手部舵机", has_hands_ ? "" : "不带");
ESP_LOGI(TAG, "舵机引脚配置: LL=%d, RL=%d, LF=%d, RF=%d, LH=%d, RH=%d",
hw_config.left_leg_pin, hw_config.right_leg_pin,
hw_config.left_foot_pin, hw_config.right_foot_pin,
hw_config.left_hand_pin, hw_config.right_hand_pin);
LoadTrimsFromNVS();
action_queue_ = xQueueCreate(10, sizeof(OttoActionParams));
QueueAction(ACTION_HOME, 1, 1000, 1, 0); // direction=1表示复位手部
RegisterMcpTools();
}
void RegisterMcpTools() {
auto& mcp_server = McpServer::GetInstance();
ESP_LOGI(TAG, "开始注册MCP工具...");
// 统一动作工具(除了舵机序列外的所有动作)
mcp_server.AddTool("self.otto.action",
"执行机器人动作。action: 动作名称根据动作类型提供相应参数direction: 方向1=前进/左转,-1=后退/右转0=左右同时"
"steps: 动作步数1-100speed: 动作速度100-3000数值越小越快amount: 动作幅度0-170arm_swing: 手臂摆动幅度0-170"
"基础动作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(爱的魔力转圈圈)",
PropertyList({
Property("action", kPropertyTypeString, "sit"),
Property("steps", kPropertyTypeInteger, 3, 1, 100),
Property("speed", kPropertyTypeInteger, 700, 100, 3000),
Property("direction", kPropertyTypeInteger, 1, -1, 1),
Property("amount", kPropertyTypeInteger, 30, 0, 170),
Property("arm_swing", kPropertyTypeInteger, 50, 0, 170)
}),
[this](const PropertyList& properties) -> ReturnValue {
std::string action = properties["action"].value<std::string>();
// 所有参数都有默认值,直接访问即可
int steps = properties["steps"].value<int>();
int speed = properties["speed"].value<int>();
int direction = properties["direction"].value<int>();
int amount = properties["amount"].value<int>();
int arm_swing = properties["arm_swing"].value<int>();
// 基础移动动作
if (action == "walk") {
QueueAction(ACTION_WALK, steps, speed, direction, arm_swing);
return true;
} else if (action == "turn") {
QueueAction(ACTION_TURN, steps, speed, direction, arm_swing);
return true;
} else if (action == "jump") {
QueueAction(ACTION_JUMP, steps, speed, 0, 0);
return true;
} else if (action == "swing") {
QueueAction(ACTION_SWING, steps, speed, 0, amount);
return true;
} else if (action == "moonwalk") {
QueueAction(ACTION_MOONWALK, steps, speed, direction, amount);
return true;
} else if (action == "bend") {
QueueAction(ACTION_BEND, steps, speed, direction, 0);
return true;
} else if (action == "shake_leg") {
QueueAction(ACTION_SHAKE_LEG, steps, speed, direction, 0);
return true;
} else if (action == "updown") {
QueueAction(ACTION_UPDOWN, steps, speed, 0, amount);
return true;
} else if (action == "whirlwind_leg") {
QueueAction(ACTION_WHIRLWIND_LEG, steps, speed, 0, amount);
return true;
}
// 固定动作
else if (action == "sit") {
QueueAction(ACTION_SIT, 1, 0, 0, 0);
return true;
} else if (action == "showcase") {
QueueAction(ACTION_SHOWCASE, 1, 0, 0, 0);
return true;
} else if (action == "home") {
QueueAction(ACTION_HOME, 1, 1000, 1, 0);
return true;
}
// 手部动作
else if (action == "hands_up") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_HANDS_UP, 1, speed, direction, 0);
return true;
} else if (action == "hands_down") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_HANDS_DOWN, 1, speed, direction, 0);
return true;
} else if (action == "hand_wave") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_HAND_WAVE, 1, 0, 0, direction);
return true;
} else if (action == "windmill") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_WINDMILL, steps, speed, 0, amount);
return true;
} else if (action == "takeoff") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_TAKEOFF, steps, speed, 0, amount);
return true;
} else if (action == "fitness") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_FITNESS, steps, speed, 0, amount);
return true;
} else if (action == "greeting") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_GREETING, steps, 0, direction, 0);
return true;
} else if (action == "shy") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_SHY, steps, 0, direction, 0);
return true;
} else if (action == "radio_calisthenics") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_RADIO_CALISTHENICS, 1, 0, 0, 0);
return true;
} else if (action == "magic_circle") {
if (!has_hands_) {
return "错误:此动作需要手部舵机支持";
}
QueueAction(ACTION_MAGIC_CIRCLE, 1, 0, 0, 0);
return true;
} else {
return "错误无效的动作名称。可用动作walk, turn, jump, swing, moonwalk, bend, shake_leg, updown, whirlwind_leg, sit, showcase, home, hands_up, hands_down, hand_wave, windmill, takeoff, fitness, greeting, shy, radio_calisthenics, magic_circle";
}
});
// 舵机序列工具(支持分段发送,每次发送一个序列,自动排队执行)
mcp_server.AddTool(
"self.otto.servo_sequences",
"AI自定义动作编程即兴动作。支持分段发送序列超过5个序列建议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工具进行复位。"
"振荡器模式示例:"
"示例1-双臂同步摆动:{\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"lh\\\":30,\\\"rh\\\":30},\\\"o\\\":{\\\"lh\\\":90,\\\"rh\\\":-90},\\\"p\\\":500,\\\"c\\\":5.0}}],\\\"d\\\":0}\"}"
"示例2-双腿交替振荡(波浪效果):{\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"ll\\\":20,\\\"rl\\\":20},\\\"o\\\":{\\\"ll\\\":90,\\\"rl\\\":-90},\\\"ph\\\":{\\\"rl\\\":180},\\\"p\\\":600,\\\"c\\\":3.0}}],\\\"d\\\":0}\"}"
"示例3-单腿振荡配合固定脚(安全):{\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"ll\\\":45},\\\"o\\\":{\\\"ll\\\":90,\\\"lf\\\":90},\\\"p\\\":400,\\\"c\\\":4.0}}],\\\"d\\\":0}\"}"
"示例4-复杂多舵机振荡(手和腿):{\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"lh\\\":25,\\\"rh\\\":25,\\\"ll\\\":15},\\\"o\\\":{\\\"lh\\\":90,\\\"rh\\\":90,\\\"ll\\\":90,\\\"lf\\\":90},\\\"ph\\\":{\\\"rh\\\":180},\\\"p\\\":800,\\\"c\\\":6.0}}],\\\"d\\\":500}\"}"
"示例5-快速摇摆:{\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"ll\\\":30,\\\"rl\\\":30},\\\"o\\\":{\\\"ll\\\":90,\\\"rl\\\":90},\\\"ph\\\":{\\\"rl\\\":180},\\\"p\\\":300,\\\"c\\\":10.0}}],\\\"d\\\":0}\"}。",
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(),
[this](const PropertyList& properties) -> ReturnValue {
if (action_task_handle_ != nullptr) {
vTaskDelete(action_task_handle_);
action_task_handle_ = nullptr;
}
is_action_in_progress_ = false;
PowerManager::ResumeBatteryUpdate(); // 停止动作时恢复电量更新
xQueueReset(action_queue_);
QueueAction(ACTION_HOME, 1, 1000, 1, 0);
return true;
});
mcp_server.AddTool(
"self.otto.set_trim",
"校准单个舵机位置。设置指定舵机的微调参数以调整机器人的初始站立姿态,设置将永久保存。"
"servo_type: 舵机类型(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand); "
"trim_value: 微调值(-50到50度)",
PropertyList({Property("servo_type", kPropertyTypeString, "left_leg"),
Property("trim_value", kPropertyTypeInteger, 0, -50, 50)}),
[this](const PropertyList& properties) -> ReturnValue {
std::string servo_type = properties["servo_type"].value<std::string>();
int trim_value = properties["trim_value"].value<int>();
ESP_LOGI(TAG, "设置舵机微调: %s = %d度", servo_type.c_str(), trim_value);
// 获取当前所有微调值
Settings settings("otto_trims", true);
int left_leg = settings.GetInt("left_leg", 0);
int right_leg = settings.GetInt("right_leg", 0);
int left_foot = settings.GetInt("left_foot", 0);
int right_foot = settings.GetInt("right_foot", 0);
int left_hand = settings.GetInt("left_hand", 0);
int right_hand = settings.GetInt("right_hand", 0);
// 更新指定舵机的微调值
if (servo_type == "left_leg") {
left_leg = trim_value;
settings.SetInt("left_leg", left_leg);
} else if (servo_type == "right_leg") {
right_leg = trim_value;
settings.SetInt("right_leg", right_leg);
} else if (servo_type == "left_foot") {
left_foot = trim_value;
settings.SetInt("left_foot", left_foot);
} else if (servo_type == "right_foot") {
right_foot = trim_value;
settings.SetInt("right_foot", right_foot);
} else if (servo_type == "left_hand") {
if (!has_hands_) {
return "错误:机器人没有配置手部舵机";
}
left_hand = trim_value;
settings.SetInt("left_hand", left_hand);
} else if (servo_type == "right_hand") {
if (!has_hands_) {
return "错误:机器人没有配置手部舵机";
}
right_hand = trim_value;
settings.SetInt("right_hand", right_hand);
} else {
return "错误:无效的舵机类型,请使用: left_leg, right_leg, left_foot, "
"right_foot, left_hand, right_hand";
}
otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
QueueAction(ACTION_JUMP, 1, 500, 0, 0);
return "舵机 " + servo_type + " 微调设置为 " + std::to_string(trim_value) +
" 度,已永久保存";
});
mcp_server.AddTool("self.otto.get_trims", "获取当前的舵机微调设置", PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
Settings settings("otto_trims", false);
int left_leg = settings.GetInt("left_leg", 0);
int right_leg = settings.GetInt("right_leg", 0);
int left_foot = settings.GetInt("left_foot", 0);
int right_foot = settings.GetInt("right_foot", 0);
int left_hand = settings.GetInt("left_hand", 0);
int right_hand = settings.GetInt("right_hand", 0);
std::string result =
"{\"left_leg\":" + std::to_string(left_leg) +
",\"right_leg\":" + std::to_string(right_leg) +
",\"left_foot\":" + std::to_string(left_foot) +
",\"right_foot\":" + std::to_string(right_foot) +
",\"left_hand\":" + std::to_string(left_hand) +
",\"right_hand\":" + std::to_string(right_hand) + "}";
ESP_LOGI(TAG, "获取微调设置: %s", result.c_str());
return result;
});
mcp_server.AddTool("self.otto.get_status", "获取机器人状态,返回 moving 或 idle",
PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return is_action_in_progress_ ? "moving" : "idle";
});
mcp_server.AddTool("self.battery.get_level", "获取机器人电池电量和充电状态", PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& board = Board::GetInstance();
int level = 0;
bool charging = false;
bool discharging = false;
board.GetBatteryLevel(level, charging, discharging);
std::string status =
"{\"level\":" + std::to_string(level) +
",\"charging\":" + (charging ? "true" : "false") + "}";
return status;
});
mcp_server.AddTool("self.otto.get_ip", "获取机器人WiFi IP地址", PropertyList(),
[](const PropertyList& properties) -> ReturnValue {
auto& wifi = WifiManager::GetInstance();
std::string ip = wifi.GetIpAddress();
if (ip.empty()) {
return "{\"ip\":\"\",\"connected\":false}";
}
std::string status = "{\"ip\":\"" + ip + "\",\"connected\":true}";
return status;
});
ESP_LOGI(TAG, "MCP工具注册完成");
}
~OttoController() {
if (action_task_handle_ != nullptr) {
vTaskDelete(action_task_handle_);
action_task_handle_ = nullptr;
}
vQueueDelete(action_queue_);
}
};
static OttoController* g_otto_controller = nullptr;
void InitializeOttoController(const HardwareConfig& hw_config) {
if (g_otto_controller == nullptr) {
g_otto_controller = new OttoController(hw_config);
ESP_LOGI(TAG, "Otto控制器已初始化并注册MCP工具");
}
}