diff --git a/STM32F103_App1/HardWare/NETWORK/net_wifi.c b/STM32F103_App1/HardWare/NETWORK/net_wifi.c index c22df8f..be07dbb 100644 --- a/STM32F103_App1/HardWare/NETWORK/net_wifi.c +++ b/STM32F103_App1/HardWare/NETWORK/net_wifi.c @@ -180,6 +180,13 @@ void net_wifi_process(void) } /*==== 上行发布 ====*/ +/* QoS1 发布(仅平台指令应答用): 带报文标识符, 调用方自行等 PUBACK */ +int net_wifi_publish_qos1(const char *json, uint16_t pkt_id) +{ + if (!g_wifi_mqtt_ready) return -1; + return mqttc_publish_qos1(s_topic_up, json, pkt_id); +} + void net_wifi_publish_up(const char *json) { if (!g_wifi_mqtt_ready) return; diff --git a/STM32F103_App1/HardWare/NETWORK/net_wifi.h b/STM32F103_App1/HardWare/NETWORK/net_wifi.h index 4aeabb1..fd0e4f1 100644 --- a/STM32F103_App1/HardWare/NETWORK/net_wifi.h +++ b/STM32F103_App1/HardWare/NETWORK/net_wifi.h @@ -13,6 +13,7 @@ extern volatile uint8_t g_wifi_restore_req; /* int net_wifi_init(void); /* 阻塞建链:复位→连AP(已存→固定→SmartConfig)→透传→MQTT, 0=就绪 */ void net_wifi_process(void); /* 非阻塞轮询:解析下行报文 + 心跳保活 */ -void net_wifi_publish_up(const char *json); /* 向 /iot/data/up/ 发布 JSON */ +void net_wifi_publish_up(const char *json); +int net_wifi_publish_qos1(const char *json, uint16_t pkt_id); /* QoS1 发布(指令应答用), 0=已发 -1=未就绪 */ /* 向 /iot/data/up/ 发布 JSON */ #endif /* NET_WIFI_H */ diff --git a/STM32F103_App1/HardWare/NETWORK/network.c b/STM32F103_App1/HardWare/NETWORK/network.c index 312d78a..7bfea01 100644 --- a/STM32F103_App1/HardWare/NETWORK/network.c +++ b/STM32F103_App1/HardWare/NETWORK/network.c @@ -11,6 +11,7 @@ #include "network.h" #include "net_wifi.h" +#include "mqtt_client.h" #include "hlw8032.h" #include "feed_scale.h" #include "net_wifi_ota.h" @@ -347,10 +348,10 @@ void NET_publish_response(const char *cmd_id, int ok, const char *msg) int NET_publish_response_wait(const char *cmd_id, int ok, const char *msg) { if (g_net_tx_busy) { log_info("> NET: 应答上报被丢弃 (OTA忙)"); return -1; } - if (net_at_trylock() != 0) { log_info("> NET: 应答上报被丢弃 (AT 忙)"); return -1; } const char *text = msg ? msg : (ok ? "OK" : "command failed"); int rc = 0; if (s_backend == NET_BACKEND_4G) { + if (net_at_trylock() != 0) { log_info("> NET: 应答上报被丢弃 (AT 忙)"); return -1; } static char cmd[256]; sprintf(cmd, "AT+MPUB=\\22/iot/data/up/%s\\22,0,0,\\22{\\22id\\22:\\22%s\\22,\\22code\\22:0,\\22message\\22:\\22%s\\22}\\22", MqttInfoStr.ClientID, cmd_id, text); @@ -358,13 +359,27 @@ int NET_publish_response_wait(const char *cmd_id, int ok, const char *msg) rc = -1; log_warn("> NET: 指令应答发送失败(重试后仍无 OK)"); } + net_at_unlock(); } else { + /* WiFi(软件 MQTT): QoS1 发布 + 等 PUBACK(broker 确认收到), 最多 3 次 + * 注意: 收包解析在 net 任务, 等 PUBACK 必须释放 AT 锁, 否则死锁 */ char json[200]; snprintf(json, sizeof(json), "{\"id\":\"%s\",\"code\":0,\"message\":\"%s\"}", cmd_id, text); - net_wifi_publish_up(json); + static uint16_t pkt_id = 0; + rc = -1; + for (int attempt = 0; attempt < 3; attempt++) { + net_at_lock(); + pkt_id++; + if (pkt_id == 0) pkt_id = 1; + int prc = net_wifi_publish_qos1(json, pkt_id); + net_at_unlock(); + if (prc != 0) break; /* 未就绪, 不必重试 */ + if (mqttc_wait_puback(pkt_id, 1500) == 0) { rc = 0; break; } + log_warn("> NET: 应答 PUBACK 超时, 重试 %d/3", attempt + 1); + } + if (rc != 0) log_warn("> NET: 指令应答发送失败(PUBACK 超时)"); } - net_at_unlock(); return rc; } diff --git a/STM32F103_App1/HardWare/WIFI/mqtt_client.c b/STM32F103_App1/HardWare/WIFI/mqtt_client.c index c6dd82e..57b506c 100644 --- a/STM32F103_App1/HardWare/WIFI/mqtt_client.c +++ b/STM32F103_App1/HardWare/WIFI/mqtt_client.c @@ -154,6 +154,45 @@ int mqttc_publish(const char *topic, const char *payload) return 0; } +/*==== QoS1 发布 + PUBACK 等待(仅平台指令应答用, 确认 broker 收到) ====*/ +static volatile uint8_t s_puback_ok = 0; +static volatile uint16_t s_puback_id = 0; + +int mqttc_publish_qos1(const char *topic, const char *payload, uint16_t pkt_id) +{ + uint16_t pos = 0; + uint32_t rl = 2 + strlen(topic) + 2 + strlen(payload); /* +2 为报文标识符 */ + + if (rl + 5 > MQTT_TX_BUF_SIZE) { + log_warn("> WiFi MQTT: publish 报文过长 (%u)", (unsigned)rl); + return -1; + } + + tx_buf[pos++] = 0x32; /* PUBLISH, QoS1 */ + pos += encode_rl(tx_buf + pos, rl); + pos = put_str(tx_buf, pos, topic); + tx_buf[pos++] = (uint8_t)(pkt_id >> 8); + tx_buf[pos++] = (uint8_t)(pkt_id & 0xFF); + memcpy(tx_buf + pos, payload, strlen(payload)); + pos += strlen(payload); + + s_puback_id = pkt_id; + s_puback_ok = 0; + esp_send(tx_buf, pos); + return 0; +} + +/* 等 PUBACK: 收包解析在 net 任务(mqttc_process), 这里只轮询标志 */ +int mqttc_wait_puback(uint16_t pkt_id, int timeout_ms) +{ + while (timeout_ms > 0) { + if (s_puback_ok && s_puback_id == pkt_id) return 0; + bg_delay(20); + timeout_ms -= 20; + } + return -1; +} + void mqttc_ping(void) { tx_buf[0] = 0xC0; @@ -242,6 +281,12 @@ int mqttc_process(void) if (rl >= 3 && pkt[2] != 0x80) s_suback_ok = 1; else log_warn("> WiFi MQTT: SUBACK 被拒绝"); break; + case 4: /* PUBACK: 0x40 0x02 pktIdHi pktIdLo */ + if (rl >= 2) { + uint16_t id = (uint16_t)(((uint16_t)pkt[0] << 8) | pkt[1]); + if (id == s_puback_id) s_puback_ok = 1; + } + break; case 13: /* PINGRESP */ g_mqttc_ping_outstanding = 0; break; diff --git a/STM32F103_App1/HardWare/WIFI/mqtt_client.h b/STM32F103_App1/HardWare/WIFI/mqtt_client.h index 7de0353..e9e7868 100644 --- a/STM32F103_App1/HardWare/WIFI/mqtt_client.h +++ b/STM32F103_App1/HardWare/WIFI/mqtt_client.h @@ -16,7 +16,9 @@ extern volatile uint8_t g_mqttc_ping_outstanding; void mqttc_init(const char *client_id, const char *user, const char *pass); int mqttc_connect(void); /* 发 CONNECT 等 CONNACK, 0=成功 */ int mqttc_subscribe(const char *topic); /* 发 SUBSCRIBE(QoS0) 等 SUBACK, 0=成功 */ -int mqttc_publish(const char *topic, const char *payload); /* QoS0 发布, 0=成功 */ +int mqttc_publish(const char *topic, const char *payload); /* QoS0 发布, 0=成功 */ +int mqttc_publish_qos1(const char *topic, const char *payload, uint16_t pkt_id); /* QoS1 发布(带报文标识符), 0=成功 */ +int mqttc_wait_puback(uint16_t pkt_id, int timeout_ms); /* 等 PUBACK(调用方不得持有 net_at 锁), 0=收到 */ /* QoS0 发布, 0=成功 */ void mqttc_ping(void); /* 发 PINGREQ */ /* 从共享接收缓冲解析一条完整 MQTT 报文(粘包/半包安全)。 diff --git a/鐗堟湰璇存槑.md b/鐗堟湰璇存槑.md index 7c46d66..07e3022 100644 --- a/鐗堟湰璇存槑.md +++ b/鐗堟湰璇存槑.md @@ -21,9 +21,12 @@ 鎺у埗鍛戒护鏀逛笁娈靛紡锛欶C03 棰勮锛堝垽瀹氬湪绾 + 蹇収鍛戒护鍓嶅弽棣堬級鈫 FC05 鈫 150ms 鈫 FC03 鏍¢獙璺冲彉锛 鍛戒护閾捐矾鏈缁堟棤搴旂瓟鍗冲垽绂荤嚎骞跺簲绛斿钩鍙 `ext relay board offline`锛屼笅娆″懡浠よ嚜鍔ㄩ噸鏂版帰娴嬫仮澶嶏紱 鏂數鎭㈠瑙﹀彂鐐逛笉鍙橈紙绂荤嚎鈫掍笂绾挎瘮瀵 EEPROM 閫愯矾琛ュ彂锛 +- **缁х數鍣/鎶曞杺骞冲彴搴旂瓟鏂囨鍏ㄩ儴涓枃鍖**锛圲TF-8 瀛楄妭杞箟锛屽钩鍙板彧璁 UTF-8锛涚洿鎺ュ啓姹夊瓧=GBK 瀹炴祴涔辩爜锛夛細 + 璐熻浇鏂紑鎴栫户鐢靛櫒鎹熷潖 / 寮鍏陈锋姇鍠傚弬鏁版棤鏁 / 閲嶉噺鏈氨缁殏涓嶈兘鎶曞杺 / 鏈澶囨棤绉伴噸绉 / 鎵╁睍鏉夸笉鍦ㄧ嚎路蹇櫬锋棤搴旂瓟绛 - **鎸囦护搴旂瓟鏀瑰彲闈犲彂閫**锛堜慨澶"缁х數鍣ㄥ凡鍔ㄤ綔浣嗗钩鍙板搷搴旇秴鏃"锛夛細鏂板 `NET_publish_response_wait()`锛 4G 閫氶亾绛夋ā缁勫洖 OK锛垀1.5s/娆★紝閲嶈瘯 2 娆★紝浠嶅け璐ユ墦 WARN锛夛紱缁х數鍣ㄥ弽棣堝簲绛旓紙鏉胯浇 relayCmdProcess 3 澶+ - 鎵╁睍鏉 ext_respond锛夊叏閮ㄦ崲鐢ㄣ傚師鍥狅細鏅氱増 NET_publish_response 鍙戝畬灏辫蛋涓嶇瓑 OK锛孶ART 搴旂瓟浜ょ粐/ + 鎵╁睍鏉 ext_respond锛夊叏閮ㄦ崲鐢ㄣ俉iFi 閫氶亾鍙﹀姞 QoS1+PUBACK 纭锛堣蒋浠 MQTT 鏂板 QoS1 鍙戝竷涓 + PUBACK 瑙f瀽锛宐roker 鏀跺埌鎵嶇畻鎴愬姛锛屾渶澶氶噸璇 3 娆★紱绛 PUBACK 鏃堕噴鏀 AT 閿侀槻姝婚攣锛夊師鍥狅細鏅氱増 NET_publish_response 鍙戝畬灏辫蛋涓嶇瓑 OK锛孶ART 搴旂瓟浜ょ粐/ 閾捐矾鍗婃鏃跺簲绛斾涪澶便傛敞鎰忔帓鏌ユ椂韪╄繃 `` 琚啓鎴 0x12 鐨勫潙锛圕 婧愮爜蹇呴』鏄 `\22`锛 - **寮鏈轰簩缁寸爜椤**锛氬凡閰嶇疆韬唤鐨勮澶囧紑鏈哄悗 OLED 鏄剧ず 10s 浜岀淮鐮佲斺斿唴瀹逛负璁惧 ClientID 锛坄MqttInfoStr.ClientID`锛屼笉鏄骇鍝 ID/瀵嗙爜锛夛紝宸︿晶 QR 鐧藉簳鏆楃爜锛圢ayuki qrcodegen锛孧IT锛孷1~V3 鑷姩/ECC-L/2px 妯″潡锛夛紝