单片机互通调试(未完成)

This commit is contained in:
2025-05-26 22:52:17 +08:00
parent 17cbec048e
commit 22e1109d81
5 changed files with 249 additions and 187 deletions

View File

@@ -4,8 +4,8 @@
// ----------- 设备配置 (需要为您自己的设备和环境修改) -----------
// WiFi
const char *ssid = "xxxxx"; // 请输入您的 Wi-Fi 名称
const char *password = "xxxxx"; // 请输入您的 Wi-Fi 密码
const char *ssid = "UFI_DB50CD"; // 请输入您的 Wi-Fi 名称
const char *password = "1349534012"; // 请输入您的 Wi-Fi 密码
// MQTT Broker
const char *mqtt_broker = "broker.emqx.io"; // 您的 MQTT Broker 地址
@@ -17,15 +17,9 @@ 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
// 基于 application.yml 和后端服务实现
String topic_uplink_to_backend; // 上报给后端: yupi_mqtt_power_project/robot/status/{spotUid}
String topic_downlink_from_backend; // 从后端接收指令: yupi_mqtt_power_project/robot/command/{spotUid}
// ----------- 全局变量 -----------
WiFiClient espClient;
@@ -47,20 +41,15 @@ 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";
String backend_status_base = "yupi_mqtt_power_project/robot/status/";
String backend_command_base = "yupi_mqtt_power_project/robot/command/";
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);
topic_uplink_to_backend = backend_status_base + String(spotUid);
topic_downlink_from_backend = backend_command_base + String(spotUid);
Serial.println("MQTT 主题初始化完成 (匹配后端实现):");
Serial.println(" 上行主题 (状态/心跳/ACK): " + topic_uplink_to_backend);
Serial.println(" 下行主题 (接收指令): " + topic_downlink_from_backend);
}
void connect_wifi() {
@@ -85,15 +74,11 @@ void reconnect_mqtt() {
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();
// 订阅唯一下行指令主题
client.subscribe(topic_downlink_from_backend.c_str());
Serial.println("已订阅指令主题: " + topic_downlink_from_backend);
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); // 连接成功后发送一次常规状态更新
} else {
Serial.print("连接失败, rc=");
Serial.print(client.state());
@@ -110,11 +95,16 @@ void callback(char *topic, byte *payload, unsigned int length) {
char message[length + 1];
memcpy(message, payload, length);
message[length] = \'\\0\'; // 添加字符串结束符
message[length] = '\0';
Serial.print("消息内容: ");
Serial.println(message);
StaticJsonDocument<256> doc; // 适当调整JSON文档大小
if (String(topic) != topic_downlink_from_backend) {
Serial.println("消息非来自预期的指令主题,忽略。");
return;
}
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, message);
if (error) {
@@ -123,38 +113,60 @@ void callback(char *topic, byte *payload, unsigned int length) {
return;
}
const char* commandId = doc["commandId"]; // 提取 commandId 用于回执
const char* cmdType = doc["commandType"]; // 例如: "START_CHARGE", "STOP_CHARGE"
const char* taskId = doc["taskId"]; // 用于ACK
// 根据主题处理指令
if (String(topic) == topic_command_start) {
if (cmdType == nullptr || taskId == nullptr) {
Serial.println("指令JSON缺少 commandType 或 taskId 字段。");
publish_ack_message(taskId, false, "Command JSON invalid", nullptr); // 尝试ACK错误
return;
}
if (strcmp(cmdType, "MOVE_TO_SPOT") == 0) {
Serial.println("接收到 [移动到车位] 指令");
// const char* targetSpotUid = doc["target_spot_uid"]; // 可选: 从payload中获取目标车位ID (如果存在且需要进一步处理)
// if (targetSpotUid) {
// Serial.println("目标车位UID: " + String(targetSpotUid));
// }
// 模拟机器人移动到指定位置的动作
Serial.println("模拟: 机器人正在移动到目标车位...");
delay(1000); // 模拟移动耗时 (缩短演示时间)
Serial.println("模拟: 机器人已到达目标车位。");
publish_ack_message(taskId, true, "Robot arrived at spot (simulated)", nullptr);
// 注意:此时设备状态 currentDeviceStatus 可以保持不变,或根据业务逻辑更新
// 例如: currentDeviceStatus = "IDLE_AT_SPOT";
// 如果需要,可以立即上报一次状态: publish_regular_status_update();
} else if (strcmp(cmdType, "START_CHARGE") == 0) {
Serial.println("接收到 [启动充电] 指令");
// 实际硬件操作: 启动充电
// 例如: digitalWrite(RELAY_PIN, HIGH);
currentDeviceStatus = "CHARGING";
if (doc.containsKey("sessionId")) {
currentSessionId = String(doc["sessionId"].as<const char*>());
} else {
currentSessionId = ""; //确保没有sessionId时清空
}
Serial.println("模拟: 充电已启动。会话ID: " + currentSessionId);
publish_command_ack(commandId, true, "Charging started successfully");
publish_status_update(); // 立即更新状态
publish_ack_message(taskId, true, "Charging started successfully", currentSessionId.c_str());
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); // 立即更新状态
} else if (String(topic) == topic_command_stop) {
} else if (strcmp(cmdType, "STOP_CHARGE") == 0) {
Serial.println("接收到 [停止充电] 指令");
// 实际硬件操作: 停止充电
// 例如: digitalWrite(RELAY_PIN, LOW);
currentDeviceStatus = "COMPLETED"; // 或者 "IDLE",取决于逻辑
currentDeviceStatus = "COMPLETED";
Serial.println("模拟: 充电已停止。");
publish_command_ack(commandId, true, "Charging stopped successfully");
currentSessionId = ""; // 清除会话ID
publish_status_update(); // 立即更新状态
String previousSessionId = currentSessionId; // 保存一下以防ACK需要
currentSessionId = "";
publish_ack_message(taskId, true, "Charging stopped successfully", previousSessionId.c_str());
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); // 立即更新状态
}
// else if (String(topic) == topic_command_query_status) {
// Serial.println("接收到 [查询状态] 指令");
// publish_status_update(); // 回复当前状态
// publish_command_ack(commandId, true, "Status reported");
// Add other commandType handling here if needed, e.g., "QUERY_STATUS"
// else if (strcmp(cmdType, "QUERY_STATUS") == 0) {
// Serial.println("接收到 [查询状态] 指令");
// publish_regular_status_update(); // 回复当前状态
// publish_ack_message(taskId, true, "Status reported", nullptr);
// }
else {
Serial.println("未知指令主题");
Serial.println("未知指令 commandType: " + String(cmdType));
publish_ack_message(taskId, false, ("Unknown commandType: " + String(cmdType)).c_str(), nullptr);
}
Serial.println("-----------------------");
}
@@ -176,48 +188,70 @@ void publish_message(const String& topic, const JsonDocument& doc, const char* m
}
}
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; // 或者不包含此字段
// isAckOrTaskUpdate: true if this is an ACK or a task-specific update, false for general status/heartbeat
// ackTaskId: The taskId if this is an ACK for a command. Null otherwise.
void publish_status_update(bool isAckOrTaskUpdate, const char* ackTaskId, const char* ackStatus, const char* ackMessage, const char* ackErrorCode, const char* ackSessionId) {
StaticJsonDocument<512> doc;
doc["robotUid"] = spotUid;
if (isAckOrTaskUpdate) {
if (ackTaskId) doc["taskId"] = ackTaskId;
if (ackStatus) doc["status"] = ackStatus; // e.g., "SUCCESS", "FAILURE" or task-specific status
if (ackMessage) doc["message"] = ackMessage;
if (ackErrorCode) doc["errorCode"] = ackErrorCode;
// actualRobotStatus should still be sent to reflect current state after ACK
doc["actualRobotStatus"] = currentDeviceStatus;
if (ackSessionId && strlen(ackSessionId) > 0) doc["activeTaskId"] = ackSessionId; // Assuming activeTaskId can hold sessionId for context in ACKs
// Or, if RobotStatusMessage is extended for sessionId in future.
// For now, activeTaskId might be a way to correlate, or it might be ignored by backend for ACKs.
} else { // General status update / heartbeat
doc["actualRobotStatus"] = currentDeviceStatus;
doc["voltage"] = currentVoltage; // Example: Add these if backend expects them with general status
doc["current"] = currentCurrent;
doc["power"] = currentPower;
doc["energyConsumed"] = currentEnergyConsumed;
doc["errorCode"] = currentErrorCode; // General device error code
if (currentSessionId.length() > 0) {
// For general status, if a session is active, it might be relevant as activeTaskId
// This depends on how backend interprets activeTaskId outside of specific task ACKs.
doc["activeTaskId"] = currentSessionId; // Or a more generic field if RobotStatusMessage evolves
}
}
publish_message(topic_status_update, doc, "状态更新");
lastStatusUpdateTime = millis();
// Common fields (timestamp can be added by backend or here if NTP is used)
// doc["timestamp"] = String(millis()); // Already using millis()
publish_message(topic_uplink_to_backend, doc, isAckOrTaskUpdate ? "ACK/TaskUpdate" : "StatusUpdate");
if (!isAckOrTaskUpdate) { // Only update lastStatusUpdateTime for general status updates, not for ACKs triggered by commands
lastStatusUpdateTime = millis();
}
}
void publish_regular_status_update() {
// This is a wrapper for general periodic status updates
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr);
}
void publish_heartbeat() {
StaticJsonDocument<256> doc; // 适当调整JSON文档大小
doc["spotUid"] = spotUid;
doc["timestamp"] = String(WiFi.getTime()); // 同上NTP时间问题
doc["status"] = currentDeviceStatus; // 心跳中也带上当前状态
publish_message(topic_heartbeat, doc, "心跳");
StaticJsonDocument<256> doc; // Heartbeat can be simpler
doc["robotUid"] = spotUid;
doc["actualRobotStatus"] = currentDeviceStatus; // Heartbeat includes current status
// Optionally, add a specific "messageType": "HEARTBEAT" if backend needs explicit differentiation
// beyond just a minimal status update. For now, relying on RobotStatusMessage structure.
publish_message(topic_uplink_to_backend, doc, "Heartbeat");
lastHeartbeatTime = millis();
}
void publish_command_ack(const char* commandId, bool success, const char* message) {
if (!commandId || strlen(commandId) == 0) {
Serial.println("无法发送ACK: commandId 为空");
return;
// Simplified ACK message function
void publish_ack_message(const char* taskId, bool success, const char* message, const char* sessionIdForAckContext) {
if (!taskId || strlen(taskId) == 0) {
Serial.println("无法发送ACK: taskId 为空");
// Potentially send a general error status if appropriate, but usually ACK needs a taskId
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, "指令回执");
// Use the main publish_status_update function formatted as an ACK
publish_status_update(true, taskId, success ? "SUCCESS" : "FAILURE", message, success ? "0" : "GENERAL_ERROR_ON_ACK", sessionIdForAckContext);
}
void setup() {
@@ -233,22 +267,18 @@ void setup() {
void loop() {
if (!client.connected()) {
reconnect_mqtt(); // 如果MQTT未连接则重连
reconnect_mqtt();
}
client.loop(); // 维持MQTT连接和处理消息
client.loop();
unsigned long currentTime = millis();
// 定时发送状态更新
if (currentTime - lastStatusUpdateTime > statusUpdateInterval) {
// 在实际项目中,这里应该先更新 currentVoltage, currentCurrent 等状态值
// 例如: currentVoltage = readVoltageSensor();
publish_status_update();
publish_regular_status_update(); // Use the new wrapper
}
// 定时发送心跳
if (currentTime - lastHeartbeatTime > heartbeatInterval) {
publish_heartbeat();
publish_heartbeat(); // Uses the new heartbeat logic
}
// 模拟充电过程中的电量和功率变化 (仅为演示)