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:
@@ -133,4 +133,13 @@ uint16_t offlog_count(void); /* 有效记录数 */
|
||||
int offlog_read_idx(uint16_t idx, OfflogEvt *out); /* 按逻辑序号读, 0=成功 -1=越界 */
|
||||
void offlog_clear(void); /* 清空事件区 (清空后写 LOG_CLEAR 审计) */
|
||||
|
||||
/*===========================================================================
|
||||
* 协议导出 (MQTT V1.06 / TCP JSON V1.02)
|
||||
*===========================================================================*/
|
||||
uint8_t offlog_enabled(void); /* 日志功能是否启用 (Flash 初始化成功) */
|
||||
uint32_t offlog_seq_last(void); /* 最新一条记录全局序号 (0=空) */
|
||||
const char *offlog_type_str(uint8_t type); /* 事件类型数字 → 协议字符串名 */
|
||||
int offlog_evt_to_json(const OfflogEvt *e, char *buf, int buf_len); /* 记录 → JSON 对象 */
|
||||
#define OFFLOG_MAX_QUERY_RECORDS 4 /* log_query 分页上限 (MQTT ≤500B 发布限制) */
|
||||
|
||||
#endif /* _OFFLOG_H__ */
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -344,6 +344,104 @@ int offlog_read_idx(uint16_t idx, OfflogEvt *out)
|
||||
return offlog_evt_read_at(read_off, out);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* 协议导出 (MQTT V1.06 / TCP JSON V1.02 共用)
|
||||
*===========================================================================*/
|
||||
uint8_t offlog_enabled(void)
|
||||
{
|
||||
return _ready;
|
||||
}
|
||||
|
||||
uint32_t offlog_seq_last(void)
|
||||
{
|
||||
return (_wr_seq > 0) ? (_wr_seq - 1) : 0;
|
||||
}
|
||||
|
||||
const char *offlog_type_str(uint8_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case OFFLOG_EVT_BOOT: return "boot";
|
||||
case OFFLOG_EVT_IOT_CONNECT: return "iot_connect";
|
||||
case OFFLOG_EVT_IOT_READY: return "iot_ready";
|
||||
case OFFLOG_EVT_IOT_DISCONN: return "iot_disconnect";
|
||||
case OFFLOG_EVT_IOT_RECONN: return "iot_reconn";
|
||||
case OFFLOG_EVT_EVT_RETRY: return "evt_retry";
|
||||
case OFFLOG_EVT_EVT_GIVEUP: return "evt_giveup";
|
||||
case OFFLOG_EVT_COIL: return "coil";
|
||||
case OFFLOG_EVT_TIME_ANCHOR: return "time_anchor";
|
||||
case OFFLOG_EVT_LOG_CLEAR: return "log_clear";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* 线圈事件子类型: 1=进 2=出 3=断开 4=恢复
|
||||
(与 iot_mqtt_srv.c offlog_coil 调用一致) */
|
||||
static const char *offlog_coil_sub_str(uint8_t sub)
|
||||
{
|
||||
switch (sub) {
|
||||
case 1: return "car_enter";
|
||||
case 2: return "car_leave";
|
||||
case 3: return "loop_cut";
|
||||
case 4: return "loop_restore";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* payload 大端 uint32 (与 offlog_boot/offlog_coil 写入一致) */
|
||||
static uint32_t offlog_payload_u32(const uint8_t *p)
|
||||
{
|
||||
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
/* 记录 → JSON 对象 (data 按事件类型组装; 无参数事件 data=null)
|
||||
回返 snprintf 写入长度; 调用方须保证 buf_len 充足 (一条最大约130B) */
|
||||
int offlog_evt_to_json(const OfflogEvt *e, char *buf, int buf_len)
|
||||
{
|
||||
const char *type = offlog_type_str(e->type);
|
||||
char data[96];
|
||||
|
||||
switch (e->type) {
|
||||
case OFFLOG_EVT_BOOT:
|
||||
snprintf(data, sizeof(data), "{\"rst\":%lu}",
|
||||
(unsigned long)offlog_payload_u32(e->payload));
|
||||
break;
|
||||
case OFFLOG_EVT_IOT_DISCONN:
|
||||
snprintf(data, sizeof(data), "{\"reason\":%u}", e->payload[0]);
|
||||
break;
|
||||
case OFFLOG_EVT_IOT_RECONN:
|
||||
snprintf(data, sizeof(data), "{\"backoff_ms\":%lu}",
|
||||
(unsigned long)offlog_payload_u32(e->payload));
|
||||
break;
|
||||
case OFFLOG_EVT_EVT_RETRY:
|
||||
snprintf(data, sizeof(data), "{\"msg_id\":%lu,\"retry\":%u}",
|
||||
(unsigned long)offlog_payload_u32(e->payload), e->payload[4]);
|
||||
break;
|
||||
case OFFLOG_EVT_EVT_GIVEUP:
|
||||
snprintf(data, sizeof(data), "{\"msg_id\":%lu}",
|
||||
(unsigned long)offlog_payload_u32(e->payload));
|
||||
break;
|
||||
case OFFLOG_EVT_COIL:
|
||||
snprintf(data, sizeof(data), "{\"sub\":\"%s\",\"ch\":%u,\"value\":%lu}",
|
||||
offlog_coil_sub_str(e->payload[0]), e->payload[1],
|
||||
(unsigned long)offlog_payload_u32(e->payload + 2));
|
||||
break;
|
||||
default:
|
||||
data[0] = '\0'; /* iot_connect/iot_ready/time_anchor/log_clear → null */
|
||||
break;
|
||||
}
|
||||
|
||||
if (data[0] == '\0') {
|
||||
return snprintf(buf, buf_len,
|
||||
"{\"seq\":%lu,\"boot_seq\":%u,\"ts_ms\":%lu,\"unix_ts\":%lu,\"type\":\"%s\",\"data\":null}",
|
||||
(unsigned long)e->seq, (unsigned)e->boot_seq,
|
||||
(unsigned long)e->ts_ms, (unsigned long)e->unix_ts, type);
|
||||
}
|
||||
return snprintf(buf, buf_len,
|
||||
"{\"seq\":%lu,\"boot_seq\":%u,\"ts_ms\":%lu,\"unix_ts\":%lu,\"type\":\"%s\",\"data\":%s}",
|
||||
(unsigned long)e->seq, (unsigned)e->boot_seq,
|
||||
(unsigned long)e->ts_ms, (unsigned long)e->unix_ts, type, data);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* 清空 (审计: 清空后写一条 LOG_CLEAR)
|
||||
*===========================================================================*/
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "loop_uart_proto.h"
|
||||
#include "simple_json.h"
|
||||
#include "storage.h"
|
||||
#include "offlog.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@@ -636,7 +637,62 @@ static void handle_report_config(uint8_t socket, uint32_t msg_id, const char *js
|
||||
g_report_cfg.enable, g_report_cfg.interval, g_report_cfg.sensor_type);
|
||||
}
|
||||
|
||||
/* 4.16 loop_version_query — 获取地感MCU版本号 (CMD 0x4A) */
|
||||
/* 4.16 log_stat — 查询脱机事件日志统计 */
|
||||
static void handle_log_stat(uint8_t socket, uint32_t msg_id, const char *json) {
|
||||
char data_json[256];
|
||||
uint32_t count = offlog_count();
|
||||
uint32_t seq_last = offlog_seq_last();
|
||||
uint32_t seq_first = (count > 0) ? (seq_last - count + 1) : 0;
|
||||
snprintf(data_json, sizeof(data_json),
|
||||
"{\"stream\":\"event\",\"enabled\":%s,\"boot_seq\":%lu,"
|
||||
"\"count\":%lu,\"capacity\":%u,\"seq_first\":%lu,\"seq_last\":%lu}",
|
||||
offlog_enabled() ? "true" : "false",
|
||||
(unsigned long)offlog_boot_seq(),
|
||||
(unsigned long)count, (unsigned)OFFLOG_MAX_RECORDS,
|
||||
(unsigned long)seq_first, (unsigned long)seq_last);
|
||||
json_send_ok(socket, msg_id, "log_stat", data_json);
|
||||
PRINT("JSON: log_stat count=%lu seq_first=%lu seq_last=%lu\n",
|
||||
(unsigned long)count, (unsigned long)seq_first, (unsigned long)seq_last);
|
||||
}
|
||||
|
||||
/* 4.17 log_query — 分页拉取脱机事件日志 (按全局序号, count≤4) */
|
||||
static void handle_log_query(uint8_t socket, uint32_t msg_id, const char *json) {
|
||||
char data_json[TCP_JSON_DATA_BUF_LEN];
|
||||
uint32_t start_seq = json_get_uint_field(json, "\"start_seq\"");
|
||||
uint32_t req_count = json_get_uint_field(json, "\"count\"");
|
||||
uint32_t total = offlog_count();
|
||||
uint32_t seq_last = offlog_seq_last();
|
||||
uint32_t seq_first = (total > 0) ? (seq_last - total + 1) : 0;
|
||||
if (req_count > OFFLOG_MAX_QUERY_RECORDS) req_count = OFFLOG_MAX_QUERY_RECORDS;
|
||||
|
||||
int pos = snprintf(data_json, sizeof(data_json),
|
||||
"{\"start_seq\":%lu,\"records\":[",
|
||||
(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(data_json + pos, sizeof(data_json) - pos, ",");
|
||||
pos += offlog_evt_to_json(&evt, data_json + pos, sizeof(data_json) - pos);
|
||||
fetched++;
|
||||
}
|
||||
}
|
||||
snprintf(data_json + pos, sizeof(data_json) - pos, "]}");
|
||||
json_send_ok(socket, msg_id, "log_query", data_json);
|
||||
PRINT("JSON: log_query start_seq=%lu req=%lu fetched=%lu\n",
|
||||
(unsigned long)start_seq, (unsigned long)req_count, (unsigned long)fetched);
|
||||
}
|
||||
|
||||
/* 4.18 log_clear — 清除脱机事件日志 (审计留痕, 阻塞 ~2.8s) */
|
||||
static void handle_log_clear(uint8_t socket, uint32_t msg_id, const char *json) {
|
||||
offlog_clear();
|
||||
json_send_ok(socket, msg_id, "log_clear", NULL);
|
||||
PRINT("JSON: log_clear done\n");
|
||||
}
|
||||
|
||||
/* 4.19 loop_version_query — 获取地感MCU版本号 (CMD 0x4A) */
|
||||
static void handle_loop_version_query(uint8_t socket, uint32_t msg_id, const char *json) {
|
||||
lup_send_get_version();
|
||||
g_json_pending.active = 1;
|
||||
@@ -754,6 +810,9 @@ static const JsonCmdEntry g_cmd_table[] = {
|
||||
{"loop_sens_read", handle_loop_sens_read, 1},
|
||||
{"loop_sens_write", handle_loop_sens_write, 1},
|
||||
{"report_config", handle_report_config, 1},
|
||||
{"log_stat", handle_log_stat, 1},
|
||||
{"log_query", handle_log_query, 1},
|
||||
{"log_clear", handle_log_clear, 1},
|
||||
};
|
||||
|
||||
#define JSON_CMD_COUNT (sizeof(g_cmd_table) / sizeof(g_cmd_table[0]))
|
||||
|
||||
Reference in New Issue
Block a user