#include "navigation.h" #include // ----------- 内部状态变量 ----------- 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; } }