Files
mqtt_power/README.md
2025-06-04 18:46:57 +08:00

372 lines
21 KiB
Markdown
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.

# 智能充电桩管理系统 (Charging Pile Management System)
## 1. 项目简介
本项目是一个基于 Spring Boot (后端) 和 Next.js (前端) 构建的智能充电桩管理与计费系统。旨在为用户提供便捷的充电服务,并为管理员提供高效的充电桩、用户及计费管理功能。支持通过 MQTT 协议与充电桩硬件(单片机)进行实时通信和控制。
### 核心功能
* **用户端**:
* 用户注册与登录
* 查看账户余额、充电记录
* 扫码/输入ID启动充电
* 停止充电
* 激活码兑换充值
* **管理端**:
* 用户管理(查询、添加、编辑、删除)
* 充电桩/车位管理(状态监控、添加、编辑、删除)
* 充电会话管理(查询、监控)
* 激活码管理(生成、查询、删除)
* MQTT通信日志查看查询、审计所有MQTT消息
* 系统概览与统计
* **硬件交互 (通过 MQTT)**:
* 充电桩状态上报 (空闲、连接中、充电中、故障等)
* 远程启动/停止充电指令下发
* 心跳维持
## 2. 技术栈
* **后端**: Spring Boot, Spring Security, MyBatis Plus, MySQL, Mosquitto (MQTT Broker), Spring AOP (用于日志)
* **前端**: Next.js, React, TypeScript, Ant Design, Axios
* **数据库**: MySQL
* **通信协议**: HTTP/HTTPS, MQTT
## 3. 项目部署与运行
### 3.1. 环境准备
* Java JDK 1.8 或更高版本
* Maven 3.x
* Node.js 16.x 或更高版本
* Yarn 或 npm
* MySQL 5.7 或更高版本
* MQTT Broker (例如 Mosquitto) 已安装并运行 (默认端口 1883)
### 3.2. 后端服务 (springboot-init-main)
1. **数据库配置**:
* 创建一个数据库 (例如 `charging_system_db`)。
* 修改 `springboot-init-main/src/main/resources/application.yml` (或对应环境的配置文件) 中的数据库连接信息:
```yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database_name?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: your_db_username
password: your_db_password
```
2. **MQTT Broker 配置**:
* 确保 MQTT Broker 正在运行。
* 在 `application.yml` 中配置 MQTT 连接参数 (如果需要修改默认值或添加认证):
```yaml
mqtt:
broker-url: tcp://localhost:1883 # 替换为您的 MQTT Broker 地址
client-id-publisher: backend-publisher-default
client-id-subscriber: backend-subscriber-default
default-topic: "charging/default/topic" # 示例,具体业务主题见单片机对接部分
# username: your_mqtt_username # 如果 Broker 需要认证
# password: your_mqtt_password # 如果 Broker 需要认证
```
3. **启动后端服务**:
* 在 `springboot-init-main` 目录下,使用 Maven 运行:
```bash
mvn spring-boot:run
```
* 或者打包成 jar 文件后运行:
```bash
mvn clean package
java -jar target/springboot-init-main-0.0.1-SNAPSHOT.jar # 注意替换为实际生成的jar包名
```
* **配置文件外部化**: `application.yml` 在打包时已配置为外部化,并会复制到 `target/config/application.yml`。在生产环境部署时,可以将此文件放置于 JAR 包同级目录的 `config` 文件夹下 (例如: `your_app_dir/config/application.yml`)Spring Boot 将自动加载。或者,您也可以通过 `--spring.config.location` 启动参数来指定外部配置文件的具体路径,例如:`java -jar your_app.jar --spring.config.location=file:/path/to/your/external/application.yml`。
* 服务默认运行在 `http://localhost:7529` (或您在 `application.yml` 中配置的端口)。
### 3.3. 前端应用 (charging_web_app)
1. **安装依赖**:
在 `charging_web_app` 目录下运行:
```bash
yarn install
# 或者
# npm install
```
2. **API 代理配置**:
前端应用通过 Next.js 的代理将 `/api` 请求转发到后端服务。此配置在 `next.config.js` 中:
```javascript
// next.config.js (示例)
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:7529/api/:path*', // 后端服务地址
},
];
},
};
module.exports = nextConfig;
```
确保 `destination` 指向您后端服务的正确地址和端口。
3. **启动前端开发服务器**:
```bash
yarn dev
# 或者
# npm run dev
```
前端应用默认运行在 `http://localhost:3000`。
4. **构建生产版本**:
```bash
yarn build
yarn start
# 或者
# npm run build
# npm run start
```
## 4. 单片机 (充电桩硬件) 对接指南
智能充电桩硬件在本例中为基于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。对于新固件这在 `esp32_robot_firmware/config.h` 中通过 `DEVICE_UID` 配置必须与后端系统中注册的机器人ID一致。
* **认证**: 如果 MQTT Broker 配置了用户名/密码认证,单片机连接时也需提供 (在 `config.h` 中配置)。
### 4.2. MQTT 主题 (Topics) 约定
根据后端服务的实际MQTT通信实现主题约定如下
#### 4.2.1. 上行消息 (单片机 -> 后端)
所有类型的上行消息,包括设备状态更新、心跳以及对后端指令的执行回执 (ACK),都统一发送到以下主题:
* **统一上行主题格式**: `yupi_mqtt_power_project/robot/status/{spotUid}`
* `{spotUid}`: 充电桩/车位的唯一标识符 (例如 `ESP32_SPOT_001`,即 `DEVICE_UID`)。
* **消息格式 (Payload)**: JSON。
消息体结构应与后端 `com.yupi.project.model.dto.mqtt.RobotStatusMessage` 类对应。通过消息体内的字段来区分具体的消息含义。
**示例 - 常规状态更新 (如移动中、充电中、空闲等) 或心跳包 (Heartbeat):**
```json
{
"robotUid": "ESP32_SPOT_001",
"actualRobotStatus": "CHARGING", // 设备当前状态, 如 IDLE, MOVING, CHARGING, COMPLETED, FAULTED
// 以下为可选的详细状态,根据实际需求和后端处理逻辑添加
"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中的task_id来驱动任务和会话状态变更此字段更多用于状态同步或辅助信息。
}
```
**示例 - 对后端指令的ACK (成功, 以MOVE_TO_SPOT为例):**
机器人成功到达目标车位并开始充电后发送此ACK。
**注意后端期望的ACK格式包含 `command_ack` (指令的中文描述), `task_id` (数字类型), 和 `success` (布尔类型)。**
```json
{
"robotUid": "ESP32_SPOT_001",
"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"
}
```
#### 4.2.2. 下行指令 (后端 -> 单片机)
后端服务向特定单片机下发指令时,使用以下主题格式。单片机应订阅其自身的这个专属指令主题。
* **统一下行指令主题格式**: `yupi_mqtt_power_project/robot/command/{spotUid}`
* `{spotUid}`: 目标充电桩的唯一标识符。
* **消息格式 (Payload)**: JSON。
JSON消息体内部包含了具体的指令类型和所需参数。后端 `MqttServiceImpl.sendCommand` 方法会自动向 payload 中注入 `taskId`。
**示例 - `MOVE_TO_SPOT` 指令 (后端实际发送格式):**
```json
{
"command": "MOVE",
"targetSpotUid": "SPOT_UID_001",
"sessionId": "session_xyz123", // 充电会话ID由后端业务逻辑注入
"taskId": 123 // 机器人任务ID由MqttServiceImpl自动注入
}
```
*说明:单片机固件已兼容处理 `command` 字段为 `MOVE` 的指令,并将其映射为 `MOVE_TO_SPOT` 行为。*
**示例 - `STOP_CHARGE` 指令:**
```json
{
"command": "STOP_CHARGE",
"sessionId": "session_xyz123", // 充电会话ID
"taskId": 456 // 机器人任务ID由MqttServiceImpl自动注入
}
```
**示例 - `START_CHARGE` 指令 (目前后端不再主动发送,仅作兼容性说明):**
```json
{
"command": "START_CHARGE",
"sessionId": "session_xyz123", // 充电会话ID
"taskId": 789 // 机器人任务ID由MqttServiceImpl自动注入
}
```
### 4.3. 单片机主要指令处理逻辑
#### 4.3.1. `MOVE_TO_SPOT` 指令处理
* **指令接收**: 单片机收到 `command` 为 `MOVE` 的指令。
* **执行流程**:
1. 解析 `targetSpotUid`,开始向目标车位移动。
2. 将自身 `actualRobotStatus` 更新为 `MOVING`,并周期性上报状态。
3. 成功到达目标车位后,根据“到达即充电”的业务逻辑,机器人**立即**将自身 `actualRobotStatus` 更新为 `CHARGING`。
4. 向后端发送 `MOVE_TO_SPOT` 任务的 ACK 消息,其中 `success` 为 `true``actualRobotStatus` 为 `CHARGING`。
* **后端响应**: 后端收到 ACK 后,将 `RobotTask` 标记为 `COMPLETED`,并调用 `ChargingSessionServiceImpl.handleRobotArrival()` 将充电会话状态更新为 `CHARGING_STARTED`,记录充电开始时间。
#### 4.3.2. `STOP_CHARGE` 指令处理
* **指令接收**: 单片机收到 `command` 为 `STOP_CHARGE` 的指令。
* **执行流程**:
1. 停止充电。
2. 将自身 `actualRobotStatus` 更新为 `IDLE` 或 `AVAILABLE`。
3. 向后端发送 `STOP_CHARGE` 任务的 ACK 消息,其中 `success` 为 `true`。
* **后端响应**: 后端收到 ACK 后,将 `RobotTask` 标记为 `COMPLETED`,并调用 `ChargingSessionServiceImpl.handleChargingEnd()` 处理充电结束、费用计算和会话终结。
#### 4.3.3. `START_CHARGE` 指令处理
* **指令接收**: 单片机收到 `command` 为 `START_CHARGE` 的指令。
* **执行流程**:
1. 开始充电。
2. 将自身 `actualRobotStatus` 更新为 `CHARGING`。
3. 向后端发送 `START_CHARGE` 任务的 ACK 消息,其中 `success` 为 `true`。
* **后端响应**: 后端收到 ACK 后,将 `RobotTask` 标记为 `COMPLETED`,并调用 `ChargingSessionServiceImpl.handleChargingStart()` 处理充电开始。
* **重要说明**: **在当前“到达即充电”的业务逻辑下,后端不再主动向机器人发送 `START_CHARGE` MQTT 指令。机器人完成 `MOVE_TO_SPOT` 任务并到达车位后,会立即自动开始充电并上报 `CHARGING` 状态。此指令的保留主要是为了兼容性或未来可能的业务扩展。**
### 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. 后端分配机器人 (例如 `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. 将机器人 `ESP32_BOT_001` 在数据库中的状态更新为 `CHARGING`。
c. 将充电会话 `sessionId=789` 的状态更新为 `CHARGING_STARTED`,并记录充电开始时间。
7. 前端界面同步更新,显示充电已开始。
## 5. 项目结构 (简要)
```
. 项目根目录
├── charging_web_app/ # 前端 Next.js 应用
│ ├── 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/
│ │ │ ├── java/com/yupi/project/
│ │ │ └── resources/
│ │ └── test/
│ ├── doc/ # 项目文档 (如数据库DDL, 阶段计划等)
│ └── pom.xml
├── mqtt_esp32_client.ino # 旧的、简易的ESP32 MQTT模拟器 (不推荐新开发使用)
└── README.md # 本文件
```
## 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 文档和错误处理机制。
* 根据实际运营需求,增加更细致的统计报表功能。
* MQTT通信日志模块
* (可选) 进一步优化Payload的展示例如对JSON格式的Payload进行美化或提供折叠/展开功能。
* (可选) 根据实际需求,考虑日志数据的定期归档或清理策略的实现。
* 考虑引入更高级的 MQTT 特性如QoS、遗嘱消息等。
* 前端 UI/UX 持续打磨,提升用户体验。