单片机互通调试初步完成
This commit is contained in:
@@ -26,19 +26,26 @@ WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
|
||||
// 模拟硬件状态 (实际项目中需要从传感器或硬件逻辑获取)
|
||||
const char* currentDeviceStatus = "IDLE"; // 设备当前状态: IDLE, CHARGING, FAULTED 等
|
||||
const char* currentDeviceStatus = "IDLE"; // 设备当前状态: IDLE, CHARGING, COMPLETED, FAULTED 等
|
||||
float currentVoltage = 220.0;
|
||||
float currentCurrent = 0.0;
|
||||
float currentPower = 0.0;
|
||||
float currentEnergyConsumed = 0.0;
|
||||
int currentErrorCode = 0;
|
||||
String currentSessionId = ""; // 当前充电会话ID
|
||||
String currentSimulatedLocation = "BASE_STATION"; // 新增:模拟当前位置
|
||||
int currentSimulatedBattery = 95; // 新增:模拟当前电量
|
||||
String currentTargetSpot = ""; // 新增:用于移动状态的目标车位
|
||||
String currentSpotId = ""; // 新增:当前所在车位 (用于充电等状态)
|
||||
unsigned long chargeStartTimeMillis = 0; // 新增:用于计算充电时长
|
||||
|
||||
// 定时发送相关
|
||||
unsigned long lastStatusUpdateTime = 0;
|
||||
unsigned long lastHeartbeatTime = 0;
|
||||
const long statusUpdateInterval = 30000; // 状态上报间隔 (例如: 30秒)
|
||||
const long heartbeatInterval = 60000; // 心跳间隔 (例如: 60秒)
|
||||
unsigned long lastBatteryUpdateTime = 0; // 新增:用于模拟电池电量变化的时间戳
|
||||
const long batteryUpdateInterval = 5000; // 新增:电池状态更新间隔(例如5秒)
|
||||
|
||||
void setup_mqtt_topics() {
|
||||
String backend_status_base = "yupi_mqtt_power_project/robot/status/";
|
||||
@@ -113,50 +120,102 @@ void callback(char *topic, byte *payload, unsigned int length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* cmdType = doc["commandType"]; // 例如: "START_CHARGE", "STOP_CHARGE"
|
||||
const char* taskId = doc["taskId"]; // 用于ACK
|
||||
const char* cmdType = nullptr;
|
||||
if (doc.containsKey("commandType")) {
|
||||
cmdType = doc["commandType"];
|
||||
} else if (doc.containsKey("command")) {
|
||||
cmdType = doc["command"];
|
||||
}
|
||||
const char* taskId = doc["taskId"];
|
||||
|
||||
if (cmdType == nullptr || taskId == nullptr) {
|
||||
Serial.println("指令JSON缺少 commandType 或 taskId 字段。");
|
||||
publish_ack_message(taskId, false, "Command JSON invalid", nullptr); // 尝试ACK错误
|
||||
Serial.println("指令JSON缺少 commandType/command 或 taskId 字段。");
|
||||
publish_ack_message(taskId, false, "Command JSON invalid (missing commandType/command or taskId)", nullptr);
|
||||
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();
|
||||
const char* spotIdFromCommand = nullptr;
|
||||
if (doc.containsKey("spotId")) {
|
||||
spotIdFromCommand = doc["spotId"].as<const char*>();
|
||||
} else if (doc.containsKey("target_spot_uid")) {
|
||||
spotIdFromCommand = doc["target_spot_uid"].as<const char*>();
|
||||
}
|
||||
|
||||
} else if (strcmp(cmdType, "START_CHARGE") == 0) {
|
||||
if (strcmp(cmdType, "MOVE_TO_SPOT") == 0 || strcmp(cmdType, "MOVE") == 0) {
|
||||
Serial.println("接收到 [移动] 指令 (MOVE_TO_SPOT 或 MOVE)");
|
||||
if (spotIdFromCommand) {
|
||||
currentTargetSpot = String(spotIdFromCommand);
|
||||
currentSpotId = "";
|
||||
} else {
|
||||
currentTargetSpot = "UNKNOWN_SPOT";
|
||||
}
|
||||
|
||||
currentDeviceStatus = "MOVING";
|
||||
currentSimulatedLocation = "EN_ROUTE_TO_" + currentTargetSpot;
|
||||
Serial.println("状态更新: MOVING (前往目标车位: " + currentTargetSpot + ")");
|
||||
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
Serial.println("模拟: 机器人正在移动到目标车位 " + currentTargetSpot + "...");
|
||||
// 模拟移动过程中的电量消耗,更细致的可以在 loop 中做
|
||||
if (currentSimulatedBattery > 10) currentSimulatedBattery -= 5; // 假设移动固定消耗一些电
|
||||
else currentSimulatedBattery = 5; // 最低电量
|
||||
|
||||
delay(3000);
|
||||
Serial.println("模拟: 机器人已到达目标车位 " + currentTargetSpot + "。");
|
||||
|
||||
currentDeviceStatus = "CHARGING";
|
||||
currentSpotId = currentTargetSpot;
|
||||
currentTargetSpot = "";
|
||||
currentSimulatedLocation = currentSpotId;
|
||||
chargeStartTimeMillis = millis();
|
||||
Serial.println("状态更新: CHARGING (已到达目标车位 " + currentSpotId + ",视为开始充电)");
|
||||
|
||||
publish_ack_message(taskId, true, "Robot arrived at spot and started charging (simulated)", currentSpotId.c_str());
|
||||
|
||||
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
} else if (strcmp(cmdType, "START_CHARGE") == 0) {
|
||||
Serial.println("接收到 [启动充电] 指令");
|
||||
currentDeviceStatus = "CHARGING";
|
||||
if (strcmp(currentDeviceStatus, "CHARGING") != 0) { // 仅当未在充电时才响应
|
||||
currentDeviceStatus = "CHARGING";
|
||||
if (spotIdFromCommand) {
|
||||
currentSpotId = String(spotIdFromCommand);
|
||||
} else if (currentSpotId.length() == 0) {
|
||||
currentSpotId = "DEFAULT_SPOT"; // 如果没有从指令获取且之前也没有,给个默认值
|
||||
}
|
||||
currentSimulatedLocation = currentSpotId;
|
||||
chargeStartTimeMillis = millis();
|
||||
Serial.println("状态更新: CHARGING (指令启动于 " + currentSpotId + ")");
|
||||
} else {
|
||||
Serial.println("设备已在充电中,忽略 START_CHARGE 指令。");
|
||||
}
|
||||
|
||||
if (doc.containsKey("sessionId")) {
|
||||
currentSessionId = String(doc["sessionId"].as<const char*>());
|
||||
} else {
|
||||
currentSessionId = ""; //确保没有sessionId时清空
|
||||
currentSessionId = "";
|
||||
}
|
||||
Serial.println("模拟: 充电已启动。会话ID: " + currentSessionId);
|
||||
Serial.println("模拟: 充电已启动。会话ID: " + currentSessionId + ", 车位: " + currentSpotId);
|
||||
publish_ack_message(taskId, true, "Charging started successfully", currentSessionId.c_str());
|
||||
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); // 立即更新状态
|
||||
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
} else if (strcmp(cmdType, "STOP_CHARGE") == 0) {
|
||||
Serial.println("接收到 [停止充电] 指令");
|
||||
bool wasCharging = strcmp(currentDeviceStatus, "CHARGING") == 0;
|
||||
currentDeviceStatus = "COMPLETED";
|
||||
Serial.println("模拟: 充电已停止。");
|
||||
String previousSessionId = currentSessionId; // 保存一下,以防ACK需要
|
||||
currentSimulatedLocation = currentSpotId;
|
||||
unsigned long chargeDuration = 0;
|
||||
if (chargeStartTimeMillis > 0 && wasCharging) {
|
||||
chargeDuration = (millis() - chargeStartTimeMillis) / 1000;
|
||||
}
|
||||
chargeStartTimeMillis = 0;
|
||||
Serial.println("模拟: 充电已停止。车位: " + currentSpotId + ", 本次充电时长约: " + String(chargeDuration) + "s");
|
||||
String previousSessionId = currentSessionId;
|
||||
currentSessionId = "";
|
||||
publish_ack_message(taskId, true, "Charging stopped successfully", previousSessionId.c_str());
|
||||
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); // 立即更新状态
|
||||
// 在ACK中上报准确的充电时长,如果需要的话,可以通过修改 publish_ack_message 或在 message 字段中添加
|
||||
// For now, the generic ACK is sent.
|
||||
publish_ack_message(taskId, true, ("Charging stopped. Duration: " + String(chargeDuration) + "s").c_str(), previousSessionId.c_str());
|
||||
publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
// Add other commandType handling here if needed, e.g., "QUERY_STATUS"
|
||||
// else if (strcmp(cmdType, "QUERY_STATUS") == 0) {
|
||||
@@ -196,26 +255,47 @@ void publish_status_update(bool isAckOrTaskUpdate, const char* ackTaskId, const
|
||||
|
||||
if (isAckOrTaskUpdate) {
|
||||
if (ackTaskId) doc["taskId"] = ackTaskId;
|
||||
if (ackStatus) doc["status"] = ackStatus; // e.g., "SUCCESS", "FAILURE" or task-specific status
|
||||
if (ackStatus) doc["status"] = ackStatus;
|
||||
if (ackMessage) doc["message"] = ackMessage;
|
||||
if (ackErrorCode) doc["errorCode"] = ackErrorCode;
|
||||
// actualRobotStatus should still be sent to reflect current state after ACK
|
||||
// 根据用户要求,ACK中不发送errorCode
|
||||
// if (ackErrorCode) doc["errorCode"] = ackErrorCode;
|
||||
|
||||
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.
|
||||
if (ackSessionId && strlen(ackSessionId) > 0) {
|
||||
// For ACKs, if we have a sessionId or a spotId that's relevant for context, we can add it.
|
||||
// Let's use "spotId" as suggested by requirements for charging related ACKs
|
||||
if (strcmp(currentDeviceStatus, "CHARGING") == 0 || strcmp(currentDeviceStatus, "COMPLETED") == 0 || (ackMessage && strstr(ackMessage, "arrived"))) {
|
||||
if(currentSpotId.length() > 0) doc["spotId"] = currentSpotId;
|
||||
}
|
||||
}
|
||||
|
||||
} 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
|
||||
// Add fields as per requirements.md
|
||||
doc["location"] = currentSimulatedLocation;
|
||||
doc["battery"] = currentSimulatedBattery; // Example value, should be updated by a battery sim function
|
||||
// doc["errorCode"] = currentErrorCode; // 根据用户要求,常规状态下不发送errorCode
|
||||
|
||||
// Fields specific to certain statuses
|
||||
if (strcmp(currentDeviceStatus, "MOVING") == 0) {
|
||||
if (currentTargetSpot.length() > 0) doc["targetSpot"] = currentTargetSpot;
|
||||
} else if (strcmp(currentDeviceStatus, "CHARGING") == 0) {
|
||||
if (currentSpotId.length() > 0) doc["spotId"] = currentSpotId;
|
||||
if (chargeStartTimeMillis > 0) {
|
||||
doc["durationSeconds"] = (millis() - chargeStartTimeMillis) / 1000;
|
||||
} else {
|
||||
doc["durationSeconds"] = 0;
|
||||
}
|
||||
} else if (strcmp(currentDeviceStatus, "COMPLETED") == 0) {
|
||||
if (currentSpotId.length() > 0) doc["spotId"] = currentSpotId;
|
||||
// totalDurationSeconds would typically be set upon actual completion event,
|
||||
// for now, a regular status update might not have final total, or we can omit.
|
||||
// Let's assume for now that if status is COMPLETED, we send a placeholder or last known duration.
|
||||
// A more robust solution is needed for totalDurationSeconds.
|
||||
// For now, publish_ack_message for STOP_CHARGE should probably carry the final duration.
|
||||
} else if (strcmp(currentDeviceStatus, "ERROR") == 0) {
|
||||
// errorCode is already included. message for error could be added if available.
|
||||
// doc["message"] = "Simulated error description"; // if we have one
|
||||
}
|
||||
}
|
||||
// Common fields (timestamp can be added by backend or here if NTP is used)
|
||||
@@ -244,14 +324,17 @@ void publish_heartbeat() {
|
||||
}
|
||||
|
||||
// Simplified ACK message function
|
||||
void publish_ack_message(const char* taskId, bool success, const char* message, const char* sessionIdForAckContext) {
|
||||
void publish_ack_message(const char* taskId, bool success, const char* message, const char* contextInfo) {
|
||||
if (!taskId || strlen(taskId) == 0) {
|
||||
Serial.println("无法发送ACK: taskId 为空");
|
||||
// Potentially send a general error status if appropriate, but usually ACK needs a taskId
|
||||
return;
|
||||
}
|
||||
// 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);
|
||||
// For contextInfo, we can pass spotId if relevant, or sessionId if that's what backend expects for ACKs.
|
||||
// The 'true' indicates it's an ACK.
|
||||
// The ackErrorCode field in publish_status_update will be set to "SUCCESS_ACK" or "FAILURE_ACK"
|
||||
// 根据用户要求,ACK中的errorCode也暂时简化或移除。如果保留,确保含义清晰。
|
||||
publish_status_update(true, taskId, success ? "SUCCESS" : "FAILURE", message, nullptr, contextInfo);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
@@ -293,4 +376,52 @@ void loop() {
|
||||
currentPower = 0.0;
|
||||
}
|
||||
delay(100); // 短暂延时,避免loop过于频繁,给其他任务一点时间 (可选)
|
||||
|
||||
// 模拟电量消耗和位置变化 (更符合需求文档)
|
||||
if (strcmp(currentDeviceStatus, "CHARGING") == 0) {
|
||||
if (currentSimulatedBattery > 0) { // 充电时电量可以缓慢增加,或保持不变,取决于模拟逻辑
|
||||
// currentSimulatedBattery = min(100, currentSimulatedBattery + 1); // 简单模拟充电增加
|
||||
}
|
||||
// durationSeconds is calculated in publish_status_update
|
||||
} else if (strcmp(currentDeviceStatus, "MOVING") == 0) {
|
||||
if (currentSimulatedBattery > 5) { // 移动时消耗电量
|
||||
// currentSimulatedBattery--; // 简单模拟电量消耗
|
||||
}
|
||||
} else { // IDLE, COMPLETED, ERROR
|
||||
//电量可能不变或缓慢消耗
|
||||
}
|
||||
|
||||
// 简单模拟位置更新 (可以更复杂)
|
||||
// currentSimulatedLocation = ... ; // 可以在特定事件中更新
|
||||
|
||||
// --- 动态模拟数据更新 ---
|
||||
if (currentTime - lastBatteryUpdateTime > batteryUpdateInterval) {
|
||||
if (strcmp(currentDeviceStatus, "CHARGING") == 0) {
|
||||
if (currentSimulatedBattery < 100) {
|
||||
currentSimulatedBattery += 1; // 每 batteryUpdateInterval 增加 1% 电量
|
||||
Serial.println("模拟: 电量增加至 " + String(currentSimulatedBattery) + "%");
|
||||
} else {
|
||||
currentSimulatedBattery = 100; // 防止超过100
|
||||
}
|
||||
} else if (strcmp(currentDeviceStatus, "MOVING") == 0) {
|
||||
if (currentSimulatedBattery > 2) {
|
||||
currentSimulatedBattery -= 2; // 每 batteryUpdateInterval 消耗 2% 电量
|
||||
Serial.println("模拟: 电量消耗至 " + String(currentSimulatedBattery) + "%");
|
||||
} else {
|
||||
currentSimulatedBattery = 2; // 防止低于2,极端情况
|
||||
}
|
||||
} else { // IDLE, COMPLETED, FAULTED
|
||||
if (currentSimulatedBattery > 1) {
|
||||
// 非常缓慢的自然消耗,或者不消耗
|
||||
// currentSimulatedBattery -= 1;
|
||||
}
|
||||
}
|
||||
lastBatteryUpdateTime = currentTime;
|
||||
}
|
||||
|
||||
// 位置模拟: 通常在状态转换时(callback中)已经更新了主要位置。
|
||||
// loop中可以添加更细致的移动中的位置更新,但目前保持简单,依赖callback中的设定。
|
||||
// 例如: if (strcmp(currentDeviceStatus, "MOVING") == 0) { /* update location based on time/progress */ }
|
||||
|
||||
delay(100);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user