From 5612b2e782212084169ac16658f6abe6e92f0729 Mon Sep 17 00:00:00 2001 From: lingyunxsh Date: Tue, 27 May 2025 16:56:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=95=E7=89=87=E6=9C=BA=E4=BA=92=E9=80=9A?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LogBook.md | 38 ++++++++++- README.md | 180 +++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 171 insertions(+), 47 deletions(-) diff --git a/LogBook.md b/LogBook.md index caa6cbb..33d3fbe 100644 --- a/LogBook.md +++ b/LogBook.md @@ -40,4 +40,40 @@ - 更新了MQTT通信约定、消息示例、单片机开发关键逻辑以及示例充电流程,以反映"到达即充电"的新业务规则。 - **待考虑/后续**: - `ChargingSessionServiceImpl.handleChargingStart` 方法目前因 `START_CHARGE` 指令不再由后端主动发送而可能不再被主要流程调用,其定位和用途需要进一步明确或在未来重构中调整。 - - 前端界面和用户交互流程可能需要相应调整,以匹配后端"到达即充电"的逻辑(例如,可能不再需要用户在机器人到达后点击"开始充电"按钮)。 \ No newline at end of file + - 前端界面和用户交互流程可能需要相应调整,以匹配后端"到达即充电"的逻辑(例如,可能不再需要用户在机器人到达后点击"开始充电"按钮)。 + +## YYYY-MM-DD (自动填充日期) + +### ESP32 固件与后端通信逻辑校准与文档更新 + +**主要变更点:** + +1. **MQTT ACK 消息格式校准 (关键)**: + * **问题分析**: 通过深入分析后端代码 (`MqttMessageHandlerImpl.java` 和 `CommandTypeEnum.java`),发现后端期望的MQTT ACK消息格式与之前`README.md`及单片机固件发送的格式存在显著差异。 + * 后端期望ACK中包含 `command_ack` 字段,其值为指令的**中文描述** (例如 "移动到指定点"),用于 `CommandTypeEnum.fromAck()` 方法匹配。 + * 后端期望任务ID字段为 `task_id` (数字类型)。 + * 后端期望成功状态字段为 `success` (布尔类型)。 + * **`README.md` 更新**: 已更新 `README.md` (4.2.1节) 中的ACK消息示例,以准确反映后端期望的格式。包括添加了 `command_ack`,并将 `taskId` 改为 `task_id`,`status` (String) 改为 `success` (Boolean)。 + * **行动项 (开发者)**: **开发者需要修改 `esp32_robot_firmware/mqtt_handler.cpp` 中的 `mqtt_publish_ack` 函数,使其发送的ACK JSON符合后端 `MqttMessageHandlerImpl.java` 的实际解析逻辑 (即包含中文描述的 `command_ack`,数字类型的 `task_id`,和布尔类型的 `success`)。** + +2. **MQTT 下行 `MOVE_TO_SPOT` 指令校准**: + * **问题分析**: 后端 `ChargingSessionServiceImpl.java` 在发送移动指令时,实际使用的payload是 `{"command":"MOVE", ...}`,而 `README.md` 先前示例为 `{"commandType":"MOVE_TO_SPOT", ...}`。 + * **`README.md` 更新**: 已更新 `README.md` (4.2.2节) 中移动指令的示例,以反映后端实际发送的 `command: "MOVE"`格式,并添加了说明指出单片机固件已做兼容处理。 + * **行动项 (开发者, 可选)**: 为保持一致性,可考虑更新后端 `ChargingSessionServiceImpl.java`,使其发送移动指令时使用 `commandType: "MOVE_TO_SPOT"`。 + +3. **文档整体一致性提升**: + * `README.md` 中关于单片机处理逻辑 (4.3节) 和示例流程 (4.4节) 的部分也相应作了调整,以强调新的ACK格式要求和指令细节。 + +**代码分析涉及文件:** + +* `springboot-init-main/src/main/java/com/yupi/project/service/impl/ChargingSessionServiceImpl.java` (下行指令发送) +* `springboot-init-main/src/main/java/com/yupi/project/service/impl/MqttServiceImpl.java` (下行指令封装与 `taskId` 注入) +* `springboot-init-main/src/main/java/com/yupi/project/mqtt/handler/MqttMessageHandlerImpl.java` (上行消息及ACK处理核心逻辑) +* `springboot-init-main/src/main/java/com/yupi/project/model/enums/CommandTypeEnum.java` (ACK中`command_ack`的解析方式) +* `esp32_robot_firmware/mqtt_handler.cpp` (单片机ACK发送逻辑 - **需修改**) +* `esp32_robot_firmware/esp32_robot_firmware.ino` (单片机指令回调处理) + +**后续影响与建议**: + +* **首要任务是修正单片机固件的ACK发送逻辑**,确保与后端正确对接。 +* 在完成固件修改后,建议进行完整的MQTT通信测试,覆盖所有指令类型及其ACK流程。 \ No newline at end of file diff --git a/README.md b/README.md index cde7f36..290ec13 100644 --- a/README.md +++ b/README.md @@ -122,12 +122,13 @@ ## 4. 单片机 (充电桩硬件) 对接指南 -智能充电桩硬件需通过 MQTT 协议与本系统的后端服务进行通信。 +智能充电桩硬件(在本例中为基于ESP32的移动机器人)需通过 MQTT 协议与本系统的后端服务进行通信。 +**注意**: 本项目提供了一个新的、功能更完整的ESP32固件项目,位于 `esp32_robot_firmware/` 文件夹下。本文档后续的对接指南主要基于此新固件的逻辑。旧的 `mqtt_esp32_client.ino` 主要作为简易模拟器,其功能已被新固件覆盖和扩展。 ### 4.1. MQTT 连接配置 * **Broker 地址**: 单片机需配置连接到与后端服务相同的 MQTT Broker (例如 `tcp://your_mqtt_broker_address:1883`)。 -* **Client ID**: 每个充电桩应使用唯一的 Client ID。建议使用充电桩的物理ID或序列号,例如 `charger_spot_001`。 -* **认证**: 如果 MQTT Broker 配置了用户名/密码认证,单片机连接时也需提供。 +* **Client ID**: 每个充电桩应使用唯一的 Client ID。对于新固件,这在 `esp32_robot_firmware/config.h` 中通过 `DEVICE_UID` 配置,必须与后端系统中注册的机器人ID一致。 +* **认证**: 如果 MQTT Broker 配置了用户名/密码认证,单片机连接时也需提供 (在 `config.h` 中配置)。 ### 4.2. MQTT 主题 (Topics) 约定 @@ -138,7 +139,7 @@ 所有类型的上行消息,包括设备状态更新、心跳以及对后端指令的执行回执 (ACK),都统一发送到以下主题: * **统一上行主题格式**: `yupi_mqtt_power_project/robot/status/{spotUid}` - * `{spotUid}`: 充电桩/车位的唯一标识符 (例如 `ESP32_SPOT_001`,必须与后端系统中注册的设备ID一致)。 + * `{spotUid}`: 充电桩/车位的唯一标识符 (例如 `ESP32_SPOT_001`,即 `DEVICE_UID`)。 * **消息格式 (Payload)**: JSON。 消息体结构应与后端 `com.yupi.project.model.dto.mqtt.RobotStatusMessage` 类对应。通过消息体内的字段来区分具体的消息含义。 @@ -148,24 +149,43 @@ "robotUid": "ESP32_SPOT_001", "actualRobotStatus": "CHARGING", // 设备当前状态, 如 IDLE, MOVING, CHARGING, COMPLETED, FAULTED // 以下为可选的详细状态,根据实际需求和后端处理逻辑添加 - "voltage": 220.5, // 充电时相关 - "current": 5.1, // 充电时相关 - "power": 1.12, // kW, 充电时相关 - "energyConsumed": 0.5, // kWh, 本次充电已消耗电量 + "voltage": 0, // 充电时相关 (示例值,实际由固件模拟或测量) + "current": 0, // 充电时相关 (示例值) + "power": 0, // kW, 充电时相关 (示例值) + "energyConsumed": 0, // kWh, 本次充电已消耗电量 (示例值) + "batteryLevel": 85, // 机器人当前电量百分比 + "currentLocation": "SPOT_ID_001", // 机器人当前所在或最后到达的位置ID + "chargeDurationSeconds": 120, // 如果在充电状态,已充电时长(秒) "errorCode": 0, // 0表示无错误,其他值表示特定错误类型 + "message": "Status normal", // 附加信息,例如错误描述 "activeTaskId": "session_xyz123" // 如果设备当前正在执行某个任务或会话,上报其ID } ``` - **示例 - 对后端指令的ACK (成功):** + **示例 - 对后端指令的ACK (成功, 以MOVE_TO_SPOT为例):** + 机器人成功到达目标车位并开始充电后,发送此ACK。 + **注意:后端期望的ACK格式包含 `command_ack` (指令的中文描述), `task_id` (数字类型), 和 `success` (布尔类型)。** ```json { "robotUid": "ESP32_SPOT_001", - "taskId": "backend_task_789", // 对应后端指令中的taskId - "status": "SUCCESS", // 指令执行结果: SUCCESS 或 FAILURE - "message": "Command executed successfully", - "actualRobotStatus": "IDLE", // ACK发生时,设备的当前核心状态 - "activeTaskId": "session_abc456" // 可选,如果ACK与特定会话相关 + "command_ack": "移动到指定点", + "task_id": 789, + "success": true, + "message": "Move to spot completed, charging started.", + "actualRobotStatus": "CHARGING", + "spotId": "SPOT_UID_001" + } + ``` + + **示例 - 对后端指令的ACK (失败):** + ```json + { + "robotUid": "ESP32_SPOT_001", + "command_ack": "移动到指定点", + "task_id": 790, + "success": false, + "message": "Failed to reach spot, obstacle detected.", + "actualRobotStatus": "FAULTED" } ``` @@ -178,16 +198,18 @@ * **消息格式 (Payload)**: JSON。 JSON消息体内部包含了具体的指令类型和所需参数。 - **示例 - 移动到车位指令 (MOVE_TO_SPOT):** - 此指令指示机器人移动到指定车位。机器人到达后将自动进入充电状态。 + **示例 - 移动到车位指令 (后端实际发送格式):** + 此指令指示机器人移动到指定车位。**机器人到达后将自动进入充电状态并上报。** + 后端实际发送的指令可能使用 `command` 字段。 ```json { - "commandType": "MOVE_TO_SPOT", - "taskId": "backend_task_id_for_ack_789", // 供单片机ACK时使用的任务ID - "target_spot_uid": "SPOT_UID_001" // 目标车位ID (单片机仅用于确认,主要由 spotUid 主题参数决定目标) - // "sessionId": "session_abc_123" // 可选,如果需要在payload中也传递会话ID + "command": "MOVE", + "target_spot_uid": "SPOT_UID_001", + "taskId": "backend_task_id_for_ack_789", + "sessionId": "session_abc_123" } ``` + * 单片机固件 (`esp32_robot_firmware`) 已兼容解析 `command` 或 `commandType` 字段作为指令类型。 **示例 - 停止充电指令 (STOP_CHARGE):** ```json @@ -197,37 +219,68 @@ "sessionId": "session_abc_123" // 关联的充电会话ID } ``` - * **单片机处理逻辑**: 单片机收到消息后,需解析JSON负载,识别 `commandType`,提取 `taskId` (用于后续ACK),并获取其他指令参数来执行相应操作。 对于 `MOVE_TO_SPOT`,到达后自动转换为 `CHARGING` 状态。 -### 4.3. 单片机开发关键逻辑 -1. **MQTT 初始化与重连机制**。 -2. **订阅指令主题**。 -3. **JSON 指令消息解析**。 -4. **移动控制** (如果机器人需要物理移动)。 -5. **充电启停控制** (继电器等硬件操作,停止充电时需要)。 -6. **状态监测与实时上报**: - * 收到 `MOVE_TO_SPOT` 后,状态变为 `MOVING` 并上报。 - * 到达目标车位后,状态变为 `CHARGING` 并上报,同时对 `MOVE_TO_SPOT` 指令进行ACK。 - * 收到 `STOP_CHARGE` 后,停止充电,状态变为 `COMPLETED` (或 `IDLE`) 并上报,同时对 `STOP_CHARGE` 指令进行ACK。 -7. **心跳包定时发送**。 -8. **故障检测与上报**。 -9. **安全性**: 确保 `spotUid` 唯一性;推荐 MQTT 通信使用 TLS/SSL 加密。 + **示例 - 开始充电指令 (START_CHARGE):** + **注意: 此指令当前未被后端主动使用。业务逻辑已调整为机器人到达车位后自动开始充电 (`MOVE_TO_SPOT` 完成即充电)。保留此指令定义主要为了兼容性或未来可能的扩展。单片机固件目前仅简单ACK此指令。** + ```json + { + "commandType": "START_CHARGE", + "taskId": "backend_task_id_for_ack_111", + "sessionId": "session_def_456" // 关联的充电会话ID + } + ``` -### 4.4. 示例流程:用户启动充电 (更新后) + * **单片机处理逻辑**: 单片机收到消息后,需解析JSON负载,识别 `commandType`,提取 `taskId` (用于后续ACK),并获取其他指令参数来执行相应操作。 + * 对于 `MOVE_TO_SPOT` (或后端发送的`MOVE`): 执行移动,到达后自动转换到 `CHARGING` 状态,并上报符合后端期望格式的ACK (包含`command_ack`, `task_id`, `success`等)及新状态。 + * 对于 `STOP_CHARGE`: 停止充电(如断开继电器),更新状态为 `COMPLETED` (或 `IDLE`),并上报符合后端期望格式的ACK及新状态。 + * 对于 `START_CHARGE` (当前): 仅发送符合后端期望格式的ACK表示收到指令。 + +### 4.3. 单片机开发关键逻辑 (基于 `esp32_robot_firmware`) +1. **配置 (`config.h`)**: 仔细配置WiFi凭据、MQTT Broker、`DEVICE_UID`以及所有硬件引脚。 +2. **MQTT 初始化与重连机制** (由 `mqtt_handler.cpp` 处理)。 +3. **订阅指令主题** (由 `mqtt_handler.cpp` 处理)。 +4. **JSON 指令消息解析** (在 `esp32_robot_firmware.ino` 的 `handle_mqtt_command` 中处理, `mqtt_handler.cpp`的`callback`负责通用解析并调用回调)。 + * 需能正确解析后端下发的指令,如 `MOVE` (实际) 或 `MOVE_TO_SPOT` (枚举定义)。 +5. **移动控制** (由 `motor_control.cpp` 和 `navigation.cpp` 实现)。 +6. **充电启停控制** (由 `relay_control.cpp` 实现,停止充电时操作继电器)。 +7. **状态监测与实时上报** (主要在 `esp32_robot_firmware.ino` 的 `loop()` 函数和 `handle_mqtt_command` 中管理和触发): + * 收到 `MOVE` (或 `MOVE_TO_SPOT`) 指令后,`current_robot_status` 变为 `MOVING`,`target_spot_for_move` 被设置,并立即上报状态。 + * `loop()` 函数中的导航逻辑 (`navigation_move_to_spot()`) 执行移动。 + * 成功到达目标车位后 (`navigation_move_to_spot()` 返回成功): + * `current_robot_status` 变为 `CHARGING`。 + * `robot_current_location` 更新为到达的车位ID。 + * 调用 `relay_start_charging()`。 + * 向上行主题发送 `MOVE` 指令的成功ACK。**此ACK需符合后端期望格式**: 包含 `robotUid`, `command_ack` (值为"移动到指定点"), `task_id` (来自原指令, 类型为数字), `success` (true), `message`, `actualRobotStatus` ("CHARGING"), 和 `spotId`。 + * 立即上报一次完整的 `CHARGING` 状态。 + * 导航失败后 (`navigation_move_to_spot()` 返回失败): + * `current_robot_status` 变为 `FAULTED`。 + * 向上行主题发送 `MOVE` 指令的失败ACK。**此ACK需符合后端期望格式**: 包含 `command_ack` (值为"移动到指定点"), `task_id`, `success` (false), `message` (错误信息), `actualRobotStatus` ("FAULTED")。 + * 立即上报一次完整的 `FAULTED` 状态。 + * 收到 `STOP_CHARGE` 后: + * 调用 `relay_stop_charging()`。 + * `current_robot_status` 变为 `COMPLETED` (或 `IDLE`,根据固件逻辑)。 + * 向上行主题发送 `STOP_CHARGE` 的成功ACK。**此ACK需符合后端期望格式**: 包含 `command_ack` (值为"停止充电"), `task_id`, `success` (true), `message`, `actualRobotStatus` ("COMPLETED")。 + * 立即上报一次完整的状态。 +8. **心跳包定时发送** (由 `esp32_robot_firmware.ino` 的 `loop()` 函数处理)。 +9. **故障检测与上报** (部分在导航逻辑中处理,状态上报时包含错误码)。 +10. **安全性**: 确保 `DEVICE_UID` 唯一性;推荐 MQTT 通信使用 TLS/SSL 加密 (当前固件未直接集成,需额外配置)。 +11. **电池电量模拟与上报** (由 `esp32_robot_firmware.ino` 的 `simulate_battery_update()` 和状态上报逻辑处理)。 + +### 4.4. 示例流程:用户启动充电 (已更新为到达即充逻辑) 1. 用户在前端 App 请求在车位 `SPOT001` 进行充电。 2. 后端服务验证,创建充电会话 `sessionId=789`,状态为 `REQUESTED`。 -3. 后端分配机器人,创建 `MOVE_TO_SPOT` 任务 (例如 `taskId=task_move_123`),并将机器人DB状态更新为 `MOVING`,会话状态更新为 `ROBOT_ASSIGNED`。 -4. 后端向 MQTT 主题 `yupi_mqtt_power_project/robot/command/SPOT001` 发布 `MOVE_TO_SPOT` 指令,payload 包含 `taskId=task_move_123`。 -5. 单片机 `SPOT001` 接收指令: - a. 将其内部状态更新为 `MOVING`。 - b. 向上行主题 `yupi_mqtt_power_project/robot/status/SPOT001` 上报状态 `MOVING`。 - c. (模拟)执行移动。 - d. 移动到达后,将其内部状态更新为 `CHARGING`。 - e. 向上行主题发送 ACK 消息,确认 `taskId=task_move_123` 完成,消息中 `actualRobotStatus` 为 `CHARGING`。 - f. (可选,或由ACK消息覆盖) 再次向上行主题上报状态 `CHARGING`,包含 `sessionId`。 +3. 后端分配机器人 (例如 `DEVICE_UID=ESP32_BOT_001`),创建 `MOVE_TO_SPOT` 任务 (例如 `taskId=task_move_123`),并将机器人DB状态更新为 `ASSIGNED` (或 `MOVING`,取决于后端状态流转设计),会话状态更新为 `ROBOT_ASSIGNED`。 +4. 后端向 MQTT 主题 `yupi_mqtt_power_project/robot/command/ESP32_BOT_001` 发布 `MOVE_TO_SPOT` 指令,payload 包含 `taskId=task_move_123` 和 `target_spot_uid="SPOT001"`。 +5. 单片机 `ESP32_BOT_001` 接收指令: + a. 将其内部状态 `current_robot_status` 更新为 `MOVING`。 + b. 向上行主题 `yupi_mqtt_power_project/robot/status/ESP32_BOT_001` 上报状态 `MOVING`。 + c. 执行导航到 `SPOT001`。 + d. 移动到达后,将其内部状态 `current_robot_status` 更新为 `CHARGING`,`robot_current_location` 更新为 `SPOT001`,并启动充电 (闭合继电器)。 + e. 向上行主题发送 ACK 消息,确认 `taskId=task_move_123` 完成,消息中 `actualRobotStatus` 为 `CHARGING`,并包含 `spotId="SPOT001"`。 + f. (通常ACK消息已包含最新状态,但为确保) 再次向上行主题上报完整的 `CHARGING` 状态,包含 `sessionId` 和 `chargeDurationSeconds` (初始为0)。 6. 后端接收到 `MOVE_TO_SPOT` 任务 ( `taskId=task_move_123`) 的 ACK (其中机器人状态为 `CHARGING`): a. 将 `RobotTask` ( `taskId=task_move_123`) 标记为 `COMPLETED`。 - b. 将机器人 `SPOT001` 在数据库中的状态更新为 `CHARGING`。 + b. 将机器人 `ESP32_BOT_001` 在数据库中的状态更新为 `CHARGING`。 c. 将充电会话 `sessionId=789` 的状态更新为 `CHARGING_STARTED`,并记录充电开始时间。 7. 前端界面同步更新,显示充电已开始。 @@ -239,6 +292,15 @@ │ ├── src/ │ ├── public/ │ └── package.json +├── esp32_robot_firmware/ # ESP32 机器人固件项目 (推荐使用) +│ ├── esp32_robot_firmware.ino # 主程序文件 +│ ├── config.h # 配置文件 (WiFi, MQTT, 引脚等) +│ ├── mqtt_handler.h/.cpp # MQTT 通信处理 +│ ├── motor_control.h/.cpp # 电机与舵机控制 +│ ├── sensor_handler.h/.cpp # 传感器数据读取 +│ ├── navigation.h/.cpp # 导航与循迹逻辑 +│ ├── relay_control.h/.cpp # 继电器控制 (模拟充电) +│ └── NEXT_STEPS.md # 后续开发与测试步骤指南 ├── springboot-init-main/ # 后端 Spring Boot 应用 │ ├── src/ │ │ ├── main/ @@ -247,10 +309,36 @@ │ │ └── test/ │ ├── doc/ # 项目文档 (如数据库DDL, 阶段计划等) │ └── pom.xml +├── mqtt_esp32_client.ino # 旧的、简易的ESP32 MQTT模拟器 (不推荐新开发使用) └── README.md # 本文件 ``` -## 6. 后续工作与展望 +## 6. ESP32 固件 (`esp32_robot_firmware`) 使用入门 + +1. **环境准备**: + * 安装 Arduino IDE 或 PlatformIO IDE。 + * 在IDE中安装 ESP32 开发板支持。 + * 根据需要安装相关库 (如 `PubSubClient` for MQTT, `ESP32Servo` for Servo control等,大部分已包含在ESP32核心库中或作为标准库提供)。 +2. **打开项目**: + * 使用IDE打开 `esp32_robot_firmware/esp32_robot_firmware.ino`。IDE通常会自动加载文件夹内所有 `.h` 和 `.cpp` 文件。 +3. **配置 `config.h`**: + * **非常重要**: 打开 `config.h` 文件。 + * 填写您的 `WIFI_SSID` 和 `WIFI_PASSWORD`。 + * 配置 `MQTT_BROKER_HOST`, `MQTT_BROKER_PORT`, 以及可选的 `MQTT_USERNAME`, `MQTT_PASSWORD`。 + * **设置 `DEVICE_UID`**: 此ID必须与您在后端系统中为该机器人注册的ID完全一致。 + * **核对并修改所有硬件引脚定义** (`MOTOR_*_PIN`, `SERVO_PIN`, `ULTRASONIC_*_PIN`, `TCRT5000_*_PIN`, `RELAY_PIN`) 以匹配您的实际硬件连接。 + * 调整其他业务参数如 `SIDE_ULTRASONIC_SPOT_THRESHOLD_CM`, `NUM_CHARGING_SPOTS` 等。 +4. **编译与烧录**: + * 选择正确的ESP32开发板型号和端口。 + * 编译并上传固件到您的ESP32设备。 +5. **监控与调试**: + * 打开串口监视器 (波特率在 `config.h` 中 `SERIAL_BAUD_RATE` 定义,默认为 115200)。 + * 观察串口输出,检查WiFi连接、MQTT连接状态、传感器读数、指令接收和状态上报等。 + * 参考 `esp32_robot_firmware/NEXT_STEPS.md` 文件进行详细的模块化测试和导航逻辑调试。 + +**重要提示**: `navigation.cpp` 中的循迹逻辑 (`navigation_follow_line_once`) 是一个非常基础的实现,**您极有可能需要根据您的具体机器人平台、传感器和环境对其进行大幅修改和参数调优,甚至采用更高级的控制算法(如PID)才能获得满意的循迹效果。** `NEXT_STEPS.md` 提供了更详细的指导。 + +## 7. 后续工作与展望 * 完善各模块的单元测试和集成测试。 * 进一步优化 API 文档和错误处理机制。 * 根据实际运营需求,增加更细致的统计报表功能。