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