整理文档

This commit is contained in:
2025-05-25 15:28:16 +08:00
parent 53575fa21e
commit 17cbec048e

View File

@@ -0,0 +1,266 @@
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h> // 用于JSON操作
// ----------- 设备配置 (需要为您自己的设备和环境修改) -----------
// WiFi
const char *ssid = "xxxxx"; // 请输入您的 Wi-Fi 名称
const char *password = "xxxxx"; // 请输入您的 Wi-Fi 密码
// MQTT Broker
const char *mqtt_broker = "broker.emqx.io"; // 您的 MQTT Broker 地址
const char *mqtt_username = "emqx"; // 您的 MQTT 用户名 (如果需要)
const char *mqtt_password = "public"; // 您的 MQTT 密码 (如果需要)
const int mqtt_port = 1883; // 您的 MQTT 端口
// 设备唯一标识符 (非常重要, 必须与后端注册的一致)
const char *spotUid = "ESP32_SPOT_001"; // 例如: "SPOT001", "P005-A1" 等
// ----------- MQTT 主题定义 -----------
// 上行 (ESP32 -> 后端)
String topic_status_update; // 状态上报: charging/spot/{spotUid}/status
String topic_command_ack; // 指令回执: charging/spot/{spotUid}/command/ack
String topic_heartbeat; // 心跳: charging/spot/{spotUid}/heartbeat
// 下行 (后端 -> ESP32, ESP32需要订阅这些)
String topic_command_start; // 启动充电: charging/spot/{spotUid}/command/start
String topic_command_stop; // 停止充电: charging/spot/{spotUid}/command/stop
// String topic_command_query_status; // 查询状态 (可选): charging/spot/{spotUid}/command/query_status
// ----------- 全局变量 -----------
WiFiClient espClient;
PubSubClient client(espClient);
// 模拟硬件状态 (实际项目中需要从传感器或硬件逻辑获取)
const char* currentDeviceStatus = "IDLE"; // 设备当前状态: IDLE, CHARGING, FAULTED 等
float currentVoltage = 220.0;
float currentCurrent = 0.0;
float currentPower = 0.0;
float currentEnergyConsumed = 0.0;
int currentErrorCode = 0;
String currentSessionId = ""; // 当前充电会话ID
// 定时发送相关
unsigned long lastStatusUpdateTime = 0;
unsigned long lastHeartbeatTime = 0;
const long statusUpdateInterval = 30000; // 状态上报间隔 (例如: 30秒)
const long heartbeatInterval = 60000; // 心跳间隔 (例如: 60秒)
void setup_mqtt_topics() {
String baseTopic = "charging/spot/" + String(spotUid) + "/";
topic_status_update = baseTopic + "status";
topic_command_ack = baseTopic + "command/ack";
topic_heartbeat = baseTopic + "heartbeat";
topic_command_start = baseTopic + "command/start";
topic_command_stop = baseTopic + "command/stop";
// topic_command_query_status = baseTopic + "command/query_status";
Serial.println("MQTT 主题初始化完成:");
Serial.println(" 状态上报: " + topic_status_update);
Serial.println(" 指令回执: " + topic_command_ack);
Serial.println(" 心跳: " + topic_heartbeat);
Serial.println(" 订阅启动指令: " + topic_command_start);
Serial.println(" 订阅停止指令: " + topic_command_stop);
}
void connect_wifi() {
Serial.println("正在连接 Wi-Fi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi 连接成功!");
Serial.print("IP 地址: ");
Serial.println(WiFi.localIP());
}
void reconnect_mqtt() {
while (!client.connected()) {
Serial.print("尝试连接 MQTT Broker: ");
Serial.println(mqtt_broker);
String client_id = "esp32-client-" + String(spotUid) + "-";
client_id += String(WiFi.macAddress());
Serial.printf("客户端 ID: %s \n", client_id.c_str());
if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
Serial.println("MQTT Broker 连接成功!");
// 重新订阅主题
client.subscribe(topic_command_start.c_str());
Serial.println("已订阅: " + topic_command_start);
client.subscribe(topic_command_stop.c_str());
Serial.println("已订阅: " + topic_command_stop);
// if (topic_command_query_status.length() > 0) client.subscribe(topic_command_query_status.c_str());
// (可选) 连接成功后立即发送一次状态
publish_status_update();
} else {
Serial.print("连接失败, rc=");
Serial.print(client.state());
Serial.println(" 2秒后重试...");
delay(2000);
}
}
}
void callback(char *topic, byte *payload, unsigned int length) {
Serial.println("-----------------------");
Serial.print("消息抵达, 主题: ");
Serial.println(topic);
char message[length + 1];
memcpy(message, payload, length);
message[length] = \'\\0\'; // 添加字符串结束符
Serial.print("消息内容: ");
Serial.println(message);
StaticJsonDocument<256> doc; // 适当调整JSON文档大小
DeserializationError error = deserializeJson(doc, message);
if (error) {
Serial.print("JSON 解析失败: ");
Serial.println(error.f_str());
return;
}
const char* commandId = doc["commandId"]; // 提取 commandId 用于回执
// 根据主题处理指令
if (String(topic) == topic_command_start) {
Serial.println("接收到 [启动充电] 指令");
// 实际硬件操作: 启动充电
// 例如: digitalWrite(RELAY_PIN, HIGH);
currentDeviceStatus = "CHARGING";
if (doc.containsKey("sessionId")) {
currentSessionId = String(doc["sessionId"].as<const char*>());
}
Serial.println("模拟: 充电已启动。会话ID: " + currentSessionId);
publish_command_ack(commandId, true, "Charging started successfully");
publish_status_update(); // 立即更新状态
} else if (String(topic) == topic_command_stop) {
Serial.println("接收到 [停止充电] 指令");
// 实际硬件操作: 停止充电
// 例如: digitalWrite(RELAY_PIN, LOW);
currentDeviceStatus = "COMPLETED"; // 或者 "IDLE",取决于逻辑
Serial.println("模拟: 充电已停止。");
publish_command_ack(commandId, true, "Charging stopped successfully");
currentSessionId = ""; // 清除会话ID
publish_status_update(); // 立即更新状态
}
// else if (String(topic) == topic_command_query_status) {
// Serial.println("接收到 [查询状态] 指令");
// publish_status_update(); // 回复当前状态
// publish_command_ack(commandId, true, "Status reported");
// }
else {
Serial.println("未知指令主题");
}
Serial.println("-----------------------");
}
void publish_message(const String& topic, const JsonDocument& doc, const char* message_type) {
String jsonBuffer;
serializeJson(doc, jsonBuffer);
Serial.print("发送 ");
Serial.print(message_type);
Serial.print(" 到主题 [");
Serial.print(topic);
Serial.print("]: ");
Serial.println(jsonBuffer);
if (client.publish(topic.c_str(), jsonBuffer.c_str())) {
Serial.println(String(message_type) + " 发送成功");
} else {
Serial.println(String(message_type) + " 发送失败");
}
}
void publish_status_update() {
StaticJsonDocument<512> doc; // 适当调整JSON文档大小
doc["spotUid"] = spotUid;
doc["timestamp"] = String(WiFi.getTime()); // 需要NTP同步时间才能获取正确UTC时间此处仅为示例
doc["status"] = currentDeviceStatus;
doc["voltage"] = currentVoltage;
doc["current"] = currentCurrent;
doc["power"] = currentPower;
doc["energyConsumed"] = currentEnergyConsumed;
doc["errorCode"] = currentErrorCode;
if (currentSessionId.length() > 0) {
doc["sessionId"] = currentSessionId;
} else {
doc["sessionId"] = nullptr; // 或者不包含此字段
}
publish_message(topic_status_update, doc, "状态更新");
lastStatusUpdateTime = millis();
}
void publish_heartbeat() {
StaticJsonDocument<256> doc; // 适当调整JSON文档大小
doc["spotUid"] = spotUid;
doc["timestamp"] = String(WiFi.getTime()); // 同上NTP时间问题
doc["status"] = currentDeviceStatus; // 心跳中也带上当前状态
publish_message(topic_heartbeat, doc, "心跳");
lastHeartbeatTime = millis();
}
void publish_command_ack(const char* commandId, bool success, const char* message) {
if (!commandId || strlen(commandId) == 0) {
Serial.println("无法发送ACK: commandId 为空");
return;
}
StaticJsonDocument<256> doc; // 适当调整JSON文档大小
doc["commandId"] = commandId;
doc["spotUid"] = spotUid;
doc["status"] = success ? "SUCCESS" : "FAILURE";
doc["message"] = message;
publish_message(topic_command_ack, doc, "指令回执");
}
void setup() {
Serial.begin(115200);
Serial.println("\nESP32 充电桩模拟客户端启动...");
setup_mqtt_topics(); // 初始化MQTT主题
connect_wifi(); // 连接Wi-Fi
client.setServer(mqtt_broker, mqtt_port);
client.setCallback(callback); // 设置消息回调函数
}
void loop() {
if (!client.connected()) {
reconnect_mqtt(); // 如果MQTT未连接则重连
}
client.loop(); // 维持MQTT连接和处理消息
unsigned long currentTime = millis();
// 定时发送状态更新
if (currentTime - lastStatusUpdateTime > statusUpdateInterval) {
// 在实际项目中,这里应该先更新 currentVoltage, currentCurrent 等状态值
// 例如: currentVoltage = readVoltageSensor();
publish_status_update();
}
// 定时发送心跳
if (currentTime - lastHeartbeatTime > heartbeatInterval) {
publish_heartbeat();
}
// 模拟充电过程中的电量和功率变化 (仅为演示)
if (String(currentDeviceStatus) == "CHARGING") {
currentEnergyConsumed += 0.01; // 假设每秒消耗0.01kWh (不精确,仅为演示)
currentCurrent = 5.0; // 假设充电电流5A
currentPower = (currentVoltage * currentCurrent) / 1000.0; // kW
// 注意:实际项目中这些值应来自传感器或充电控制器
// 这里为了演示,每隔一段时间简单更新一下
} else {
currentCurrent = 0.0;
currentPower = 0.0;
}
delay(100); // 短暂延时避免loop过于频繁给其他任务一点时间 (可选)
}