WiFi通道指令应答改QoS1+PUBACK端到端确认(重试3次),修复WiFi下继电器动作了但平台响应超时

This commit is contained in:
helei 2026-09-14 10:57:28 +08:00
parent 3cdaba312f
commit 721b0f479a
6 changed files with 79 additions and 6 deletions

View File

@ -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) void net_wifi_publish_up(const char *json)
{ {
if (!g_wifi_mqtt_ready) return; if (!g_wifi_mqtt_ready) return;

View File

@ -13,6 +13,7 @@ extern volatile uint8_t g_wifi_restore_req; /*
int net_wifi_init(void); /* 阻塞建链复位→连AP(已存→固定→SmartConfig)→透传→MQTT, 0=就绪 */ int net_wifi_init(void); /* 阻塞建链复位→连AP(已存→固定→SmartConfig)→透传→MQTT, 0=就绪 */
void net_wifi_process(void); /* 非阻塞轮询:解析下行报文 + 心跳保活 */ void net_wifi_process(void); /* 非阻塞轮询:解析下行报文 + 心跳保活 */
void net_wifi_publish_up(const char *json); /* 向 /iot/data/up/<cid> 发布 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/<cid> 发布 JSON */
#endif /* NET_WIFI_H */ #endif /* NET_WIFI_H */

View File

@ -11,6 +11,7 @@
#include "network.h" #include "network.h"
#include "net_wifi.h" #include "net_wifi.h"
#include "mqtt_client.h"
#include "hlw8032.h" #include "hlw8032.h"
#include "feed_scale.h" #include "feed_scale.h"
#include "net_wifi_ota.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) 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 (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"); const char *text = msg ? msg : (ok ? "OK" : "command failed");
int rc = 0; int rc = 0;
if (s_backend == NET_BACKEND_4G) { if (s_backend == NET_BACKEND_4G) {
if (net_at_trylock() != 0) { log_info("> NET: 应答上报被丢弃 (AT 忙)"); return -1; }
static char cmd[256]; 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", 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); 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; rc = -1;
log_warn("> NET: 指令应答发送失败(重试后仍无 OK)"); log_warn("> NET: 指令应答发送失败(重试后仍无 OK)");
} }
net_at_unlock();
} else { } else {
/* WiFi(软件 MQTT): QoS1 发布 + 等 PUBACK(broker 确认收到), 最多 3 次
* : net , PUBACK AT , */
char json[200]; char json[200];
snprintf(json, sizeof(json), snprintf(json, sizeof(json),
"{\"id\":\"%s\",\"code\":0,\"message\":\"%s\"}", cmd_id, text); "{\"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(); 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 超时)");
}
return rc; return rc;
} }

View File

@ -154,6 +154,45 @@ int mqttc_publish(const char *topic, const char *payload)
return 0; 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) void mqttc_ping(void)
{ {
tx_buf[0] = 0xC0; tx_buf[0] = 0xC0;
@ -242,6 +281,12 @@ int mqttc_process(void)
if (rl >= 3 && pkt[2] != 0x80) s_suback_ok = 1; if (rl >= 3 && pkt[2] != 0x80) s_suback_ok = 1;
else log_warn("> WiFi MQTT: SUBACK ±»¾Ü¾ø"); else log_warn("> WiFi MQTT: SUBACK ±»¾Ü¾ø");
break; 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 */ case 13: /* PINGRESP */
g_mqttc_ping_outstanding = 0; g_mqttc_ping_outstanding = 0;
break; break;

View File

@ -17,6 +17,8 @@ void mqttc_init(const char *client_id, const char *user, const char *pass);
int mqttc_connect(void); /* 发 CONNECT 等 CONNACK, 0=成功 */ int mqttc_connect(void); /* 发 CONNECT 等 CONNACK, 0=成功 */
int mqttc_subscribe(const char *topic); /* 发 SUBSCRIBE(QoS0) 等 SUBACK, 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 */ void mqttc_ping(void); /* 发 PINGREQ */
/* 从共享接收缓冲解析一条完整 MQTT 报文(粘包/半包安全)。 /* 从共享接收缓冲解析一条完整 MQTT 报文(粘包/半包安全)。

View File

@ -21,9 +21,12 @@
控制命令改三段式FC03 预读(判定在线 + 快照命令前反馈)→ FC05 → 150ms → FC03 校验跳变; 控制命令改三段式FC03 预读(判定在线 + 快照命令前反馈)→ FC05 → 150ms → FC03 校验跳变;
命令链路最终无应答即判离线并应答平台 `ext relay board offline`,下次命令自动重新探测恢复; 命令链路最终无应答即判离线并应答平台 `ext relay board offline`,下次命令自动重新探测恢复;
断电恢复触发点不变(离线→上线比对 EEPROM 逐路补发) 断电恢复触发点不变(离线→上线比对 EEPROM 逐路补发)
- **继电器/投喂平台应答文案全部中文化**UTF-8 字节转义,平台只认 UTF-8直接写汉字=GBK 实测乱码):
负载断开或继电器损坏 / 开关·投喂参数无效 / 重量未就绪暂不能投喂 / 本设备无称重秤 / 扩展板不在线·忙·无应答等
- **指令应答改可靠发送**(修复"继电器已动作但平台响应超时"):新增 `NET_publish_response_wait()` - **指令应答改可靠发送**(修复"继电器已动作但平台响应超时"):新增 `NET_publish_response_wait()`
4G 通道等模组回 OK~1.5s/次,重试 2 次,仍失败打 WARN继电器反馈应答板载 relayCmdProcess 3 处+ 4G 通道等模组回 OK~1.5s/次,重试 2 次,仍失败打 WARN继电器反馈应答板载 relayCmdProcess 3 处+
扩展板 ext_respond全部换用。原因普通版 NET_publish_response 发完就走不等 OKUART 应答交织/ 扩展板 ext_respond全部换用。WiFi 通道另加 QoS1+PUBACK 确认(软件 MQTT 新增 QoS1 发布与
PUBACK 解析broker 收到才算成功,最多重试 3 次;等 PUBACK 时释放 AT 锁防死锁)原因:普通版 NET_publish_response 发完就走不等 OKUART 应答交织/
链路半死时应答丢失。注意排查时踩过 `` 被写成 0x12 的坑C 源码必须是 `\22` 链路半死时应答丢失。注意排查时踩过 `` 被写成 0x12 的坑C 源码必须是 `\22`
- **开机二维码页**:已配置身份的设备开机后 OLED 显示 10s 二维码——内容为设备 ClientID - **开机二维码页**:已配置身份的设备开机后 OLED 显示 10s 二维码——内容为设备 ClientID
`MqttInfoStr.ClientID`,不是产品 ID/密码),左侧 QR 白底暗码Nayuki qrcodegenMITV1~V3 自动/ECC-L/2px 模块), `MqttInfoStr.ClientID`,不是产品 ID/密码),左侧 QR 白底暗码Nayuki qrcodegenMITV1~V3 自动/ECC-L/2px 模块),