Files
mqtt_power/mqtt_esp32_client/mqtt_esp32_client.ino
2025-05-25 15:28:16 +08:00

267 lines
10 KiB
C++
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.

#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过于频繁给其他任务一点时间 (可选)
}