feat(vd960DBN): offlog 协议导出命令分发 — log_stat/log_query/log_clear 落地

协议先行 (08893d5, MQTT V1.06 + TCP JSON V1.02) 的固件侧实现:
- offlog 新增导出 API: enabled/seq_last/type_str/evt_to_json
  (seq_first=seq_last-count+1, idx=start_seq-seq_first, count≤4)
- MQTT iot_handle_publish 三分支: log_stat/log_query/log_clear
  (log_query 响应 static buf 防大栈; log_clear 阻塞~2.8s 主循环可接受)
- TCP JSON 三 handler + cmd 表 (需鉴权)
- 单测新增 test_export_json (8 例全过): boot rst 大端/coil sub/evt_retry/data:null/seq 公式
- devlog V3.9
This commit is contained in:
wangfq
2026-08-05 08:53:18 +08:00
parent 08893d5134
commit 1a01316c7f
6 changed files with 331 additions and 1 deletions
@@ -538,6 +538,73 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_
iot_mqtt_publish(resp_topic, resp, strlen(resp), 1);
}
} else if (strcmp(cmd_str, "log_stat") == 0) {
/* 日志统计/分页定位: seq_first = seq_last - count + 1 */
uint32_t total = offlog_count();
uint32_t seq_last = offlog_seq_last();
uint32_t seq_first = (total > 0) ? (seq_last - total + 1) : 0;
char resp[300];
snprintf(resp, sizeof(resp),
"{\"msg_id\":%lu,\"cmd\":\"log_stat\","
"\"ts\":%lu,\"code\":0,\"msg\":\"success\","
"\"data\":{\"stream\":\"event\",\"enabled\":%s,\"boot_seq\":%lu,"
"\"count\":%lu,\"capacity\":%u,\"seq_first\":%lu,\"seq_last\":%lu}}",
msg_id, dev_time_now(),
offlog_enabled() ? "true" : "false",
(unsigned long)offlog_boot_seq(),
(unsigned long)total, (unsigned)OFFLOG_MAX_RECORDS,
(unsigned long)seq_first, (unsigned long)seq_last);
iot_mqtt_publish(resp_topic, resp, strlen(resp), 1);
PRINT("IOT: log_stat count=%lu seq_first=%lu seq_last=%lu\n",
(unsigned long)total, (unsigned long)seq_first, (unsigned long)seq_last);
} else if (strcmp(cmd_str, "log_query") == 0) {
/* 分页拉取脱机日志: 按全局序号, count≤4 (500B 发布限制) */
static char resp[IOT_MQTT_SEND_BUF_LEN]; /* static: 避免大栈开销 */
uint32_t start_seq = 0, req_count = 0;
uint32_t total = offlog_count();
uint32_t seq_last = offlog_seq_last();
uint32_t seq_first = (total > 0) ? (seq_last - total + 1) : 0;
memset(tmp, 0, sizeof(tmp));
simple_parse_json(json, "\"start_seq\"", tmp);
if (strlen(tmp) > 0) start_seq = (uint32_t)strtoul(tmp, NULL, 10);
memset(tmp, 0, sizeof(tmp));
simple_parse_json(json, "\"count\"", tmp);
if (strlen(tmp) > 0) req_count = (uint32_t)strtoul(tmp, NULL, 10);
if (req_count > OFFLOG_MAX_QUERY_RECORDS) req_count = OFFLOG_MAX_QUERY_RECORDS;
int pos = snprintf(resp, sizeof(resp),
"{\"msg_id\":%lu,\"cmd\":\"log_query\",\"ts\":%lu,\"code\":0,"
"\"msg\":\"success\",\"data\":{\"start_seq\":%lu,\"records\":[",
msg_id, dev_time_now(), (unsigned long)start_seq);
uint32_t fetched = 0;
if (total > 0 && start_seq >= seq_first && start_seq <= seq_last) {
uint32_t idx = start_seq - seq_first; /* 逻辑索引 = 全局序号 - seq_first */
while (fetched < req_count && (idx + fetched) < total) {
OfflogEvt evt;
if (offlog_read_idx((uint16_t)(idx + fetched), &evt) != 0) break;
if (fetched > 0) pos += snprintf(resp + pos, sizeof(resp) - pos, ",");
pos += offlog_evt_to_json(&evt, resp + pos, sizeof(resp) - pos);
fetched++;
}
}
snprintf(resp + pos, sizeof(resp) - pos, "]}}");
PRINT("IOT: log_query start_seq=%lu req=%lu fetched=%lu\n",
(unsigned long)start_seq, (unsigned long)req_count, (unsigned long)fetched);
iot_mqtt_publish(resp_topic, resp, strlen(resp), 1);
} else if (strcmp(cmd_str, "log_clear") == 0) {
/* 清除日志 (审计留痕). 阻塞 ~2.8s (63 扇区擦除), 主循环上下文可接受 */
offlog_clear();
char resp[256];
snprintf(resp, sizeof(resp),
"{\"msg_id\":%lu,\"cmd\":\"log_clear\","
"\"ts\":%lu,\"code\":0,\"msg\":\"success\"}",
msg_id, dev_time_now());
iot_mqtt_publish(resp_topic, resp, strlen(resp), 1);
PRINT("IOT: log_clear done\n");
} else {
// 暂不支持的命令, 返回错误
char resp[256];