From e6dbdb52967042584d7cefb4db21b3833ddb6979 Mon Sep 17 00:00:00 2001 From: wangfq Date: Mon, 6 Jul 2026 10:24:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(vd960DBN):=20=E4=BF=AE=E5=A4=8D=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E8=AE=BE=E7=BD=AE=20IoT=20=E6=A8=A1=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OnlyUpdateApp_Peripheral/APP/cfig_flash.c | 5 +- .../APP/dbn_ble_srv.c | 22 + .../APP/include/iot_mqtt_srv.h | 110 +- .../APP/include/loop_uart_proto.h | 488 ++-- .../APP/include/net_config.h | 2 +- .../APP/include/storage.h | 2 + .../APP/include/tcp_json_srv.h | 168 +- .../APP/iot_mqtt_srv.c | 1268 ++++---- .../APP/loop_uart_proto.c | 1418 ++++----- .../APP/tcp_json_srv.c | 2566 ++++++++--------- 10 files changed, 3038 insertions(+), 3011 deletions(-) diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/cfig_flash.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/cfig_flash.c index efb4474..71433b4 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/cfig_flash.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/cfig_flash.c @@ -303,6 +303,8 @@ void load_cfg_from_flash(void) g_sub_code_enable.wbdus_enable = (g_sub_code_enable.code_set >> 5) & 0x01; g_sub_code_enable.radar_enable = (g_sub_code_enable.code_set >> 6) & 0x01; + PRINT("\nSub_Code: 0x%04X, net_enable:%d, iot_enable:%d\n", g_sub_code_enable.code_set, g_sub_code_enable.net_enable, g_sub_code_enable.iot_enable); + // g_sub_code_enable.laser_enable = (g_sub_code_enable.code_set >> 9) & 0x01; // g_sub_code_enable.lora_enable = (g_sub_code_enable.code_set >> 15) & 0x01; @@ -314,6 +316,7 @@ void output_cfg_from_flash(void) // if(g_flag_debug) // { PRINT("\nBaud1:%d, Baud2:%d\n", g_storage_uart_baud, g_storage_uart_baud_2); + PRINT("\nSub_Code:0x%04X\n", g_sub_code_enable.code_set); PRINT("\nGet Net config:\n"); PRINT("Local Net Config: " " mac:%02X-%02X-%02X-%02X-%02X-%02X\n" @@ -368,7 +371,6 @@ void update_sub_code_enable(void) if(mBuff == NULL) { PRINT("Err_When_malloc__\n"); - free(mBuff); return; } memset(mBuff, 0, CFG_FLASH_BLOCK_SIZE); @@ -381,6 +383,7 @@ void update_sub_code_enable(void) SPI_Flash_Write(mBuff, DEV_CNG_ADDR_BASE, CFG_FLASH_BLOCK_SIZE); free(mBuff); + Delay_Ms(50); } diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c index b40fceb..86ad554 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c @@ -535,6 +535,23 @@ static uint8_t unpack_packs(uint8_t *pkg, uint8_t len) } set_response_buf(&g_buf_ble_response, MAGIC_BYTE_DBN_DEFAULT, _cmd, tmp_ble_buf, 1); } //end if g_buf_ble_response.cmd == CMD_DBN_MODIFY_PASS + else if(g_buf_ble_response.cmd == CMD_DBN_SET_SUB_CODE){ + g_sub_code_enable.code_set = pkg[4] | (pkg[5] << 8); + PRINT("\nWill_set_sub_code:0x%04X\n", g_sub_code_enable.code_set); + + g_sub_code_enable.net_enable = g_sub_code_enable.code_set & 0x01; + g_sub_code_enable.iot_enable = (g_sub_code_enable.code_set >> 1) & 0x01; + g_sub_code_enable.custom_enable = (g_sub_code_enable.code_set >> 2) & 0x01; + g_sub_code_enable.loop_enable = (g_sub_code_enable.code_set >> 3) & 0x01; + g_sub_code_enable.dgdus_enable = (g_sub_code_enable.code_set >> 4) & 0x01; + g_sub_code_enable.wbdus_enable = (g_sub_code_enable.code_set >> 5) & 0x01; + g_sub_code_enable.radar_enable = (g_sub_code_enable.code_set >> 6) & 0x01; + g_sub_code_enable.laser_enable = (g_sub_code_enable.code_set >> 9) & 0x01; + g_sub_code_enable.lora_enable = (g_sub_code_enable.code_set >> 15) & 0x01; + + + update_sub_code_enable(); + } else if(g_buf_ble_response.cmd == CMD_DBN_SET_IOT_NET) { for(i = 0, j = 0, k = 0; i < g_buf_ble_response.dat_len; i++) @@ -751,6 +768,11 @@ void manage_dbn_ble_default(uint8_t *pkg, uint8_t len) { set_response_iot_topic(&g_buf_ble_response); } break; + case CMD_DBN_SET_SUB_CODE: + { + PRINT("\nSet_Sub_Code\n"); + unpack_packs(pkg, len); + } break; case CMD_DBN_RESET_DEV: { //Restart Dev diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/iot_mqtt_srv.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/iot_mqtt_srv.h index 378b22a..260e653 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/iot_mqtt_srv.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/iot_mqtt_srv.h @@ -1,55 +1,55 @@ -/** - ****************************************************************************** - * @file iot_mqtt_srv.h - * @author wangfq - * @version V1.0 - * @date 2026-07-03 - * @brief IoT MQTT 客户端 — DLD960 IoT 协议 (MQTT + JSON) - * 当 g_sub_code_enable.iot_enable=1 时启用, 替代 SSC 模式 - * 协议参考: docs/DLD960_IoT_MQTT协议.md - ****************************************************************************** - */ -#ifndef __IOT_MQTT_SRV_H__ -#define __IOT_MQTT_SRV_H__ - -#include - -/*=========================================================================== - * Protocol Constants - *===========================================================================*/ -#define IOT_MQTT_KEEPALIVE_SEC 60 // MQTT keepalive (s) -#define IOT_MQTT_RECONNECT_MIN_MS 5000 // 最小重连间隔 -#define IOT_MQTT_RECONNECT_MAX_MS 60000 // 最大重连间隔 (指数退避上限) -#define IOT_MQTT_HEARTBEAT_MS 60000 // 心跳上报周期 (60s) -#define IOT_MQTT_RECV_BUF_LEN 2048 // 接收缓冲区 -#define IOT_MQTT_SEND_BUF_LEN 2048 // 发送缓冲区 -#define IOT_MQTT_TOPIC_MAX_LEN 128 // Topic 最大长度 - -/*=========================================================================== - * MQTT 连接状态 - *===========================================================================*/ -typedef enum { - IOT_STATE_DISCONNECTED = 0, // 未连接 - IOT_STATE_TCP_CONNECTING, // TCP 连接中 - IOT_STATE_TCP_CONNECTED, // TCP 已连接, 等待 MQTT CONNECT - IOT_STATE_MQTT_CONNECTING, // MQTT CONNECT 已发送, 等待 CONNACK - IOT_STATE_MQTT_CONNECTED, // MQTT 已连接, 等待 SUBACK - IOT_STATE_READY, // 完全就绪 (已订阅) -} IotMqttState; - -/*=========================================================================== - * Externs - *===========================================================================*/ -extern uint8_t g_iot_socket; // MQTT TCP socket ID -extern IotMqttState g_iot_state; // MQTT 连接状态 -extern uint8_t g_iot_msg_id; // 递增消息 ID - -/*=========================================================================== - * API - *===========================================================================*/ -void iot_mqtt_init(void); // 初始化 MQTT 连接 (TCP→CONNECT→SUBSCRIBE) -void iot_mqtt_poll(void); // 主循环轮询 (状态机 + 心跳 + 重连) -void iot_mqtt_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket 中断处理 -void iot_mqtt_publish_sensor(void); // 推送传感器数据到 MQTT - -#endif /* __IOT_MQTT_SRV_H__ */ +/** + ****************************************************************************** + * @file iot_mqtt_srv.h + * @author wangfq + * @version V1.0 + * @date 2026-07-03 + * @brief IoT MQTT 客户端 — DLD960 IoT 协议 (MQTT + JSON) + * 当 g_sub_code_enable.iot_enable=1 时启用, 替代 SSC 模式 + * 协议参考: docs/DLD960_IoT_MQTT协议.md + ****************************************************************************** + */ +#ifndef __IOT_MQTT_SRV_H__ +#define __IOT_MQTT_SRV_H__ + +#include + +/*=========================================================================== + * Protocol Constants + *===========================================================================*/ +#define IOT_MQTT_KEEPALIVE_SEC 60 // MQTT keepalive (s) +#define IOT_MQTT_RECONNECT_MIN_MS 5000 // 最小重连间隔 +#define IOT_MQTT_RECONNECT_MAX_MS 60000 // 最大重连间隔 (指数退避上限) +#define IOT_MQTT_HEARTBEAT_MS 60000 // 心跳上报周期 (60s) +#define IOT_MQTT_RECV_BUF_LEN 2048 // 接收缓冲区 +#define IOT_MQTT_SEND_BUF_LEN 2048 // 发送缓冲区 +#define IOT_MQTT_TOPIC_MAX_LEN 128 // Topic 最大长度 + +/*=========================================================================== + * MQTT 连接状态 + *===========================================================================*/ +typedef enum { + IOT_STATE_DISCONNECTED = 0, // 未连接 + IOT_STATE_TCP_CONNECTING, // TCP 连接中 + IOT_STATE_TCP_CONNECTED, // TCP 已连接, 等待 MQTT CONNECT + IOT_STATE_MQTT_CONNECTING, // MQTT CONNECT 已发送, 等待 CONNACK + IOT_STATE_MQTT_CONNECTED, // MQTT 已连接, 等待 SUBACK + IOT_STATE_READY, // 完全就绪 (已订阅) +} IotMqttState; + +/*=========================================================================== + * Externs + *===========================================================================*/ +extern uint8_t g_iot_socket; // MQTT TCP socket ID +extern IotMqttState g_iot_state; // MQTT 连接状态 +extern uint8_t g_iot_msg_id; // 递增消息 ID + +/*=========================================================================== + * API + *===========================================================================*/ +void iot_mqtt_init(void); // 初始化 MQTT 连接 (TCP→CONNECT→SUBSCRIBE) +void iot_mqtt_poll(void); // 主循环轮询 (状态机 + 心跳 + 重连) +void iot_mqtt_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket 中断处理 +void iot_mqtt_publish_sensor(void); // 推送传感器数据到 MQTT + +#endif /* __IOT_MQTT_SRV_H__ */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h index 509700a..951985f 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h @@ -1,244 +1,244 @@ -/** - ****************************************************************************** - * @file loop_uart_proto.h - * @author wangfq - * @version V1.0 - * @date 2026-07-02 - * @brief DLD960Loop 串口通信协议 (7F) — CH32V208 ↔ DLD960Loop MCU - * - * 帧格式: 7F | Addr | LEN | CMD | Data... | XOR | SUM - * UART: USART2, 115200bps, PA2(Tx)/PA3(Rx) - * - * 该协议是通信MCU(CH32V208)与地感MCU(DLD960Loop/AT32F421)之间的接口。 - * 网络接口(TCP JSON/5960)通过本模块向地感MCU下发命令并获取数据。 - ****************************************************************************** - */ -#ifndef __LOOP_UART_PROTO_H__ -#define __LOOP_UART_PROTO_H__ - -#include - -/*=========================================================================== - * Protocol Constants - *===========================================================================*/ -#define LUP_MAGIC 0x7F // 配置/查询协议 -#define LUP_MAGIC_OTA 0x9F // OTA - -#define LUP_ADDR_DEFAULT 0x00 // 默认地址 - -#define LUP_MAX_VALUE_LEN 64 // Value 字段最大长度 -#define LUP_MAX_PKG_LEN (1 + 3 + LUP_MAX_VALUE_LEN + 2) // 70 bytes 最大帧 - -/*=========================================================================== - * Command Bytes (CMD) - *===========================================================================*/ -#define LUP_CMD_GET_VERSION 0x4A // 获取设备版本号 -#define LUP_CMD_RESET 0x6D // 设备复位(无回复) -#define LUP_CMD_FACTORY_INIT 0x92 // 出厂初始化 -#define LUP_CMD_SENSITIVITY 0x8A // 读写线圈灵敏度列表 -#define LUP_CMD_SET_PARAM 0x63 // 设置多路参数 -#define LUP_CMD_GET_PARAM 0x64 // 读取多路参数 -#define LUP_CMD_SENSOR_REPORT 0xC0 // 设备主动上报传感信息 - -/*=========================================================================== - * Sensor Types (0xC0 SensType) - *===========================================================================*/ -#define LUP_SENS_TYPE_MULTI_COIL 0x0C // 多路线圈传感信息(四路) - -/*=========================================================================== - * R/W byte for sensitivity (0x8A) - *===========================================================================*/ -#define LUP_SENS_RW_READ 0x00 -#define LUP_SENS_RW_WRITE 0x01 - -/*=========================================================================== - * 线圈数 - *===========================================================================*/ -#define LUP_COIL_COUNT 4 - -/*=========================================================================== - * Data Structures - *===========================================================================*/ - -/* 线圈配置 (0x63 Set / 0x64 Get 中使用) */ -typedef struct { - uint8_t sensitivity; // 低4位灵敏度(0~9), 高4位频率等级[4]=高频,[5]=低频 - uint8_t loop_delay; // 延时时间 0~200, 0.1s/级 - uint8_t output_mode; // 低3位: 0=存在,1=进入脉冲,2=离开脉冲,3=方向 - // 高5位: SafeMode 分钟数(0=关闭) - uint8_t exist_mode; // 低4位: 0=永久, 非0=有限分钟 - uint8_t direction_mode; // 低3位: 0=触发, 1~6=方向模式 - // 高4位: Function_Mode -} LUP_CoilParam; - -/* 多路参数设置 (0x63) */ -typedef struct { - uint8_t auto_mode; // 0=不启用自动调频, 默认0 - uint8_t amount; // 线圈数量 - LUP_CoilParam params[LUP_COIL_COUNT]; -} LUP_ParamSet; - -/* 多路参数读取返回 (0x64 Response) */ -typedef struct { - uint8_t auto_mode; - uint8_t amount; - LUP_CoilParam params[LUP_COIL_COUNT]; - uint32_t freq[LUP_COIL_COUNT][3]; // [ch][0]=freq1, [1]=freq2, [2]=freq3 -} LUP_ParamGet; - -/* 单路线圈传感数据单元 (0xC0/0x0C 中的每一路线圈) */ -typedef struct { - uint8_t freq_level : 2; // 00=高频33nF, 01=中高43nF, 10=中低66nF, 11=低频76nF - uint8_t direction : 1; // 0=触发, 1=方向判别 - uint8_t freq_type : 1; // 0=初始频率, 1=实时频率 - uint8_t sensitivity : 4; // 灵敏度等级 - // 线圈评估条件 - uint8_t condition : 4; // 环境状态评估值(0=正常, 值越大干扰越大) - uint8_t loop_state : 1; // 0=正常, 1=断开 - uint8_t car_state : 1; // 0=无车, 1=有车 - uint8_t misc_type : 2; // 0=时间量, 1=断开次数, 2=车流量, 3=继电器输出次数 - uint32_t freq; // 频率 (3 bytes, little-endian) - uint16_t variation; // 变化量 (2 bytes, little-endian) - union { - uint32_t passtime_ms; // 通过时间/车间距, 5ms单位 (misc_type=0) - uint32_t cut_amount; // 线圈断开次数 (misc_type=1) - uint32_t flow_amount; // 车流量数 (misc_type=2) - uint32_t relay_count; // 继电器输出次数 (misc_type=3) - } misc; -} LUP_CoilSensor; - -/* 主动上报传感信息 (0xC0/0x0C) */ -typedef struct { - uint8_t sens_type; // 0x0C - uint8_t sub_amount; // 分包数量(高4位), 0=无分包 - uint8_t sub_sequence; // 当前分包序号(低4位), 1-based - uint8_t coil_count; // 线圈数 (解析出的) - LUP_CoilSensor coils[LUP_COIL_COUNT]; -} LUP_SensorReport; - -/* 灵敏度读写 (0x8A) */ -typedef struct { - uint8_t rw; // 0=Read, 1=Write - uint8_t amount; // 灵敏度值数量 - uint16_t sens_in[LUP_COIL_COUNT]; // 触发灵敏度 - uint16_t sens_out[LUP_COIL_COUNT]; // 释放灵敏度 -} LUP_Sensitivity; - -/* 版本信息 (0x4A Response) */ -typedef struct { - uint8_t status; // Status byte - uint8_t hard_main; - uint8_t hard_sub; - uint8_t hard_ssub; - uint8_t soft_main; - uint8_t soft_sub; - uint8_t soft_ssub; - char version_str[32]; // 格式化字符串 -} LUP_VersionInfo; - -/* 命令状态机 */ -typedef enum { - LUP_STATE_IDLE = 0, // 空闲 - LUP_STATE_WAIT_RESPONSE, // 等待响应 - LUP_STATE_RESPONSE_READY, // 响应已就绪 - LUP_STATE_TIMEOUT // 超时 -} LUP_CmdState; - -/* 挂起的命令跟踪 */ -typedef struct { - uint8_t pending_cmd; // 当前等待响应的命令字节 - uint32_t send_tick; // 发送时刻 (ms) - uint32_t timeout_ms; // 超时时间 - LUP_CmdState state; - // 响应数据缓冲区 - uint8_t resp_buf[LUP_MAX_PKG_LEN]; - uint16_t resp_len; -} LUP_CmdTracker; - -/*=========================================================================== - * Global State - *===========================================================================*/ -extern LUP_CmdTracker g_lup_cmd; - -/*=========================================================================== - * Public API - *===========================================================================*/ - -/* --- Checksum --- */ -uint8_t lup_calc_xor(const uint8_t *data, uint16_t len); -uint8_t lup_calc_sum(const uint8_t *data, uint16_t len); -int lup_verify_checksum(const uint8_t *pkg, uint16_t len); -void lup_append_checksum(uint8_t *pkg); - -/* --- Packet Builders (returns total packet length) --- */ -uint16_t lup_build_get_version(uint8_t *buf); -uint16_t lup_build_reset(uint8_t *buf); -uint16_t lup_build_factory_init(uint8_t *buf); -uint16_t lup_build_sensitivity_read(uint8_t *buf); -uint16_t lup_build_sensitivity_write(uint8_t *buf, const LUP_Sensitivity *sens); -uint16_t lup_build_set_param(uint8_t *buf, const LUP_ParamSet *ps); -uint16_t lup_build_get_param(uint8_t *buf); - -/* --- Packet Parsers (returns 0=success, <0=error) --- */ -int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info); -int lup_parse_factory_init_resp(const uint8_t *pkg, uint16_t len, uint8_t *success); -int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens); -int lup_parse_param_get(const uint8_t *pkg, uint16_t len, LUP_ParamGet *pg); -int lup_parse_sensor_report(const uint8_t *pkg, uint16_t len, LUP_SensorReport *report); -int lup_parse_set_param_resp(const uint8_t *pkg, uint16_t len, uint8_t *success); - -/* --- Command State Machine --- */ -void lup_cmd_begin(uint8_t cmd, uint32_t timeout_ms); -void lup_cmd_done(void); -int lup_cmd_check_timeout(void); -void lup_cmd_on_response(const uint8_t *pkg, uint16_t len); - -/* --- High-level Commands (send + wait handled by state machine) --- */ -void lup_send_get_version(void); -void lup_send_reset(void); -void lup_send_factory_init(void); -void lup_send_sensitivity_read(void); -void lup_send_set_param(const LUP_ParamSet *ps); -void lup_send_get_param(void); - -/* --- Mid-packet ACK for 0xC0 multi-packet --- */ -uint16_t lup_build_sensor_ack(uint8_t *buf, uint8_t sens_type, - uint8_t seq, uint8_t sub_amount); - -/* --- UART Frame Parser (called from USART2 ISR context) --- */ -typedef enum { - LUP_FRAME_STATE_IDLE = 0, // 等待 0x7F - LUP_FRAME_STATE_HEADER, // 接收 Header (Addr, LEN, CMD) - LUP_FRAME_STATE_VALUE, // 接收 Value - LUP_FRAME_STATE_CHECK, // 接收 Checksum - LUP_FRAME_STATE_COMPLETE // 帧完成 -} LUP_FrameState; - -typedef struct { - LUP_FrameState state; - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t idx; // 当前写入位置 - uint16_t value_len; // 期望的 Value 长度 (从 LEN 字段解析) - uint16_t value_idx; // 当前已接收的 Value 字节数 -} LUP_FrameParser; - -extern LUP_FrameParser g_lup_parser; - -/* 喂一个字节给帧解析器,返回 1 表示帧接收完成 */ -int lup_feed_byte(uint8_t byte); - -/* 取完整帧的指针和数据长度 */ -const uint8_t *lup_frame_data(void); -uint16_t lup_frame_len(void); - -/* 重置帧解析器 */ -void lup_frame_reset(void); - -/* --- Process a complete frame (call from main loop context) --- */ -void lup_process_frame(const uint8_t *pkg, uint16_t len); - -/* --- Sensor report callback (called when valid 0xC0 frame received) --- */ -typedef void (*lup_sensor_cb_t)(const uint8_t *pkg, uint16_t len); -void lup_set_sensor_callback(lup_sensor_cb_t cb); - -#endif /* __LOOP_UART_PROTO_H__ */ +/** + ****************************************************************************** + * @file loop_uart_proto.h + * @author wangfq + * @version V1.0 + * @date 2026-07-02 + * @brief DLD960Loop 串口通信协议 (7F) — CH32V208 ↔ DLD960Loop MCU + * + * 帧格式: 7F | Addr | LEN | CMD | Data... | XOR | SUM + * UART: USART2, 115200bps, PA2(Tx)/PA3(Rx) + * + * 该协议是通信MCU(CH32V208)与地感MCU(DLD960Loop/AT32F421)之间的接口。 + * 网络接口(TCP JSON/5960)通过本模块向地感MCU下发命令并获取数据。 + ****************************************************************************** + */ +#ifndef __LOOP_UART_PROTO_H__ +#define __LOOP_UART_PROTO_H__ + +#include + +/*=========================================================================== + * Protocol Constants + *===========================================================================*/ +#define LUP_MAGIC 0x7F // 配置/查询协议 +#define LUP_MAGIC_OTA 0x9F // OTA + +#define LUP_ADDR_DEFAULT 0x00 // 默认地址 + +#define LUP_MAX_VALUE_LEN 64 // Value 字段最大长度 +#define LUP_MAX_PKG_LEN (1 + 3 + LUP_MAX_VALUE_LEN + 2) // 70 bytes 最大帧 + +/*=========================================================================== + * Command Bytes (CMD) + *===========================================================================*/ +#define LUP_CMD_GET_VERSION 0x4A // 获取设备版本号 +#define LUP_CMD_RESET 0x6D // 设备复位(无回复) +#define LUP_CMD_FACTORY_INIT 0x92 // 出厂初始化 +#define LUP_CMD_SENSITIVITY 0x8A // 读写线圈灵敏度列表 +#define LUP_CMD_SET_PARAM 0x63 // 设置多路参数 +#define LUP_CMD_GET_PARAM 0x64 // 读取多路参数 +#define LUP_CMD_SENSOR_REPORT 0xC0 // 设备主动上报传感信息 + +/*=========================================================================== + * Sensor Types (0xC0 SensType) + *===========================================================================*/ +#define LUP_SENS_TYPE_MULTI_COIL 0x0C // 多路线圈传感信息(四路) + +/*=========================================================================== + * R/W byte for sensitivity (0x8A) + *===========================================================================*/ +#define LUP_SENS_RW_READ 0x00 +#define LUP_SENS_RW_WRITE 0x01 + +/*=========================================================================== + * 线圈数 + *===========================================================================*/ +#define LUP_COIL_COUNT 4 + +/*=========================================================================== + * Data Structures + *===========================================================================*/ + +/* 线圈配置 (0x63 Set / 0x64 Get 中使用) */ +typedef struct { + uint8_t sensitivity; // 低4位灵敏度(0~9), 高4位频率等级[4]=高频,[5]=低频 + uint8_t loop_delay; // 延时时间 0~200, 0.1s/级 + uint8_t output_mode; // 低3位: 0=存在,1=进入脉冲,2=离开脉冲,3=方向 + // 高5位: SafeMode 分钟数(0=关闭) + uint8_t exist_mode; // 低4位: 0=永久, 非0=有限分钟 + uint8_t direction_mode; // 低3位: 0=触发, 1~6=方向模式 + // 高4位: Function_Mode +} LUP_CoilParam; + +/* 多路参数设置 (0x63) */ +typedef struct { + uint8_t auto_mode; // 0=不启用自动调频, 默认0 + uint8_t amount; // 线圈数量 + LUP_CoilParam params[LUP_COIL_COUNT]; +} LUP_ParamSet; + +/* 多路参数读取返回 (0x64 Response) */ +typedef struct { + uint8_t auto_mode; + uint8_t amount; + LUP_CoilParam params[LUP_COIL_COUNT]; + uint32_t freq[LUP_COIL_COUNT][3]; // [ch][0]=freq1, [1]=freq2, [2]=freq3 +} LUP_ParamGet; + +/* 单路线圈传感数据单元 (0xC0/0x0C 中的每一路线圈) */ +typedef struct { + uint8_t freq_level : 2; // 00=高频33nF, 01=中高43nF, 10=中低66nF, 11=低频76nF + uint8_t direction : 1; // 0=触发, 1=方向判别 + uint8_t freq_type : 1; // 0=初始频率, 1=实时频率 + uint8_t sensitivity : 4; // 灵敏度等级 + // 线圈评估条件 + uint8_t condition : 4; // 环境状态评估值(0=正常, 值越大干扰越大) + uint8_t loop_state : 1; // 0=正常, 1=断开 + uint8_t car_state : 1; // 0=无车, 1=有车 + uint8_t misc_type : 2; // 0=时间量, 1=断开次数, 2=车流量, 3=继电器输出次数 + uint32_t freq; // 频率 (3 bytes, little-endian) + uint16_t variation; // 变化量 (2 bytes, little-endian) + union { + uint32_t passtime_ms; // 通过时间/车间距, 5ms单位 (misc_type=0) + uint32_t cut_amount; // 线圈断开次数 (misc_type=1) + uint32_t flow_amount; // 车流量数 (misc_type=2) + uint32_t relay_count; // 继电器输出次数 (misc_type=3) + } misc; +} LUP_CoilSensor; + +/* 主动上报传感信息 (0xC0/0x0C) */ +typedef struct { + uint8_t sens_type; // 0x0C + uint8_t sub_amount; // 分包数量(高4位), 0=无分包 + uint8_t sub_sequence; // 当前分包序号(低4位), 1-based + uint8_t coil_count; // 线圈数 (解析出的) + LUP_CoilSensor coils[LUP_COIL_COUNT]; +} LUP_SensorReport; + +/* 灵敏度读写 (0x8A) */ +typedef struct { + uint8_t rw; // 0=Read, 1=Write + uint8_t amount; // 灵敏度值数量 + uint16_t sens_in[LUP_COIL_COUNT]; // 触发灵敏度 + uint16_t sens_out[LUP_COIL_COUNT]; // 释放灵敏度 +} LUP_Sensitivity; + +/* 版本信息 (0x4A Response) */ +typedef struct { + uint8_t status; // Status byte + uint8_t hard_main; + uint8_t hard_sub; + uint8_t hard_ssub; + uint8_t soft_main; + uint8_t soft_sub; + uint8_t soft_ssub; + char version_str[32]; // 格式化字符串 +} LUP_VersionInfo; + +/* 命令状态机 */ +typedef enum { + LUP_STATE_IDLE = 0, // 空闲 + LUP_STATE_WAIT_RESPONSE, // 等待响应 + LUP_STATE_RESPONSE_READY, // 响应已就绪 + LUP_STATE_TIMEOUT // 超时 +} LUP_CmdState; + +/* 挂起的命令跟踪 */ +typedef struct { + uint8_t pending_cmd; // 当前等待响应的命令字节 + uint32_t send_tick; // 发送时刻 (ms) + uint32_t timeout_ms; // 超时时间 + LUP_CmdState state; + // 响应数据缓冲区 + uint8_t resp_buf[LUP_MAX_PKG_LEN]; + uint16_t resp_len; +} LUP_CmdTracker; + +/*=========================================================================== + * Global State + *===========================================================================*/ +extern LUP_CmdTracker g_lup_cmd; + +/*=========================================================================== + * Public API + *===========================================================================*/ + +/* --- Checksum --- */ +uint8_t lup_calc_xor(const uint8_t *data, uint16_t len); +uint8_t lup_calc_sum(const uint8_t *data, uint16_t len); +int lup_verify_checksum(const uint8_t *pkg, uint16_t len); +void lup_append_checksum(uint8_t *pkg); + +/* --- Packet Builders (returns total packet length) --- */ +uint16_t lup_build_get_version(uint8_t *buf); +uint16_t lup_build_reset(uint8_t *buf); +uint16_t lup_build_factory_init(uint8_t *buf); +uint16_t lup_build_sensitivity_read(uint8_t *buf); +uint16_t lup_build_sensitivity_write(uint8_t *buf, const LUP_Sensitivity *sens); +uint16_t lup_build_set_param(uint8_t *buf, const LUP_ParamSet *ps); +uint16_t lup_build_get_param(uint8_t *buf); + +/* --- Packet Parsers (returns 0=success, <0=error) --- */ +int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info); +int lup_parse_factory_init_resp(const uint8_t *pkg, uint16_t len, uint8_t *success); +int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens); +int lup_parse_param_get(const uint8_t *pkg, uint16_t len, LUP_ParamGet *pg); +int lup_parse_sensor_report(const uint8_t *pkg, uint16_t len, LUP_SensorReport *report); +int lup_parse_set_param_resp(const uint8_t *pkg, uint16_t len, uint8_t *success); + +/* --- Command State Machine --- */ +void lup_cmd_begin(uint8_t cmd, uint32_t timeout_ms); +void lup_cmd_done(void); +int lup_cmd_check_timeout(void); +void lup_cmd_on_response(const uint8_t *pkg, uint16_t len); + +/* --- High-level Commands (send + wait handled by state machine) --- */ +void lup_send_get_version(void); +void lup_send_reset(void); +void lup_send_factory_init(void); +void lup_send_sensitivity_read(void); +void lup_send_set_param(const LUP_ParamSet *ps); +void lup_send_get_param(void); + +/* --- Mid-packet ACK for 0xC0 multi-packet --- */ +uint16_t lup_build_sensor_ack(uint8_t *buf, uint8_t sens_type, + uint8_t seq, uint8_t sub_amount); + +/* --- UART Frame Parser (called from USART2 ISR context) --- */ +typedef enum { + LUP_FRAME_STATE_IDLE = 0, // 等待 0x7F + LUP_FRAME_STATE_HEADER, // 接收 Header (Addr, LEN, CMD) + LUP_FRAME_STATE_VALUE, // 接收 Value + LUP_FRAME_STATE_CHECK, // 接收 Checksum + LUP_FRAME_STATE_COMPLETE // 帧完成 +} LUP_FrameState; + +typedef struct { + LUP_FrameState state; + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t idx; // 当前写入位置 + uint16_t value_len; // 期望的 Value 长度 (从 LEN 字段解析) + uint16_t value_idx; // 当前已接收的 Value 字节数 +} LUP_FrameParser; + +extern LUP_FrameParser g_lup_parser; + +/* 喂一个字节给帧解析器,返回 1 表示帧接收完成 */ +int lup_feed_byte(uint8_t byte); + +/* 取完整帧的指针和数据长度 */ +const uint8_t *lup_frame_data(void); +uint16_t lup_frame_len(void); + +/* 重置帧解析器 */ +void lup_frame_reset(void); + +/* --- Process a complete frame (call from main loop context) --- */ +void lup_process_frame(const uint8_t *pkg, uint16_t len); + +/* --- Sensor report callback (called when valid 0xC0 frame received) --- */ +typedef void (*lup_sensor_cb_t)(const uint8_t *pkg, uint16_t len); +void lup_set_sensor_callback(lup_sensor_cb_t cb); + +#endif /* __LOOP_UART_PROTO_H__ */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h index 90841b9..0a3c173 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h @@ -28,7 +28,7 @@ extern "C" { * 1 UDP + 2 TCP + 1 TCP_LISTEN = 4 */ #define WCHNET_NUM_UDP 1 /* SSC 模式需要 UDP */ -#define WCHNET_NUM_TCP 2 /* JSON(1) + SSC client(1) max */ +#define WCHNET_NUM_TCP 1 //2 /* JSON(1) + SSC client(1) max */ #define WCHNET_NUM_IPRAW 0 /* Number of IPRAW connections */ #define WCHNET_NUM_TCP_LISTEN 1 /* Number of TCP listening (required internally) */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/storage.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/storage.h index 84ef5d3..e611ea8 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/storage.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/storage.h @@ -23,6 +23,8 @@ void set_ble_safe_pass(uint8_t * devpass); void alter_dev_serila(uint8_t *serial); void alter_dev_baud(uint8_t *baudbuf); +void update_sub_code_enable(void); + void factory_dev_info(void); void output_cfg_from_flash(void); void load_cfg_from_flash(void); diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h index ec636e1..aeb900e 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h @@ -1,84 +1,84 @@ -/** - ****************************************************************************** - * @file tcp_json_srv.h - * @author wangfq - * @version V1.0 - * @date 2026-06-30 - * @brief TCP JSON protocol server — DLD960 TCP JSON protocol handler - * Port 5960, line-delimited JSON, auth + command dispatch - ****************************************************************************** - */ -#ifndef __TCP_JSON_SRV_H__ -#define __TCP_JSON_SRV_H__ - -#include - -/*=========================================================================== - * Protocol Constants - *===========================================================================*/ -#define TCP_JSON_PORT 5960 // TCP JSON protocol default port -#define TCP_JSON_MAX_FRAME 4096 // Max JSON frame length -#define TCP_JSON_AUTH_TIMEOUT_MS 60000 // 60s auth timeout (ms) -#define TCP_JSON_RECV_BUF_LEN (TCP_JSON_MAX_FRAME * 2) -#define TCP_JSON_MAX_PWD_RETRY 3 // Max password retries before lockout -#define TCP_JSON_LOCKOUT_TIMEOUT_MS 60000 // 60s lockout after max retries -#define TCP_JSON_IDLE_TIMEOUT_MS 300000 // 5min idle → restart (no connect / no comm) -#define TCP_JSON_MAX_RESTART_COUNT 3 // Max auto-restart count -#define TCP_JSON_RESTART_COOLDOWN_MS 600000 // 10min cooldown after max restarts - -/*=========================================================================== - * Auth / State - *===========================================================================*/ -typedef enum { - JSON_STATE_WAIT_AUTH = 0, // Waiting for pwd_verify - JSON_STATE_AUTHED, // Authenticated, commands accepted - JSON_STATE_LOCKOUT // Locked out (max retries exceeded) -} TcpJsonAuthState; - -/*=========================================================================== - * Error Codes - *===========================================================================*/ -#define JSON_CODE_SUCCESS 0 -#define JSON_CODE_PARAM_ERR 1 -#define JSON_CODE_AUTH_FAIL 2 -#define JSON_CODE_BUSY 3 -#define JSON_CODE_UNSUPPORTED 4 -#define JSON_CODE_INTERNAL_ERR 5 -#define JSON_CODE_DATA_TOO_LONG 6 -#define JSON_CODE_NOT_AUTHED 7 - -/*=========================================================================== - * Pending Loop MCU Command Response (deferred) - *===========================================================================*/ -typedef struct { - uint8_t active; // 1 = pending response - uint8_t socket; // TCP socket to reply to - uint32_t msg_id; // Original msg_id - char cmd[32]; // Original command string - uint32_t deadline; // ms deadline for timeout -} TcpJsonPending; - -/*=========================================================================== - * Externs - *===========================================================================*/ -extern uint8_t g_json_socket_listen; // TCP socket ID (listen + data) -extern TcpJsonAuthState g_json_auth_state; -extern uint32_t g_json_auth_timer; // ms timer for auth timeout -extern uint8_t g_json_pwd_retry; // password retry counter -extern TcpJsonPending g_json_pending; // Pending Loop MCU command state -extern uint32_t g_json_last_comm_ts; // Last communication timestamp (ms) -extern uint32_t g_json_last_connect_ts; // Last client connect timestamp (ms) -extern uint8_t g_json_restart_count; // Auto-restart counter -extern uint32_t g_json_restart_cooldown; // Cooldown deadline (ms) -extern uint8_t g_report_active; // 0=inactive 1=active report - -/*=========================================================================== - * API - *===========================================================================*/ -void tcp_json_srv_init(void); // Create listen socket on port 5960 -void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket interrupt handler -void tcp_json_poll(void); // Periodic poll (auth timeout, etc.) -void tcp_json_push_sensor(void); // Push sensor data (0xC0) to TCP client -void tcp_json_restart(void); // Close + reopen socket (auto-restart) - -#endif /* __TCP_JSON_SRV_H__ */ +/** + ****************************************************************************** + * @file tcp_json_srv.h + * @author wangfq + * @version V1.0 + * @date 2026-06-30 + * @brief TCP JSON protocol server — DLD960 TCP JSON protocol handler + * Port 5960, line-delimited JSON, auth + command dispatch + ****************************************************************************** + */ +#ifndef __TCP_JSON_SRV_H__ +#define __TCP_JSON_SRV_H__ + +#include + +/*=========================================================================== + * Protocol Constants + *===========================================================================*/ +#define TCP_JSON_PORT 5960 // TCP JSON protocol default port +#define TCP_JSON_MAX_FRAME 4096 // Max JSON frame length +#define TCP_JSON_AUTH_TIMEOUT_MS 60000 // 60s auth timeout (ms) +#define TCP_JSON_RECV_BUF_LEN (TCP_JSON_MAX_FRAME * 2) +#define TCP_JSON_MAX_PWD_RETRY 3 // Max password retries before lockout +#define TCP_JSON_LOCKOUT_TIMEOUT_MS 60000 // 60s lockout after max retries +#define TCP_JSON_IDLE_TIMEOUT_MS 300000 // 5min idle → restart (no connect / no comm) +#define TCP_JSON_MAX_RESTART_COUNT 3 // Max auto-restart count +#define TCP_JSON_RESTART_COOLDOWN_MS 600000 // 10min cooldown after max restarts + +/*=========================================================================== + * Auth / State + *===========================================================================*/ +typedef enum { + JSON_STATE_WAIT_AUTH = 0, // Waiting for pwd_verify + JSON_STATE_AUTHED, // Authenticated, commands accepted + JSON_STATE_LOCKOUT // Locked out (max retries exceeded) +} TcpJsonAuthState; + +/*=========================================================================== + * Error Codes + *===========================================================================*/ +#define JSON_CODE_SUCCESS 0 +#define JSON_CODE_PARAM_ERR 1 +#define JSON_CODE_AUTH_FAIL 2 +#define JSON_CODE_BUSY 3 +#define JSON_CODE_UNSUPPORTED 4 +#define JSON_CODE_INTERNAL_ERR 5 +#define JSON_CODE_DATA_TOO_LONG 6 +#define JSON_CODE_NOT_AUTHED 7 + +/*=========================================================================== + * Pending Loop MCU Command Response (deferred) + *===========================================================================*/ +typedef struct { + uint8_t active; // 1 = pending response + uint8_t socket; // TCP socket to reply to + uint32_t msg_id; // Original msg_id + char cmd[32]; // Original command string + uint32_t deadline; // ms deadline for timeout +} TcpJsonPending; + +/*=========================================================================== + * Externs + *===========================================================================*/ +extern uint8_t g_json_socket_listen; // TCP socket ID (listen + data) +extern TcpJsonAuthState g_json_auth_state; +extern uint32_t g_json_auth_timer; // ms timer for auth timeout +extern uint8_t g_json_pwd_retry; // password retry counter +extern TcpJsonPending g_json_pending; // Pending Loop MCU command state +extern uint32_t g_json_last_comm_ts; // Last communication timestamp (ms) +extern uint32_t g_json_last_connect_ts; // Last client connect timestamp (ms) +extern uint8_t g_json_restart_count; // Auto-restart counter +extern uint32_t g_json_restart_cooldown; // Cooldown deadline (ms) +extern uint8_t g_report_active; // 0=inactive 1=active report + +/*=========================================================================== + * API + *===========================================================================*/ +void tcp_json_srv_init(void); // Create listen socket on port 5960 +void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket interrupt handler +void tcp_json_poll(void); // Periodic poll (auth timeout, etc.) +void tcp_json_push_sensor(void); // Push sensor data (0xC0) to TCP client +void tcp_json_restart(void); // Close + reopen socket (auto-restart) + +#endif /* __TCP_JSON_SRV_H__ */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c index ef22f5a..dfc4709 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c @@ -1,634 +1,634 @@ -/** - ****************************************************************************** - * @file iot_mqtt_srv.c - * @author wangfq - * @version V1.0 - * @date 2026-07-03 - * @brief IoT MQTT 客户端实现 — DLD960 IoT 协议 - * - * 连接流程: TCP connect → MQTT CONNECT → CONNACK → SUBSCRIBE → SUBACK → READY - * 断线重连: 指数退避 5s~60s - * 数据上报: loop_data / event_report / heartbeat - ****************************************************************************** - */ - -#include "CONFIG.h" -#include "iot_mqtt_srv.h" -#include "eth_driver.h" -#include "wchnet.h" -#include "net_srv.h" -#include "MQTTPacket.h" -#include "cmcng.h" -#include "loop_uart_proto.h" -#include "simple_json.h" -#include "storage.h" -#include -#include -#include - -/*=========================================================================== - * Global State - *===========================================================================*/ - - -extern uint8_t g_report_active; // from tcp_json_srv.c, 0=inactive 1=active -uint8_t g_iot_socket = 0xFF; // MQTT TCP socket ID -IotMqttState g_iot_state = IOT_STATE_DISCONNECTED; -uint8_t g_iot_msg_id = 0; // MQTT packet identifier -static uint32_t _iot_last_heartbeat = 0; // 上次心跳时刻 (ms) -static uint32_t _iot_reconnect_deadline = 0; -static uint32_t _iot_reconnect_backoff = 0; -static uint32_t _iot_connect_start = 0; // TCP connect 开始时刻 - -/*=========================================================================== - * Buffers - *===========================================================================*/ -static uint8_t _iot_recv_buf[IOT_MQTT_RECV_BUF_LEN]; -static uint16_t _iot_recv_len = 0; -static uint8_t _iot_send_buf[IOT_MQTT_SEND_BUF_LEN]; -static uint8_t _iot_wchnet_buf[RECE_BUF_LEN]; // WCHNET internal recv buffer - -/*=========================================================================== - * Topic 构建 - *===========================================================================*/ -static char g_iot_dev_serial[13]; // 设备序列码 (12 hex chars) - -/* 构建 topic: dld960/{sn}/... */ -static int iot_make_topic(char *out, uint16_t out_len, const char *direction, - const char *category, const char *sub) { - if (sub) { - return snprintf(out, out_len, "dld960/%s/%s/%s/%s", - g_iot_dev_serial, direction, category, sub); - } - return snprintf(out, out_len, "dld960/%s/%s/%s", - g_iot_dev_serial, direction, category); -} - -/*=========================================================================== - * MQTT Packet Helpers - *===========================================================================*/ - -/* 发送 MQTT 报文到 broker */ -static int iot_mqtt_send(const uint8_t *buf, uint16_t len) { - uint16_t slen = len; - uint8_t ret = WCHNET_SocketSend(g_iot_socket, (uint8_t *)buf, &slen); - return (ret == WCHNET_ERR_SUCCESS) ? 0 : -1; -} - -/* 发送 MQTT CONNECT */ -static int iot_mqtt_send_connect(void) { - MQTTPacket_connectData opts = MQTTPacket_connectData_initializer; - uint8_t buf[256]; - int len; - - opts.MQTTVersion = 4; // MQTT 3.1.1 - opts.keepAliveInterval = IOT_MQTT_KEEPALIVE_SEC; - opts.cleansession = 1; - - // clientID - if (strlen((char *)iot_net_info.client_id) > 0) { - opts.clientID.cstring = (char *)iot_net_info.client_id; - } else { - opts.clientID.cstring = g_iot_dev_serial; - } - - // username / password - if (strlen((char *)iot_net_info.username) > 0) { - opts.username.cstring = (char *)iot_net_info.username; - } - if (strlen((char *)iot_net_info.password) > 0) { - opts.password.cstring = (char *)iot_net_info.password; - } - - len = MQTTSerialize_connect(buf, sizeof(buf), &opts); - if (len <= 0) { - PRINT("IOT: MQTTSerialize_connect failed\n"); - return -1; - } - PRINT("IOT: → CONNECT clientId=%s keepAlive=%d\n", - opts.clientID.cstring, opts.keepAliveInterval); - return iot_mqtt_send(buf, (uint16_t)len); -} - -/* 发送 MQTT SUBSCRIBE */ -static int iot_mqtt_send_subscribe(void) { - uint8_t buf[256]; - int len; - char topic[IOT_MQTT_TOPIC_MAX_LEN]; - MQTTString topics[3]; - int qos[3] = {1, 1, 1}; - int count = 0; - - // 订阅服务器下发消息 - iot_make_topic(topic, sizeof(topic), "srv", "config", "set"); - topics[count].cstring = topic; topics[count].lenstring.len = 0; count++; - - iot_make_topic(topic, sizeof(topic), "srv", "config", "query"); - topics[count].cstring = topic; topics[count].lenstring.len = 0; count++; - - iot_make_topic(topic, sizeof(topic), "srv", "ctrl", NULL); - topics[count].cstring = topic; topics[count].lenstring.len = 0; count++; - - len = MQTTSerialize_subscribe(buf, sizeof(buf), 0, - ++g_iot_msg_id, count, topics, qos); - if (len <= 0) { - PRINT("IOT: MQTTSerialize_subscribe failed\n"); - return -1; - } - PRINT("IOT: → SUBSCRIBE pktId=%d topics=%d\n", g_iot_msg_id, count); - return iot_mqtt_send(buf, (uint16_t)len); -} - -/* 发送 MQTT PUBLISH */ -static int iot_mqtt_publish(const char *topic, const char *payload, - uint16_t payload_len, uint8_t qos) { - uint8_t buf[IOT_MQTT_SEND_BUF_LEN]; - int len; - MQTTString mqtt_topic = MQTTString_initializer; - mqtt_topic.cstring = (char *)topic; - - len = MQTTSerialize_publish(buf, sizeof(buf), 0, qos, 0, - ++g_iot_msg_id, mqtt_topic, - (unsigned char *)payload, payload_len); - if (len <= 0) { - PRINT("IOT: MQTTSerialize_publish failed\n"); - return -1; - } - return iot_mqtt_send(buf, (uint16_t)len); -} - -/*=========================================================================== - * MQTT 接收处理 - *===========================================================================*/ - -/* 处理一条收到的 MQTT PUBLISH 消息 */ -static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_len) { - char json[IOT_MQTT_RECV_BUF_LEN]; - int copy_len = payload_len < (int)sizeof(json) - 1 ? payload_len : (int)sizeof(json) - 1; - memcpy(json, payload, copy_len); - json[copy_len] = '\0'; - - PRINT("IOT: PUBLISH topic=%s payload=%s\n", topic, json); - - /* 提取 msg_id 和 cmd */ - char tmp[256]; - uint32_t msg_id = 0; - - // 简单提取: 解析 JSON 中的 msg_id 和 cmd - memset(tmp, 0, sizeof(tmp)); - simple_parse_json(json, "\"msg_id\"", tmp); - if (strlen(tmp) > 0) msg_id = (uint32_t)strtoul(tmp, NULL, 10); - - memset(tmp, 0, sizeof(tmp)); - simple_parse_json(json, "\"cmd\"", tmp); - // strip quotes - char *cmd_str = tmp; - if (cmd_str[0] == '"') cmd_str++; - int cmd_len = strlen(cmd_str); - if (cmd_len > 0 && cmd_str[cmd_len - 1] == '"') cmd_str[cmd_len - 1] = '\0'; - - if (strlen(cmd_str) == 0) { - PRINT("IOT: no cmd in PUBLISH\n"); - return; - } - - /* 构建响应 topic */ - char resp_topic[IOT_MQTT_TOPIC_MAX_LEN]; - iot_make_topic(resp_topic, sizeof(resp_topic), "dev", "config", "resp"); - - /* 处理命令 — 复用 TCP JSON 的命令逻辑 */ - /* 目前仅实现基本响应框架, 后续逐步对接 Loop MCU 命令 */ - - if (strcmp(cmd_str, "dev_info_query") == 0) { - char data_json[512]; - snprintf(data_json, sizeof(data_json), - "{\"msg_id\":%lu,\"cmd\":\"dev_info_query\"," - "\"ts\":%lu,\"code\":0,\"msg\":\"success\"," - "\"data\":{" - "\"dev_serial\":\"%s\",\"hard_ver\":\"%s\",\"soft_ver\":\"%s\"," - "\"model\":\"%s\",\"product_code\":\"960001\"," - "\"sub_code\":{\"net\":%s,\"iot\":%s}," - "\"bus\":{\"bus1\":0,\"bus2\":0,\"bus3\":0,\"bus4\":0}}}", - msg_id, mstick() / 1000, - g_dev_number_str, HARDWARE_VER, FIRMWARE_VER, PRODUCT_MODEL, - g_sub_code_enable.net_enable ? "true" : "false", - g_sub_code_enable.iot_enable ? "true" : "false"); - iot_mqtt_publish(resp_topic, data_json, strlen(data_json), 1); - - } else if (strcmp(cmd_str, "pwd_verify") == 0) { - // 验证密码 - char password[16] = {0}; - char *data = (char *)malloc(512); - if (data) { - memset(data, 0, 512); - simple_parse_json(json, "\"data\"", data); - if (strlen(data) > 0) { - memset(tmp, 0, sizeof(tmp)); - simple_parse_json(data, "\"password\"", tmp); - // strip quotes - char *pwd = tmp; - if (pwd[0] == '"') pwd++; - int plen = strlen(pwd); - if (plen > 0 && pwd[plen - 1] == '"') pwd[plen - 1] = '\0'; - strncpy(password, pwd, 15); - } - free(data); - } - - if (strlen(password) == 6 && memcmp(password, g_dev_password, 6) == 0) { - char resp[256]; - snprintf(resp, sizeof(resp), - "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," - "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", - msg_id, mstick() / 1000); - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); - PRINT("IOT: Auth success via MQTT\n"); - } else { - char resp[256]; - snprintf(resp, sizeof(resp), - "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," - "\"ts\":%lu,\"code\":2,\"msg\":\"password incorrect\"}", - msg_id, mstick() / 1000); - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); - } - - } else { - // 暂不支持的命令, 返回错误 - char resp[256]; - snprintf(resp, sizeof(resp), - "{\"msg_id\":%lu,\"cmd\":\"%s\"," - "\"ts\":%lu,\"code\":4,\"msg\":\"unsupported command\"}", - msg_id, cmd_str, mstick() / 1000); - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); - } -} - -/* 处理 MQTT SUBACK */ -static void iot_handle_suback(void) { - PRINT("IOT: ← SUBACK, topics subscribed\n"); - g_iot_state = IOT_STATE_READY; - _iot_reconnect_backoff = 0; // 连接成功, 重置退避 -} - -/* 处理收到的 MQTT 数据 */ -static void iot_process_recv(void) { - uint8_t header; - int qos, payload_len; - unsigned char retained; - unsigned short packet_id; - MQTTString topic; - unsigned char *payload_ptr; - - while (_iot_recv_len >= 2) { - header = _iot_recv_buf[0]; - int msg_type = (header >> 4) & 0x0F; - - /* 先尝试解析整个 MQTT 包 */ - int mqtt_pkt_len = 0; - int multiplier = 1; - int rem_len = 0; - int i = 1; - while (i < (int)_iot_recv_len && i < 5) { - rem_len += (_iot_recv_buf[i] & 0x7F) * multiplier; - multiplier *= 128; - if ((_iot_recv_buf[i] & 0x80) == 0) { - mqtt_pkt_len = 1 + (i - 1 + 1) + rem_len; // header + rem_len_bytes + payload - break; - } - i++; - } - if (mqtt_pkt_len == 0 || mqtt_pkt_len > (int)_iot_recv_len) { - return; // 包不完整, 等更多数据 - } - - PRINT("IOT: ← MQTT packet type=%d len=%d\n", msg_type, mqtt_pkt_len); - - switch (msg_type) { - case CONNACK: { - unsigned char session_present, connack_rc; - if (MQTTDeserialize_connack(&session_present, &connack_rc, - _iot_recv_buf, mqtt_pkt_len) == 1) { - PRINT("IOT: ← CONNACK rc=%d\n", connack_rc); - if (connack_rc == 0) { - // 连接成功 → 订阅 - g_iot_state = IOT_STATE_MQTT_CONNECTED; - iot_mqtt_send_subscribe(); - } else { - PRINT("IOT: CONNACK rejected, rc=%d\n", connack_rc); - g_iot_state = IOT_STATE_DISCONNECTED; - } - } - break; - } - - case SUBACK: - iot_handle_suback(); - break; - - case PUBLISH: { - unsigned char pub_dup; - if (MQTTDeserialize_publish(&pub_dup, &qos, &retained, - &packet_id, &topic, - &payload_ptr, &payload_len, - _iot_recv_buf, mqtt_pkt_len) == 1) { - char topic_str[IOT_MQTT_TOPIC_MAX_LEN]; - memcpy(topic_str, topic.cstring ? topic.cstring : "", - topic.lenstring.len < (int)sizeof(topic_str) - 1 - ? topic.lenstring.len : (int)sizeof(topic_str) - 1); - topic_str[topic.lenstring.len] = '\0'; - - iot_handle_publish(topic_str, payload_ptr, payload_len); - - // 如果 QoS > 0, 发送 PUBACK - if (qos > 0) { - uint8_t ack_buf[4]; - int ack_len = MQTTSerialize_ack(ack_buf, sizeof(ack_buf), - PUBACK, 0, packet_id); - iot_mqtt_send(ack_buf, (uint16_t)ack_len); - } - } - break; - } - - case PINGRESP: - PRINT("IOT: ← PINGRESP\n"); - break; - - default: - break; - } - - /* 移除已处理的包 */ - memmove(_iot_recv_buf, _iot_recv_buf + mqtt_pkt_len, - _iot_recv_len - mqtt_pkt_len); - _iot_recv_len -= mqtt_pkt_len; - } -} - -/*=========================================================================== - * 传感器数据上报 - *===========================================================================*/ -void iot_mqtt_publish_sensor(void) { - if (g_iot_state != IOT_STATE_READY) return; - if (!g_report_active) return; - - /* 复用 g_pkg_uart_2 中的传感器帧 */ - if (g_pkg_uart_2.flag == 0) return; - if (g_pkg_uart_2.pkg[0] != 0x7F) return; - if (g_pkg_uart_2.pkg[3] != 0xC0) return; - - LUP_SensorReport sr; - memset(&sr, 0, sizeof(sr)); - int ret = lup_parse_sensor_report(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &sr); - if (ret != 0) { - PRINT("IOT: sensor parse failed (%d)\n", ret); - InitPkgUart(&g_pkg_uart_2); - return; - } - - /* 构建 loop_data JSON */ - char data_json[2048]; - char *p = data_json; - int remaining = sizeof(data_json); - int written; - uint8_t i; - - written = snprintf(p, remaining, - "{\"channels\":["); - if (written < 0 || written >= remaining) goto done; - p += written; remaining -= written; - - for (i = 0; i < sr.coil_count; i++) { - const LUP_CoilSensor *cs = &sr.coils[i]; - const char *misc_type_str = "time"; - uint32_t misc_val = 0; - - if (cs->misc_type == 0) { misc_type_str = "time"; misc_val = cs->misc.passtime_ms; } - else if (cs->misc_type == 1) { misc_type_str = "cut_count"; misc_val = cs->misc.cut_amount; } - else if (cs->misc_type == 2) { misc_type_str = "flow_count"; misc_val = cs->misc.flow_amount; } - else if (cs->misc_type == 3) { misc_type_str = "relay_count"; misc_val = cs->misc.relay_count; } - - const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"}; - - written = snprintf(p, remaining, - "%s{\"ch\":%d,\"freq_level\":\"%s\",\"has_car\":%s," - "\"loop_ok\":%s,\"freq_current\":%lu,\"freq_diff\":%d," - "\"sensitivity\":%d,\"condition\":%d," - "\"misc\":{\"type\":\"%s\",\"value\":%lu}}", - (i > 0) ? "," : "", - i + 1, - freq_level_names[cs->freq_level], - cs->car_state ? "true" : "false", - cs->loop_state ? "false" : "true", - cs->freq, cs->variation, - cs->sensitivity, cs->condition, - misc_type_str, misc_val); - if (written < 0 || written >= remaining) goto done; - p += written; remaining -= written; - } - - snprintf(p, remaining, "]}"); - - /* 包装为通用消息格式 */ - char payload[IOT_MQTT_SEND_BUF_LEN]; - snprintf(payload, sizeof(payload), - "{\"msg_id\":%d,\"cmd\":\"loop_data\",\"ts\":%lu,\"data\":%s}", - ++g_iot_msg_id, mstick() / 1000, data_json); - - char topic[IOT_MQTT_TOPIC_MAX_LEN]; - iot_make_topic(topic, sizeof(topic), "dev", "data", "loop"); - iot_mqtt_publish(topic, payload, strlen(payload), 0); - -done: - InitPkgUart(&g_pkg_uart_2); -} - -/*=========================================================================== - * 心跳上报 - *===========================================================================*/ -static void iot_send_heartbeat(void) { - if (g_iot_state != IOT_STATE_READY) return; - - char payload[512]; - uint8_t loop_ok[4] = {1, 1, 1, 1}; - uint8_t i; - for (i = 0; i < 4; i++) { - /* 简化: 读取线圈状态 */ - loop_ok[i] = 1; // TODO: 从 Loop MCU 获取实际状态 - } - - snprintf(payload, sizeof(payload), - "{\"msg_id\":%d,\"cmd\":\"heartbeat\"," - "\"ts\":%lu,\"data\":{" - "\"uptime\":%lu,\"loop_status\":[%s,%s,%s,%s]," - "\"net_status\":true,\"iot_status\":true}}", - ++g_iot_msg_id, mstick() / 1000, - mstick() / 1000, - loop_ok[0] ? "true" : "false", loop_ok[1] ? "true" : "false", - loop_ok[2] ? "true" : "false", loop_ok[3] ? "true" : "false"); - - char topic[IOT_MQTT_TOPIC_MAX_LEN]; - iot_make_topic(topic, sizeof(topic), "dev", "status", NULL); - iot_mqtt_publish(topic, payload, strlen(payload), 0); -} - -/*=========================================================================== - * 连接管理 - *===========================================================================*/ - -/* 连接到 MQTT Broker */ -static void iot_connect_broker(void) { - uint8_t broker_ip[4]; - SOCK_INF sock_inf; - uint8_t ret; - - // 解析 broker 地址 - if (get_ipstr_to_array((char *)iot_net_info.remote_addr, broker_ip) == 0) { - // 是 IP 地址 - memcpy(broker_ip, iot_net_info.remote_addr, 4); - PRINT("IOT: broker IP %d.%d.%d.%d:%d\n", - broker_ip[0], broker_ip[1], broker_ip[2], broker_ip[3], - iot_net_info.mqtt_port); - } else { - PRINT("IOT: broker hostname=%s (DNS not implemented yet)\n", - iot_net_info.remote_addr); - return; - } - - memset(&sock_inf, 0, sizeof(SOCK_INF)); - sock_inf.DesPort = iot_net_info.mqtt_port; - sock_inf.ProtoType = PROTO_TYPE_TCP; - sock_inf.RecvBufLen = RECE_BUF_LEN; - memcpy(sock_inf.IPAddr, broker_ip, 4); - - ret = WCHNET_SocketCreat(&g_iot_socket, &sock_inf); - if (ret != WCHNET_ERR_SUCCESS) { - PRINT("IOT: SocketCreat failed: 0x%02X\n", ret); - g_iot_socket = 0xFF; - return; - } - - ret = WCHNET_SocketConnect(g_iot_socket); - if (ret != WCHNET_ERR_SUCCESS) { - PRINT("IOT: SocketConnect failed: 0x%02X\n", ret); - WCHNET_SocketClose(g_iot_socket, TCP_CLOSE_NORMAL); - g_iot_socket = 0xFF; - return; - } - - g_iot_state = IOT_STATE_TCP_CONNECTING; - _iot_connect_start = mstick(); - PRINT("IOT: TCP connecting to broker (sock=%d)...\n", g_iot_socket); -} - -/*=========================================================================== - * Socket 中断处理 - *===========================================================================*/ -void iot_mqtt_handle_sock_int(uint8_t socketid, uint8_t intstat) { - if (socketid != g_iot_socket) return; - - /* CONNECT 成功 */ - if (intstat & SINT_STAT_CONNECT) { - PRINT("IOT: TCP connected (sock=%d)\n", socketid); - WCHNET_ModifyRecvBuf(socketid, (uint32_t)_iot_wchnet_buf, RECE_BUF_LEN); - g_iot_state = IOT_STATE_TCP_CONNECTED; - _iot_recv_len = 0; - // 发送 MQTT CONNECT - iot_mqtt_send_connect(); - g_iot_state = IOT_STATE_MQTT_CONNECTING; - } - - /* 收到数据 */ - if (intstat & SINT_STAT_RECV) { - uint32_t recv_len = WCHNET_SocketRecvLen(socketid, NULL); - if (recv_len > 0) { - uint16_t space = IOT_MQTT_RECV_BUF_LEN - _iot_recv_len; - if (recv_len > space) recv_len = space; - uint32_t rd_len = recv_len; - uint8_t tmp[RECE_BUF_LEN]; - WCHNET_SocketRecv(socketid, tmp, &rd_len); - memcpy(_iot_recv_buf + _iot_recv_len, tmp, (uint16_t)rd_len); - _iot_recv_len += (uint16_t)rd_len; - iot_process_recv(); - } - } - - /* 断开 / 超时 */ - if (intstat & (SINT_STAT_DISCONNECT | SINT_STAT_TIM_OUT)) { - PRINT("IOT: socket disconnect/timeout (sock=%d)\n", socketid); - g_iot_state = IOT_STATE_DISCONNECTED; - g_iot_socket = 0xFF; - _iot_recv_len = 0; - _iot_reconnect_backoff = IOT_MQTT_RECONNECT_MIN_MS; - _iot_reconnect_deadline = mstick() + _iot_reconnect_backoff; - } -} - -/*=========================================================================== - * 主循环轮询 - *===========================================================================*/ -void iot_mqtt_poll(void) { - /* 断线重连 */ - if (g_iot_state == IOT_STATE_DISCONNECTED && g_iot_socket == 0xFF) { - if (_iot_reconnect_deadline == 0 || mstick() > _iot_reconnect_deadline) { - iot_connect_broker(); - if (g_iot_socket == 0xFF) { - // 连接失败, 退避 - if (_iot_reconnect_backoff == 0) { - _iot_reconnect_backoff = IOT_MQTT_RECONNECT_MIN_MS; - } else { - _iot_reconnect_backoff *= 2; - if (_iot_reconnect_backoff > IOT_MQTT_RECONNECT_MAX_MS) - _iot_reconnect_backoff = IOT_MQTT_RECONNECT_MAX_MS; - } - _iot_reconnect_deadline = mstick() + _iot_reconnect_backoff; - PRINT("IOT: reconnect in %lu ms\n", _iot_reconnect_backoff); - } - } - return; - } - - /* TCP 连接超时 (10s) */ - if (g_iot_state == IOT_STATE_TCP_CONNECTING) { - if (mstick() - _iot_connect_start > 10000) { - PRINT("IOT: TCP connect timeout\n"); - WCHNET_SocketClose(g_iot_socket, TCP_CLOSE_NORMAL); - g_iot_socket = 0xFF; - g_iot_state = IOT_STATE_DISCONNECTED; - return; - } - } - - /* MQTT Keepalive — PINGREQ */ - if (g_iot_state == IOT_STATE_READY) { - uint32_t now = mstick(); - if (now - _iot_last_heartbeat > IOT_MQTT_HEARTBEAT_MS) { - uint8_t ping_buf[2]; - int ping_len = MQTTSerialize_pingreq(ping_buf, sizeof(ping_buf)); - iot_mqtt_send(ping_buf, (uint16_t)ping_len); - _iot_last_heartbeat = now; - // 心跳上报 - iot_send_heartbeat(); - } - } -} - -/*=========================================================================== - * 初始化 - *===========================================================================*/ -void iot_mqtt_init(void) { - /* 初始化设备序列码 (12 hex chars) */ - snprintf(g_iot_dev_serial, sizeof(g_iot_dev_serial), - "%02X%02X%02X%02X%02X%02X", - g_dev_number[0], g_dev_number[1], g_dev_number[2], - g_dev_number[3], g_dev_number[4], g_dev_number[5]); - - PRINT("IOT: dev_serial=%s\n", g_iot_dev_serial); - - /* 初始化传感器回调 — 用于 MQTT 上报 */ - lup_set_sensor_callback(NULL); // MQTT 模式不需要 JSON sensor_cb - - _iot_reconnect_backoff = 0; - _iot_reconnect_deadline = mstick() + 2000; // 启动后 2s 开始首次连接 - g_iot_state = IOT_STATE_DISCONNECTED; -} +/** + ****************************************************************************** + * @file iot_mqtt_srv.c + * @author wangfq + * @version V1.0 + * @date 2026-07-03 + * @brief IoT MQTT 客户端实现 — DLD960 IoT 协议 + * + * 连接流程: TCP connect → MQTT CONNECT → CONNACK → SUBSCRIBE → SUBACK → READY + * 断线重连: 指数退避 5s~60s + * 数据上报: loop_data / event_report / heartbeat + ****************************************************************************** + */ + +#include "CONFIG.h" +#include "iot_mqtt_srv.h" +#include "eth_driver.h" +#include "wchnet.h" +#include "net_srv.h" +#include "MQTTPacket.h" +#include "cmcng.h" +#include "loop_uart_proto.h" +#include "simple_json.h" +#include "storage.h" +#include +#include +#include + +/*=========================================================================== + * Global State + *===========================================================================*/ + + +extern uint8_t g_report_active; // from tcp_json_srv.c, 0=inactive 1=active +uint8_t g_iot_socket = 0xFF; // MQTT TCP socket ID +IotMqttState g_iot_state = IOT_STATE_DISCONNECTED; +uint8_t g_iot_msg_id = 0; // MQTT packet identifier +static uint32_t _iot_last_heartbeat = 0; // 上次心跳时刻 (ms) +static uint32_t _iot_reconnect_deadline = 0; +static uint32_t _iot_reconnect_backoff = 0; +static uint32_t _iot_connect_start = 0; // TCP connect 开始时刻 + +/*=========================================================================== + * Buffers + *===========================================================================*/ +static uint8_t _iot_recv_buf[IOT_MQTT_RECV_BUF_LEN]; +static uint16_t _iot_recv_len = 0; +static uint8_t _iot_send_buf[IOT_MQTT_SEND_BUF_LEN]; +static uint8_t _iot_wchnet_buf[RECE_BUF_LEN]; // WCHNET internal recv buffer + +/*=========================================================================== + * Topic 构建 + *===========================================================================*/ +static char g_iot_dev_serial[13]; // 设备序列码 (12 hex chars) + +/* 构建 topic: dld960/{sn}/... */ +static int iot_make_topic(char *out, uint16_t out_len, const char *direction, + const char *category, const char *sub) { + if (sub) { + return snprintf(out, out_len, "dld960/%s/%s/%s/%s", + g_iot_dev_serial, direction, category, sub); + } + return snprintf(out, out_len, "dld960/%s/%s/%s", + g_iot_dev_serial, direction, category); +} + +/*=========================================================================== + * MQTT Packet Helpers + *===========================================================================*/ + +/* 发送 MQTT 报文到 broker */ +static int iot_mqtt_send(const uint8_t *buf, uint16_t len) { + uint16_t slen = len; + uint8_t ret = WCHNET_SocketSend(g_iot_socket, (uint8_t *)buf, &slen); + return (ret == WCHNET_ERR_SUCCESS) ? 0 : -1; +} + +/* 发送 MQTT CONNECT */ +static int iot_mqtt_send_connect(void) { + MQTTPacket_connectData opts = MQTTPacket_connectData_initializer; + uint8_t buf[256]; + int len; + + opts.MQTTVersion = 4; // MQTT 3.1.1 + opts.keepAliveInterval = IOT_MQTT_KEEPALIVE_SEC; + opts.cleansession = 1; + + // clientID + if (strlen((char *)iot_net_info.client_id) > 0) { + opts.clientID.cstring = (char *)iot_net_info.client_id; + } else { + opts.clientID.cstring = g_iot_dev_serial; + } + + // username / password + if (strlen((char *)iot_net_info.username) > 0) { + opts.username.cstring = (char *)iot_net_info.username; + } + if (strlen((char *)iot_net_info.password) > 0) { + opts.password.cstring = (char *)iot_net_info.password; + } + + len = MQTTSerialize_connect(buf, sizeof(buf), &opts); + if (len <= 0) { + PRINT("IOT: MQTTSerialize_connect failed\n"); + return -1; + } + PRINT("IOT: → CONNECT clientId=%s keepAlive=%d\n", + opts.clientID.cstring, opts.keepAliveInterval); + return iot_mqtt_send(buf, (uint16_t)len); +} + +/* 发送 MQTT SUBSCRIBE */ +static int iot_mqtt_send_subscribe(void) { + uint8_t buf[256]; + int len; + char topic[IOT_MQTT_TOPIC_MAX_LEN]; + MQTTString topics[3]; + int qos[3] = {1, 1, 1}; + int count = 0; + + // 订阅服务器下发消息 + iot_make_topic(topic, sizeof(topic), "srv", "config", "set"); + topics[count].cstring = topic; topics[count].lenstring.len = 0; count++; + + iot_make_topic(topic, sizeof(topic), "srv", "config", "query"); + topics[count].cstring = topic; topics[count].lenstring.len = 0; count++; + + iot_make_topic(topic, sizeof(topic), "srv", "ctrl", NULL); + topics[count].cstring = topic; topics[count].lenstring.len = 0; count++; + + len = MQTTSerialize_subscribe(buf, sizeof(buf), 0, + ++g_iot_msg_id, count, topics, qos); + if (len <= 0) { + PRINT("IOT: MQTTSerialize_subscribe failed\n"); + return -1; + } + PRINT("IOT: → SUBSCRIBE pktId=%d topics=%d\n", g_iot_msg_id, count); + return iot_mqtt_send(buf, (uint16_t)len); +} + +/* 发送 MQTT PUBLISH */ +static int iot_mqtt_publish(const char *topic, const char *payload, + uint16_t payload_len, uint8_t qos) { + uint8_t buf[IOT_MQTT_SEND_BUF_LEN]; + int len; + MQTTString mqtt_topic = MQTTString_initializer; + mqtt_topic.cstring = (char *)topic; + + len = MQTTSerialize_publish(buf, sizeof(buf), 0, qos, 0, + ++g_iot_msg_id, mqtt_topic, + (unsigned char *)payload, payload_len); + if (len <= 0) { + PRINT("IOT: MQTTSerialize_publish failed\n"); + return -1; + } + return iot_mqtt_send(buf, (uint16_t)len); +} + +/*=========================================================================== + * MQTT 接收处理 + *===========================================================================*/ + +/* 处理一条收到的 MQTT PUBLISH 消息 */ +static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_len) { + char json[IOT_MQTT_RECV_BUF_LEN]; + int copy_len = payload_len < (int)sizeof(json) - 1 ? payload_len : (int)sizeof(json) - 1; + memcpy(json, payload, copy_len); + json[copy_len] = '\0'; + + PRINT("IOT: PUBLISH topic=%s payload=%s\n", topic, json); + + /* 提取 msg_id 和 cmd */ + char tmp[256]; + uint32_t msg_id = 0; + + // 简单提取: 解析 JSON 中的 msg_id 和 cmd + memset(tmp, 0, sizeof(tmp)); + simple_parse_json(json, "\"msg_id\"", tmp); + if (strlen(tmp) > 0) msg_id = (uint32_t)strtoul(tmp, NULL, 10); + + memset(tmp, 0, sizeof(tmp)); + simple_parse_json(json, "\"cmd\"", tmp); + // strip quotes + char *cmd_str = tmp; + if (cmd_str[0] == '"') cmd_str++; + int cmd_len = strlen(cmd_str); + if (cmd_len > 0 && cmd_str[cmd_len - 1] == '"') cmd_str[cmd_len - 1] = '\0'; + + if (strlen(cmd_str) == 0) { + PRINT("IOT: no cmd in PUBLISH\n"); + return; + } + + /* 构建响应 topic */ + char resp_topic[IOT_MQTT_TOPIC_MAX_LEN]; + iot_make_topic(resp_topic, sizeof(resp_topic), "dev", "config", "resp"); + + /* 处理命令 — 复用 TCP JSON 的命令逻辑 */ + /* 目前仅实现基本响应框架, 后续逐步对接 Loop MCU 命令 */ + + if (strcmp(cmd_str, "dev_info_query") == 0) { + char data_json[512]; + snprintf(data_json, sizeof(data_json), + "{\"msg_id\":%lu,\"cmd\":\"dev_info_query\"," + "\"ts\":%lu,\"code\":0,\"msg\":\"success\"," + "\"data\":{" + "\"dev_serial\":\"%s\",\"hard_ver\":\"%s\",\"soft_ver\":\"%s\"," + "\"model\":\"%s\",\"product_code\":\"960001\"," + "\"sub_code\":{\"net\":%s,\"iot\":%s}," + "\"bus\":{\"bus1\":0,\"bus2\":0,\"bus3\":0,\"bus4\":0}}}", + msg_id, mstick() / 1000, + g_dev_number_str, HARDWARE_VER, FIRMWARE_VER, PRODUCT_MODEL, + g_sub_code_enable.net_enable ? "true" : "false", + g_sub_code_enable.iot_enable ? "true" : "false"); + iot_mqtt_publish(resp_topic, data_json, strlen(data_json), 1); + + } else if (strcmp(cmd_str, "pwd_verify") == 0) { + // 验证密码 + char password[16] = {0}; + char *data = (char *)malloc(512); + if (data) { + memset(data, 0, 512); + simple_parse_json(json, "\"data\"", data); + if (strlen(data) > 0) { + memset(tmp, 0, sizeof(tmp)); + simple_parse_json(data, "\"password\"", tmp); + // strip quotes + char *pwd = tmp; + if (pwd[0] == '"') pwd++; + int plen = strlen(pwd); + if (plen > 0 && pwd[plen - 1] == '"') pwd[plen - 1] = '\0'; + strncpy(password, pwd, 15); + } + free(data); + } + + if (strlen(password) == 6 && memcmp(password, g_dev_password, 6) == 0) { + char resp[256]; + snprintf(resp, sizeof(resp), + "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," + "\"ts\":%lu,\"code\":0,\"msg\":\"success\"}", + msg_id, mstick() / 1000); + iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + PRINT("IOT: Auth success via MQTT\n"); + } else { + char resp[256]; + snprintf(resp, sizeof(resp), + "{\"msg_id\":%lu,\"cmd\":\"pwd_verify\"," + "\"ts\":%lu,\"code\":2,\"msg\":\"password incorrect\"}", + msg_id, mstick() / 1000); + iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + } + + } else { + // 暂不支持的命令, 返回错误 + char resp[256]; + snprintf(resp, sizeof(resp), + "{\"msg_id\":%lu,\"cmd\":\"%s\"," + "\"ts\":%lu,\"code\":4,\"msg\":\"unsupported command\"}", + msg_id, cmd_str, mstick() / 1000); + iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + } +} + +/* 处理 MQTT SUBACK */ +static void iot_handle_suback(void) { + PRINT("IOT: ← SUBACK, topics subscribed\n"); + g_iot_state = IOT_STATE_READY; + _iot_reconnect_backoff = 0; // 连接成功, 重置退避 +} + +/* 处理收到的 MQTT 数据 */ +static void iot_process_recv(void) { + uint8_t header; + int qos, payload_len; + unsigned char retained; + unsigned short packet_id; + MQTTString topic; + unsigned char *payload_ptr; + + while (_iot_recv_len >= 2) { + header = _iot_recv_buf[0]; + int msg_type = (header >> 4) & 0x0F; + + /* 先尝试解析整个 MQTT 包 */ + int mqtt_pkt_len = 0; + int multiplier = 1; + int rem_len = 0; + int i = 1; + while (i < (int)_iot_recv_len && i < 5) { + rem_len += (_iot_recv_buf[i] & 0x7F) * multiplier; + multiplier *= 128; + if ((_iot_recv_buf[i] & 0x80) == 0) { + mqtt_pkt_len = 1 + (i - 1 + 1) + rem_len; // header + rem_len_bytes + payload + break; + } + i++; + } + if (mqtt_pkt_len == 0 || mqtt_pkt_len > (int)_iot_recv_len) { + return; // 包不完整, 等更多数据 + } + + PRINT("IOT: ← MQTT packet type=%d len=%d\n", msg_type, mqtt_pkt_len); + + switch (msg_type) { + case CONNACK: { + unsigned char session_present, connack_rc; + if (MQTTDeserialize_connack(&session_present, &connack_rc, + _iot_recv_buf, mqtt_pkt_len) == 1) { + PRINT("IOT: ← CONNACK rc=%d\n", connack_rc); + if (connack_rc == 0) { + // 连接成功 → 订阅 + g_iot_state = IOT_STATE_MQTT_CONNECTED; + iot_mqtt_send_subscribe(); + } else { + PRINT("IOT: CONNACK rejected, rc=%d\n", connack_rc); + g_iot_state = IOT_STATE_DISCONNECTED; + } + } + break; + } + + case SUBACK: + iot_handle_suback(); + break; + + case PUBLISH: { + unsigned char pub_dup; + if (MQTTDeserialize_publish(&pub_dup, &qos, &retained, + &packet_id, &topic, + &payload_ptr, &payload_len, + _iot_recv_buf, mqtt_pkt_len) == 1) { + char topic_str[IOT_MQTT_TOPIC_MAX_LEN]; + memcpy(topic_str, topic.cstring ? topic.cstring : "", + topic.lenstring.len < (int)sizeof(topic_str) - 1 + ? topic.lenstring.len : (int)sizeof(topic_str) - 1); + topic_str[topic.lenstring.len] = '\0'; + + iot_handle_publish(topic_str, payload_ptr, payload_len); + + // 如果 QoS > 0, 发送 PUBACK + if (qos > 0) { + uint8_t ack_buf[4]; + int ack_len = MQTTSerialize_ack(ack_buf, sizeof(ack_buf), + PUBACK, 0, packet_id); + iot_mqtt_send(ack_buf, (uint16_t)ack_len); + } + } + break; + } + + case PINGRESP: + PRINT("IOT: ← PINGRESP\n"); + break; + + default: + break; + } + + /* 移除已处理的包 */ + memmove(_iot_recv_buf, _iot_recv_buf + mqtt_pkt_len, + _iot_recv_len - mqtt_pkt_len); + _iot_recv_len -= mqtt_pkt_len; + } +} + +/*=========================================================================== + * 传感器数据上报 + *===========================================================================*/ +void iot_mqtt_publish_sensor(void) { + if (g_iot_state != IOT_STATE_READY) return; + if (!g_report_active) return; + + /* 复用 g_pkg_uart_2 中的传感器帧 */ + if (g_pkg_uart_2.flag == 0) return; + if (g_pkg_uart_2.pkg[0] != 0x7F) return; + if (g_pkg_uart_2.pkg[3] != 0xC0) return; + + LUP_SensorReport sr; + memset(&sr, 0, sizeof(sr)); + int ret = lup_parse_sensor_report(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &sr); + if (ret != 0) { + PRINT("IOT: sensor parse failed (%d)\n", ret); + InitPkgUart(&g_pkg_uart_2); + return; + } + + /* 构建 loop_data JSON */ + char data_json[2048]; + char *p = data_json; + int remaining = sizeof(data_json); + int written; + uint8_t i; + + written = snprintf(p, remaining, + "{\"channels\":["); + if (written < 0 || written >= remaining) goto done; + p += written; remaining -= written; + + for (i = 0; i < sr.coil_count; i++) { + const LUP_CoilSensor *cs = &sr.coils[i]; + const char *misc_type_str = "time"; + uint32_t misc_val = 0; + + if (cs->misc_type == 0) { misc_type_str = "time"; misc_val = cs->misc.passtime_ms; } + else if (cs->misc_type == 1) { misc_type_str = "cut_count"; misc_val = cs->misc.cut_amount; } + else if (cs->misc_type == 2) { misc_type_str = "flow_count"; misc_val = cs->misc.flow_amount; } + else if (cs->misc_type == 3) { misc_type_str = "relay_count"; misc_val = cs->misc.relay_count; } + + const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"}; + + written = snprintf(p, remaining, + "%s{\"ch\":%d,\"freq_level\":\"%s\",\"has_car\":%s," + "\"loop_ok\":%s,\"freq_current\":%lu,\"freq_diff\":%d," + "\"sensitivity\":%d,\"condition\":%d," + "\"misc\":{\"type\":\"%s\",\"value\":%lu}}", + (i > 0) ? "," : "", + i + 1, + freq_level_names[cs->freq_level], + cs->car_state ? "true" : "false", + cs->loop_state ? "false" : "true", + cs->freq, cs->variation, + cs->sensitivity, cs->condition, + misc_type_str, misc_val); + if (written < 0 || written >= remaining) goto done; + p += written; remaining -= written; + } + + snprintf(p, remaining, "]}"); + + /* 包装为通用消息格式 */ + char payload[IOT_MQTT_SEND_BUF_LEN]; + snprintf(payload, sizeof(payload), + "{\"msg_id\":%d,\"cmd\":\"loop_data\",\"ts\":%lu,\"data\":%s}", + ++g_iot_msg_id, mstick() / 1000, data_json); + + char topic[IOT_MQTT_TOPIC_MAX_LEN]; + iot_make_topic(topic, sizeof(topic), "dev", "data", "loop"); + iot_mqtt_publish(topic, payload, strlen(payload), 0); + +done: + InitPkgUart(&g_pkg_uart_2); +} + +/*=========================================================================== + * 心跳上报 + *===========================================================================*/ +static void iot_send_heartbeat(void) { + if (g_iot_state != IOT_STATE_READY) return; + + char payload[512]; + uint8_t loop_ok[4] = {1, 1, 1, 1}; + uint8_t i; + for (i = 0; i < 4; i++) { + /* 简化: 读取线圈状态 */ + loop_ok[i] = 1; // TODO: 从 Loop MCU 获取实际状态 + } + + snprintf(payload, sizeof(payload), + "{\"msg_id\":%d,\"cmd\":\"heartbeat\"," + "\"ts\":%lu,\"data\":{" + "\"uptime\":%lu,\"loop_status\":[%s,%s,%s,%s]," + "\"net_status\":true,\"iot_status\":true}}", + ++g_iot_msg_id, mstick() / 1000, + mstick() / 1000, + loop_ok[0] ? "true" : "false", loop_ok[1] ? "true" : "false", + loop_ok[2] ? "true" : "false", loop_ok[3] ? "true" : "false"); + + char topic[IOT_MQTT_TOPIC_MAX_LEN]; + iot_make_topic(topic, sizeof(topic), "dev", "status", NULL); + iot_mqtt_publish(topic, payload, strlen(payload), 0); +} + +/*=========================================================================== + * 连接管理 + *===========================================================================*/ + +/* 连接到 MQTT Broker */ +static void iot_connect_broker(void) { + uint8_t broker_ip[4]; + SOCK_INF sock_inf; + uint8_t ret; + + // 解析 broker 地址 + if (get_ipstr_to_array((char *)iot_net_info.remote_addr, broker_ip) == 0) { + // 是 IP 地址 + memcpy(broker_ip, iot_net_info.remote_addr, 4); + PRINT("IOT: broker IP %d.%d.%d.%d:%d\n", + broker_ip[0], broker_ip[1], broker_ip[2], broker_ip[3], + iot_net_info.mqtt_port); + } else { + PRINT("IOT: broker hostname=%s (DNS not implemented yet)\n", + iot_net_info.remote_addr); + return; + } + + memset(&sock_inf, 0, sizeof(SOCK_INF)); + sock_inf.DesPort = iot_net_info.mqtt_port; + sock_inf.ProtoType = PROTO_TYPE_TCP; + sock_inf.RecvBufLen = RECE_BUF_LEN; + memcpy(sock_inf.IPAddr, broker_ip, 4); + + ret = WCHNET_SocketCreat(&g_iot_socket, &sock_inf); + if (ret != WCHNET_ERR_SUCCESS) { + PRINT("IOT: SocketCreat failed: 0x%02X\n", ret); + g_iot_socket = 0xFF; + return; + } + + ret = WCHNET_SocketConnect(g_iot_socket); + if (ret != WCHNET_ERR_SUCCESS) { + PRINT("IOT: SocketConnect failed: 0x%02X\n", ret); + WCHNET_SocketClose(g_iot_socket, TCP_CLOSE_NORMAL); + g_iot_socket = 0xFF; + return; + } + + g_iot_state = IOT_STATE_TCP_CONNECTING; + _iot_connect_start = mstick(); + PRINT("IOT: TCP connecting to broker (sock=%d)...\n", g_iot_socket); +} + +/*=========================================================================== + * Socket 中断处理 + *===========================================================================*/ +void iot_mqtt_handle_sock_int(uint8_t socketid, uint8_t intstat) { + if (socketid != g_iot_socket) return; + + /* CONNECT 成功 */ + if (intstat & SINT_STAT_CONNECT) { + PRINT("IOT: TCP connected (sock=%d)\n", socketid); + WCHNET_ModifyRecvBuf(socketid, (uint32_t)_iot_wchnet_buf, RECE_BUF_LEN); + g_iot_state = IOT_STATE_TCP_CONNECTED; + _iot_recv_len = 0; + // 发送 MQTT CONNECT + iot_mqtt_send_connect(); + g_iot_state = IOT_STATE_MQTT_CONNECTING; + } + + /* 收到数据 */ + if (intstat & SINT_STAT_RECV) { + uint32_t recv_len = WCHNET_SocketRecvLen(socketid, NULL); + if (recv_len > 0) { + uint16_t space = IOT_MQTT_RECV_BUF_LEN - _iot_recv_len; + if (recv_len > space) recv_len = space; + uint32_t rd_len = recv_len; + uint8_t tmp[RECE_BUF_LEN]; + WCHNET_SocketRecv(socketid, tmp, &rd_len); + memcpy(_iot_recv_buf + _iot_recv_len, tmp, (uint16_t)rd_len); + _iot_recv_len += (uint16_t)rd_len; + iot_process_recv(); + } + } + + /* 断开 / 超时 */ + if (intstat & (SINT_STAT_DISCONNECT | SINT_STAT_TIM_OUT)) { + PRINT("IOT: socket disconnect/timeout (sock=%d)\n", socketid); + g_iot_state = IOT_STATE_DISCONNECTED; + g_iot_socket = 0xFF; + _iot_recv_len = 0; + _iot_reconnect_backoff = IOT_MQTT_RECONNECT_MIN_MS; + _iot_reconnect_deadline = mstick() + _iot_reconnect_backoff; + } +} + +/*=========================================================================== + * 主循环轮询 + *===========================================================================*/ +void iot_mqtt_poll(void) { + /* 断线重连 */ + if (g_iot_state == IOT_STATE_DISCONNECTED && g_iot_socket == 0xFF) { + if (_iot_reconnect_deadline == 0 || mstick() > _iot_reconnect_deadline) { + iot_connect_broker(); + if (g_iot_socket == 0xFF) { + // 连接失败, 退避 + if (_iot_reconnect_backoff == 0) { + _iot_reconnect_backoff = IOT_MQTT_RECONNECT_MIN_MS; + } else { + _iot_reconnect_backoff *= 2; + if (_iot_reconnect_backoff > IOT_MQTT_RECONNECT_MAX_MS) + _iot_reconnect_backoff = IOT_MQTT_RECONNECT_MAX_MS; + } + _iot_reconnect_deadline = mstick() + _iot_reconnect_backoff; + PRINT("IOT: reconnect in %lu ms\n", _iot_reconnect_backoff); + } + } + return; + } + + /* TCP 连接超时 (10s) */ + if (g_iot_state == IOT_STATE_TCP_CONNECTING) { + if (mstick() - _iot_connect_start > 10000) { + PRINT("IOT: TCP connect timeout\n"); + WCHNET_SocketClose(g_iot_socket, TCP_CLOSE_NORMAL); + g_iot_socket = 0xFF; + g_iot_state = IOT_STATE_DISCONNECTED; + return; + } + } + + /* MQTT Keepalive — PINGREQ */ + if (g_iot_state == IOT_STATE_READY) { + uint32_t now = mstick(); + if (now - _iot_last_heartbeat > IOT_MQTT_HEARTBEAT_MS) { + uint8_t ping_buf[2]; + int ping_len = MQTTSerialize_pingreq(ping_buf, sizeof(ping_buf)); + iot_mqtt_send(ping_buf, (uint16_t)ping_len); + _iot_last_heartbeat = now; + // 心跳上报 + iot_send_heartbeat(); + } + } +} + +/*=========================================================================== + * 初始化 + *===========================================================================*/ +void iot_mqtt_init(void) { + /* 初始化设备序列码 (12 hex chars) */ + snprintf(g_iot_dev_serial, sizeof(g_iot_dev_serial), + "%02X%02X%02X%02X%02X%02X", + g_dev_number[0], g_dev_number[1], g_dev_number[2], + g_dev_number[3], g_dev_number[4], g_dev_number[5]); + + PRINT("IOT: dev_serial=%s\n", g_iot_dev_serial); + + /* 初始化传感器回调 — 用于 MQTT 上报 */ + lup_set_sensor_callback(NULL); // MQTT 模式不需要 JSON sensor_cb + + _iot_reconnect_backoff = 0; + _iot_reconnect_deadline = mstick() + 2000; // 启动后 2s 开始首次连接 + g_iot_state = IOT_STATE_DISCONNECTED; +} diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c index 6ff19f3..cab7c90 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c @@ -1,709 +1,709 @@ -/** - ****************************************************************************** - * @file loop_uart_proto.c - * @author wangfq - * @version V1.0 - * @date 2026-07-02 - * @brief DLD960Loop 串口通信协议实现 - * - 校验计算 (XOR + SUM) - * - 命令组包 / 响应解析 - * - 帧接收状态机 (替代 timeout heuristic) - * - 命令发送 + 超时跟踪 - ****************************************************************************** - */ -#include "CONFIG.h" -#include "loop_uart_proto.h" -#include "cmcng.h" -#include - -/*=========================================================================== - * Global State - *===========================================================================*/ -LUP_CmdTracker g_lup_cmd = {0, 0, 0, LUP_STATE_IDLE, {0}, 0}; - -/* --- Frame Parser --- */ -LUP_FrameParser g_lup_parser = { - .state = LUP_FRAME_STATE_IDLE, - .idx = 0, - .value_len = 0, - .value_idx = 0 -}; - -/*=========================================================================== - * Checksum - *===========================================================================*/ - -/* - * XOR 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束 - */ -uint8_t lup_calc_xor(const uint8_t *data, uint16_t len) -{ - uint8_t x = 0; - uint16_t i; - for (i = 0; i < len; i++) { - x ^= data[i]; - } - return x; -} - -/* - * SUM 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束 - */ -uint8_t lup_calc_sum(const uint8_t *data, uint16_t len) -{ - uint8_t s = 0; - uint16_t i; - for (i = 0; i < len; i++) { - s += data[i]; - } - return s; -} - -/* - * 校验整包: pkg = [7F] [Addr] [LEN] [CMD] [Data...] [XOR] [SUM] - * Checksum covers: Addr + LEN_field + Value (=CMD + Data) = 2 + LEN bytes - * starting at pkg[1] - */ -int lup_verify_checksum(const uint8_t *pkg, uint16_t len) -{ - if (len < 6) return -1; // 最小: 7F + 3 header + 2 checksum = 6 - - uint8_t pkg_len = pkg[2]; // LEN field - uint16_t check_len = 2 + pkg_len; // Addr(1)+LEN_field(1)+Value(LEN) - - if (len < check_len + 3) return -4; // Not enough data: Magic(1) + covered + Check(2) - - uint8_t expect_xor = lup_calc_xor(pkg + 1, check_len); - uint8_t expect_sum = lup_calc_sum(pkg + 1, check_len); - - uint8_t got_xor = pkg[1 + check_len]; - uint8_t got_sum = pkg[2 + check_len]; - - if (expect_xor != got_xor) return -2; - if (expect_sum != got_sum) return -3; - return 0; -} - -/* - * 为已填充好 Data 的 buf 追加 checksum。 - * buf 格式: [7F] [Addr] [LEN] [CMD] [Data...] - * Checksum 覆盖: Addr(1) + LEN(1) + Value(LEN) = 2 + LEN bytes - * Output: [7F][Addr][LEN][CMD][Data...][XOR][SUM] - */ -void lup_append_checksum(uint8_t *pkg) -{ - // Frame: [Magic][Addr][LEN][Value(LEN bytes)][XOR][SUM] - // Total = 1 + 1 + 1 + LEN + 2 = 5 + LEN - // (Addr + LEN_field are NOT inside Value; CMD is the 1st byte of Value) - uint8_t total_len = 5 + pkg[2]; // Magic(1)+Addr(1)+LEN(1)+Value(LEN)+Check(2) - uint16_t check_len = 2 + pkg[2]; // Addr(1)+LEN_field(1)+Value(LEN) - - pkg[total_len - 2] = lup_calc_xor(pkg + 1, check_len); - pkg[total_len - 1] = lup_calc_sum(pkg + 1, check_len); -} - -/*=========================================================================== - * Packet Builders - *===========================================================================*/ - -/* - * 0x4A — 获取设备版本号 - * Req: 7F 00 01 4A 4B 4B - */ -uint16_t lup_build_get_version(uint8_t *buf) -{ - buf[0] = LUP_MAGIC; // 7F - buf[1] = LUP_ADDR_DEFAULT; // 0x00 - buf[2] = 0x01; // LEN = 1 (CMD byte) - buf[3] = LUP_CMD_GET_VERSION; // 0x4A - lup_append_checksum(buf); - return 6; // Magic(1) + Header(3) + Check(2) -} - -/* - * 0x6D — 设备复位 (无回复) - * Req: 7F 00 01 6D 6C 6E - */ -uint16_t lup_build_reset(uint8_t *buf) -{ - buf[0] = LUP_MAGIC; - buf[1] = LUP_ADDR_DEFAULT; - buf[2] = 0x01; - buf[3] = LUP_CMD_RESET; - lup_append_checksum(buf); - return 6; -} - -/* - * 0x92 — 出厂初始化 - * Req: 7F 00 01 92 93 93 - */ -uint16_t lup_build_factory_init(uint8_t *buf) -{ - buf[0] = LUP_MAGIC; - buf[1] = LUP_ADDR_DEFAULT; - buf[2] = 0x01; - buf[3] = LUP_CMD_FACTORY_INIT; - lup_append_checksum(buf); - return 6; -} - -/* - * 0x8A — 读灵敏度列表 - * Req: 7F 00 03 8A 00 00 xx xx - * LEN=3: CMD(1) + R/W(1) + Amount(1) - */ -uint16_t lup_build_sensitivity_read(uint8_t *buf) -{ - buf[0] = LUP_MAGIC; - buf[1] = LUP_ADDR_DEFAULT; - buf[2] = 0x03; // LEN = 3 (CMD + R/W + Amount) - buf[3] = LUP_CMD_SENSITIVITY; // 0x8A - buf[4] = LUP_SENS_RW_READ; // 0x00 - buf[5] = 0x00; // Amount (ignored for read) - lup_append_checksum(buf); - return 5 + buf[2]; // = 8 -} - -/* - * 0x8A — 写灵敏度列表 - */ -uint16_t lup_build_sensitivity_write(uint8_t *buf, const LUP_Sensitivity *sens) -{ - uint8_t i; - uint16_t idx = 0; - - buf[idx++] = LUP_MAGIC; - buf[idx++] = LUP_ADDR_DEFAULT; - // LEN = CMD(1) + R/W(1) + Amount(1) + Amount*(SensIn(2)+SensOut(2)) - // = 3 + sens->amount * 4 - uint8_t val_len = 3 + sens->amount * 4; - buf[idx++] = val_len; - buf[idx++] = LUP_CMD_SENSITIVITY; - - buf[idx++] = sens->rw; // 0x01 (Write) - buf[idx++] = sens->amount; - - for (i = 0; i < sens->amount; i++) { - buf[idx++] = (uint8_t)(sens->sens_in[i] & 0xFF); - buf[idx++] = (uint8_t)((sens->sens_in[i] >> 8) & 0xFF); - buf[idx++] = (uint8_t)(sens->sens_out[i] & 0xFF); - buf[idx++] = (uint8_t)((sens->sens_out[i] >> 8) & 0xFF); - } - lup_append_checksum(buf); - return 5 + val_len; // Magic(1)+Addr(1)+LEN(1)+Value(val_len)+Check(2) -} - -/* - * 0x63 — 设置多路参数 - * Value: AutoMode(1) + Amount(1) + Amount * Param[5] - * LEN = CMD(1) + AutoMode(1) + Amount(1) + Amount*5 = 3 + 5*Amount - */ -uint16_t lup_build_set_param(uint8_t *buf, const LUP_ParamSet *ps) -{ - uint8_t i; - uint16_t idx = 0; - - buf[idx++] = LUP_MAGIC; - buf[idx++] = LUP_ADDR_DEFAULT; - uint8_t val_len = 3 + 5 * ps->amount; // CMD + AutoMode + Amount + Amount*Param - buf[idx++] = val_len; - buf[idx++] = LUP_CMD_SET_PARAM; - - buf[idx++] = ps->auto_mode; - buf[idx++] = ps->amount; - - for (i = 0; i < ps->amount; i++) { - buf[idx++] = ps->params[i].sensitivity; - buf[idx++] = ps->params[i].loop_delay; - buf[idx++] = ps->params[i].output_mode; - buf[idx++] = ps->params[i].exist_mode; - buf[idx++] = ps->params[i].direction_mode; - } - lup_append_checksum(buf); - return 5 + val_len; // Magic(1)+Addr(1)+LEN(1)+Value(val_len)+Check(2) -} - -/* - * 0x64 — 读取多路参数 - * Req: 7F 00 01 64 ... - */ -uint16_t lup_build_get_param(uint8_t *buf) -{ - buf[0] = LUP_MAGIC; - buf[1] = LUP_ADDR_DEFAULT; - buf[2] = 0x01; // LEN = 1 - buf[3] = LUP_CMD_GET_PARAM; // 0x64 - lup_append_checksum(buf); - return 6; -} - -/* - * 0xC0 mid-packet ACK - * Req: 7F | 00 | 07 | C0 | SensType | Seq | SubAmount | XOR | SUM - */ -uint16_t lup_build_sensor_ack(uint8_t *buf, uint8_t sens_type, - uint8_t seq, uint8_t sub_amount) -{ - buf[0] = LUP_MAGIC; - buf[1] = LUP_ADDR_DEFAULT; - buf[2] = 0x07; // LEN = 7 (CMD + SensType + Seq + SubAmount + 3 padding) - buf[3] = LUP_CMD_SENSOR_REPORT; - buf[4] = sens_type; - buf[5] = seq; - buf[6] = sub_amount; - buf[7] = 0x00; // padding - buf[8] = 0x00; - buf[9] = 0x00; - lup_append_checksum(buf); - return 5 + buf[2]; // = 12: Magic+Addr+LEN+Value(7)+Check(2) -} - -/*=========================================================================== - * Packet Parsers - *===========================================================================*/ - -/* - * Parse 0x4A Response: - * Header: 00 08 4A (Addr, LEN=8, CMD) - * Value: Status + Hard_Main + Hard_Sub + Hard_SSub + Soft_Main + Soft_Sub + Soft_SSub - */ -int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info) -{ - if (len < 10) return -1; // 1+3+7+2 = 13 min - - // pkg layout: [7F][Addr=0][LEN=8][CMD=0x4A][Status...7bytes][XOR][SUM] - if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_VERSION) return -2; - if (pkg[2] < 7) return -3; // LEN should be >= 7 - - const uint8_t *val = pkg + 4; // skip magic + header(3) - info->status = val[0]; - info->hard_main = val[1]; - info->hard_sub = val[2]; - info->hard_ssub = val[3]; - info->soft_main = val[4]; - info->soft_sub = val[5]; - info->soft_ssub = val[6]; - - return 0; -} - -/* - * Parse 0x92 Response: - * Value: 00/01 (00=success) - */ -int lup_parse_factory_init_resp(const uint8_t *pkg, uint16_t len, uint8_t *success) -{ - if (len < 7) return -1; - if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_FACTORY_INIT) return -2; - *success = (pkg[4] == 0x00) ? 1 : 0; - return 0; -} - -/* - * Parse 0x8A Response: - * Value: Ret(1) + Amount(1) + Amount*(SensIn(2) + SensOut(2)) - * Ret = 0x10 (Read return) | 0x11 (Write return) - * - * eg: 7F 80 13 8A 10 04 A2 00 51 00 51 00 36 00 ... - * Ret=0x10=Read, Amount=4 - * Lv0: SensIn=0x00A2, SensOut=0x0051 - */ -int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens) -{ - uint8_t i; - if (len < 8) return -1; // min: 7F+Addr+LEN+CMD+Ret+Amount+XOR+SUM = 8 - if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSITIVITY) return -2; - - const uint8_t *val = pkg + 4; - sens->rw = val[0]; // 0x10 or 0x11 - sens->amount = val[1]; - - if (sens->amount > LUP_COIL_COUNT) sens->amount = LUP_COIL_COUNT; - - for (i = 0; i < sens->amount; i++) { - sens->sens_in[i] = val[2 + i * 4] | ((uint16_t)val[3 + i * 4] << 8); - sens->sens_out[i] = val[4 + i * 4] | ((uint16_t)val[5 + i * 4] << 8); - } - return 0; -} - -/* - * Parse 0x64 Response: - * Value: AutoMode(1) + Amount(1) + Amount * [Sensitivity, Loop_Delay, Output_Mode, - * Exist_Mode, Direction_Mode, freq1(2), freq2(2), freq3(2)] - * Each coil = 8 bytes - */ -int lup_parse_param_get(const uint8_t *pkg, uint16_t len, LUP_ParamGet *pg) -{ - uint8_t i; - if (len < 6) return -1; - if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_PARAM) return -2; - - const uint8_t *val = pkg + 4; - pg->auto_mode = val[0]; - pg->amount = val[1]; - - if (pg->amount > LUP_COIL_COUNT) pg->amount = LUP_COIL_COUNT; - - for (i = 0; i < pg->amount; i++) { - const uint8_t *c = val + 2 + i * 8; - pg->params[i].sensitivity = c[0]; - pg->params[i].loop_delay = c[1]; - pg->params[i].output_mode = c[2]; - pg->params[i].exist_mode = c[3]; - pg->params[i].direction_mode = c[4]; - pg->freq[i][0] = c[5] | ((uint32_t)c[6] << 8); - pg->freq[i][1] = c[7] | ((uint32_t)c[8] << 8); - pg->freq[i][2] = c[9] | ((uint32_t)c[10] << 8); - } - - return 0; -} - -/* - * Parse 0x63 Response (Set Param): - * Value: Status(1 byte, 0x00=success) - */ -int lup_parse_set_param_resp(const uint8_t *pkg, uint16_t len, uint8_t *success) -{ - if (len < 7) return -1; - if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SET_PARAM) return -2; - *success = (pkg[4] == 0x00) ? 1 : 0; - return 0; -} - -/* - * Parse 0xC0/0x0C — 多路线圈传感信息 - * - * 每路线圈数据单元 = 11 bytes: - * [0] 配置1: freq_level(2) | Direction(1) | freq_type(1) | sens(4) - * [1] 评估条件: condition(4) | loop_state(1) | car_state(1) | misc_type(2) - * [2-4] 频率 (3 bytes, LE) - * [5-6] 变化量 (2 bytes, LE) - * [7-10] 杂项 (4 bytes) - */ -int lup_parse_sensor_report(const uint8_t *pkg, uint16_t len, LUP_SensorReport *report) -{ - uint8_t i; - if (len < 9) return -1; // 7F + 3 header + 2 sensType/SubPkgFlag + 2 checksum min - if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSOR_REPORT) return -2; - - uint8_t sens_type = pkg[4]; - if (sens_type != LUP_SENS_TYPE_MULTI_COIL) return -3; - - uint8_t sub_pkg = pkg[5]; - report->sens_type = sens_type; - report->sub_amount = (sub_pkg >> 4) & 0x0F; - report->sub_sequence = sub_pkg & 0x0F; - - // 计算线圈数: Value = sensType(1) + SubPkgFlag(1) + coil_count * 11 - uint16_t val_len = pkg[2]; // LEN field - uint16_t data_offset = 6; // 跳过 7F + Addr + LEN + CMD + SensType + SubPkgFlag - uint16_t data_len = val_len - 2; // 减去 SensType + SubPkgFlag - - uint8_t coil_count = data_len / 11; - if (coil_count > LUP_COIL_COUNT) coil_count = LUP_COIL_COUNT; - report->coil_count = coil_count; - - for (i = 0; i < coil_count; i++) { - const uint8_t *c = pkg + data_offset + i * 11; - - LUP_CoilSensor *cs = &report->coils[i]; - cs->freq_level = (c[0] >> 6) & 0x03; - cs->direction = (c[0] >> 5) & 0x01; - cs->freq_type = (c[0] >> 4) & 0x01; - cs->sensitivity = c[0] & 0x0F; - - cs->condition = (c[1] >> 4) & 0x0F; - cs->loop_state = (c[1] >> 3) & 0x01; - cs->car_state = (c[1] >> 2) & 0x01; - cs->misc_type = c[1] & 0x03; - - cs->freq = c[2] | ((uint32_t)c[3] << 8) | ((uint32_t)c[4] << 16); - cs->variation = c[5] | ((uint16_t)c[6] << 8); - cs->misc.passtime_ms = c[7] | ((uint32_t)c[8] << 8) - | ((uint32_t)c[9] << 16) | ((uint32_t)c[10] << 24); - } - - return 0; -} - -/*=========================================================================== - * Command State Machine - *===========================================================================*/ - -void lup_cmd_begin(uint8_t cmd, uint32_t timeout_ms) -{ - g_lup_cmd.pending_cmd = cmd; - g_lup_cmd.send_tick = mstick(); - g_lup_cmd.timeout_ms = timeout_ms; - g_lup_cmd.state = LUP_STATE_WAIT_RESPONSE; - g_lup_cmd.resp_len = 0; - memset(g_lup_cmd.resp_buf, 0, sizeof(g_lup_cmd.resp_buf)); -} - -void lup_cmd_done(void) -{ - g_lup_cmd.pending_cmd = 0; - g_lup_cmd.state = LUP_STATE_IDLE; - g_lup_cmd.resp_len = 0; -} - -int lup_cmd_check_timeout(void) -{ - if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return 0; - if (mstick() - g_lup_cmd.send_tick > g_lup_cmd.timeout_ms) { - g_lup_cmd.state = LUP_STATE_TIMEOUT; - PRINT("LUP: cmd 0x%02X timeout\n", g_lup_cmd.pending_cmd); - return 1; - } - return 0; -} - -/* - * 收到响应时调用 — 如果当前有挂起命令且 CMD 匹配,则保存响应 - */ -void lup_cmd_on_response(const uint8_t *pkg, uint16_t len) -{ - if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return; - if (len < 4) return; - - uint8_t resp_cmd = pkg[3]; - if (resp_cmd == g_lup_cmd.pending_cmd) { - // 保存响应数据 - uint16_t copy_len = (len < sizeof(g_lup_cmd.resp_buf)) ? len : sizeof(g_lup_cmd.resp_buf); - memcpy(g_lup_cmd.resp_buf, pkg, copy_len); - g_lup_cmd.resp_len = copy_len; - g_lup_cmd.state = LUP_STATE_RESPONSE_READY; - } -} - -/*=========================================================================== - * High-level Send Commands - *===========================================================================*/ - -static void lup_debug_tx(const uint8_t *buf, uint16_t len) -{ - uint8_t i; - PRINT("LUP Tx:"); - for (i = 0; i < len; i++) { - PRINT(" %02X", buf[i]); - } - PRINT("\n"); -} - -void lup_send_get_version(void) -{ - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t len = lup_build_get_version(buf); - lup_debug_tx(buf, len); - UART2_SendString(buf, len); - lup_cmd_begin(LUP_CMD_GET_VERSION, 200); -} - -void lup_send_reset(void) -{ - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t len = lup_build_reset(buf); - lup_debug_tx(buf, len); - UART2_SendString(buf, len); - // No response expected -} - -void lup_send_factory_init(void) -{ - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t len = lup_build_factory_init(buf); - lup_debug_tx(buf, len); - UART2_SendString(buf, len); - lup_cmd_begin(LUP_CMD_FACTORY_INIT, 500); -} - -void lup_send_sensitivity_read(void) -{ - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t len = lup_build_sensitivity_read(buf); - lup_debug_tx(buf, len); - UART2_SendString(buf, len); - lup_cmd_begin(LUP_CMD_SENSITIVITY, 200); -} - -void lup_send_set_param(const LUP_ParamSet *ps) -{ - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t len = lup_build_set_param(buf, ps); - lup_debug_tx(buf, len); - UART2_SendString(buf, len); - lup_cmd_begin(LUP_CMD_SET_PARAM, 500); -} - -void lup_send_get_param(void) -{ - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t len = lup_build_get_param(buf); - lup_debug_tx(buf, len); - UART2_SendString(buf, len); - lup_cmd_begin(LUP_CMD_GET_PARAM, 500); -} - -/*=========================================================================== - * Frame Parser (called per-byte from USART2 ISR / timer context) - * - * 帧格式: [7F] [Addr] [LEN] [CMD] [Value: LEN bytes] [XOR] [SUM] - * LEN = Value 字段的长度,包括 CMD byte - * 所以: Value data bytes = LEN - 1 (减掉 CMD) - * - * 状态机: - * IDLE → 找 0x7F → HEADER - * HEADER → 收完 3 bytes Header → VALUE (若 LEN>1) - * VALUE → 收完 data_bytes → CHECK - * CHECK → 收完 2 bytes → COMPLETE - *===========================================================================*/ - -void lup_frame_reset(void) -{ - g_lup_parser.state = LUP_FRAME_STATE_IDLE; - g_lup_parser.idx = 0; - g_lup_parser.value_len = 0; - g_lup_parser.value_idx = 0; - memset(g_lup_parser.buf, 0, sizeof(g_lup_parser.buf)); -} - -const uint8_t *lup_frame_data(void) -{ - return g_lup_parser.buf; -} - -uint16_t lup_frame_len(void) -{ - return g_lup_parser.idx; -} - -/* - * 喂一个字节。返回 1 = 帧接收完成(在 buf 中) - */ -int lup_feed_byte(uint8_t byte) -{ - LUP_FrameParser *p = &g_lup_parser; - - switch (p->state) { - case LUP_FRAME_STATE_IDLE: - if (byte == LUP_MAGIC) { - p->buf[0] = byte; - p->idx = 1; - p->state = LUP_FRAME_STATE_HEADER; - } - break; - - case LUP_FRAME_STATE_HEADER: - p->buf[p->idx++] = byte; - if (p->idx == 4) { - // Header 收完: [7F][Addr][LEN][CMD] - // Value data bytes = LEN - 1 (CMD byte 已计入 LEN) - uint8_t len_field = p->buf[2]; - uint16_t data_bytes; // Value 中除 CMD 之外的 data 字节数 - if (len_field < 1) { - data_bytes = 0; - } else { - data_bytes = len_field - 1; - } - - if (data_bytes > LUP_MAX_VALUE_LEN) { - // 非法长度,丢弃 - lup_frame_reset(); - break; - } - - if (data_bytes > 0) { - p->value_len = data_bytes; - p->value_idx = 0; - p->state = LUP_FRAME_STATE_VALUE; - } else { - // 无 Data,直接等 Checksum - p->value_len = 0; - p->value_idx = 0; - p->state = LUP_FRAME_STATE_CHECK; - } - } - break; - - case LUP_FRAME_STATE_VALUE: - p->buf[p->idx++] = byte; - p->value_idx++; - if (p->value_idx >= p->value_len) { - p->state = LUP_FRAME_STATE_CHECK; - } - break; - - case LUP_FRAME_STATE_CHECK: - p->buf[p->idx++] = byte; - if (p->idx >= p->value_len + 1 + 3 + 2) { // Magic(1) + Header(3) + Value data + Check(2) - p->state = LUP_FRAME_STATE_COMPLETE; - return 1; - } - break; - - case LUP_FRAME_STATE_COMPLETE: - // 不应该到达这里,调用方应在 COMPLETE 后 reset - lup_frame_reset(); - break; - } - - return 0; -} - -/*=========================================================================== - * Sensor Report Callback - *===========================================================================*/ -static lup_sensor_cb_t g_lup_sensor_cb = NULL; - -void lup_set_sensor_callback(lup_sensor_cb_t cb) -{ - g_lup_sensor_cb = cb; - PRINT("LUP: sensor callback %s\n", cb ? "registered" : "cleared"); -} - -/* - * 收到完整帧后的处理: - * 1. 校验 checksum - * 2. 如果是主动上报 (0xC0) → 调用回调(网络推送等) - * 3. 如果是命令响应 → 匹配挂起命令 - */ -void lup_process_frame(const uint8_t *pkg, uint16_t len) -{ - int csr; - uint8_t i; - - if (len < 6) return; - - // Debug: print raw - PRINT("LUP Rx:"); - for (i = 0; i < len; i++) { - PRINT(" %02X", pkg[i]); - } - PRINT("\n"); - - // --- Checksum --- - csr = lup_verify_checksum(pkg, len); - if (csr != 0) { - PRINT("LUP: checksum fail (%d)\n", csr); - return; - } - - // --- Dispatch by CMD --- - uint8_t cmd = pkg[3]; - - // Active reports (0xC0) → trigger callback for network/upstream forwarding - if (cmd == LUP_CMD_SENSOR_REPORT) { - if (g_lup_sensor_cb) { - g_lup_sensor_cb(pkg, len); - } else { - PRINT("LUP: 0xC0 received but no callback registered!\n"); - } - return; - } - - // Try matching with pending command - lup_cmd_on_response(pkg, len); -} +/** + ****************************************************************************** + * @file loop_uart_proto.c + * @author wangfq + * @version V1.0 + * @date 2026-07-02 + * @brief DLD960Loop 串口通信协议实现 + * - 校验计算 (XOR + SUM) + * - 命令组包 / 响应解析 + * - 帧接收状态机 (替代 timeout heuristic) + * - 命令发送 + 超时跟踪 + ****************************************************************************** + */ +#include "CONFIG.h" +#include "loop_uart_proto.h" +#include "cmcng.h" +#include + +/*=========================================================================== + * Global State + *===========================================================================*/ +LUP_CmdTracker g_lup_cmd = {0, 0, 0, LUP_STATE_IDLE, {0}, 0}; + +/* --- Frame Parser --- */ +LUP_FrameParser g_lup_parser = { + .state = LUP_FRAME_STATE_IDLE, + .idx = 0, + .value_len = 0, + .value_idx = 0 +}; + +/*=========================================================================== + * Checksum + *===========================================================================*/ + +/* + * XOR 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束 + */ +uint8_t lup_calc_xor(const uint8_t *data, uint16_t len) +{ + uint8_t x = 0; + uint16_t i; + for (i = 0; i < len; i++) { + x ^= data[i]; + } + return x; +} + +/* + * SUM 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束 + */ +uint8_t lup_calc_sum(const uint8_t *data, uint16_t len) +{ + uint8_t s = 0; + uint16_t i; + for (i = 0; i < len; i++) { + s += data[i]; + } + return s; +} + +/* + * 校验整包: pkg = [7F] [Addr] [LEN] [CMD] [Data...] [XOR] [SUM] + * Checksum covers: Addr + LEN_field + Value (=CMD + Data) = 2 + LEN bytes + * starting at pkg[1] + */ +int lup_verify_checksum(const uint8_t *pkg, uint16_t len) +{ + if (len < 6) return -1; // 最小: 7F + 3 header + 2 checksum = 6 + + uint8_t pkg_len = pkg[2]; // LEN field + uint16_t check_len = 2 + pkg_len; // Addr(1)+LEN_field(1)+Value(LEN) + + if (len < check_len + 3) return -4; // Not enough data: Magic(1) + covered + Check(2) + + uint8_t expect_xor = lup_calc_xor(pkg + 1, check_len); + uint8_t expect_sum = lup_calc_sum(pkg + 1, check_len); + + uint8_t got_xor = pkg[1 + check_len]; + uint8_t got_sum = pkg[2 + check_len]; + + if (expect_xor != got_xor) return -2; + if (expect_sum != got_sum) return -3; + return 0; +} + +/* + * 为已填充好 Data 的 buf 追加 checksum。 + * buf 格式: [7F] [Addr] [LEN] [CMD] [Data...] + * Checksum 覆盖: Addr(1) + LEN(1) + Value(LEN) = 2 + LEN bytes + * Output: [7F][Addr][LEN][CMD][Data...][XOR][SUM] + */ +void lup_append_checksum(uint8_t *pkg) +{ + // Frame: [Magic][Addr][LEN][Value(LEN bytes)][XOR][SUM] + // Total = 1 + 1 + 1 + LEN + 2 = 5 + LEN + // (Addr + LEN_field are NOT inside Value; CMD is the 1st byte of Value) + uint8_t total_len = 5 + pkg[2]; // Magic(1)+Addr(1)+LEN(1)+Value(LEN)+Check(2) + uint16_t check_len = 2 + pkg[2]; // Addr(1)+LEN_field(1)+Value(LEN) + + pkg[total_len - 2] = lup_calc_xor(pkg + 1, check_len); + pkg[total_len - 1] = lup_calc_sum(pkg + 1, check_len); +} + +/*=========================================================================== + * Packet Builders + *===========================================================================*/ + +/* + * 0x4A — 获取设备版本号 + * Req: 7F 00 01 4A 4B 4B + */ +uint16_t lup_build_get_version(uint8_t *buf) +{ + buf[0] = LUP_MAGIC; // 7F + buf[1] = LUP_ADDR_DEFAULT; // 0x00 + buf[2] = 0x01; // LEN = 1 (CMD byte) + buf[3] = LUP_CMD_GET_VERSION; // 0x4A + lup_append_checksum(buf); + return 6; // Magic(1) + Header(3) + Check(2) +} + +/* + * 0x6D — 设备复位 (无回复) + * Req: 7F 00 01 6D 6C 6E + */ +uint16_t lup_build_reset(uint8_t *buf) +{ + buf[0] = LUP_MAGIC; + buf[1] = LUP_ADDR_DEFAULT; + buf[2] = 0x01; + buf[3] = LUP_CMD_RESET; + lup_append_checksum(buf); + return 6; +} + +/* + * 0x92 — 出厂初始化 + * Req: 7F 00 01 92 93 93 + */ +uint16_t lup_build_factory_init(uint8_t *buf) +{ + buf[0] = LUP_MAGIC; + buf[1] = LUP_ADDR_DEFAULT; + buf[2] = 0x01; + buf[3] = LUP_CMD_FACTORY_INIT; + lup_append_checksum(buf); + return 6; +} + +/* + * 0x8A — 读灵敏度列表 + * Req: 7F 00 03 8A 00 00 xx xx + * LEN=3: CMD(1) + R/W(1) + Amount(1) + */ +uint16_t lup_build_sensitivity_read(uint8_t *buf) +{ + buf[0] = LUP_MAGIC; + buf[1] = LUP_ADDR_DEFAULT; + buf[2] = 0x03; // LEN = 3 (CMD + R/W + Amount) + buf[3] = LUP_CMD_SENSITIVITY; // 0x8A + buf[4] = LUP_SENS_RW_READ; // 0x00 + buf[5] = 0x00; // Amount (ignored for read) + lup_append_checksum(buf); + return 5 + buf[2]; // = 8 +} + +/* + * 0x8A — 写灵敏度列表 + */ +uint16_t lup_build_sensitivity_write(uint8_t *buf, const LUP_Sensitivity *sens) +{ + uint8_t i; + uint16_t idx = 0; + + buf[idx++] = LUP_MAGIC; + buf[idx++] = LUP_ADDR_DEFAULT; + // LEN = CMD(1) + R/W(1) + Amount(1) + Amount*(SensIn(2)+SensOut(2)) + // = 3 + sens->amount * 4 + uint8_t val_len = 3 + sens->amount * 4; + buf[idx++] = val_len; + buf[idx++] = LUP_CMD_SENSITIVITY; + + buf[idx++] = sens->rw; // 0x01 (Write) + buf[idx++] = sens->amount; + + for (i = 0; i < sens->amount; i++) { + buf[idx++] = (uint8_t)(sens->sens_in[i] & 0xFF); + buf[idx++] = (uint8_t)((sens->sens_in[i] >> 8) & 0xFF); + buf[idx++] = (uint8_t)(sens->sens_out[i] & 0xFF); + buf[idx++] = (uint8_t)((sens->sens_out[i] >> 8) & 0xFF); + } + lup_append_checksum(buf); + return 5 + val_len; // Magic(1)+Addr(1)+LEN(1)+Value(val_len)+Check(2) +} + +/* + * 0x63 — 设置多路参数 + * Value: AutoMode(1) + Amount(1) + Amount * Param[5] + * LEN = CMD(1) + AutoMode(1) + Amount(1) + Amount*5 = 3 + 5*Amount + */ +uint16_t lup_build_set_param(uint8_t *buf, const LUP_ParamSet *ps) +{ + uint8_t i; + uint16_t idx = 0; + + buf[idx++] = LUP_MAGIC; + buf[idx++] = LUP_ADDR_DEFAULT; + uint8_t val_len = 3 + 5 * ps->amount; // CMD + AutoMode + Amount + Amount*Param + buf[idx++] = val_len; + buf[idx++] = LUP_CMD_SET_PARAM; + + buf[idx++] = ps->auto_mode; + buf[idx++] = ps->amount; + + for (i = 0; i < ps->amount; i++) { + buf[idx++] = ps->params[i].sensitivity; + buf[idx++] = ps->params[i].loop_delay; + buf[idx++] = ps->params[i].output_mode; + buf[idx++] = ps->params[i].exist_mode; + buf[idx++] = ps->params[i].direction_mode; + } + lup_append_checksum(buf); + return 5 + val_len; // Magic(1)+Addr(1)+LEN(1)+Value(val_len)+Check(2) +} + +/* + * 0x64 — 读取多路参数 + * Req: 7F 00 01 64 ... + */ +uint16_t lup_build_get_param(uint8_t *buf) +{ + buf[0] = LUP_MAGIC; + buf[1] = LUP_ADDR_DEFAULT; + buf[2] = 0x01; // LEN = 1 + buf[3] = LUP_CMD_GET_PARAM; // 0x64 + lup_append_checksum(buf); + return 6; +} + +/* + * 0xC0 mid-packet ACK + * Req: 7F | 00 | 07 | C0 | SensType | Seq | SubAmount | XOR | SUM + */ +uint16_t lup_build_sensor_ack(uint8_t *buf, uint8_t sens_type, + uint8_t seq, uint8_t sub_amount) +{ + buf[0] = LUP_MAGIC; + buf[1] = LUP_ADDR_DEFAULT; + buf[2] = 0x07; // LEN = 7 (CMD + SensType + Seq + SubAmount + 3 padding) + buf[3] = LUP_CMD_SENSOR_REPORT; + buf[4] = sens_type; + buf[5] = seq; + buf[6] = sub_amount; + buf[7] = 0x00; // padding + buf[8] = 0x00; + buf[9] = 0x00; + lup_append_checksum(buf); + return 5 + buf[2]; // = 12: Magic+Addr+LEN+Value(7)+Check(2) +} + +/*=========================================================================== + * Packet Parsers + *===========================================================================*/ + +/* + * Parse 0x4A Response: + * Header: 00 08 4A (Addr, LEN=8, CMD) + * Value: Status + Hard_Main + Hard_Sub + Hard_SSub + Soft_Main + Soft_Sub + Soft_SSub + */ +int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info) +{ + if (len < 10) return -1; // 1+3+7+2 = 13 min + + // pkg layout: [7F][Addr=0][LEN=8][CMD=0x4A][Status...7bytes][XOR][SUM] + if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_VERSION) return -2; + if (pkg[2] < 7) return -3; // LEN should be >= 7 + + const uint8_t *val = pkg + 4; // skip magic + header(3) + info->status = val[0]; + info->hard_main = val[1]; + info->hard_sub = val[2]; + info->hard_ssub = val[3]; + info->soft_main = val[4]; + info->soft_sub = val[5]; + info->soft_ssub = val[6]; + + return 0; +} + +/* + * Parse 0x92 Response: + * Value: 00/01 (00=success) + */ +int lup_parse_factory_init_resp(const uint8_t *pkg, uint16_t len, uint8_t *success) +{ + if (len < 7) return -1; + if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_FACTORY_INIT) return -2; + *success = (pkg[4] == 0x00) ? 1 : 0; + return 0; +} + +/* + * Parse 0x8A Response: + * Value: Ret(1) + Amount(1) + Amount*(SensIn(2) + SensOut(2)) + * Ret = 0x10 (Read return) | 0x11 (Write return) + * + * eg: 7F 80 13 8A 10 04 A2 00 51 00 51 00 36 00 ... + * Ret=0x10=Read, Amount=4 + * Lv0: SensIn=0x00A2, SensOut=0x0051 + */ +int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens) +{ + uint8_t i; + if (len < 8) return -1; // min: 7F+Addr+LEN+CMD+Ret+Amount+XOR+SUM = 8 + if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSITIVITY) return -2; + + const uint8_t *val = pkg + 4; + sens->rw = val[0]; // 0x10 or 0x11 + sens->amount = val[1]; + + if (sens->amount > LUP_COIL_COUNT) sens->amount = LUP_COIL_COUNT; + + for (i = 0; i < sens->amount; i++) { + sens->sens_in[i] = val[2 + i * 4] | ((uint16_t)val[3 + i * 4] << 8); + sens->sens_out[i] = val[4 + i * 4] | ((uint16_t)val[5 + i * 4] << 8); + } + return 0; +} + +/* + * Parse 0x64 Response: + * Value: AutoMode(1) + Amount(1) + Amount * [Sensitivity, Loop_Delay, Output_Mode, + * Exist_Mode, Direction_Mode, freq1(2), freq2(2), freq3(2)] + * Each coil = 8 bytes + */ +int lup_parse_param_get(const uint8_t *pkg, uint16_t len, LUP_ParamGet *pg) +{ + uint8_t i; + if (len < 6) return -1; + if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_PARAM) return -2; + + const uint8_t *val = pkg + 4; + pg->auto_mode = val[0]; + pg->amount = val[1]; + + if (pg->amount > LUP_COIL_COUNT) pg->amount = LUP_COIL_COUNT; + + for (i = 0; i < pg->amount; i++) { + const uint8_t *c = val + 2 + i * 8; + pg->params[i].sensitivity = c[0]; + pg->params[i].loop_delay = c[1]; + pg->params[i].output_mode = c[2]; + pg->params[i].exist_mode = c[3]; + pg->params[i].direction_mode = c[4]; + pg->freq[i][0] = c[5] | ((uint32_t)c[6] << 8); + pg->freq[i][1] = c[7] | ((uint32_t)c[8] << 8); + pg->freq[i][2] = c[9] | ((uint32_t)c[10] << 8); + } + + return 0; +} + +/* + * Parse 0x63 Response (Set Param): + * Value: Status(1 byte, 0x00=success) + */ +int lup_parse_set_param_resp(const uint8_t *pkg, uint16_t len, uint8_t *success) +{ + if (len < 7) return -1; + if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SET_PARAM) return -2; + *success = (pkg[4] == 0x00) ? 1 : 0; + return 0; +} + +/* + * Parse 0xC0/0x0C — 多路线圈传感信息 + * + * 每路线圈数据单元 = 11 bytes: + * [0] 配置1: freq_level(2) | Direction(1) | freq_type(1) | sens(4) + * [1] 评估条件: condition(4) | loop_state(1) | car_state(1) | misc_type(2) + * [2-4] 频率 (3 bytes, LE) + * [5-6] 变化量 (2 bytes, LE) + * [7-10] 杂项 (4 bytes) + */ +int lup_parse_sensor_report(const uint8_t *pkg, uint16_t len, LUP_SensorReport *report) +{ + uint8_t i; + if (len < 9) return -1; // 7F + 3 header + 2 sensType/SubPkgFlag + 2 checksum min + if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSOR_REPORT) return -2; + + uint8_t sens_type = pkg[4]; + if (sens_type != LUP_SENS_TYPE_MULTI_COIL) return -3; + + uint8_t sub_pkg = pkg[5]; + report->sens_type = sens_type; + report->sub_amount = (sub_pkg >> 4) & 0x0F; + report->sub_sequence = sub_pkg & 0x0F; + + // 计算线圈数: Value = sensType(1) + SubPkgFlag(1) + coil_count * 11 + uint16_t val_len = pkg[2]; // LEN field + uint16_t data_offset = 6; // 跳过 7F + Addr + LEN + CMD + SensType + SubPkgFlag + uint16_t data_len = val_len - 2; // 减去 SensType + SubPkgFlag + + uint8_t coil_count = data_len / 11; + if (coil_count > LUP_COIL_COUNT) coil_count = LUP_COIL_COUNT; + report->coil_count = coil_count; + + for (i = 0; i < coil_count; i++) { + const uint8_t *c = pkg + data_offset + i * 11; + + LUP_CoilSensor *cs = &report->coils[i]; + cs->freq_level = (c[0] >> 6) & 0x03; + cs->direction = (c[0] >> 5) & 0x01; + cs->freq_type = (c[0] >> 4) & 0x01; + cs->sensitivity = c[0] & 0x0F; + + cs->condition = (c[1] >> 4) & 0x0F; + cs->loop_state = (c[1] >> 3) & 0x01; + cs->car_state = (c[1] >> 2) & 0x01; + cs->misc_type = c[1] & 0x03; + + cs->freq = c[2] | ((uint32_t)c[3] << 8) | ((uint32_t)c[4] << 16); + cs->variation = c[5] | ((uint16_t)c[6] << 8); + cs->misc.passtime_ms = c[7] | ((uint32_t)c[8] << 8) + | ((uint32_t)c[9] << 16) | ((uint32_t)c[10] << 24); + } + + return 0; +} + +/*=========================================================================== + * Command State Machine + *===========================================================================*/ + +void lup_cmd_begin(uint8_t cmd, uint32_t timeout_ms) +{ + g_lup_cmd.pending_cmd = cmd; + g_lup_cmd.send_tick = mstick(); + g_lup_cmd.timeout_ms = timeout_ms; + g_lup_cmd.state = LUP_STATE_WAIT_RESPONSE; + g_lup_cmd.resp_len = 0; + memset(g_lup_cmd.resp_buf, 0, sizeof(g_lup_cmd.resp_buf)); +} + +void lup_cmd_done(void) +{ + g_lup_cmd.pending_cmd = 0; + g_lup_cmd.state = LUP_STATE_IDLE; + g_lup_cmd.resp_len = 0; +} + +int lup_cmd_check_timeout(void) +{ + if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return 0; + if (mstick() - g_lup_cmd.send_tick > g_lup_cmd.timeout_ms) { + g_lup_cmd.state = LUP_STATE_TIMEOUT; + PRINT("LUP: cmd 0x%02X timeout\n", g_lup_cmd.pending_cmd); + return 1; + } + return 0; +} + +/* + * 收到响应时调用 — 如果当前有挂起命令且 CMD 匹配,则保存响应 + */ +void lup_cmd_on_response(const uint8_t *pkg, uint16_t len) +{ + if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return; + if (len < 4) return; + + uint8_t resp_cmd = pkg[3]; + if (resp_cmd == g_lup_cmd.pending_cmd) { + // 保存响应数据 + uint16_t copy_len = (len < sizeof(g_lup_cmd.resp_buf)) ? len : sizeof(g_lup_cmd.resp_buf); + memcpy(g_lup_cmd.resp_buf, pkg, copy_len); + g_lup_cmd.resp_len = copy_len; + g_lup_cmd.state = LUP_STATE_RESPONSE_READY; + } +} + +/*=========================================================================== + * High-level Send Commands + *===========================================================================*/ + +static void lup_debug_tx(const uint8_t *buf, uint16_t len) +{ + uint8_t i; + PRINT("LUP Tx:"); + for (i = 0; i < len; i++) { + PRINT(" %02X", buf[i]); + } + PRINT("\n"); +} + +void lup_send_get_version(void) +{ + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t len = lup_build_get_version(buf); + lup_debug_tx(buf, len); + UART2_SendString(buf, len); + lup_cmd_begin(LUP_CMD_GET_VERSION, 200); +} + +void lup_send_reset(void) +{ + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t len = lup_build_reset(buf); + lup_debug_tx(buf, len); + UART2_SendString(buf, len); + // No response expected +} + +void lup_send_factory_init(void) +{ + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t len = lup_build_factory_init(buf); + lup_debug_tx(buf, len); + UART2_SendString(buf, len); + lup_cmd_begin(LUP_CMD_FACTORY_INIT, 500); +} + +void lup_send_sensitivity_read(void) +{ + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t len = lup_build_sensitivity_read(buf); + lup_debug_tx(buf, len); + UART2_SendString(buf, len); + lup_cmd_begin(LUP_CMD_SENSITIVITY, 200); +} + +void lup_send_set_param(const LUP_ParamSet *ps) +{ + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t len = lup_build_set_param(buf, ps); + lup_debug_tx(buf, len); + UART2_SendString(buf, len); + lup_cmd_begin(LUP_CMD_SET_PARAM, 500); +} + +void lup_send_get_param(void) +{ + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t len = lup_build_get_param(buf); + lup_debug_tx(buf, len); + UART2_SendString(buf, len); + lup_cmd_begin(LUP_CMD_GET_PARAM, 500); +} + +/*=========================================================================== + * Frame Parser (called per-byte from USART2 ISR / timer context) + * + * 帧格式: [7F] [Addr] [LEN] [CMD] [Value: LEN bytes] [XOR] [SUM] + * LEN = Value 字段的长度,包括 CMD byte + * 所以: Value data bytes = LEN - 1 (减掉 CMD) + * + * 状态机: + * IDLE → 找 0x7F → HEADER + * HEADER → 收完 3 bytes Header → VALUE (若 LEN>1) + * VALUE → 收完 data_bytes → CHECK + * CHECK → 收完 2 bytes → COMPLETE + *===========================================================================*/ + +void lup_frame_reset(void) +{ + g_lup_parser.state = LUP_FRAME_STATE_IDLE; + g_lup_parser.idx = 0; + g_lup_parser.value_len = 0; + g_lup_parser.value_idx = 0; + memset(g_lup_parser.buf, 0, sizeof(g_lup_parser.buf)); +} + +const uint8_t *lup_frame_data(void) +{ + return g_lup_parser.buf; +} + +uint16_t lup_frame_len(void) +{ + return g_lup_parser.idx; +} + +/* + * 喂一个字节。返回 1 = 帧接收完成(在 buf 中) + */ +int lup_feed_byte(uint8_t byte) +{ + LUP_FrameParser *p = &g_lup_parser; + + switch (p->state) { + case LUP_FRAME_STATE_IDLE: + if (byte == LUP_MAGIC) { + p->buf[0] = byte; + p->idx = 1; + p->state = LUP_FRAME_STATE_HEADER; + } + break; + + case LUP_FRAME_STATE_HEADER: + p->buf[p->idx++] = byte; + if (p->idx == 4) { + // Header 收完: [7F][Addr][LEN][CMD] + // Value data bytes = LEN - 1 (CMD byte 已计入 LEN) + uint8_t len_field = p->buf[2]; + uint16_t data_bytes; // Value 中除 CMD 之外的 data 字节数 + if (len_field < 1) { + data_bytes = 0; + } else { + data_bytes = len_field - 1; + } + + if (data_bytes > LUP_MAX_VALUE_LEN) { + // 非法长度,丢弃 + lup_frame_reset(); + break; + } + + if (data_bytes > 0) { + p->value_len = data_bytes; + p->value_idx = 0; + p->state = LUP_FRAME_STATE_VALUE; + } else { + // 无 Data,直接等 Checksum + p->value_len = 0; + p->value_idx = 0; + p->state = LUP_FRAME_STATE_CHECK; + } + } + break; + + case LUP_FRAME_STATE_VALUE: + p->buf[p->idx++] = byte; + p->value_idx++; + if (p->value_idx >= p->value_len) { + p->state = LUP_FRAME_STATE_CHECK; + } + break; + + case LUP_FRAME_STATE_CHECK: + p->buf[p->idx++] = byte; + if (p->idx >= p->value_len + 1 + 3 + 2) { // Magic(1) + Header(3) + Value data + Check(2) + p->state = LUP_FRAME_STATE_COMPLETE; + return 1; + } + break; + + case LUP_FRAME_STATE_COMPLETE: + // 不应该到达这里,调用方应在 COMPLETE 后 reset + lup_frame_reset(); + break; + } + + return 0; +} + +/*=========================================================================== + * Sensor Report Callback + *===========================================================================*/ +static lup_sensor_cb_t g_lup_sensor_cb = NULL; + +void lup_set_sensor_callback(lup_sensor_cb_t cb) +{ + g_lup_sensor_cb = cb; + PRINT("LUP: sensor callback %s\n", cb ? "registered" : "cleared"); +} + +/* + * 收到完整帧后的处理: + * 1. 校验 checksum + * 2. 如果是主动上报 (0xC0) → 调用回调(网络推送等) + * 3. 如果是命令响应 → 匹配挂起命令 + */ +void lup_process_frame(const uint8_t *pkg, uint16_t len) +{ + int csr; + uint8_t i; + + if (len < 6) return; + + // Debug: print raw + PRINT("LUP Rx:"); + for (i = 0; i < len; i++) { + PRINT(" %02X", pkg[i]); + } + PRINT("\n"); + + // --- Checksum --- + csr = lup_verify_checksum(pkg, len); + if (csr != 0) { + PRINT("LUP: checksum fail (%d)\n", csr); + return; + } + + // --- Dispatch by CMD --- + uint8_t cmd = pkg[3]; + + // Active reports (0xC0) → trigger callback for network/upstream forwarding + if (cmd == LUP_CMD_SENSOR_REPORT) { + if (g_lup_sensor_cb) { + g_lup_sensor_cb(pkg, len); + } else { + PRINT("LUP: 0xC0 received but no callback registered!\n"); + } + return; + } + + // Try matching with pending command + lup_cmd_on_response(pkg, len); +} diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c index a5bf261..bc2045a 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c @@ -1,1283 +1,1283 @@ -/** - ****************************************************************************** - * @file tcp_json_srv.c - * @author wangfq - * @version V1.0 - * @date 2026-06-30 - * @brief TCP JSON protocol server implementation - * DLD960 TCP JSON protocol (port 5960) — auth + 15 commands - ****************************************************************************** - */ - -#include "CONFIG.h" -#include "tcp_json_srv.h" -#include "eth_driver.h" -#include "wchnet.h" -#include "net_srv.h" -#include "cmcng.h" -#include "loop_uart_proto.h" -#include "simple_json.h" -#include "storage.h" -#include -#include -#include - -/*=========================================================================== - * Global State - *===========================================================================*/ -uint8_t g_json_socket_listen = 0xFF; // TCP socket ID — handles both listen + client data -TcpJsonAuthState g_json_auth_state = JSON_STATE_WAIT_AUTH; -uint32_t g_json_auth_timer = 0; -uint8_t g_json_pwd_retry = 0; -uint32_t g_json_lockout_timer = 0; -TcpJsonPending g_json_pending = {0, 0, 0, "", 0}; -uint8_t g_report_active = 0; // 0=不主动上报, 1=主动上报传感器数据 -uint32_t g_json_last_comm_ts = 0; // 最后通信时间戳 -uint32_t g_json_last_connect_ts = 0; // 最后连接时间戳 -uint8_t g_json_restart_count = 0; // 自动重启计数 -uint32_t g_json_restart_cooldown = 0; // 冷却期截止时间 - -/*=========================================================================== - * Frame Receive Buffer (line-delimited JSON) - *===========================================================================*/ -static uint8_t g_json_recv_buf[TCP_JSON_RECV_BUF_LEN]; -static uint16_t g_json_recv_len = 0; -static uint8_t g_json_wchnet_buf[RECE_BUF_LEN]; // WCHNET internal recv buffer - -/*=========================================================================== - * Internal helpers - *===========================================================================*/ -static char g_tmp_value[256]; // reusable buffer for simple_parse_json - -static void reset_tmp(void) { - memset(g_tmp_value, 0, sizeof(g_tmp_value)); -} - -static void json_send_error(uint8_t socket, uint32_t msg_id, const char *cmd, - int code, const char *msg) { - char *out = (char *)malloc(256); - if (!out) return; - snprintf(out, 256, - "{\"msg_id\":%lu,\"cmd\":\"%s\",\"ts\":%lu,\"code\":%d,\"msg\":\"%s\"}\n", - msg_id, cmd ? cmd : "", (unsigned long)mstick(), code, msg); - uint32_t slen = strlen(out); - WCHNET_SocketSend(socket, (uint8_t *)out, &slen); - free(out); -} - -static void json_send_ok(uint8_t socket, uint32_t msg_id, const char *cmd, - const char *data_json) { - char *out = (char *)malloc(TCP_JSON_MAX_FRAME); - if (!out) return; - if (data_json && strlen(data_json) > 0) { - snprintf(out, TCP_JSON_MAX_FRAME, - "{\"msg_id\":%lu,\"cmd\":\"%s\",\"ts\":%lu,\"code\":0,\"msg\":\"success\",\"data\":%s}\n", - msg_id, cmd, (unsigned long)mstick(), data_json); - } else { - snprintf(out, TCP_JSON_MAX_FRAME, - "{\"msg_id\":%lu,\"cmd\":\"%s\",\"ts\":%lu,\"code\":0,\"msg\":\"success\"}\n", - msg_id, cmd, (unsigned long)mstick()); - } - uint32_t slen = strlen(out); - WCHNET_SocketSend(socket, (uint8_t *)out, &slen); - free(out); -} - -/* JSON field extractors — parse msg_id, cmd, and data object from raw JSON */ -static uint32_t json_get_msg_id(const char *json) { - reset_tmp(); - simple_parse_json(json, "\"msg_id\"", g_tmp_value); - if (strlen(g_tmp_value) == 0) return 0; - return (uint32_t)strtoul(g_tmp_value, NULL, 10); -} - - -static char *json_get_data_str(const char *json) { - char *data = (char *)malloc(TCP_JSON_MAX_FRAME); - if (!data) return NULL; - memset(data, 0, TCP_JSON_MAX_FRAME); - simple_parse_json(json, "\"data\"", data); - if (strlen(data) == 0) { - free(data); - return NULL; - } - return data; -} - -static void json_get_str_field(const char *json, const char *key, char *out, int out_len) { - reset_tmp(); - simple_parse_json(json, key, g_tmp_value); - // strip surrounding quotes if present - char *src = g_tmp_value; - if (src[0] == '"') src++; - int len = strlen(src); - if (len > 0 && src[len - 1] == '"') len--; - int copy_len = (len < out_len - 1) ? len : out_len - 1; - memcpy(out, src, copy_len); - out[copy_len] = '\0'; -} - -static void json_get_cmd(const char *json, char *out, int out_len) { - json_get_str_field(json, "\"cmd\"", out, out_len); -} - -static uint32_t json_get_uint_field(const char *json, const char *key) { - reset_tmp(); - simple_parse_json(json, key, g_tmp_value); - if (strlen(g_tmp_value) == 0) return 0; - return (uint32_t)strtoul(g_tmp_value, NULL, 10); -} - -static uint8_t json_get_bool_field(const char *json, const char *key) { - reset_tmp(); - simple_parse_json(json, key, g_tmp_value); - if (strstr(g_tmp_value, "true")) return 1; - return 0; -} - - -/*=========================================================================== - * Frame parser: extract line-delimited JSON frames from receive buffer - *===========================================================================*/ -static int json_extract_frame(uint8_t *buf, uint16_t *len, char *frame_out, uint16_t frame_size) { - uint16_t i; - for (i = 0; i < *len; i++) { - if (buf[i] == '\n') { - uint16_t frame_len = i; - if (frame_len >= frame_size) frame_len = frame_size - 1; - memcpy(frame_out, buf, frame_len); - frame_out[frame_len] = '\0'; - - // Remove consumed frame from buffer - uint16_t remaining = *len - i - 1; - if (remaining > 0) { - memmove(buf, buf + i + 1, remaining); - } - *len = remaining; - return 1; - } - } - return 0; // no complete frame yet -} - - -/*=========================================================================== - * Command Handlers - *===========================================================================*/ - -/* 4.1 pwd_verify */ -static void handle_pwd_verify(uint8_t socket, uint32_t msg_id, const char *json) { - PRINT("JSON: pwd_verify handler entered\n"); - char *data = json_get_data_str(json); - if (!data) { - PRINT("JSON: pwd_verify — data is NULL\n"); - json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - PRINT("JSON: pwd_verify — data=[%s]\n", data); - - char password[16] = {0}; - json_get_str_field(data, "\"password\"", password, sizeof(password)); - free(data); - - PRINT("JSON: pwd_verify — password=[%s] len=%d\n", password, strlen(password)); - PRINT("JSON: pwd_verify — dev_pwd=[%c%c%c%c%c%c]\n", - g_dev_password[0], g_dev_password[1], g_dev_password[2], - g_dev_password[3], g_dev_password[4], g_dev_password[5]); - - if (strlen(password) == 0) { - json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_PARAM_ERR, "missing password"); - return; - } - - // Compare password - if (memcmp(password, g_dev_password, 6) == 0 && strlen(password) == 6) { - g_json_auth_state = JSON_STATE_AUTHED; - g_json_pwd_retry = 0; - g_json_auth_timer = 0; - json_send_ok(socket, msg_id, "pwd_verify", NULL); - PRINT("JSON: Auth success\n"); - } else { - g_json_pwd_retry++; - if (g_json_pwd_retry >= TCP_JSON_MAX_PWD_RETRY) { - PRINT("JSON: Auth failed %d times → restart\n", g_json_pwd_retry); - // 先发错误再重启,让 TCP 栈有时间 flush - json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_AUTH_FAIL, - "password incorrect, connection will restart"); - Delay_Ms(100); - tcp_json_restart(); - } else { - json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_AUTH_FAIL, "password incorrect"); - } - } -} - -/* 4.2 dev_serial_set */ -static void handle_dev_serial_set(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "dev_serial_set", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - char serial[32] = {0}; - json_get_str_field(data, "\"dev_serial\"", serial, sizeof(serial)); - free(data); - - if (strlen(serial) != 12) { - json_send_error(socket, msg_id, "dev_serial_set", JSON_CODE_PARAM_ERR, "invalid serial length"); - return; - } - - // Convert hex string to bytes - uint8_t serial_bytes[6]; - for (int i = 0; i < 6; i++) { - char hex[3] = {serial[i * 2], serial[i * 2 + 1], '\0'}; - serial_bytes[i] = (uint8_t)strtoul(hex, NULL, 16); - } - - alter_dev_serila(serial_bytes); - memcpy(g_dev_number, serial_bytes, 6); - sprintf(g_dev_number_str, "%02X%02X%02X%02X%02X%02X", - g_dev_number[0], g_dev_number[1], g_dev_number[2], - g_dev_number[3], g_dev_number[4], g_dev_number[5]); - - json_send_ok(socket, msg_id, "dev_serial_set", NULL); - PRINT("JSON: dev_serial_set -> %s\n", g_dev_number_str); -} - -/* 4.3 dev_info_query */ -static void handle_dev_info_query(uint8_t socket, uint32_t msg_id, const char *json) { - char data_json[512]; - snprintf(data_json, sizeof(data_json), - "{\"dev_serial\":\"%s\",\"hard_ver\":\"%s\",\"soft_ver\":\"%s\"," - "\"model\":\"%s\",\"product_code\":\"960001\"," - "\"sub_code\":{\"net\":%s,\"iot\":%s}," - "\"bus\":{\"bus1\":0,\"bus2\":0,\"bus3\":0,\"bus4\":0}}", - g_dev_number_str, HARDWARE_VER, FIRMWARE_VER, - PRODUCT_MODEL, - g_sub_code_enable.net_enable ? "true" : "false", - g_sub_code_enable.iot_enable ? "true" : "false"); - - json_send_ok(socket, msg_id, "dev_info_query", data_json); -} - -/* 4.4 ssc_net_set */ -static void handle_ssc_net_set(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "ssc_net_set", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - Local_Net_Cfg lcfg = local_net_cfg; // copy current - NET_CENTER_INFO ccfg = net_center_info; - - char ip_str[16] = {0}; - json_get_str_field(data, "\"dev_ip\"", ip_str, sizeof(ip_str)); - if (strlen(ip_str) > 0) get_ipstr_to_array(ip_str, lcfg.lip); - - char mask_str[16] = {0}; - json_get_str_field(data, "\"subnet_mask\"", mask_str, sizeof(mask_str)); - if (strlen(mask_str) > 0) get_ipstr_to_array(mask_str, lcfg.sub); - - char gw_str[16] = {0}; - json_get_str_field(data, "\"route_ip\"", gw_str, sizeof(gw_str)); - if (strlen(gw_str) > 0) get_ipstr_to_array(gw_str, lcfg.gw); - - char lssc_ip_str[16] = {0}; - json_get_str_field(data, "\"lssc_ip\"", lssc_ip_str, sizeof(lssc_ip_str)); - if (strlen(lssc_ip_str) > 0) get_ipstr_to_array(lssc_ip_str, ccfg.lssc_ip); - - char dns_str[16] = {0}; - json_get_str_field(data, "\"dns\"", dns_str, sizeof(dns_str)); - if (strlen(dns_str) > 0) get_ipstr_to_array(dns_str, lcfg.dns); - - uint32_t port = json_get_uint_field(data, "\"port\""); - if (port > 0 && port <= 65535) ccfg.tcp_port = (uint16_t)port; - - free(data); - - // Write to flash and update globals - write_net_config(&lcfg, &ccfg, &iot_net_info, &g_iot_topic); - local_net_cfg = lcfg; - net_center_info = ccfg; - - json_send_ok(socket, msg_id, "ssc_net_set", NULL); - PRINT("JSON: ssc_net_set done\n"); -} - -/* 4.5 ssc_net_query */ -static void handle_ssc_net_query(uint8_t socket, uint32_t msg_id, const char *json) { - char data_json[400]; - snprintf(data_json, sizeof(data_json), - "{\"dev_ip\":\"%d.%d.%d.%d\",\"subnet_mask\":\"%d.%d.%d.%d\"," - "\"route_ip\":\"%d.%d.%d.%d\",\"lssc_ip\":\"%d.%d.%d.%d\"," - "\"dns\":\"%d.%d.%d.%d\",\"port\":%d}", - local_net_cfg.lip[0], local_net_cfg.lip[1], local_net_cfg.lip[2], local_net_cfg.lip[3], - local_net_cfg.sub[0], local_net_cfg.sub[1], local_net_cfg.sub[2], local_net_cfg.sub[3], - local_net_cfg.gw[0], local_net_cfg.gw[1], local_net_cfg.gw[2], local_net_cfg.gw[3], - net_center_info.lssc_ip[0], net_center_info.lssc_ip[1], - net_center_info.lssc_ip[2], net_center_info.lssc_ip[3], - local_net_cfg.dns[0], local_net_cfg.dns[1], local_net_cfg.dns[2], local_net_cfg.dns[3], - net_center_info.tcp_port); - - json_send_ok(socket, msg_id, "ssc_net_query", data_json); -} - -/* 4.6 iot_net_set */ -static void handle_iot_net_set(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "iot_net_set", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - IOT_NET_INFO icfg = iot_net_info; - - char host[64] = {0}; - json_get_str_field(data, "\"host\"", host, sizeof(host)); - if (strlen(host) > 0) strncpy((char *)icfg.remote_addr, host, 63); - - uint32_t port = json_get_uint_field(data, "\"port\""); - if (port > 0 && port <= 65535) icfg.mqtt_port = (uint16_t)port; - - char client_id[64] = {0}; - json_get_str_field(data, "\"client_id\"", client_id, sizeof(client_id)); - if (strlen(client_id) > 0) strncpy((char *)icfg.client_id, client_id, 63); - - char username[64] = {0}; - json_get_str_field(data, "\"username\"", username, sizeof(username)); - if (strlen(username) > 0) strncpy((char *)icfg.username, username, 63); - - char password[32] = {0}; - json_get_str_field(data, "\"password\"", password, sizeof(password)); - if (strlen(password) > 0) strncpy((char *)icfg.password, password, 31); - - free(data); - - write_net_config(&local_net_cfg, &net_center_info, &icfg, &g_iot_topic); - iot_net_info = icfg; - - json_send_ok(socket, msg_id, "iot_net_set", NULL); - PRINT("JSON: iot_net_set done\n"); -} - -/* 4.7 iot_net_query */ -static void handle_iot_net_query(uint8_t socket, uint32_t msg_id, const char *json) { - char data_json[400]; - snprintf(data_json, sizeof(data_json), - "{\"host\":\"%s\",\"port\":%d,\"client_id\":\"%s\"," - "\"username\":\"%s\",\"password\":\"%s\"}", - iot_net_info.remote_addr, iot_net_info.mqtt_port, - iot_net_info.client_id, iot_net_info.username, iot_net_info.password); - - json_send_ok(socket, msg_id, "iot_net_query", data_json); -} - -/* 4.8 iot_topic_set */ -static void handle_iot_topic_set(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "iot_topic_set", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - IOT_Topic topic = g_iot_topic; - - topic.clientid_enable = json_get_bool_field(data, "\"client_id_enable\""); - - char topic_pub[64] = {0}; - json_get_str_field(data, "\"topic_pub\"", topic_pub, sizeof(topic_pub)); - if (strlen(topic_pub) > 0) strncpy((char *)topic.topic_pub, topic_pub, 63); - - char topic_sub[64] = {0}; - json_get_str_field(data, "\"topic_sub\"", topic_sub, sizeof(topic_sub)); - if (strlen(topic_sub) > 0) strncpy((char *)topic.topic_sub, topic_sub, 63); - - free(data); - - write_net_config(&local_net_cfg, &net_center_info, &iot_net_info, &topic); - g_iot_topic = topic; - - json_send_ok(socket, msg_id, "iot_topic_set", NULL); - PRINT("JSON: iot_topic_set done\n"); -} - -/* 4.9 iot_topic_query */ -static void handle_iot_topic_query(uint8_t socket, uint32_t msg_id, const char *json) { - char data_json[300]; - snprintf(data_json, sizeof(data_json), - "{\"client_id_enable\":%s,\"topic_pub\":\"%s\",\"topic_sub\":\"%s\"}", - g_iot_topic.clientid_enable ? "true" : "false", - g_iot_topic.topic_pub, g_iot_topic.topic_sub); - - json_send_ok(socket, msg_id, "iot_topic_query", data_json); -} - -/* 4.10 pwd_set */ -static void handle_pwd_set(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "pwd_set", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - char old_pwd[16] = {0}, new_pwd[16] = {0}; - json_get_str_field(data, "\"old_password\"", old_pwd, sizeof(old_pwd)); - json_get_str_field(data, "\"new_password\"", new_pwd, sizeof(new_pwd)); - free(data); - - if (strlen(old_pwd) != 6 || strlen(new_pwd) != 6) { - json_send_error(socket, msg_id, "pwd_set", JSON_CODE_PARAM_ERR, "password must be 6 digits"); - return; - } - - if (memcmp(old_pwd, g_dev_password, 6) != 0) { - json_send_error(socket, msg_id, "pwd_set", JSON_CODE_AUTH_FAIL, "old password incorrect"); - return; - } - - memcpy(g_dev_password, new_pwd, 6); - set_ble_safe_pass((uint8_t *)new_pwd); - - json_send_ok(socket, msg_id, "pwd_set", NULL); - PRINT("JSON: pwd_set done\n"); -} - -/* 4.11 factory_reset */ -static void handle_factory_reset(uint8_t socket, uint32_t msg_id, const char *json) { - json_send_ok(socket, msg_id, "factory_reset", NULL); - PRINT("JSON: factory_reset — resetting...\n"); - - Delay_Ms(100); - factory_dev_info(); - Delay_Ms(100); - NVIC_SystemReset(); -} - -/* 4.12 device_reset */ -static void handle_device_reset(uint8_t socket, uint32_t msg_id, const char *json) { - // No response — device resets immediately - char *out = (char *)malloc(128); - if (out) { - snprintf(out, 128, - "{\"msg_id\":%lu,\"cmd\":\"device_reset\",\"ts\":%lu,\"code\":0,\"msg\":\"resetting\"}\n", - msg_id, (unsigned long)mstick()); - uint32_t slen = strlen(out); - WCHNET_SocketSend(socket, (uint8_t *)out, &slen); - free(out); - } - Delay_Ms(100); - NVIC_SystemReset(); -} - -/* 4.13 loop_param_set — 设置地感线圈多路参数 (CMD 0x63) */ -static void handle_loop_param_set(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "loop_param_set", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - LUP_ParamSet ps; - memset(&ps, 0, sizeof(ps)); - - ps.auto_mode = (uint8_t)json_get_uint_field(data, "\"auto_mode\""); - - // Parse channels array - // Use a simple approach: parse top-level fields for each known channel key - uint32_t amount = json_get_uint_field(data, "\"amount\""); - if (amount == 0 || amount > LUP_COIL_COUNT) { - amount = LUP_COIL_COUNT; - } - ps.amount = (uint8_t)amount; - - // Parse per-channel data - char ch_key[16]; - uint8_t ch; - for (ch = 0; ch < ps.amount; ch++) { - snprintf(ch_key, sizeof(ch_key), "\"ch%d\"", ch + 1); - - char ch_data[512] = {0}; - reset_tmp(); - simple_parse_json(data, ch_key, g_tmp_value); - if (strlen(g_tmp_value) == 0) continue; - strncpy(ch_data, g_tmp_value, sizeof(ch_data) - 1); - - LUP_CoilParam *cp = &ps.params[ch]; - - // sensitivity: 低4位=灵敏度, 高4位=freq_level - uint32_t sens_val = json_get_uint_field(ch_data, "\"sensitivity\""); - uint32_t freq_lev = json_get_uint_field(ch_data, "\"freq_level\""); // 0~3 - cp->sensitivity = ((uint8_t)(freq_lev & 0x03) << 4) | (uint8_t)(sens_val & 0x0F); - - cp->loop_delay = (uint8_t)json_get_uint_field(ch_data, "\"loop_delay\""); - - // output_mode: 低3位=输出方式, 高5位=SafeMode - uint32_t out_mode = json_get_uint_field(ch_data, "\"output_mode\""); - uint32_t safe_mode = json_get_uint_field(ch_data, "\"safe_mode\""); - cp->output_mode = ((uint8_t)(safe_mode & 0x1F) << 3) | (uint8_t)(out_mode & 0x07); - - cp->exist_mode = (uint8_t)json_get_uint_field(ch_data, "\"exist_mode\""); - - // direction_mode: 低3位=方向模式, 高4位=Function_Mode - uint32_t dir_mode = json_get_uint_field(ch_data, "\"direction_mode\""); - uint32_t func_mode = json_get_uint_field(ch_data, "\"function_mode\""); - cp->direction_mode = ((uint8_t)(func_mode & 0x0F) << 4) | (uint8_t)(dir_mode & 0x07); - } - free(data); - - // Send to Loop MCU and defer response - lup_send_set_param(&ps); - - // Save pending state for async response - g_json_pending.active = 1; - g_json_pending.socket = socket; - g_json_pending.msg_id = msg_id; - strncpy(g_json_pending.cmd, "loop_param_set", sizeof(g_json_pending.cmd) - 1); - g_json_pending.deadline = mstick() + 500; - - PRINT("JSON: loop_param_set sent (ch=%d)\n", ps.amount); -} - -/* 4.14 loop_param_query — 读取地感线圈多路参数 (CMD 0x64) */ -static void handle_loop_param_query(uint8_t socket, uint32_t msg_id, const char *json) { - // Send query to Loop MCU and defer response - lup_send_get_param(); - - g_json_pending.active = 1; - g_json_pending.socket = socket; - g_json_pending.msg_id = msg_id; - strncpy(g_json_pending.cmd, "loop_param_query", sizeof(g_json_pending.cmd) - 1); - g_json_pending.deadline = mstick() + 500; - - PRINT("JSON: loop_param_query sent\n"); -} - -/* 4.15 report_config — 配置主动上报开关 */ -static void handle_report_config(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "report_config", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - // Parse active_report flag - reset_tmp(); - simple_parse_json(data, "\"active_report\"", g_tmp_value); - if (strlen(g_tmp_value) > 0) { - if (strstr(g_tmp_value, "true")) { - g_report_active = 1; - } else { - g_report_active = 0; - } - } - free(data); - - json_send_ok(socket, msg_id, "report_config", - g_report_active ? "{\"active_report\":true}" : "{\"active_report\":false}"); - PRINT("JSON: report_config active_report=%d\n", g_report_active); -} - -/* 4.16 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; - g_json_pending.socket = socket; - g_json_pending.msg_id = msg_id; - strncpy(g_json_pending.cmd, "loop_version_query", sizeof(g_json_pending.cmd) - 1); - g_json_pending.deadline = mstick() + 300; - PRINT("JSON: loop_version_query sent\n"); -} - -/* 4.17 loop_reset — 复位地感MCU (CMD 0x6D, 无回复) */ -static void handle_loop_reset(uint8_t socket, uint32_t msg_id, const char *json) { - lup_send_reset(); - json_send_ok(socket, msg_id, "loop_reset", NULL); - PRINT("JSON: loop_reset sent (no response expected)\n"); -} - -/* 4.18 loop_factory_init — 地感MCU出厂初始化 (CMD 0x92) */ -static void handle_loop_factory_init(uint8_t socket, uint32_t msg_id, const char *json) { - lup_send_factory_init(); - g_json_pending.active = 1; - g_json_pending.socket = socket; - g_json_pending.msg_id = msg_id; - strncpy(g_json_pending.cmd, "loop_factory_init", sizeof(g_json_pending.cmd) - 1); - g_json_pending.deadline = mstick() + 600; - PRINT("JSON: loop_factory_init sent\n"); -} - -/* 4.19 loop_sens_read — 读取线圈灵敏度列表 (CMD 0x8A Read) */ -static void handle_loop_sens_read(uint8_t socket, uint32_t msg_id, const char *json) { - lup_send_sensitivity_read(); - g_json_pending.active = 1; - g_json_pending.socket = socket; - g_json_pending.msg_id = msg_id; - strncpy(g_json_pending.cmd, "loop_sens_read", sizeof(g_json_pending.cmd) - 1); - g_json_pending.deadline = mstick() + 300; - PRINT("JSON: loop_sens_read sent\n"); -} - -/* 4.20 loop_sens_write — 写入线圈灵敏度列表 (CMD 0x8A Write) */ -static void handle_loop_sens_write(uint8_t socket, uint32_t msg_id, const char *json) { - char *data = json_get_data_str(json); - if (!data) { - json_send_error(socket, msg_id, "loop_sens_write", JSON_CODE_PARAM_ERR, "missing data"); - return; - } - - LUP_Sensitivity sens; - memset(&sens, 0, sizeof(sens)); - sens.rw = LUP_SENS_RW_WRITE; - - uint32_t amount = json_get_uint_field(data, "\"amount\""); - if (amount == 0 || amount > LUP_COIL_COUNT) amount = LUP_COIL_COUNT; - sens.amount = (uint8_t)amount; - - char key[16]; - uint8_t i; - for (i = 0; i < sens.amount; i++) { - snprintf(key, sizeof(key), "\"ch%d\"", i + 1); - char ch_data[256] = {0}; - reset_tmp(); - simple_parse_json(data, key, g_tmp_value); - if (strlen(g_tmp_value) == 0) continue; - strncpy(ch_data, g_tmp_value, sizeof(ch_data) - 1); - - sens.sens_in[i] = (uint16_t)json_get_uint_field(ch_data, "\"sens_in\""); - sens.sens_out[i] = (uint16_t)json_get_uint_field(ch_data, "\"sens_out\""); - } - free(data); - - uint8_t buf[LUP_MAX_PKG_LEN]; - uint16_t len = lup_build_sensitivity_write(buf, &sens); - UART2_SendString(buf, len); - PRINT("LUP Tx:"); - for (i = 0; i < len; i++) PRINT(" %02X", buf[i]); - PRINT("\n"); - lup_cmd_begin(LUP_CMD_SENSITIVITY, 300); - - g_json_pending.active = 1; - g_json_pending.socket = socket; - g_json_pending.msg_id = msg_id; - strncpy(g_json_pending.cmd, "loop_sens_write", sizeof(g_json_pending.cmd) - 1); - g_json_pending.deadline = mstick() + 300; - PRINT("JSON: loop_sens_write sent (ch=%d)\n", sens.amount); -} - - -/*=========================================================================== - * Command Dispatch Table - *===========================================================================*/ -typedef struct { - const char *cmd; - void (*handler)(uint8_t socket, uint32_t msg_id, const char *json); - uint8_t need_auth; // 1 = requires auth, 0 = pre-auth ok -} JsonCmdEntry; - -static const JsonCmdEntry g_cmd_table[] = { - {"pwd_verify", handle_pwd_verify, 0}, - {"dev_serial_set", handle_dev_serial_set, 1}, - {"dev_info_query", handle_dev_info_query, 1}, - {"ssc_net_set", handle_ssc_net_set, 1}, - {"ssc_net_query", handle_ssc_net_query, 1}, - {"iot_net_set", handle_iot_net_set, 1}, - {"iot_net_query", handle_iot_net_query, 1}, - {"iot_topic_set", handle_iot_topic_set, 1}, - {"iot_topic_query", handle_iot_topic_query, 1}, - {"pwd_set", handle_pwd_set, 1}, - {"factory_reset", handle_factory_reset, 1}, - {"device_reset", handle_device_reset, 1}, - {"loop_param_set", handle_loop_param_set, 1}, - {"loop_param_query", handle_loop_param_query, 1}, - {"loop_version_query", handle_loop_version_query, 1}, - {"loop_reset", handle_loop_reset, 1}, - {"loop_factory_init", handle_loop_factory_init, 1}, - {"loop_sens_read", handle_loop_sens_read, 1}, - {"loop_sens_write", handle_loop_sens_write, 1}, - {"report_config", handle_report_config, 1}, -}; - -#define JSON_CMD_COUNT (sizeof(g_cmd_table) / sizeof(g_cmd_table[0])) - - -/*=========================================================================== - * Frame Processing - *===========================================================================*/ -static void json_process_frame(uint8_t socket, const char *frame) { - if (strlen(frame) == 0) return; - - uint32_t msg_id = json_get_msg_id(frame); - char cmd[32] = {0}; - json_get_cmd(frame, cmd, sizeof(cmd)); - - PRINT("JSON dispatch: msg_id=%lu cmd=[%s] len=%d\n", msg_id, cmd, strlen(cmd)); - - if (strlen(cmd) == 0) { - PRINT("JSON: empty cmd, sending error\n"); - json_send_error(socket, msg_id, "", JSON_CODE_PARAM_ERR, "missing cmd field"); - return; - } - - // Dispatch - int handled = 0; - for (int i = 0; i < JSON_CMD_COUNT; i++) { - if (strcmp(cmd, g_cmd_table[i].cmd) == 0) { - PRINT("JSON: matched cmd[%d]=%s, need_auth=%d, auth_state=%d\n", - i, g_cmd_table[i].cmd, g_cmd_table[i].need_auth, g_json_auth_state); - // Check auth - if (g_cmd_table[i].need_auth && g_json_auth_state != JSON_STATE_AUTHED) { - json_send_error(socket, msg_id, cmd, JSON_CODE_NOT_AUTHED, "not authenticated"); - handled = 1; - break; - } - PRINT("JSON: calling handler for %s\n", cmd); - g_cmd_table[i].handler(socket, msg_id, frame); - handled = 1; - break; - } - } - - if (!handled) { - json_send_error(socket, msg_id, cmd, JSON_CODE_UNSUPPORTED, "unsupported command"); - PRINT("JSON: unsupported cmd: [%s]\n", cmd); - } -} - - -/*=========================================================================== - * json_sensor_callback — 注册到 lup_process_frame,0xC0 帧到达时直接推送 - *===========================================================================*/ - -/* Forward declarations */ -static int format_sensor_json(char *buf, uint16_t buf_size, const LUP_SensorReport *sr); -static int format_loop_param_json(char *buf, uint16_t buf_size, const LUP_ParamGet *pg); - -static void json_sensor_callback(const uint8_t *pkg, uint16_t len) -{ - PRINT("JSON: sensor_cb enter socket=%d auth=%d report=%d\n", - g_json_socket_listen, g_json_auth_state, g_report_active); - - // Check: socket active, authed, report enabled - if (g_json_socket_listen == 0xFF) { PRINT("JSON: sensor_cb skip: no socket\n"); return; } - if (g_json_auth_state != JSON_STATE_AUTHED) { PRINT("JSON: sensor_cb skip: not authed\n"); return; } - if (!g_report_active) { PRINT("JSON: sensor_cb skip: report disabled\n"); return; } - - LUP_SensorReport sr; - memset(&sr, 0, sizeof(sr)); - int ret = lup_parse_sensor_report(pkg, len, &sr); - if (ret != 0) { - PRINT("JSON: sensor callback parse failed (%d)\n", ret); - return; - } - - char data_json[2048]; - if (format_sensor_json(data_json, sizeof(data_json), &sr) == 0) { - char *out = (char *)malloc(TCP_JSON_MAX_FRAME); - if (out) { - snprintf(out, TCP_JSON_MAX_FRAME, - "{\"msg_id\":0,\"cmd\":\"sensor_report\",\"ts\":%lu," - "\"code\":0,\"msg\":\"success\",\"data\":%s}\n", - (unsigned long)mstick(), data_json); - uint32_t slen = strlen(out); - // WCHNET TCP: listen socket=N, data socket=N+1 - WCHNET_SocketSend(g_json_socket_listen + 1, (uint8_t *)out, &slen); - free(out); - } - } -} - -/*=========================================================================== - * Public API - *===========================================================================*/ - -void tcp_json_restart(void) { - PRINT("JSON: === restart (count=%d) ===\n", g_json_restart_count); - - /* - * WCHNET 限制: listen socket 关闭后无法在同端口立即重建 (ERR_ISCONN). - * 因此只关数据 socket 断开客户端, listen socket 保持不变. - */ - if (g_json_socket_listen != 0xFF) { - WCHNET_SocketClose(g_json_socket_listen + 1, TCP_CLOSE_NORMAL); - } - - /* 重置应用层状态 — listen socket 继续监听 */ - g_json_auth_state = JSON_STATE_WAIT_AUTH; - g_json_pwd_retry = 0; - g_json_recv_len = 0; - g_json_last_comm_ts = 0; - g_json_last_connect_ts = 0; - g_json_auth_timer = mstick(); // 从此刻重新计时 - g_json_lockout_timer = 0; - g_json_pending.active = 0; -} - -#define TCP_JSON_SRV_INIT_MAX_RETRY 3 - -void tcp_json_srv_init(void) { - uint8_t ret; - SOCK_INF sock_inf; - uint8_t attempt; - - for (attempt = 0; attempt < TCP_JSON_SRV_INIT_MAX_RETRY; attempt++) { - memset(&sock_inf, 0, sizeof(SOCK_INF)); - sock_inf.SourPort = TCP_JSON_PORT; - sock_inf.ProtoType = PROTO_TYPE_TCP; - sock_inf.RecvBufLen = RECE_BUF_LEN; - - ret = WCHNET_SocketCreat(&g_json_socket_listen, &sock_inf); - if (ret != WCHNET_ERR_SUCCESS) { - PRINT("JSON: SocketCreat attempt %d failed: 0x%02X\n", attempt + 1, ret); - Delay_Ms(100); - continue; - } - - ret = WCHNET_SocketListen(g_json_socket_listen); - if (ret != WCHNET_ERR_SUCCESS) { - PRINT("JSON: SocketListen attempt %d failed: 0x%02X\n", attempt + 1, ret); - WCHNET_SocketClose(g_json_socket_listen, TCP_CLOSE_NORMAL); - g_json_socket_listen = 0xFF; - Delay_Ms(100); - continue; - } - - // 注册传感器回调(幂等,重复调用无副作用) - lup_set_sensor_callback(json_sensor_callback); - g_json_auth_timer = mstick(); // 记录 listen 创建时刻 - - PRINT("JSON: TCP listen on port %d (socket %d)\n", TCP_JSON_PORT, g_json_socket_listen); - return; - } - - PRINT("JSON: SocketCreat/Listen failed after %d retries\n", TCP_JSON_SRV_INIT_MAX_RETRY); -} - -void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat) { - // === CONNECT: client connected — configure recv buffer, init auth state === - if (intstat & SINT_STAT_CONNECT) { - WCHNET_ModifyRecvBuf(socketid, (uint32_t)g_json_wchnet_buf, RECE_BUF_LEN); - g_json_auth_state = JSON_STATE_WAIT_AUTH; - g_json_pwd_retry = 0; - g_json_auth_timer = mstick(); - g_json_recv_len = 0; - g_json_last_connect_ts = mstick(); - g_json_last_comm_ts = mstick(); - g_json_restart_count = 0; // 有连接就清零重启计数 - memset(g_json_recv_buf, 0, sizeof(g_json_recv_buf)); - PRINT("JSON: Client connected on socket %d, recv buf configured\n", socketid); - } - - // === RECV: read and process incoming data === - if (intstat & SINT_STAT_RECV) { - uint32_t recv_len = WCHNET_SocketRecvLen(socketid, NULL); - if (recv_len > 0) { - uint16_t space = TCP_JSON_RECV_BUF_LEN - g_json_recv_len; - if (recv_len > space) recv_len = space; - uint32_t rd_len = recv_len; - uint8_t tmp_buf[RECE_BUF_LEN]; - WCHNET_SocketRecv(socketid, tmp_buf, &rd_len); - g_json_last_comm_ts = mstick(); // 有数据交互 - memcpy(g_json_recv_buf + g_json_recv_len, tmp_buf, (uint16_t)rd_len); - g_json_recv_len += (uint16_t)rd_len; - - char frame[TCP_JSON_MAX_FRAME]; - while (json_extract_frame(g_json_recv_buf, &g_json_recv_len, frame, sizeof(frame))) { - PRINT("JSON recv: %s\n", frame); - json_process_frame(socketid, frame); - if (g_json_auth_state == JSON_STATE_WAIT_AUTH) { - g_json_auth_timer = mstick(); - } - } - if (g_json_recv_len >= TCP_JSON_MAX_FRAME) { - PRINT("JSON: frame overflow, discarding buffer\n"); - g_json_recv_len = 0; - memset(g_json_recv_buf, 0, sizeof(g_json_recv_buf)); - } - } - } - - // === DISCONNECT: client disconnected, reset state === - if (intstat & SINT_STAT_DISCONNECT) { - PRINT("JSON: Client disconnected (id=%d)\n", socketid); - g_json_auth_state = JSON_STATE_WAIT_AUTH; - g_json_pwd_retry = 0; - g_json_recv_len = 0; - } - - // === TIMEOUT: connection timed out === - if (intstat & SINT_STAT_TIM_OUT) { - PRINT("JSON: Client timeout (id=%d)\n", socketid); - g_json_auth_state = JSON_STATE_WAIT_AUTH; - g_json_pwd_retry = 0; - g_json_recv_len = 0; - } -} - -/*=========================================================================== - * Deferred Response Helpers - *===========================================================================*/ - -/* - * Format coil param query response as JSON - */ -static int format_loop_param_json(char *buf, uint16_t buf_size, const LUP_ParamGet *pg) { - const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"}; - char *p = buf; - int remaining = buf_size; - int written; - uint8_t i; - - written = snprintf(p, remaining, - "{\"auto_mode\":%s,\"amount\":%d,\"channels\":[", - pg->auto_mode ? "true" : "false", pg->amount); - if (written < 0 || written >= remaining) return -1; - p += written; remaining -= written; - - for (i = 0; i < pg->amount; i++) { - const LUP_CoilParam *cp = &pg->params[i]; - uint8_t freq_lev = (cp->sensitivity >> 4) & 0x03; - uint8_t sens = cp->sensitivity & 0x0F; - uint8_t out_mode = cp->output_mode & 0x07; - uint8_t safe_mod = (cp->output_mode >> 3) & 0x1F; - uint8_t dir_mode = cp->direction_mode & 0x07; - uint8_t func_mod = (cp->direction_mode >> 4) & 0x0F; - - written = snprintf(p, remaining, - "%s{\"ch\":%d,\"sensitivity\":%d,\"freq_level\":\"%s\"," - "\"loop_delay\":%d,\"output_mode\":%d,\"safe_mode\":%d," - "\"exist_mode\":%d,\"direction_mode\":%d,\"function_mode\":%d," - "\"freq_initial\":%lu,\"freq_current\":%lu,\"freq_diff\":%lu}", - (i > 0) ? "," : "", - i + 1, sens, freq_level_names[freq_lev], - cp->loop_delay, out_mode, safe_mod, - cp->exist_mode, dir_mode, func_mod, - pg->freq[i][0], pg->freq[i][1], pg->freq[i][2]); - if (written < 0 || written >= remaining) return -1; - p += written; remaining -= written; - } - - written = snprintf(p, remaining, "]}"); - if (written < 0 || written >= remaining) return -1; - return 0; -} - -/* - * Format sensor report as JSON - */ -static int format_sensor_json(char *buf, uint16_t buf_size, const LUP_SensorReport *sr) { - const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"}; - char *p = buf; - int remaining = buf_size; - int written; - uint8_t i; - - written = snprintf(p, remaining, - "{\"sens_type\":\"multi_coil\",\"sub_amount\":%d,\"sub_seq\":%d,\"coils\":[", - sr->sub_amount, sr->sub_sequence); - if (written < 0 || written >= remaining) return -1; - p += written; remaining -= written; - - for (i = 0; i < sr->coil_count; i++) { - const LUP_CoilSensor *cs = &sr->coils[i]; - const char *misc_field; - uint32_t misc_val; - - switch (cs->misc_type) { - case 0: - // car_state=0(无车)→通过时间, car_state=1(有车)→车间距 - misc_field = cs->car_state ? "\"gap_ms5\"" : "\"passtime_ms\""; - misc_val = cs->misc.passtime_ms; - break; - case 1: misc_field = "\"cut_amount\""; misc_val = cs->misc.cut_amount; break; - case 2: misc_field = "\"flow_amount\""; misc_val = cs->misc.flow_amount; break; - case 3: misc_field = "\"relay_count\""; misc_val = cs->misc.relay_count; break; - default: misc_field = "\"misc\""; misc_val = 0; break; - } - - written = snprintf(p, remaining, - "%s{\"ch\":%d,\"freq_level\":\"%s\",\"direction\":%d," - "\"freq_type\":\"%s\",\"sensitivity\":%d," - "\"condition\":%d,\"loop_state\":\"%s\",\"car_state\":\"%s\"," - "\"freq\":%lu,\"variation\":%d,%s:%lu}", - (i > 0) ? "," : "", - i + 1, - freq_level_names[cs->freq_level], - cs->direction, - cs->freq_type ? "current" : "initial", - cs->sensitivity, - cs->condition, - cs->loop_state ? "broken" : "normal", - cs->car_state ? "car" : "no_car", - cs->freq, - cs->variation, - misc_field, misc_val); - if (written < 0 || written >= remaining) return -1; - p += written; remaining -= written; - } - - written = snprintf(p, remaining, "]}"); - if (written < 0 || written >= remaining) return -1; - return 0; -} - -/* - * Check for and handle pending Loop MCU deferred responses - */ -static void json_check_pending(void) { - if (!g_json_pending.active) return; - - if (g_lup_cmd.state == LUP_STATE_RESPONSE_READY) { - if (strcmp(g_json_pending.cmd, "loop_param_set") == 0) { - uint8_t success = 0; - int ret = lup_parse_set_param_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &success); - if (ret == 0 && success) { - json_send_ok(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, NULL); - } else { - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, - ret == 0 ? "Loop MCU returned failure" : "Parse error"); - } - } else if (strcmp(g_json_pending.cmd, "loop_param_query") == 0) { - LUP_ParamGet pg; - memset(&pg, 0, sizeof(pg)); - int ret = lup_parse_param_get(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &pg); - if (ret == 0) { - char data_json[2048]; - if (format_loop_param_json(data_json, sizeof(data_json), &pg) == 0) { - json_send_ok(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, data_json); - } else { - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, - "Response too large"); - } - } else { - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, - "Failed to parse Loop MCU response"); - } - } else if (strcmp(g_json_pending.cmd, "loop_version_query") == 0) { - LUP_VersionInfo info; - memset(&info, 0, sizeof(info)); - int ret = lup_parse_version(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &info); - if (ret == 0) { - snprintf(info.version_str, sizeof(info.version_str), - "V%d.%d.%d (HW:%d.%d.%d)", - info.soft_main, info.soft_sub, info.soft_ssub, - info.hard_main, info.hard_sub, info.hard_ssub); - char data_json[256]; - snprintf(data_json, sizeof(data_json), - "{\"soft_ver\":\"%d.%d.%d\",\"hard_ver\":\"%d.%d.%d\"," - "\"version_str\":\"%s\"}", - info.soft_main, info.soft_sub, info.soft_ssub, - info.hard_main, info.hard_sub, info.hard_ssub, - info.version_str); - json_send_ok(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, data_json); - } else { - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, - "Failed to parse version"); - } - } else if (strcmp(g_json_pending.cmd, "loop_factory_init") == 0) { - uint8_t success = 0; - int ret = lup_parse_factory_init_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &success); - if (ret == 0 && success) { - json_send_ok(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, NULL); - } else { - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, - ret == 0 ? "Factory init failed" : "Parse error"); - } - } else if (strcmp(g_json_pending.cmd, "loop_sens_read") == 0) { - LUP_Sensitivity sens; - memset(&sens, 0, sizeof(sens)); - int ret = lup_parse_sensitivity_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &sens); - if (ret == 0) { - char data_json[1024]; - char *p = data_json; - int rem = sizeof(data_json); - int w = snprintf(p, rem, - "{\"ret\":\"%s\",\"amount\":%d,\"channels\":[", - (sens.rw == 0x10) ? "read" : (sens.rw == 0x11) ? "write" : "?", - sens.amount); - p += w; rem -= w; - uint8_t i; - for (i = 0; i < sens.amount; i++) { - w = snprintf(p, rem, - "%s{\"ch\":%d,\"sens_in\":%d,\"sens_out\":%d}", - (i > 0) ? "," : "", i + 1, - sens.sens_in[i], sens.sens_out[i]); - p += w; rem -= w; - } - snprintf(p, rem, "]}"); - json_send_ok(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, data_json); - } else { - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, - "Failed to parse sensitivity"); - } - } else if (strcmp(g_json_pending.cmd, "loop_sens_write") == 0) { - LUP_Sensitivity sens; - memset(&sens, 0, sizeof(sens)); - int ret = lup_parse_sensitivity_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &sens); - if (ret == 0) { - char data_json[1024]; - char *p = data_json; - int rem = sizeof(data_json); - int w = snprintf(p, rem, - "{\"ret\":\"%s\",\"amount\":%d,\"channels\":[", - (sens.rw == 0x10) ? "read" : (sens.rw == 0x11) ? "write" : "?", - sens.amount); - p += w; rem -= w; - uint8_t i; - for (i = 0; i < sens.amount; i++) { - w = snprintf(p, rem, - "%s{\"ch\":%d,\"sens_in\":%d,\"sens_out\":%d}", - (i > 0) ? "," : "", i + 1, - sens.sens_in[i], sens.sens_out[i]); - p += w; rem -= w; - } - snprintf(p, rem, "]}"); - json_send_ok(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, data_json); - } else { - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, - "Failed to parse sensitivity response"); - } - } - - lup_cmd_done(); - g_json_pending.active = 0; - PRINT("JSON: deferred response sent for %s\n", g_json_pending.cmd); - } - else if (g_lup_cmd.state == LUP_STATE_TIMEOUT - || mstick() > g_json_pending.deadline) { - // Timeout - json_send_error(g_json_pending.socket, g_json_pending.msg_id, - g_json_pending.cmd, JSON_CODE_BUSY, - "Loop MCU no response"); - lup_cmd_done(); - g_json_pending.active = 0; - PRINT("JSON: deferred response timeout for %s\n", g_json_pending.cmd); - } -} - -/*=========================================================================== - * tcp_json_poll (updated) - *===========================================================================*/ -void tcp_json_poll(void) { - // Only poll if we have an active connection (socket configured) - if (g_json_socket_listen == 0xFF) return; - - // === 自动重启冷却期检查 === - if (g_json_restart_cooldown > 0) { - if (mstick() < g_json_restart_cooldown) { - // 冷却期内,不做任何操作 - return; - } - g_json_restart_cooldown = 0; - g_json_restart_count = 0; - PRINT("JSON: restart cooldown expired, resetting counter\n"); - } - - // === 超时条件1: 5分钟无连接 === - if (g_json_last_connect_ts == 0) { - // 从未连接过 — 用 auth_timer (listen 创建时设) 作为起点 - if (g_json_auth_state == JSON_STATE_WAIT_AUTH - && mstick() - g_json_auth_timer > TCP_JSON_IDLE_TIMEOUT_MS) { - PRINT("JSON: %dmin no connection → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000); - goto do_restart; - } - } else if (mstick() - g_json_last_connect_ts > TCP_JSON_IDLE_TIMEOUT_MS) { - PRINT("JSON: %dmin since last connect → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000); - goto do_restart; - } - - // === 超时条件2: 5分钟无通信 (仅在有连接/已认证时检查) === - if (g_json_last_comm_ts > 0 - && mstick() - g_json_last_comm_ts > TCP_JSON_IDLE_TIMEOUT_MS) { - PRINT("JSON: %dmin no communication → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000); - goto do_restart; - } - - // Auth: 3次失败在 handle_pwd_verify 中直接 tcp_json_restart() - // 5分钟无通信/无连接由上方 do_restart 路径处理 - - // Lockout timeout check (existing) - if (g_json_auth_state == JSON_STATE_LOCKOUT) { - if (mstick() - g_json_lockout_timer > TCP_JSON_LOCKOUT_TIMEOUT_MS) { - PRINT("JSON: Lockout expired\n"); - g_json_auth_state = JSON_STATE_WAIT_AUTH; - g_json_pwd_retry = 0; - } - } - - // Check for pending Loop MCU deferred responses - json_check_pending(); - return; - -do_restart: - g_json_restart_count++; - g_json_restart_cooldown = 0; - if (g_json_restart_count > TCP_JSON_MAX_RESTART_COUNT) { - // 超过最大重启次数,进入冷却期 - g_json_restart_cooldown = mstick() + TCP_JSON_RESTART_COOLDOWN_MS; - PRINT("JSON: max restart (%d) reached, cooldown %d min\n", - TCP_JSON_MAX_RESTART_COUNT, TCP_JSON_RESTART_COOLDOWN_MS / 60000); - } else { - tcp_json_restart(); - } -} - -/*=========================================================================== - * tcp_json_push_sensor — Push sensor data (0xC0) to connected TCP client - * - * Called from main loop when uart_srv() has a sensor report frame in - * g_pkg_uart_2 that hasn't been consumed by BLE. - *===========================================================================*/ -void tcp_json_push_sensor(void) { - // Check: socket active, authed, report enabled - if (g_json_socket_listen == 0xFF) return; - if (g_json_auth_state != JSON_STATE_AUTHED) return; - if (!g_report_active) return; // 主动上报未开启 - - // Only proceed if g_pkg_uart_2 has data and it's a 0xC0 sensor report - if (g_pkg_uart_2.flag == 0) return; - if (g_pkg_uart_2.pkg[0] != 0x7F) return; - if (g_pkg_uart_2.pkg[3] != 0xC0) return; - - LUP_SensorReport sr; - memset(&sr, 0, sizeof(sr)); - int ret = lup_parse_sensor_report(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &sr); - if (ret != 0) { - PRINT("JSON: sensor parse failed (%d)\n", ret); - InitPkgUart(&g_pkg_uart_2); - return; - } - - char data_json[2048]; - if (format_sensor_json(data_json, sizeof(data_json), &sr) == 0) { - char *out = (char *)malloc(TCP_JSON_MAX_FRAME); - if (out) { - snprintf(out, TCP_JSON_MAX_FRAME, - "{\"msg_id\":0,\"cmd\":\"sensor_report\",\"ts\":%lu," - "\"code\":0,\"msg\":\"success\",\"data\":%s}\n", - (unsigned long)mstick(), data_json); - uint32_t slen = strlen(out); - // WCHNET TCP: listen socket=N, data socket=N+1 - WCHNET_SocketSend(g_json_socket_listen + 1, (uint8_t *)out, &slen); - free(out); - } - } - - // Clear after processing - InitPkgUart(&g_pkg_uart_2); -} +/** + ****************************************************************************** + * @file tcp_json_srv.c + * @author wangfq + * @version V1.0 + * @date 2026-06-30 + * @brief TCP JSON protocol server implementation + * DLD960 TCP JSON protocol (port 5960) — auth + 15 commands + ****************************************************************************** + */ + +#include "CONFIG.h" +#include "tcp_json_srv.h" +#include "eth_driver.h" +#include "wchnet.h" +#include "net_srv.h" +#include "cmcng.h" +#include "loop_uart_proto.h" +#include "simple_json.h" +#include "storage.h" +#include +#include +#include + +/*=========================================================================== + * Global State + *===========================================================================*/ +uint8_t g_json_socket_listen = 0xFF; // TCP socket ID — handles both listen + client data +TcpJsonAuthState g_json_auth_state = JSON_STATE_WAIT_AUTH; +uint32_t g_json_auth_timer = 0; +uint8_t g_json_pwd_retry = 0; +uint32_t g_json_lockout_timer = 0; +TcpJsonPending g_json_pending = {0, 0, 0, "", 0}; +uint8_t g_report_active = 0; // 0=不主动上报, 1=主动上报传感器数据 +uint32_t g_json_last_comm_ts = 0; // 最后通信时间戳 +uint32_t g_json_last_connect_ts = 0; // 最后连接时间戳 +uint8_t g_json_restart_count = 0; // 自动重启计数 +uint32_t g_json_restart_cooldown = 0; // 冷却期截止时间 + +/*=========================================================================== + * Frame Receive Buffer (line-delimited JSON) + *===========================================================================*/ +static uint8_t g_json_recv_buf[TCP_JSON_RECV_BUF_LEN]; +static uint16_t g_json_recv_len = 0; +static uint8_t g_json_wchnet_buf[RECE_BUF_LEN]; // WCHNET internal recv buffer + +/*=========================================================================== + * Internal helpers + *===========================================================================*/ +static char g_tmp_value[256]; // reusable buffer for simple_parse_json + +static void reset_tmp(void) { + memset(g_tmp_value, 0, sizeof(g_tmp_value)); +} + +static void json_send_error(uint8_t socket, uint32_t msg_id, const char *cmd, + int code, const char *msg) { + char *out = (char *)malloc(256); + if (!out) return; + snprintf(out, 256, + "{\"msg_id\":%lu,\"cmd\":\"%s\",\"ts\":%lu,\"code\":%d,\"msg\":\"%s\"}\n", + msg_id, cmd ? cmd : "", (unsigned long)mstick(), code, msg); + uint32_t slen = strlen(out); + WCHNET_SocketSend(socket, (uint8_t *)out, &slen); + free(out); +} + +static void json_send_ok(uint8_t socket, uint32_t msg_id, const char *cmd, + const char *data_json) { + char *out = (char *)malloc(TCP_JSON_MAX_FRAME); + if (!out) return; + if (data_json && strlen(data_json) > 0) { + snprintf(out, TCP_JSON_MAX_FRAME, + "{\"msg_id\":%lu,\"cmd\":\"%s\",\"ts\":%lu,\"code\":0,\"msg\":\"success\",\"data\":%s}\n", + msg_id, cmd, (unsigned long)mstick(), data_json); + } else { + snprintf(out, TCP_JSON_MAX_FRAME, + "{\"msg_id\":%lu,\"cmd\":\"%s\",\"ts\":%lu,\"code\":0,\"msg\":\"success\"}\n", + msg_id, cmd, (unsigned long)mstick()); + } + uint32_t slen = strlen(out); + WCHNET_SocketSend(socket, (uint8_t *)out, &slen); + free(out); +} + +/* JSON field extractors — parse msg_id, cmd, and data object from raw JSON */ +static uint32_t json_get_msg_id(const char *json) { + reset_tmp(); + simple_parse_json(json, "\"msg_id\"", g_tmp_value); + if (strlen(g_tmp_value) == 0) return 0; + return (uint32_t)strtoul(g_tmp_value, NULL, 10); +} + + +static char *json_get_data_str(const char *json) { + char *data = (char *)malloc(TCP_JSON_MAX_FRAME); + if (!data) return NULL; + memset(data, 0, TCP_JSON_MAX_FRAME); + simple_parse_json(json, "\"data\"", data); + if (strlen(data) == 0) { + free(data); + return NULL; + } + return data; +} + +static void json_get_str_field(const char *json, const char *key, char *out, int out_len) { + reset_tmp(); + simple_parse_json(json, key, g_tmp_value); + // strip surrounding quotes if present + char *src = g_tmp_value; + if (src[0] == '"') src++; + int len = strlen(src); + if (len > 0 && src[len - 1] == '"') len--; + int copy_len = (len < out_len - 1) ? len : out_len - 1; + memcpy(out, src, copy_len); + out[copy_len] = '\0'; +} + +static void json_get_cmd(const char *json, char *out, int out_len) { + json_get_str_field(json, "\"cmd\"", out, out_len); +} + +static uint32_t json_get_uint_field(const char *json, const char *key) { + reset_tmp(); + simple_parse_json(json, key, g_tmp_value); + if (strlen(g_tmp_value) == 0) return 0; + return (uint32_t)strtoul(g_tmp_value, NULL, 10); +} + +static uint8_t json_get_bool_field(const char *json, const char *key) { + reset_tmp(); + simple_parse_json(json, key, g_tmp_value); + if (strstr(g_tmp_value, "true")) return 1; + return 0; +} + + +/*=========================================================================== + * Frame parser: extract line-delimited JSON frames from receive buffer + *===========================================================================*/ +static int json_extract_frame(uint8_t *buf, uint16_t *len, char *frame_out, uint16_t frame_size) { + uint16_t i; + for (i = 0; i < *len; i++) { + if (buf[i] == '\n') { + uint16_t frame_len = i; + if (frame_len >= frame_size) frame_len = frame_size - 1; + memcpy(frame_out, buf, frame_len); + frame_out[frame_len] = '\0'; + + // Remove consumed frame from buffer + uint16_t remaining = *len - i - 1; + if (remaining > 0) { + memmove(buf, buf + i + 1, remaining); + } + *len = remaining; + return 1; + } + } + return 0; // no complete frame yet +} + + +/*=========================================================================== + * Command Handlers + *===========================================================================*/ + +/* 4.1 pwd_verify */ +static void handle_pwd_verify(uint8_t socket, uint32_t msg_id, const char *json) { + PRINT("JSON: pwd_verify handler entered\n"); + char *data = json_get_data_str(json); + if (!data) { + PRINT("JSON: pwd_verify — data is NULL\n"); + json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + PRINT("JSON: pwd_verify — data=[%s]\n", data); + + char password[16] = {0}; + json_get_str_field(data, "\"password\"", password, sizeof(password)); + free(data); + + PRINT("JSON: pwd_verify — password=[%s] len=%d\n", password, strlen(password)); + PRINT("JSON: pwd_verify — dev_pwd=[%c%c%c%c%c%c]\n", + g_dev_password[0], g_dev_password[1], g_dev_password[2], + g_dev_password[3], g_dev_password[4], g_dev_password[5]); + + if (strlen(password) == 0) { + json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_PARAM_ERR, "missing password"); + return; + } + + // Compare password + if (memcmp(password, g_dev_password, 6) == 0 && strlen(password) == 6) { + g_json_auth_state = JSON_STATE_AUTHED; + g_json_pwd_retry = 0; + g_json_auth_timer = 0; + json_send_ok(socket, msg_id, "pwd_verify", NULL); + PRINT("JSON: Auth success\n"); + } else { + g_json_pwd_retry++; + if (g_json_pwd_retry >= TCP_JSON_MAX_PWD_RETRY) { + PRINT("JSON: Auth failed %d times → restart\n", g_json_pwd_retry); + // 先发错误再重启,让 TCP 栈有时间 flush + json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_AUTH_FAIL, + "password incorrect, connection will restart"); + Delay_Ms(100); + tcp_json_restart(); + } else { + json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_AUTH_FAIL, "password incorrect"); + } + } +} + +/* 4.2 dev_serial_set */ +static void handle_dev_serial_set(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "dev_serial_set", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + char serial[32] = {0}; + json_get_str_field(data, "\"dev_serial\"", serial, sizeof(serial)); + free(data); + + if (strlen(serial) != 12) { + json_send_error(socket, msg_id, "dev_serial_set", JSON_CODE_PARAM_ERR, "invalid serial length"); + return; + } + + // Convert hex string to bytes + uint8_t serial_bytes[6]; + for (int i = 0; i < 6; i++) { + char hex[3] = {serial[i * 2], serial[i * 2 + 1], '\0'}; + serial_bytes[i] = (uint8_t)strtoul(hex, NULL, 16); + } + + alter_dev_serila(serial_bytes); + memcpy(g_dev_number, serial_bytes, 6); + sprintf(g_dev_number_str, "%02X%02X%02X%02X%02X%02X", + g_dev_number[0], g_dev_number[1], g_dev_number[2], + g_dev_number[3], g_dev_number[4], g_dev_number[5]); + + json_send_ok(socket, msg_id, "dev_serial_set", NULL); + PRINT("JSON: dev_serial_set -> %s\n", g_dev_number_str); +} + +/* 4.3 dev_info_query */ +static void handle_dev_info_query(uint8_t socket, uint32_t msg_id, const char *json) { + char data_json[512]; + snprintf(data_json, sizeof(data_json), + "{\"dev_serial\":\"%s\",\"hard_ver\":\"%s\",\"soft_ver\":\"%s\"," + "\"model\":\"%s\",\"product_code\":\"960001\"," + "\"sub_code\":{\"net\":%s,\"iot\":%s}," + "\"bus\":{\"bus1\":0,\"bus2\":0,\"bus3\":0,\"bus4\":0}}", + g_dev_number_str, HARDWARE_VER, FIRMWARE_VER, + PRODUCT_MODEL, + g_sub_code_enable.net_enable ? "true" : "false", + g_sub_code_enable.iot_enable ? "true" : "false"); + + json_send_ok(socket, msg_id, "dev_info_query", data_json); +} + +/* 4.4 ssc_net_set */ +static void handle_ssc_net_set(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "ssc_net_set", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + Local_Net_Cfg lcfg = local_net_cfg; // copy current + NET_CENTER_INFO ccfg = net_center_info; + + char ip_str[16] = {0}; + json_get_str_field(data, "\"dev_ip\"", ip_str, sizeof(ip_str)); + if (strlen(ip_str) > 0) get_ipstr_to_array(ip_str, lcfg.lip); + + char mask_str[16] = {0}; + json_get_str_field(data, "\"subnet_mask\"", mask_str, sizeof(mask_str)); + if (strlen(mask_str) > 0) get_ipstr_to_array(mask_str, lcfg.sub); + + char gw_str[16] = {0}; + json_get_str_field(data, "\"route_ip\"", gw_str, sizeof(gw_str)); + if (strlen(gw_str) > 0) get_ipstr_to_array(gw_str, lcfg.gw); + + char lssc_ip_str[16] = {0}; + json_get_str_field(data, "\"lssc_ip\"", lssc_ip_str, sizeof(lssc_ip_str)); + if (strlen(lssc_ip_str) > 0) get_ipstr_to_array(lssc_ip_str, ccfg.lssc_ip); + + char dns_str[16] = {0}; + json_get_str_field(data, "\"dns\"", dns_str, sizeof(dns_str)); + if (strlen(dns_str) > 0) get_ipstr_to_array(dns_str, lcfg.dns); + + uint32_t port = json_get_uint_field(data, "\"port\""); + if (port > 0 && port <= 65535) ccfg.tcp_port = (uint16_t)port; + + free(data); + + // Write to flash and update globals + write_net_config(&lcfg, &ccfg, &iot_net_info, &g_iot_topic); + local_net_cfg = lcfg; + net_center_info = ccfg; + + json_send_ok(socket, msg_id, "ssc_net_set", NULL); + PRINT("JSON: ssc_net_set done\n"); +} + +/* 4.5 ssc_net_query */ +static void handle_ssc_net_query(uint8_t socket, uint32_t msg_id, const char *json) { + char data_json[400]; + snprintf(data_json, sizeof(data_json), + "{\"dev_ip\":\"%d.%d.%d.%d\",\"subnet_mask\":\"%d.%d.%d.%d\"," + "\"route_ip\":\"%d.%d.%d.%d\",\"lssc_ip\":\"%d.%d.%d.%d\"," + "\"dns\":\"%d.%d.%d.%d\",\"port\":%d}", + local_net_cfg.lip[0], local_net_cfg.lip[1], local_net_cfg.lip[2], local_net_cfg.lip[3], + local_net_cfg.sub[0], local_net_cfg.sub[1], local_net_cfg.sub[2], local_net_cfg.sub[3], + local_net_cfg.gw[0], local_net_cfg.gw[1], local_net_cfg.gw[2], local_net_cfg.gw[3], + net_center_info.lssc_ip[0], net_center_info.lssc_ip[1], + net_center_info.lssc_ip[2], net_center_info.lssc_ip[3], + local_net_cfg.dns[0], local_net_cfg.dns[1], local_net_cfg.dns[2], local_net_cfg.dns[3], + net_center_info.tcp_port); + + json_send_ok(socket, msg_id, "ssc_net_query", data_json); +} + +/* 4.6 iot_net_set */ +static void handle_iot_net_set(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "iot_net_set", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + IOT_NET_INFO icfg = iot_net_info; + + char host[64] = {0}; + json_get_str_field(data, "\"host\"", host, sizeof(host)); + if (strlen(host) > 0) strncpy((char *)icfg.remote_addr, host, 63); + + uint32_t port = json_get_uint_field(data, "\"port\""); + if (port > 0 && port <= 65535) icfg.mqtt_port = (uint16_t)port; + + char client_id[64] = {0}; + json_get_str_field(data, "\"client_id\"", client_id, sizeof(client_id)); + if (strlen(client_id) > 0) strncpy((char *)icfg.client_id, client_id, 63); + + char username[64] = {0}; + json_get_str_field(data, "\"username\"", username, sizeof(username)); + if (strlen(username) > 0) strncpy((char *)icfg.username, username, 63); + + char password[32] = {0}; + json_get_str_field(data, "\"password\"", password, sizeof(password)); + if (strlen(password) > 0) strncpy((char *)icfg.password, password, 31); + + free(data); + + write_net_config(&local_net_cfg, &net_center_info, &icfg, &g_iot_topic); + iot_net_info = icfg; + + json_send_ok(socket, msg_id, "iot_net_set", NULL); + PRINT("JSON: iot_net_set done\n"); +} + +/* 4.7 iot_net_query */ +static void handle_iot_net_query(uint8_t socket, uint32_t msg_id, const char *json) { + char data_json[400]; + snprintf(data_json, sizeof(data_json), + "{\"host\":\"%s\",\"port\":%d,\"client_id\":\"%s\"," + "\"username\":\"%s\",\"password\":\"%s\"}", + iot_net_info.remote_addr, iot_net_info.mqtt_port, + iot_net_info.client_id, iot_net_info.username, iot_net_info.password); + + json_send_ok(socket, msg_id, "iot_net_query", data_json); +} + +/* 4.8 iot_topic_set */ +static void handle_iot_topic_set(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "iot_topic_set", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + IOT_Topic topic = g_iot_topic; + + topic.clientid_enable = json_get_bool_field(data, "\"client_id_enable\""); + + char topic_pub[64] = {0}; + json_get_str_field(data, "\"topic_pub\"", topic_pub, sizeof(topic_pub)); + if (strlen(topic_pub) > 0) strncpy((char *)topic.topic_pub, topic_pub, 63); + + char topic_sub[64] = {0}; + json_get_str_field(data, "\"topic_sub\"", topic_sub, sizeof(topic_sub)); + if (strlen(topic_sub) > 0) strncpy((char *)topic.topic_sub, topic_sub, 63); + + free(data); + + write_net_config(&local_net_cfg, &net_center_info, &iot_net_info, &topic); + g_iot_topic = topic; + + json_send_ok(socket, msg_id, "iot_topic_set", NULL); + PRINT("JSON: iot_topic_set done\n"); +} + +/* 4.9 iot_topic_query */ +static void handle_iot_topic_query(uint8_t socket, uint32_t msg_id, const char *json) { + char data_json[300]; + snprintf(data_json, sizeof(data_json), + "{\"client_id_enable\":%s,\"topic_pub\":\"%s\",\"topic_sub\":\"%s\"}", + g_iot_topic.clientid_enable ? "true" : "false", + g_iot_topic.topic_pub, g_iot_topic.topic_sub); + + json_send_ok(socket, msg_id, "iot_topic_query", data_json); +} + +/* 4.10 pwd_set */ +static void handle_pwd_set(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "pwd_set", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + char old_pwd[16] = {0}, new_pwd[16] = {0}; + json_get_str_field(data, "\"old_password\"", old_pwd, sizeof(old_pwd)); + json_get_str_field(data, "\"new_password\"", new_pwd, sizeof(new_pwd)); + free(data); + + if (strlen(old_pwd) != 6 || strlen(new_pwd) != 6) { + json_send_error(socket, msg_id, "pwd_set", JSON_CODE_PARAM_ERR, "password must be 6 digits"); + return; + } + + if (memcmp(old_pwd, g_dev_password, 6) != 0) { + json_send_error(socket, msg_id, "pwd_set", JSON_CODE_AUTH_FAIL, "old password incorrect"); + return; + } + + memcpy(g_dev_password, new_pwd, 6); + set_ble_safe_pass((uint8_t *)new_pwd); + + json_send_ok(socket, msg_id, "pwd_set", NULL); + PRINT("JSON: pwd_set done\n"); +} + +/* 4.11 factory_reset */ +static void handle_factory_reset(uint8_t socket, uint32_t msg_id, const char *json) { + json_send_ok(socket, msg_id, "factory_reset", NULL); + PRINT("JSON: factory_reset — resetting...\n"); + + Delay_Ms(100); + factory_dev_info(); + Delay_Ms(100); + NVIC_SystemReset(); +} + +/* 4.12 device_reset */ +static void handle_device_reset(uint8_t socket, uint32_t msg_id, const char *json) { + // No response — device resets immediately + char *out = (char *)malloc(128); + if (out) { + snprintf(out, 128, + "{\"msg_id\":%lu,\"cmd\":\"device_reset\",\"ts\":%lu,\"code\":0,\"msg\":\"resetting\"}\n", + msg_id, (unsigned long)mstick()); + uint32_t slen = strlen(out); + WCHNET_SocketSend(socket, (uint8_t *)out, &slen); + free(out); + } + Delay_Ms(100); + NVIC_SystemReset(); +} + +/* 4.13 loop_param_set — 设置地感线圈多路参数 (CMD 0x63) */ +static void handle_loop_param_set(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "loop_param_set", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + LUP_ParamSet ps; + memset(&ps, 0, sizeof(ps)); + + ps.auto_mode = (uint8_t)json_get_uint_field(data, "\"auto_mode\""); + + // Parse channels array + // Use a simple approach: parse top-level fields for each known channel key + uint32_t amount = json_get_uint_field(data, "\"amount\""); + if (amount == 0 || amount > LUP_COIL_COUNT) { + amount = LUP_COIL_COUNT; + } + ps.amount = (uint8_t)amount; + + // Parse per-channel data + char ch_key[16]; + uint8_t ch; + for (ch = 0; ch < ps.amount; ch++) { + snprintf(ch_key, sizeof(ch_key), "\"ch%d\"", ch + 1); + + char ch_data[512] = {0}; + reset_tmp(); + simple_parse_json(data, ch_key, g_tmp_value); + if (strlen(g_tmp_value) == 0) continue; + strncpy(ch_data, g_tmp_value, sizeof(ch_data) - 1); + + LUP_CoilParam *cp = &ps.params[ch]; + + // sensitivity: 低4位=灵敏度, 高4位=freq_level + uint32_t sens_val = json_get_uint_field(ch_data, "\"sensitivity\""); + uint32_t freq_lev = json_get_uint_field(ch_data, "\"freq_level\""); // 0~3 + cp->sensitivity = ((uint8_t)(freq_lev & 0x03) << 4) | (uint8_t)(sens_val & 0x0F); + + cp->loop_delay = (uint8_t)json_get_uint_field(ch_data, "\"loop_delay\""); + + // output_mode: 低3位=输出方式, 高5位=SafeMode + uint32_t out_mode = json_get_uint_field(ch_data, "\"output_mode\""); + uint32_t safe_mode = json_get_uint_field(ch_data, "\"safe_mode\""); + cp->output_mode = ((uint8_t)(safe_mode & 0x1F) << 3) | (uint8_t)(out_mode & 0x07); + + cp->exist_mode = (uint8_t)json_get_uint_field(ch_data, "\"exist_mode\""); + + // direction_mode: 低3位=方向模式, 高4位=Function_Mode + uint32_t dir_mode = json_get_uint_field(ch_data, "\"direction_mode\""); + uint32_t func_mode = json_get_uint_field(ch_data, "\"function_mode\""); + cp->direction_mode = ((uint8_t)(func_mode & 0x0F) << 4) | (uint8_t)(dir_mode & 0x07); + } + free(data); + + // Send to Loop MCU and defer response + lup_send_set_param(&ps); + + // Save pending state for async response + g_json_pending.active = 1; + g_json_pending.socket = socket; + g_json_pending.msg_id = msg_id; + strncpy(g_json_pending.cmd, "loop_param_set", sizeof(g_json_pending.cmd) - 1); + g_json_pending.deadline = mstick() + 500; + + PRINT("JSON: loop_param_set sent (ch=%d)\n", ps.amount); +} + +/* 4.14 loop_param_query — 读取地感线圈多路参数 (CMD 0x64) */ +static void handle_loop_param_query(uint8_t socket, uint32_t msg_id, const char *json) { + // Send query to Loop MCU and defer response + lup_send_get_param(); + + g_json_pending.active = 1; + g_json_pending.socket = socket; + g_json_pending.msg_id = msg_id; + strncpy(g_json_pending.cmd, "loop_param_query", sizeof(g_json_pending.cmd) - 1); + g_json_pending.deadline = mstick() + 500; + + PRINT("JSON: loop_param_query sent\n"); +} + +/* 4.15 report_config — 配置主动上报开关 */ +static void handle_report_config(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "report_config", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + // Parse active_report flag + reset_tmp(); + simple_parse_json(data, "\"active_report\"", g_tmp_value); + if (strlen(g_tmp_value) > 0) { + if (strstr(g_tmp_value, "true")) { + g_report_active = 1; + } else { + g_report_active = 0; + } + } + free(data); + + json_send_ok(socket, msg_id, "report_config", + g_report_active ? "{\"active_report\":true}" : "{\"active_report\":false}"); + PRINT("JSON: report_config active_report=%d\n", g_report_active); +} + +/* 4.16 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; + g_json_pending.socket = socket; + g_json_pending.msg_id = msg_id; + strncpy(g_json_pending.cmd, "loop_version_query", sizeof(g_json_pending.cmd) - 1); + g_json_pending.deadline = mstick() + 300; + PRINT("JSON: loop_version_query sent\n"); +} + +/* 4.17 loop_reset — 复位地感MCU (CMD 0x6D, 无回复) */ +static void handle_loop_reset(uint8_t socket, uint32_t msg_id, const char *json) { + lup_send_reset(); + json_send_ok(socket, msg_id, "loop_reset", NULL); + PRINT("JSON: loop_reset sent (no response expected)\n"); +} + +/* 4.18 loop_factory_init — 地感MCU出厂初始化 (CMD 0x92) */ +static void handle_loop_factory_init(uint8_t socket, uint32_t msg_id, const char *json) { + lup_send_factory_init(); + g_json_pending.active = 1; + g_json_pending.socket = socket; + g_json_pending.msg_id = msg_id; + strncpy(g_json_pending.cmd, "loop_factory_init", sizeof(g_json_pending.cmd) - 1); + g_json_pending.deadline = mstick() + 600; + PRINT("JSON: loop_factory_init sent\n"); +} + +/* 4.19 loop_sens_read — 读取线圈灵敏度列表 (CMD 0x8A Read) */ +static void handle_loop_sens_read(uint8_t socket, uint32_t msg_id, const char *json) { + lup_send_sensitivity_read(); + g_json_pending.active = 1; + g_json_pending.socket = socket; + g_json_pending.msg_id = msg_id; + strncpy(g_json_pending.cmd, "loop_sens_read", sizeof(g_json_pending.cmd) - 1); + g_json_pending.deadline = mstick() + 300; + PRINT("JSON: loop_sens_read sent\n"); +} + +/* 4.20 loop_sens_write — 写入线圈灵敏度列表 (CMD 0x8A Write) */ +static void handle_loop_sens_write(uint8_t socket, uint32_t msg_id, const char *json) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "loop_sens_write", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + LUP_Sensitivity sens; + memset(&sens, 0, sizeof(sens)); + sens.rw = LUP_SENS_RW_WRITE; + + uint32_t amount = json_get_uint_field(data, "\"amount\""); + if (amount == 0 || amount > LUP_COIL_COUNT) amount = LUP_COIL_COUNT; + sens.amount = (uint8_t)amount; + + char key[16]; + uint8_t i; + for (i = 0; i < sens.amount; i++) { + snprintf(key, sizeof(key), "\"ch%d\"", i + 1); + char ch_data[256] = {0}; + reset_tmp(); + simple_parse_json(data, key, g_tmp_value); + if (strlen(g_tmp_value) == 0) continue; + strncpy(ch_data, g_tmp_value, sizeof(ch_data) - 1); + + sens.sens_in[i] = (uint16_t)json_get_uint_field(ch_data, "\"sens_in\""); + sens.sens_out[i] = (uint16_t)json_get_uint_field(ch_data, "\"sens_out\""); + } + free(data); + + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t len = lup_build_sensitivity_write(buf, &sens); + UART2_SendString(buf, len); + PRINT("LUP Tx:"); + for (i = 0; i < len; i++) PRINT(" %02X", buf[i]); + PRINT("\n"); + lup_cmd_begin(LUP_CMD_SENSITIVITY, 300); + + g_json_pending.active = 1; + g_json_pending.socket = socket; + g_json_pending.msg_id = msg_id; + strncpy(g_json_pending.cmd, "loop_sens_write", sizeof(g_json_pending.cmd) - 1); + g_json_pending.deadline = mstick() + 300; + PRINT("JSON: loop_sens_write sent (ch=%d)\n", sens.amount); +} + + +/*=========================================================================== + * Command Dispatch Table + *===========================================================================*/ +typedef struct { + const char *cmd; + void (*handler)(uint8_t socket, uint32_t msg_id, const char *json); + uint8_t need_auth; // 1 = requires auth, 0 = pre-auth ok +} JsonCmdEntry; + +static const JsonCmdEntry g_cmd_table[] = { + {"pwd_verify", handle_pwd_verify, 0}, + {"dev_serial_set", handle_dev_serial_set, 1}, + {"dev_info_query", handle_dev_info_query, 1}, + {"ssc_net_set", handle_ssc_net_set, 1}, + {"ssc_net_query", handle_ssc_net_query, 1}, + {"iot_net_set", handle_iot_net_set, 1}, + {"iot_net_query", handle_iot_net_query, 1}, + {"iot_topic_set", handle_iot_topic_set, 1}, + {"iot_topic_query", handle_iot_topic_query, 1}, + {"pwd_set", handle_pwd_set, 1}, + {"factory_reset", handle_factory_reset, 1}, + {"device_reset", handle_device_reset, 1}, + {"loop_param_set", handle_loop_param_set, 1}, + {"loop_param_query", handle_loop_param_query, 1}, + {"loop_version_query", handle_loop_version_query, 1}, + {"loop_reset", handle_loop_reset, 1}, + {"loop_factory_init", handle_loop_factory_init, 1}, + {"loop_sens_read", handle_loop_sens_read, 1}, + {"loop_sens_write", handle_loop_sens_write, 1}, + {"report_config", handle_report_config, 1}, +}; + +#define JSON_CMD_COUNT (sizeof(g_cmd_table) / sizeof(g_cmd_table[0])) + + +/*=========================================================================== + * Frame Processing + *===========================================================================*/ +static void json_process_frame(uint8_t socket, const char *frame) { + if (strlen(frame) == 0) return; + + uint32_t msg_id = json_get_msg_id(frame); + char cmd[32] = {0}; + json_get_cmd(frame, cmd, sizeof(cmd)); + + PRINT("JSON dispatch: msg_id=%lu cmd=[%s] len=%d\n", msg_id, cmd, strlen(cmd)); + + if (strlen(cmd) == 0) { + PRINT("JSON: empty cmd, sending error\n"); + json_send_error(socket, msg_id, "", JSON_CODE_PARAM_ERR, "missing cmd field"); + return; + } + + // Dispatch + int handled = 0; + for (int i = 0; i < JSON_CMD_COUNT; i++) { + if (strcmp(cmd, g_cmd_table[i].cmd) == 0) { + PRINT("JSON: matched cmd[%d]=%s, need_auth=%d, auth_state=%d\n", + i, g_cmd_table[i].cmd, g_cmd_table[i].need_auth, g_json_auth_state); + // Check auth + if (g_cmd_table[i].need_auth && g_json_auth_state != JSON_STATE_AUTHED) { + json_send_error(socket, msg_id, cmd, JSON_CODE_NOT_AUTHED, "not authenticated"); + handled = 1; + break; + } + PRINT("JSON: calling handler for %s\n", cmd); + g_cmd_table[i].handler(socket, msg_id, frame); + handled = 1; + break; + } + } + + if (!handled) { + json_send_error(socket, msg_id, cmd, JSON_CODE_UNSUPPORTED, "unsupported command"); + PRINT("JSON: unsupported cmd: [%s]\n", cmd); + } +} + + +/*=========================================================================== + * json_sensor_callback — 注册到 lup_process_frame,0xC0 帧到达时直接推送 + *===========================================================================*/ + +/* Forward declarations */ +static int format_sensor_json(char *buf, uint16_t buf_size, const LUP_SensorReport *sr); +static int format_loop_param_json(char *buf, uint16_t buf_size, const LUP_ParamGet *pg); + +static void json_sensor_callback(const uint8_t *pkg, uint16_t len) +{ + PRINT("JSON: sensor_cb enter socket=%d auth=%d report=%d\n", + g_json_socket_listen, g_json_auth_state, g_report_active); + + // Check: socket active, authed, report enabled + if (g_json_socket_listen == 0xFF) { PRINT("JSON: sensor_cb skip: no socket\n"); return; } + if (g_json_auth_state != JSON_STATE_AUTHED) { PRINT("JSON: sensor_cb skip: not authed\n"); return; } + if (!g_report_active) { PRINT("JSON: sensor_cb skip: report disabled\n"); return; } + + LUP_SensorReport sr; + memset(&sr, 0, sizeof(sr)); + int ret = lup_parse_sensor_report(pkg, len, &sr); + if (ret != 0) { + PRINT("JSON: sensor callback parse failed (%d)\n", ret); + return; + } + + char data_json[2048]; + if (format_sensor_json(data_json, sizeof(data_json), &sr) == 0) { + char *out = (char *)malloc(TCP_JSON_MAX_FRAME); + if (out) { + snprintf(out, TCP_JSON_MAX_FRAME, + "{\"msg_id\":0,\"cmd\":\"sensor_report\",\"ts\":%lu," + "\"code\":0,\"msg\":\"success\",\"data\":%s}\n", + (unsigned long)mstick(), data_json); + uint32_t slen = strlen(out); + // WCHNET TCP: listen socket=N, data socket=N+1 + WCHNET_SocketSend(g_json_socket_listen + 1, (uint8_t *)out, &slen); + free(out); + } + } +} + +/*=========================================================================== + * Public API + *===========================================================================*/ + +void tcp_json_restart(void) { + PRINT("JSON: === restart (count=%d) ===\n", g_json_restart_count); + + /* + * WCHNET 限制: listen socket 关闭后无法在同端口立即重建 (ERR_ISCONN). + * 因此只关数据 socket 断开客户端, listen socket 保持不变. + */ + if (g_json_socket_listen != 0xFF) { + WCHNET_SocketClose(g_json_socket_listen + 1, TCP_CLOSE_NORMAL); + } + + /* 重置应用层状态 — listen socket 继续监听 */ + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_recv_len = 0; + g_json_last_comm_ts = 0; + g_json_last_connect_ts = 0; + g_json_auth_timer = mstick(); // 从此刻重新计时 + g_json_lockout_timer = 0; + g_json_pending.active = 0; +} + +#define TCP_JSON_SRV_INIT_MAX_RETRY 3 + +void tcp_json_srv_init(void) { + uint8_t ret; + SOCK_INF sock_inf; + uint8_t attempt; + + for (attempt = 0; attempt < TCP_JSON_SRV_INIT_MAX_RETRY; attempt++) { + memset(&sock_inf, 0, sizeof(SOCK_INF)); + sock_inf.SourPort = TCP_JSON_PORT; + sock_inf.ProtoType = PROTO_TYPE_TCP; + sock_inf.RecvBufLen = RECE_BUF_LEN; + + ret = WCHNET_SocketCreat(&g_json_socket_listen, &sock_inf); + if (ret != WCHNET_ERR_SUCCESS) { + PRINT("JSON: SocketCreat attempt %d failed: 0x%02X\n", attempt + 1, ret); + Delay_Ms(100); + continue; + } + + ret = WCHNET_SocketListen(g_json_socket_listen); + if (ret != WCHNET_ERR_SUCCESS) { + PRINT("JSON: SocketListen attempt %d failed: 0x%02X\n", attempt + 1, ret); + WCHNET_SocketClose(g_json_socket_listen, TCP_CLOSE_NORMAL); + g_json_socket_listen = 0xFF; + Delay_Ms(100); + continue; + } + + // 注册传感器回调(幂等,重复调用无副作用) + lup_set_sensor_callback(json_sensor_callback); + g_json_auth_timer = mstick(); // 记录 listen 创建时刻 + + PRINT("JSON: TCP listen on port %d (socket %d)\n", TCP_JSON_PORT, g_json_socket_listen); + return; + } + + PRINT("JSON: SocketCreat/Listen failed after %d retries\n", TCP_JSON_SRV_INIT_MAX_RETRY); +} + +void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat) { + // === CONNECT: client connected — configure recv buffer, init auth state === + if (intstat & SINT_STAT_CONNECT) { + WCHNET_ModifyRecvBuf(socketid, (uint32_t)g_json_wchnet_buf, RECE_BUF_LEN); + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_auth_timer = mstick(); + g_json_recv_len = 0; + g_json_last_connect_ts = mstick(); + g_json_last_comm_ts = mstick(); + g_json_restart_count = 0; // 有连接就清零重启计数 + memset(g_json_recv_buf, 0, sizeof(g_json_recv_buf)); + PRINT("JSON: Client connected on socket %d, recv buf configured\n", socketid); + } + + // === RECV: read and process incoming data === + if (intstat & SINT_STAT_RECV) { + uint32_t recv_len = WCHNET_SocketRecvLen(socketid, NULL); + if (recv_len > 0) { + uint16_t space = TCP_JSON_RECV_BUF_LEN - g_json_recv_len; + if (recv_len > space) recv_len = space; + uint32_t rd_len = recv_len; + uint8_t tmp_buf[RECE_BUF_LEN]; + WCHNET_SocketRecv(socketid, tmp_buf, &rd_len); + g_json_last_comm_ts = mstick(); // 有数据交互 + memcpy(g_json_recv_buf + g_json_recv_len, tmp_buf, (uint16_t)rd_len); + g_json_recv_len += (uint16_t)rd_len; + + char frame[TCP_JSON_MAX_FRAME]; + while (json_extract_frame(g_json_recv_buf, &g_json_recv_len, frame, sizeof(frame))) { + PRINT("JSON recv: %s\n", frame); + json_process_frame(socketid, frame); + if (g_json_auth_state == JSON_STATE_WAIT_AUTH) { + g_json_auth_timer = mstick(); + } + } + if (g_json_recv_len >= TCP_JSON_MAX_FRAME) { + PRINT("JSON: frame overflow, discarding buffer\n"); + g_json_recv_len = 0; + memset(g_json_recv_buf, 0, sizeof(g_json_recv_buf)); + } + } + } + + // === DISCONNECT: client disconnected, reset state === + if (intstat & SINT_STAT_DISCONNECT) { + PRINT("JSON: Client disconnected (id=%d)\n", socketid); + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_recv_len = 0; + } + + // === TIMEOUT: connection timed out === + if (intstat & SINT_STAT_TIM_OUT) { + PRINT("JSON: Client timeout (id=%d)\n", socketid); + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_recv_len = 0; + } +} + +/*=========================================================================== + * Deferred Response Helpers + *===========================================================================*/ + +/* + * Format coil param query response as JSON + */ +static int format_loop_param_json(char *buf, uint16_t buf_size, const LUP_ParamGet *pg) { + const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"}; + char *p = buf; + int remaining = buf_size; + int written; + uint8_t i; + + written = snprintf(p, remaining, + "{\"auto_mode\":%s,\"amount\":%d,\"channels\":[", + pg->auto_mode ? "true" : "false", pg->amount); + if (written < 0 || written >= remaining) return -1; + p += written; remaining -= written; + + for (i = 0; i < pg->amount; i++) { + const LUP_CoilParam *cp = &pg->params[i]; + uint8_t freq_lev = (cp->sensitivity >> 4) & 0x03; + uint8_t sens = cp->sensitivity & 0x0F; + uint8_t out_mode = cp->output_mode & 0x07; + uint8_t safe_mod = (cp->output_mode >> 3) & 0x1F; + uint8_t dir_mode = cp->direction_mode & 0x07; + uint8_t func_mod = (cp->direction_mode >> 4) & 0x0F; + + written = snprintf(p, remaining, + "%s{\"ch\":%d,\"sensitivity\":%d,\"freq_level\":\"%s\"," + "\"loop_delay\":%d,\"output_mode\":%d,\"safe_mode\":%d," + "\"exist_mode\":%d,\"direction_mode\":%d,\"function_mode\":%d," + "\"freq_initial\":%lu,\"freq_current\":%lu,\"freq_diff\":%lu}", + (i > 0) ? "," : "", + i + 1, sens, freq_level_names[freq_lev], + cp->loop_delay, out_mode, safe_mod, + cp->exist_mode, dir_mode, func_mod, + pg->freq[i][0], pg->freq[i][1], pg->freq[i][2]); + if (written < 0 || written >= remaining) return -1; + p += written; remaining -= written; + } + + written = snprintf(p, remaining, "]}"); + if (written < 0 || written >= remaining) return -1; + return 0; +} + +/* + * Format sensor report as JSON + */ +static int format_sensor_json(char *buf, uint16_t buf_size, const LUP_SensorReport *sr) { + const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"}; + char *p = buf; + int remaining = buf_size; + int written; + uint8_t i; + + written = snprintf(p, remaining, + "{\"sens_type\":\"multi_coil\",\"sub_amount\":%d,\"sub_seq\":%d,\"coils\":[", + sr->sub_amount, sr->sub_sequence); + if (written < 0 || written >= remaining) return -1; + p += written; remaining -= written; + + for (i = 0; i < sr->coil_count; i++) { + const LUP_CoilSensor *cs = &sr->coils[i]; + const char *misc_field; + uint32_t misc_val; + + switch (cs->misc_type) { + case 0: + // car_state=0(无车)→通过时间, car_state=1(有车)→车间距 + misc_field = cs->car_state ? "\"gap_ms5\"" : "\"passtime_ms\""; + misc_val = cs->misc.passtime_ms; + break; + case 1: misc_field = "\"cut_amount\""; misc_val = cs->misc.cut_amount; break; + case 2: misc_field = "\"flow_amount\""; misc_val = cs->misc.flow_amount; break; + case 3: misc_field = "\"relay_count\""; misc_val = cs->misc.relay_count; break; + default: misc_field = "\"misc\""; misc_val = 0; break; + } + + written = snprintf(p, remaining, + "%s{\"ch\":%d,\"freq_level\":\"%s\",\"direction\":%d," + "\"freq_type\":\"%s\",\"sensitivity\":%d," + "\"condition\":%d,\"loop_state\":\"%s\",\"car_state\":\"%s\"," + "\"freq\":%lu,\"variation\":%d,%s:%lu}", + (i > 0) ? "," : "", + i + 1, + freq_level_names[cs->freq_level], + cs->direction, + cs->freq_type ? "current" : "initial", + cs->sensitivity, + cs->condition, + cs->loop_state ? "broken" : "normal", + cs->car_state ? "car" : "no_car", + cs->freq, + cs->variation, + misc_field, misc_val); + if (written < 0 || written >= remaining) return -1; + p += written; remaining -= written; + } + + written = snprintf(p, remaining, "]}"); + if (written < 0 || written >= remaining) return -1; + return 0; +} + +/* + * Check for and handle pending Loop MCU deferred responses + */ +static void json_check_pending(void) { + if (!g_json_pending.active) return; + + if (g_lup_cmd.state == LUP_STATE_RESPONSE_READY) { + if (strcmp(g_json_pending.cmd, "loop_param_set") == 0) { + uint8_t success = 0; + int ret = lup_parse_set_param_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &success); + if (ret == 0 && success) { + json_send_ok(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, NULL); + } else { + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, + ret == 0 ? "Loop MCU returned failure" : "Parse error"); + } + } else if (strcmp(g_json_pending.cmd, "loop_param_query") == 0) { + LUP_ParamGet pg; + memset(&pg, 0, sizeof(pg)); + int ret = lup_parse_param_get(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &pg); + if (ret == 0) { + char data_json[2048]; + if (format_loop_param_json(data_json, sizeof(data_json), &pg) == 0) { + json_send_ok(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, data_json); + } else { + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, + "Response too large"); + } + } else { + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, + "Failed to parse Loop MCU response"); + } + } else if (strcmp(g_json_pending.cmd, "loop_version_query") == 0) { + LUP_VersionInfo info; + memset(&info, 0, sizeof(info)); + int ret = lup_parse_version(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &info); + if (ret == 0) { + snprintf(info.version_str, sizeof(info.version_str), + "V%d.%d.%d (HW:%d.%d.%d)", + info.soft_main, info.soft_sub, info.soft_ssub, + info.hard_main, info.hard_sub, info.hard_ssub); + char data_json[256]; + snprintf(data_json, sizeof(data_json), + "{\"soft_ver\":\"%d.%d.%d\",\"hard_ver\":\"%d.%d.%d\"," + "\"version_str\":\"%s\"}", + info.soft_main, info.soft_sub, info.soft_ssub, + info.hard_main, info.hard_sub, info.hard_ssub, + info.version_str); + json_send_ok(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, data_json); + } else { + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, + "Failed to parse version"); + } + } else if (strcmp(g_json_pending.cmd, "loop_factory_init") == 0) { + uint8_t success = 0; + int ret = lup_parse_factory_init_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &success); + if (ret == 0 && success) { + json_send_ok(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, NULL); + } else { + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, + ret == 0 ? "Factory init failed" : "Parse error"); + } + } else if (strcmp(g_json_pending.cmd, "loop_sens_read") == 0) { + LUP_Sensitivity sens; + memset(&sens, 0, sizeof(sens)); + int ret = lup_parse_sensitivity_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &sens); + if (ret == 0) { + char data_json[1024]; + char *p = data_json; + int rem = sizeof(data_json); + int w = snprintf(p, rem, + "{\"ret\":\"%s\",\"amount\":%d,\"channels\":[", + (sens.rw == 0x10) ? "read" : (sens.rw == 0x11) ? "write" : "?", + sens.amount); + p += w; rem -= w; + uint8_t i; + for (i = 0; i < sens.amount; i++) { + w = snprintf(p, rem, + "%s{\"ch\":%d,\"sens_in\":%d,\"sens_out\":%d}", + (i > 0) ? "," : "", i + 1, + sens.sens_in[i], sens.sens_out[i]); + p += w; rem -= w; + } + snprintf(p, rem, "]}"); + json_send_ok(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, data_json); + } else { + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, + "Failed to parse sensitivity"); + } + } else if (strcmp(g_json_pending.cmd, "loop_sens_write") == 0) { + LUP_Sensitivity sens; + memset(&sens, 0, sizeof(sens)); + int ret = lup_parse_sensitivity_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &sens); + if (ret == 0) { + char data_json[1024]; + char *p = data_json; + int rem = sizeof(data_json); + int w = snprintf(p, rem, + "{\"ret\":\"%s\",\"amount\":%d,\"channels\":[", + (sens.rw == 0x10) ? "read" : (sens.rw == 0x11) ? "write" : "?", + sens.amount); + p += w; rem -= w; + uint8_t i; + for (i = 0; i < sens.amount; i++) { + w = snprintf(p, rem, + "%s{\"ch\":%d,\"sens_in\":%d,\"sens_out\":%d}", + (i > 0) ? "," : "", i + 1, + sens.sens_in[i], sens.sens_out[i]); + p += w; rem -= w; + } + snprintf(p, rem, "]}"); + json_send_ok(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, data_json); + } else { + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_INTERNAL_ERR, + "Failed to parse sensitivity response"); + } + } + + lup_cmd_done(); + g_json_pending.active = 0; + PRINT("JSON: deferred response sent for %s\n", g_json_pending.cmd); + } + else if (g_lup_cmd.state == LUP_STATE_TIMEOUT + || mstick() > g_json_pending.deadline) { + // Timeout + json_send_error(g_json_pending.socket, g_json_pending.msg_id, + g_json_pending.cmd, JSON_CODE_BUSY, + "Loop MCU no response"); + lup_cmd_done(); + g_json_pending.active = 0; + PRINT("JSON: deferred response timeout for %s\n", g_json_pending.cmd); + } +} + +/*=========================================================================== + * tcp_json_poll (updated) + *===========================================================================*/ +void tcp_json_poll(void) { + // Only poll if we have an active connection (socket configured) + if (g_json_socket_listen == 0xFF) return; + + // === 自动重启冷却期检查 === + if (g_json_restart_cooldown > 0) { + if (mstick() < g_json_restart_cooldown) { + // 冷却期内,不做任何操作 + return; + } + g_json_restart_cooldown = 0; + g_json_restart_count = 0; + PRINT("JSON: restart cooldown expired, resetting counter\n"); + } + + // === 超时条件1: 5分钟无连接 === + if (g_json_last_connect_ts == 0) { + // 从未连接过 — 用 auth_timer (listen 创建时设) 作为起点 + if (g_json_auth_state == JSON_STATE_WAIT_AUTH + && mstick() - g_json_auth_timer > TCP_JSON_IDLE_TIMEOUT_MS) { + PRINT("JSON: %dmin no connection → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000); + goto do_restart; + } + } else if (mstick() - g_json_last_connect_ts > TCP_JSON_IDLE_TIMEOUT_MS) { + PRINT("JSON: %dmin since last connect → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000); + goto do_restart; + } + + // === 超时条件2: 5分钟无通信 (仅在有连接/已认证时检查) === + if (g_json_last_comm_ts > 0 + && mstick() - g_json_last_comm_ts > TCP_JSON_IDLE_TIMEOUT_MS) { + PRINT("JSON: %dmin no communication → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000); + goto do_restart; + } + + // Auth: 3次失败在 handle_pwd_verify 中直接 tcp_json_restart() + // 5分钟无通信/无连接由上方 do_restart 路径处理 + + // Lockout timeout check (existing) + if (g_json_auth_state == JSON_STATE_LOCKOUT) { + if (mstick() - g_json_lockout_timer > TCP_JSON_LOCKOUT_TIMEOUT_MS) { + PRINT("JSON: Lockout expired\n"); + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + } + } + + // Check for pending Loop MCU deferred responses + json_check_pending(); + return; + +do_restart: + g_json_restart_count++; + g_json_restart_cooldown = 0; + if (g_json_restart_count > TCP_JSON_MAX_RESTART_COUNT) { + // 超过最大重启次数,进入冷却期 + g_json_restart_cooldown = mstick() + TCP_JSON_RESTART_COOLDOWN_MS; + PRINT("JSON: max restart (%d) reached, cooldown %d min\n", + TCP_JSON_MAX_RESTART_COUNT, TCP_JSON_RESTART_COOLDOWN_MS / 60000); + } else { + tcp_json_restart(); + } +} + +/*=========================================================================== + * tcp_json_push_sensor — Push sensor data (0xC0) to connected TCP client + * + * Called from main loop when uart_srv() has a sensor report frame in + * g_pkg_uart_2 that hasn't been consumed by BLE. + *===========================================================================*/ +void tcp_json_push_sensor(void) { + // Check: socket active, authed, report enabled + if (g_json_socket_listen == 0xFF) return; + if (g_json_auth_state != JSON_STATE_AUTHED) return; + if (!g_report_active) return; // 主动上报未开启 + + // Only proceed if g_pkg_uart_2 has data and it's a 0xC0 sensor report + if (g_pkg_uart_2.flag == 0) return; + if (g_pkg_uart_2.pkg[0] != 0x7F) return; + if (g_pkg_uart_2.pkg[3] != 0xC0) return; + + LUP_SensorReport sr; + memset(&sr, 0, sizeof(sr)); + int ret = lup_parse_sensor_report(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &sr); + if (ret != 0) { + PRINT("JSON: sensor parse failed (%d)\n", ret); + InitPkgUart(&g_pkg_uart_2); + return; + } + + char data_json[2048]; + if (format_sensor_json(data_json, sizeof(data_json), &sr) == 0) { + char *out = (char *)malloc(TCP_JSON_MAX_FRAME); + if (out) { + snprintf(out, TCP_JSON_MAX_FRAME, + "{\"msg_id\":0,\"cmd\":\"sensor_report\",\"ts\":%lu," + "\"code\":0,\"msg\":\"success\",\"data\":%s}\n", + (unsigned long)mstick(), data_json); + uint32_t slen = strlen(out); + // WCHNET TCP: listen socket=N, data socket=N+1 + WCHNET_SocketSend(g_json_socket_listen + 1, (uint8_t *)out, &slen); + free(out); + } + } + + // Clear after processing + InitPkgUart(&g_pkg_uart_2); +}