Files
mqtt_power/esp32_robot_firmware/navigation.cpp

242 lines
11 KiB
C++
Raw Permalink 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.

#include "navigation.h"
#include <Arduino.h>
// ----------- 内部状态变量 -----------
static NavigationState current_nav_state = NAV_IDLE;
static int detected_spot_count = 0;
static bool side_spot_debounce = false; // 用于侧向超声波检测去抖
static unsigned long last_side_spot_detect_time = 0;
const unsigned long SPOT_DEBOUNCE_TIME_MS = 2000; // 2秒去抖时间避免重复计数同一个桩
// 循迹PID相关 (如果使用)
static float pid_error = 0;
static float pid_last_error = 0;
static float pid_integral = 0;
static float pid_derivative = 0;
// 目标车位ID的映射 (从config.h中获取)
const char* SPOT_IDS[NUM_CHARGING_SPOTS] = {SPOT_ID_1, SPOT_ID_2, SPOT_ID_3};
// 将字符串的 spot_id (如 "001") 转换为索引 (0, 1, 2)
int get_spot_index(const char* target_spot_uid) {
if (!target_spot_uid) return -1;
for (int i = 0; i < NUM_CHARGING_SPOTS; ++i) {
if (strcmp(target_spot_uid, SPOT_IDS[i]) == 0) {
return i;
}
}
return -1; // 未找到
}
void navigation_setup() {
current_nav_state = NAV_IDLE;
detected_spot_count = 0;
side_spot_debounce = false;
// 初始化PID变量 (如果使用)
pid_error = 0; pid_last_error = 0; pid_integral = 0; pid_derivative = 0;
Serial.println("导航系统已初始化。");
}
void navigation_reset_spot_counter() {
detected_spot_count = 0;
side_spot_debounce = false;
Serial.println("车位计数器已重置。");
}
int navigation_get_current_spot_count() {
return detected_spot_count;
}
NavigationState navigation_get_state() {
return current_nav_state;
}
void navigation_follow_line_once() {
LineFollowValues line_values = get_line_follow_values();
int base_speed = 150; // 基础循迹速度,需要调整
int turn_speed_diff = 80; // 转向时的速度差或舵机调整量,需要调整
int servo_turn_angle_small = 15; // 小幅调整舵机角度 (例如10-20度)
int servo_turn_angle_large = 30; // 大幅调整舵机角度
// 这是一个非常基础的循迹逻辑,需要根据实际传感器和机器人进行大幅调整
// 您可能需要更复杂的PID控制或其他算法
if (line_values.left_on_line && line_values.right_on_line) {
// 1. 都在线上 (或者在两条线的中间),直行
servo_center();
motor_move_forward(base_speed);
// Serial.println("Nav: Straight");
} else if (line_values.left_on_line && !line_values.right_on_line) {
// 2. 左边在线,右边不在:车体偏右,需要向左调整
// motor_set_raw_speeds(base_speed - turn_speed_diff, base_speed + turn_speed_diff); // 左轮慢,右轮快
servo_set_angle(90 - servo_turn_angle_small); // 舵机向左
motor_move_forward(base_speed * 0.8); // 调整时可以稍微减速
// Serial.println("Nav: Turn Left (correcting rightward drift)");
} else if (!line_values.left_on_line && line_values.right_on_line) {
// 3. 右边在线,左边不在:车体偏左,需要向右调整
// motor_set_raw_speeds(base_speed + turn_speed_diff, base_speed - turn_speed_diff); // 左轮快,右轮慢
servo_set_angle(90 + servo_turn_angle_small); // 舵机向右
motor_move_forward(base_speed * 0.8);
// Serial.println("Nav: Turn Right (correcting leftward drift)");
} else {
// 4. 两边都不在线:可能偏离太远,或者到达线路末端/交叉口
// 简单处理:停止或尝试最后的方向转动
motor_stop(); // 或者执行更复杂的搜索线路逻辑
Serial.println("Nav: Both sensors off line! Stopping.");
// current_nav_state = NAV_ERROR; // 可以设置错误状态
}
// PID循迹逻辑 (示例,未完全集成)
/*
if (line_values.left_on_line && !line_values.right_on_line) pid_error = -1; // 偏右
else if (!line_values.left_on_line && line_values.right_on_line) pid_error = 1; // 偏左
else if (line_values.left_on_line && line_values.right_on_line) pid_error = 0; // 居中
else { // 都脱离,根据上次误差方向大幅调整或停车
pid_error = pid_last_error * 2; // 放大上次误差
}
pid_integral += pid_error;
pid_derivative = pid_error - pid_last_error;
float motor_adjustment = KP * pid_error + KI * pid_integral + KD * pid_derivative;
pid_last_error = pid_error;
int left_motor_speed = base_speed - motor_adjustment;
int right_motor_speed = base_speed + motor_adjustment;
servo_set_angle(90 - (int)(motor_adjustment * 10)); // 假设舵机调整与motor_adjustment相关
motor_set_raw_speeds(left_motor_speed, right_motor_speed);
*/
}
bool navigation_check_obstacle() {
long dist = get_front_obstacle_distance();
if (dist > 0 && dist < 20) { // 假设前方小于20cm为障碍物
Serial.print("Nav: Obstacle detected at "); Serial.print(dist); Serial.println(" cm");
return true;
}
return false;
}
void navigation_handle_obstacle() {
Serial.println("Nav: Handling obstacle...");
motor_stop();
// 简单处理:可以尝试后退一点,然后再次尝试,或者等待一段时间
// 更复杂的避障逻辑(如转向绕过)需要更多传感器和算法
// For now, just stop and set state to allow intervention or timeout
current_nav_state = NAV_AVOIDING_OBSTACLE; // 更新导航内部状态
// 在实际应用中这里可能需要通知主循环机器人状态为FAULTED或需要帮助
}
// 返回true表示到达目标false表示未到达或已过目标
bool navigation_check_spot_arrival(const char* target_spot_uid) {
long side_dist = get_side_spot_distance();
int target_spot_index = get_spot_index(target_spot_uid);
if (target_spot_index < 0) {
Serial.print("Nav Error: Invalid target_spot_uid for arrival check: "); Serial.println(target_spot_uid);
return false; // 无效目标
}
if (side_dist > 0 && side_dist < SIDE_ULTRASONIC_SPOT_THRESHOLD_CM) {
if (!side_spot_debounce) {
detected_spot_count++;
side_spot_debounce = true; // 进入去抖状态
last_side_spot_detect_time = millis();
Serial.print("Nav: Detected spot candidate. Count: "); Serial.println(detected_spot_count);
// 检查是否到达了目标充电桩 (目标是第 target_spot_index + 1 个桩)
if (detected_spot_count == (target_spot_index + 1)) {
Serial.print("Nav: TARGET SPOT ["
Serial.println("Nav: TARGET SPOT [" + String(target_spot_uid) + "] REACHED (Count matched)!");
return true; // 到达目标
}
}
} else {
// 如果距离大于阈值,且距离上次检测到满足条件已经超过去抖时间,则解除去抖
if (side_spot_debounce && (millis() - last_side_spot_detect_time > SPOT_DEBOUNCE_TIME_MS)) {
side_spot_debounce = false;
Serial.println("Nav: Side spot debounce period ended.");
}
}
// 如果已经计数的桩超过了目标桩的索引+1说明可能错过了或者目标逻辑有问题
if (detected_spot_count > (target_spot_index + 1)) {
Serial.println("Nav Warning: Detected spot count exceeds target. Possible overshoot or error.");
// current_nav_state = NAV_ERROR; // 可以设置为错误状态
// return false; // 或者根据业务逻辑,这里也视为一种失败
}
return false; // 未到达目标
}
bool navigation_move_to_spot(const char* target_spot_uid, RobotStatus* current_robot_status_ptr, String* current_location_ptr) {
if (!target_spot_uid || !current_robot_status_ptr || !current_location_ptr) {
Serial.println("Nav Error: Invalid arguments to navigation_move_to_spot.");
return false;
}
Serial.print("Nav: Starting move_to_spot for target: "); Serial.println(target_spot_uid);
*current_robot_status_ptr = MOVING;
*current_location_ptr = "EN_ROUTE_TO_" + String(target_spot_uid);
current_nav_state = NAV_FOLLOWING_LINE;
// navigation_reset_spot_counter(); // 在开始新的导航任务时重置计数器
// 注意: 重置计数器的时机很重要。如果机器人从任意位置启动,则应重置。
// 如果是连续任务的一部分,则可能不需要。
// 假设从基站出发或新任务开始,这里重置。
// 但如果是在多个桩之间移动,这个逻辑需要调整。
// 为了简单我们假设每次MOVE指令都是一个新的完整寻路。
if (detected_spot_count != 0) { // 如果不是从0开始说明上次任务可能未清理或逻辑错误
Serial.println("Nav Warning: Spot counter not zero at start of new navigation. Resetting.");
navigation_reset_spot_counter();
}
unsigned long nav_start_time = millis();
unsigned long MAX_NAV_TIME_MS = 120000; // 例如最大导航时间2分钟防止卡死
while (current_nav_state != NAV_TARGET_REACHED && current_nav_state != NAV_ERROR) {
if (millis() - nav_start_time > MAX_NAV_TIME_MS) {
Serial.println("Nav Error: Navigation timeout!");
motor_stop();
current_nav_state = NAV_ERROR;
*current_robot_status_ptr = FAULTED; // 更新主状态
*current_location_ptr = "LOST_OR_TIMEOUT";
break;
}
if (navigation_check_obstacle()) {
navigation_handle_obstacle();
*current_robot_status_ptr = MOVING; // 仍然是移动中,但可能需要人工介入或等待
*current_location_ptr = "OBSTACLE_AVOIDANCE";
// 在简单避障后,可能需要重新进入 NAV_FOLLOWING_LINE 状态
// motor_stop(); // 确保在避障时机器人停止,或执行避障动作
// delay(1000); // 等待
// current_nav_state = NAV_FOLLOWING_LINE; // 尝试继续 (这部分逻辑需要完善)
// 目前简单处理是停止,并依赖外部超时或指令取消
Serial.println("Nav: Obstacle detected, stopping and awaiting further logic/timeout.");
motor_stop();
// break; // 或者跳出循环,让外部处理
// 暂不break允许其他状态检查或MQTT消息进来中断
}
if (current_nav_state == NAV_FOLLOWING_LINE || current_nav_state == NAV_AT_SPOT_CANDIDATE) {
navigation_follow_line_once();
if (navigation_check_spot_arrival(target_spot_uid)) {
current_nav_state = NAV_TARGET_REACHED;
*current_location_ptr = String(target_spot_uid); // 更新位置为目标车位
}
}
// (这里可以加入MQTT消息的检查如果收到STOP指令可以中断导航)
// mqtt_loop(); // 不建议在阻塞函数中频繁调用mqtt_loop应由主循环处理
delay(50); // 短暂延时,控制循环频率
}
motor_stop(); // 确保在函数结束时电机停止
if (current_nav_state == NAV_TARGET_REACHED) {
Serial.print("Nav: Successfully reached target "); Serial.println(target_spot_uid);
return true;
} else {
Serial.print("Nav: Failed to reach target "); Serial.println(target_spot_uid);
return false;
}
}