mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-06-30 01:34:42 +08:00
feat(motion): smooth action ending and adaptive homing on otto/electron-bot (#2001)
* Enhance Otto Robot camera support by adding configuration for OV3660. Updated config.h to define camera types and GPIO settings, modified config.json to include new camera options, and refactored otto_robot.cc for improved camera detection and initialization logic. * fix: 移除 OttoEmojiDisplay 构造函数中的 SetTheme 调用以修复 LoadProhibited 崩溃 Made-with: Cursor * refactor: improve audio service error handling and codec timeout management - Updated AudioService to prevent input task termination on read timeout, introducing a delay instead. - Enhanced NoAudioCodec to implement a read timeout for I2S channel reads. - Adjusted WebSocketControlServer to set a control port for improved socket management. - Added manufacturer information to the config.json for waveshare ESP32-Touch-LCD-3.5. * fix(otto): WebSocket direct clients not receiving MCP responses When a browser connects directly to the WebSocket control server (port 8080) and sends a JSON-RPC request, the MCP response was routed through Application::SendMcpMessage -> protocol_->SendMcpMessage, which sends it to the cloud protocol channel. As a result, the direct WebSocket client never received the response, while the WeChat mini-program could because it communicates via the cloud. Fix: - Add BroadcastMessage() to WebSocketControlServer, using httpd_queue_work + httpd_ws_send_frame_async to asynchronously send responses back to all connected clients on port 8080 - Add RegisterMcpBroadcastCallback() to Application, allowing an additional MCP send callback to be registered; SendMcpMessage() now invokes it alongside the cloud protocol - Register the broadcast callback in OttoRobot after the WebSocket server starts successfully Also add WebSocket direct-connect API documentation to README.md with complete JSON-RPC 2.0 command examples. * fix(otto-robot): migrate camera backend and set safe dark default theme - Migrate `otto-robot` camera backend from `EspVideo` to `Esp32Camera` to improve capture stability after reboot/power cycle. - Keep runtime sensor detection for both OV2640 and OV3660, and rename PID macros with `OTTO_` prefix to avoid symbol conflicts. - Configure camera output as `RGB565 + FRAMESIZE_240X240` to match the 240x240 display. - Rotate OV2640 output by 180 degrees (`VFlip + HMirror`) for correct orientation. - Simplify `otto-robot` camera sdkconfig options by keeping only: - `CONFIG_CAMERA_OV2640=y` - `CONFIG_CAMERA_OV3660=y` - Move Otto default dark theme setup into `OttoEmojiDisplay::SetupUI()` (after base UI init) to avoid boot-time LVGL null-object crash caused by calling `SetTheme()` too early. * feat(motion): smooth action ending and adaptive homing on otto/electron-bot Improve servo motion transitions to reduce abrupt returns to home pose. Replace linear interpolation with ease-out movement, make homing duration adaptive to angle delta, and skip intermediate homing when queued actions are pending to keep multi-action sequences fluid.
This commit is contained in:
@@ -95,6 +95,14 @@ private:
|
||||
// 复位动作
|
||||
controller->electron_bot_.Home(true);
|
||||
}
|
||||
if (params.action_type != ACTION_HOME) {
|
||||
UBaseType_t pending_actions =
|
||||
uxQueueMessagesWaiting(controller->action_queue_);
|
||||
// 连续动作时跳过中间归位,避免动作衔接生硬
|
||||
if (pending_actions == 0) {
|
||||
controller->electron_bot_.Home(true);
|
||||
}
|
||||
}
|
||||
controller->is_action_in_progress_ = false; // 动作执行完毕
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
#include "movements.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "oscillator.h"
|
||||
|
||||
namespace {
|
||||
float EaseOutCubic(float t) {
|
||||
float inv = 1.0f - t;
|
||||
return 1.0f - inv * inv * inv;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Otto::Otto() {
|
||||
is_otto_resting_ = false;
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
@@ -80,53 +88,39 @@ void Otto::MoveServos(int time, int servo_target[]) {
|
||||
SetRestState(false);
|
||||
}
|
||||
|
||||
final_time_ = millis() + time;
|
||||
if (time > 10) {
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int iteration = 1; millis() < final_time_; iteration++) {
|
||||
partial_time_ = millis() + 10;
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
} else {
|
||||
if (time <= 10) {
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_target[i]);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(time));
|
||||
return;
|
||||
}
|
||||
|
||||
// final adjustment to the target.
|
||||
bool f = true;
|
||||
int adjustment_count = 0;
|
||||
while (f && adjustment_count < 10) {
|
||||
f = false;
|
||||
int start[SERVO_COUNT];
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
start[i] = servo_[i].GetPosition();
|
||||
}
|
||||
|
||||
int steps = std::max(1, time / 10);
|
||||
for (int step = 1; step <= steps; step++) {
|
||||
float t = static_cast<float>(step) / static_cast<float>(steps);
|
||||
float eased_t = EaseOutCubic(t);
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) {
|
||||
f = true;
|
||||
break;
|
||||
if (servo_pins_[i] != -1) {
|
||||
float interpolated = start[i] + (servo_target[i] - start[i]) * eased_t;
|
||||
servo_[i].SetPosition(static_cast<int>(std::round(interpolated)));
|
||||
}
|
||||
}
|
||||
if (f) {
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_target[i]);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
adjustment_count++;
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_target[i]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void Otto::MoveSingle(int position, int servo_number) {
|
||||
@@ -192,11 +186,18 @@ void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int peri
|
||||
///////////////////////////////////////////////////////////////////
|
||||
void Otto::Home(bool hands_down) {
|
||||
if (is_otto_resting_ == false) { // Go to rest position only if necessary
|
||||
MoveServos(1000, servo_initial_);
|
||||
int max_delta = 0;
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
max_delta = std::max(max_delta, std::abs(servo_initial_[i] - servo_[i].GetPosition()));
|
||||
}
|
||||
}
|
||||
int home_time = std::clamp(500 + max_delta * 9, 500, 1700);
|
||||
MoveServos(home_time, servo_initial_);
|
||||
is_otto_resting_ = true;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
|
||||
bool Otto::GetRestState() {
|
||||
|
||||
@@ -406,7 +406,12 @@ private:
|
||||
}
|
||||
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);
|
||||
UBaseType_t pending_actions =
|
||||
uxQueueMessagesWaiting(controller->action_queue_);
|
||||
// 如果后面还有动作,先不归位,避免“动作末尾急停+马上再启动”
|
||||
if (pending_actions == 0) {
|
||||
controller->otto_.Home(params.action_type != ACTION_HANDS_UP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "otto_movements.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "freertos/idf_additions.h"
|
||||
#include "oscillator.h"
|
||||
@@ -9,6 +10,13 @@ static const char* TAG = "OttoMovements";
|
||||
|
||||
#define HAND_HOME_POSITION 45
|
||||
|
||||
namespace {
|
||||
float EaseOutCubic(float t) {
|
||||
float inv = 1.0f - t;
|
||||
return 1.0f - inv * inv * inv;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Otto::Otto() {
|
||||
is_otto_resting_ = false;
|
||||
has_hands_ = false;
|
||||
@@ -92,53 +100,40 @@ void Otto::MoveServos(int time, int servo_target[]) {
|
||||
SetRestState(false);
|
||||
}
|
||||
|
||||
final_time_ = millis() + time;
|
||||
if (time > 10) {
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int iteration = 1; millis() < final_time_; iteration++) {
|
||||
partial_time_ = millis() + 10;
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
} else {
|
||||
if (time <= 10) {
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_target[i]);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(time));
|
||||
return;
|
||||
}
|
||||
|
||||
// final adjustment to the target.
|
||||
bool f = true;
|
||||
int adjustment_count = 0;
|
||||
while (f && adjustment_count < 10) {
|
||||
f = false;
|
||||
int start[SERVO_COUNT];
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
start[i] = servo_[i].GetPosition();
|
||||
}
|
||||
|
||||
int steps = std::max(1, time / 10);
|
||||
for (int step = 1; step <= steps; step++) {
|
||||
float t = static_cast<float>(step) / static_cast<float>(steps);
|
||||
float eased_t = EaseOutCubic(t);
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) {
|
||||
f = true;
|
||||
break;
|
||||
if (servo_pins_[i] != -1) {
|
||||
float interpolated =
|
||||
start[i] + (servo_target[i] - start[i]) * eased_t;
|
||||
servo_[i].SetPosition(static_cast<int>(std::round(interpolated)));
|
||||
}
|
||||
}
|
||||
if (f) {
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_target[i]);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
adjustment_count++;
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (servo_pins_[i] != -1) {
|
||||
servo_[i].SetPosition(servo_target[i]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void Otto::MoveSingle(int position, int servo_number) {
|
||||
@@ -239,6 +234,7 @@ void Otto::Home(bool hands_down) {
|
||||
if (is_otto_resting_ == false) { // Go to rest position only if necessary
|
||||
// 为所有舵机准备初始位置值
|
||||
int homes[SERVO_COUNT];
|
||||
int max_delta = 0;
|
||||
for (int i = 0; i < SERVO_COUNT; i++) {
|
||||
if (i == LEFT_HAND || i == RIGHT_HAND) {
|
||||
if (hands_down) {
|
||||
@@ -256,9 +252,15 @@ void Otto::Home(bool hands_down) {
|
||||
// 腿部和脚部舵机始终复位
|
||||
homes[i] = 90;
|
||||
}
|
||||
|
||||
if (servo_pins_[i] != -1) {
|
||||
max_delta = std::max(max_delta, std::abs(homes[i] - servo_[i].GetPosition()));
|
||||
}
|
||||
}
|
||||
|
||||
MoveServos(700, homes);
|
||||
// 位移越大,归位时间越长,避免末端“急刹车”感
|
||||
int home_time = std::clamp(500 + max_delta * 9, 500, 1700);
|
||||
MoveServos(home_time, homes);
|
||||
is_otto_resting_ = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user