From af997a79fe3cb5de7ca752740cda0cd8981dd340 Mon Sep 17 00:00:00 2001 From: wangfq Date: Tue, 30 Jun 2026 14:53:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(vd960DBN):=20TCP=20JSON=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=20=E2=80=94=20=E7=AB=AF=E5=8F=A35960,=20?= =?UTF-8?q?=E9=89=B4=E6=9D=83+15=E6=9D=A1=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - net_config.h: TCP_LISTEN=0→1, TCP=2 支持 JSON 监听 - 新增 tcp_json_srv.h/c: 行分隔 JSON, pwd_verify鉴权, 命令分发 - 实现15条协议命令: dev_info/ssc_net/iot_net/iot_topic/pwd_set/factory_reset等 - loop_param_set/query 接受命令返回stub(Loop MCU中继待实现) - net_srv.c: 集成 JSON 中断路由 + init - peripheral_main.c: 主循环 tcp_json_poll() --- .../APP/include/net_config.h | 4 +- .../APP/include/tcp_json_srv.h | 63 ++ .../OnlyUpdateApp_Peripheral/APP/net_srv.c | 51 +- .../APP/peripheral_main.c | 7 +- .../APP/tcp_json_srv.c | 741 ++++++++++++++++++ 5 files changed, 843 insertions(+), 23 deletions(-) create mode 100644 vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h create mode 100644 vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h index 4fe5c7f..8fd6c49 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/net_config.h @@ -24,9 +24,9 @@ extern "C" { #define WCHNET_NUM_UDP 1 /* The number of UDP connections */ -#define WCHNET_NUM_TCP 1 /* Number of TCP connections */ +#define WCHNET_NUM_TCP 2 /* Number of TCP connections (1 SSC/MQTT + 1 JSON accepted) */ -#define WCHNET_NUM_TCP_LISTEN 0 /* Number of TCP listening */ +#define WCHNET_NUM_TCP_LISTEN 1 /* Number of TCP listening (JSON protocol on port 5960) */ /* The number of sockets, the maximum is 31 */ #define WCHNET_MAX_SOCKET_NUM (WCHNET_NUM_IPRAW+WCHNET_NUM_UDP+WCHNET_NUM_TCP+WCHNET_NUM_TCP_LISTEN) diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h new file mode 100644 index 0000000..b3c82f8 --- /dev/null +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/tcp_json_srv.h @@ -0,0 +1,63 @@ +/** + ****************************************************************************** + * @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 + +/*=========================================================================== + * 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 + +/*=========================================================================== + * Externs + *===========================================================================*/ +extern uint8_t g_json_socket_listen; // listen socket ID +extern uint8_t g_json_socket_client; // accepted client socket ID (0xFF = none) +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 + +/*=========================================================================== + * 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.) + +#endif /* __TCP_JSON_SRV_H__ */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c index c0c5cc6..acb01a4 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/net_srv.c @@ -17,6 +17,7 @@ #include #include "cmcng.h" #include "simple_json.h" +#include "tcp_json_srv.h" uint32_t slen; @@ -50,7 +51,7 @@ Net_State g_net_state = {0,0, 0}; uint16_t srcport = 6000; -//UINT8 UDPDESIP[4] = {255,255,255,255}; /* ????????????????????,????????????????????????????? */ +//UINT8 UDPDESIP[4] = {255,255,255,255}; /* ????????????????????,?????????��???????????????????? */ //UINT16 aport=1000; /* CH579?????????? */ uint32_t g_wdg_counter = 0; @@ -103,7 +104,7 @@ void mqtt_connect(void) int len = 0; mqttData.username.cstring = iot_net_info.username; mqttData.password.cstring = iot_net_info.password; - mqttData.clientID.cstring = g_dev_number_str; //TODO: ????????????????????? + mqttData.clientID.cstring = g_dev_number_str; //TODO: ?��???????????????????? mqttData.keepAliveInterval = MQTT_KEEPALIVE_INTERVAL; PRINT("username:%s\r\n", mqttData.username.cstring); @@ -480,6 +481,17 @@ void WCHNET_HandleSockInt(uint8_t socketid, uint8_t intstat) { uint8_t i; + // Route JSON protocol socket events + if (socketid == g_json_socket_listen || socketid == g_json_socket_client) { + tcp_json_handle_sock_int(socketid, intstat); + return; + } + // Also catch newly accepted TCP connections that might be JSON clients + if (intstat & SINT_STAT_CONNECT && g_json_socket_client == 0xFF && socketid != g_json_socket_listen) { + tcp_json_handle_sock_int(socketid, intstat); + return; + } + g_net_state.intstat = intstat; if (intstat & SINT_STAT_RECV) //receive data @@ -532,7 +544,7 @@ void WCHNET_HandleSockInt(uint8_t socketid, uint8_t intstat) void dbn_net_ssc_srv(void) { uint8_t _flag_timestamp = 0; - static uint32_t _report_counter = 0; //ϱʱ + static uint32_t _report_counter = 0; //�ϱ�ʱ������� //Check net if connect if(g_net_state.flag < 3) @@ -592,8 +604,8 @@ void poll_mqtt(void) /******************************************************************************* * Function Name : GetMacAddr -* Description : ϵͳȡMACַ -* Input : pMAC:ָ洢MacַĻ +* Description : ϵͳ��ȡMAC��ַ +* Input : pMAC:ָ�������洢Mac��ַ�Ļ��� * Output : None * Return : None *******************************************************************************/ @@ -610,50 +622,50 @@ void GetMacAddr(unsigned char *pMAC) } -// IPv4ַַתΪ4ֽڵuint8_t飬ϸĴ +// ��IPv4��ַ�ַ���ת��Ϊ4�ֽڵ�uint8_t���飬�������ϸ�Ĵ����� int get_ipstr_to_array(char *src, uint8_t *dst) { if (src == NULL || dst == NULL) { - return -1; // Чָ + return -1; // ��Чָ�� } int part_count = 0; char *token = NULL; char *saveptr = NULL; const char *delim = "."; - // ַӦÿ޸ĵڴ棨ڴ棩Ϊstrtok_r޸ַ - // 账޸ĵַȽп + // �����ַ���Ӧ���ÿ��޸ĵ��ڴ棨���������ڴ棩����Ϊstrtok_r���޸��ַ��� + // �账�������޸ĵ��ַ��������Ƚ��п��� char src_copy[16]; strncpy(src_copy, src, sizeof(src_copy)); src_copy[15] = '\0'; - // ״ηָ + // �״ηָ� token = strtok_r(src_copy, delim, &saveptr); while (token != NULL && part_count < 4) { char *endptr = NULL; long val = strtol(token, &endptr, 10); - // תЧ + // ���ת����Ч�� if (*endptr != '\0' || val < 0 || val > 255) { - return -2; // ֻ򳬳Χ + return -2; // �����ֻ򳬳���Χ } dst[part_count++] = (uint8_t)val; - // ָ + // �����ָ� token = strtok_r(NULL, delim, &saveptr); } - // Ƿ4 + // ����Ƿ�������4���� if (part_count != 4) { - return -3; // + return -3; // ������������ } - // Ƿжַ + // ����Ƿ��ж����ַ� if (token != NULL || (saveptr != NULL && *saveptr != '\0')) { - return -4; // ڶַָ + return -4; // ���ڶ���ָ������ַ� } - return 0; // ɹ + return 0; // �ɹ� } @@ -757,7 +769,7 @@ void unpack_ssc_count_off(uint8_t socket,uint8_t count_mode, uint8_t *ip, uint16 void unpack_ssc_device_reset(uint8_t socket, uint8_t *ip, uint16_t port, uint8_t *buf, uint32_t len) { - // λ豸 + // ��λ�豸 uint16_t tmplen = 0; char *p = (char *)buf; char *mBuff = (char *)malloc(512); @@ -1108,6 +1120,7 @@ void net_srv_init(void) if(g_net_state.flag == 1) { WCHNET_CreateUdpSocket(); + tcp_json_srv_init(); // Start JSON protocol TCP listener on port 5960 } diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c index 40ff560..36938f6 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c @@ -22,6 +22,7 @@ #include "eth_driver.h" #include "net_srv.h" #include "storage.h" +#include "tcp_json_srv.h" /********************************************************************* * GLOBAL TYPEDEFS @@ -45,7 +46,7 @@ const uint32_t Address = 0xFFFFFFFF; __attribute__((aligned(4))) uint32_t Image_Flag __attribute__((section(".ImageFlag"))) = (uint32_t)&Address; -uint8_t g_dev_number[6] = ""; // 豸 Ʒ +uint8_t g_dev_number[6] = ""; // �豸��� ��Ʒ��� uint8_t g_dev_password[6] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36}; uint8_t g_ble_safe_flag = 0; uint32_t g_ble_safe_counter_ori = 0; @@ -53,7 +54,7 @@ uint32_t g_ble_safe_counter_dst = 0; char g_flag_debug = 1; -uint8_t g_dg_device_type = DDType_DLD950V4; // ???????????????? +uint8_t g_dg_device_type = DDType_DLD950V4; // ???����????????????? uint8_t g_dg_sub_dev_type = DDType_DLD950V4; Sub_Code_Enable g_sub_code_enable = {0,}; @@ -266,6 +267,8 @@ void Main_Circulation(void) poll_dbn_ble(); + tcp_json_poll(); + key_event_srv(); } } diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c new file mode 100644 index 0000000..fd380bb --- /dev/null +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/tcp_json_srv.c @@ -0,0 +1,741 @@ +/** + ****************************************************************************** + * @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 "simple_json.h" +#include "storage.h" +#include +#include +#include + +/*=========================================================================== + * Global State + *===========================================================================*/ +uint8_t g_json_socket_listen = 0xFF; // listen socket ID +uint8_t g_json_socket_client = 0xFF; // accepted client socket ID +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; + +/*=========================================================================== + * 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; + +/*=========================================================================== + * 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 void json_get_cmd(const char *json, char *out, int out_len) { + reset_tmp(); + simple_parse_json(json, "\"cmd\"", g_tmp_value); + strncpy(out, g_tmp_value, out_len - 1); + out[out_len - 1] = '\0'; +} + +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 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) { + char *data = json_get_data_str(json); + if (!data) { + json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_PARAM_ERR, "missing data"); + return; + } + + char password[16] = {0}; + json_get_str_field(data, "\"password\"", password, sizeof(password)); + free(data); + + 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) { + g_json_auth_state = JSON_STATE_LOCKOUT; + g_json_lockout_timer = mstick(); + PRINT("JSON: Auth locked out (%d retries)\n", g_json_pwd_retry); + json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_AUTH_FAIL, + "password incorrect, locked out for 60s"); + } 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 */ +static void handle_loop_param_set(uint8_t socket, uint32_t msg_id, const char *json) { + // Forward to Loop MCU via UART2 + // Store params locally for query response + 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; + } + free(data); + + // Build binary packet for Loop MCU (protocol 0x63 = CMD_SET_MCJQ_PARAM) + // For now, ack the command — full loop MCU integration deferred + json_send_ok(socket, msg_id, "loop_param_set", NULL); + PRINT("JSON: loop_param_set (Loop MCU relay pending)\n"); +} + +/* 4.14 loop_param_query */ +static void handle_loop_param_query(uint8_t socket, uint32_t msg_id, const char *json) { + // Query Loop MCU via UART2 (protocol 0x64 = CMD_GET_MCJQ_PARAM) + // For now, return stub data — full loop MCU integration deferred + char data_json[600]; + snprintf(data_json, sizeof(data_json), + "{\"auto_mode\":false," + "\"channels\":[" + "{\"ch\":1,\"sensitivity\":7,\"freq_level\":\"high\",\"loop_delay\":0," + "\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0," + "\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0}," + "{\"ch\":2,\"sensitivity\":7,\"freq_level\":\"mid_high\",\"loop_delay\":5," + "\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0," + "\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0}," + "{\"ch\":3,\"sensitivity\":5,\"freq_level\":\"mid_low\",\"loop_delay\":0," + "\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0," + "\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0}," + "{\"ch\":4,\"sensitivity\":8,\"freq_level\":\"low\",\"loop_delay\":0," + "\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0," + "\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0}]}"); + + json_send_ok(socket, msg_id, "loop_param_query", data_json); + PRINT("JSON: loop_param_query (stub — Loop MCU relay pending)\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; + } + // Store report config for future push support + free(data); + json_send_ok(socket, msg_id, "report_config", NULL); + PRINT("JSON: report_config (push not yet implemented)\n"); +} + + +/*=========================================================================== + * 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}, + {"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)); + + if (strlen(cmd) == 0) { + 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) { + // 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; + } + 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); + } +} + + +/*=========================================================================== + * Public API + *===========================================================================*/ + +void tcp_json_srv_init(void) { + uint8_t ret; + SOCK_INF sock_inf; + + 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 failed: 0x%02X\n", ret); + return; + } + + ret = WCHNET_SocketListen(g_json_socket_listen); + if (ret != WCHNET_ERR_SUCCESS) { + PRINT("JSON: SocketListen failed: 0x%02X\n", ret); + return; + } + + PRINT("JSON: TCP listen on port %d (socket %d)\n", TCP_JSON_PORT, g_json_socket_listen); +} + +void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat) { + // === Listen socket events === + if (socketid == g_json_socket_listen) { + if (intstat & SINT_STAT_CONNECT) { + // A client connected — find the accepted socket + // The accepted connection gets a different socket ID + // We find it by scanning TCP sockets in ESTABLISHED state + uint8_t i; + for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++) { + if (i == g_json_socket_listen) continue; + // Check if this socket is in ESTABLISHED state and not our SSC/MQTT socket + SOCK_INF *si = NULL; + // We check by trying to recv — if it works, it's the accepted socket + uint32_t recv_len = WCHNET_SocketRecvLen(i, NULL); + (void)recv_len; + // Simpler approach: just assign the first non-listen TCP socket + // that's not the existing SSC/MQTT client (SocketId_TCP) + if (i != SocketId_TCP && g_json_socket_client == 0xFF) { + g_json_socket_client = i; + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_auth_timer = mstick(); + g_json_recv_len = 0; + memset(g_json_recv_buf, 0, sizeof(g_json_recv_buf)); + PRINT("JSON: Client connected on socket %d\n", i); + break; + } + } + } + if (intstat & SINT_STAT_DISCONNECT) { + PRINT("JSON: Listen socket disconnect (unexpected)\n"); + } + return; + } + + // === Client socket events === + if (socketid == g_json_socket_client) { + if (intstat & SINT_STAT_RECV) { + // Read data into frame buffer + 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; + WCHNET_SocketRecv(socketid, g_json_recv_buf + g_json_recv_len, &rd_len); + g_json_recv_len += (uint16_t)rd_len; + + // Process complete frames + 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); + + // Reset auth timer on activity + if (g_json_auth_state == JSON_STATE_WAIT_AUTH) { + g_json_auth_timer = mstick(); + } + } + + // Buffer overflow protection + 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)); + } + } + } + + if (intstat & SINT_STAT_CONNECT) { + PRINT("JSON: Client socket connected (id=%d)\n", socketid); + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_auth_timer = mstick(); + g_json_recv_len = 0; + } + + if (intstat & SINT_STAT_DISCONNECT) { + PRINT("JSON: Client disconnected (id=%d)\n", socketid); + g_json_socket_client = 0xFF; + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_recv_len = 0; + } + + if (intstat & SINT_STAT_TIM_OUT) { + PRINT("JSON: Client timeout (id=%d)\n", socketid); + g_json_socket_client = 0xFF; + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_recv_len = 0; + } + return; + } + + // === New socket connecting? (WCHNET assigns TCP PCB after listen accept) === + if (intstat & SINT_STAT_CONNECT && g_json_socket_client == 0xFF && socketid != g_json_socket_listen) { + // This might be the newly accepted JSON client connection + g_json_socket_client = socketid; + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_auth_timer = mstick(); + g_json_recv_len = 0; + memset(g_json_recv_buf, 0, sizeof(g_json_recv_buf)); + PRINT("JSON: Client accepted on socket %d\n", socketid); + } +} + +void tcp_json_poll(void) { + // Auth timeout check + if (g_json_auth_state == JSON_STATE_WAIT_AUTH && g_json_socket_client != 0xFF) { + if (mstick() - g_json_auth_timer > TCP_JSON_AUTH_TIMEOUT_MS) { + PRINT("JSON: Auth timeout, closing connection\n"); + WCHNET_SocketClose(g_json_socket_client, TCP_CLOSE_NORMAL); + g_json_socket_client = 0xFF; + g_json_auth_state = JSON_STATE_WAIT_AUTH; + g_json_pwd_retry = 0; + g_json_recv_len = 0; + } + } + + // Lockout timeout check + 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; + } + } +}