/** ****************************************************************************** * @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}; /*=========================================================================== * 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) { 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 — 设置地感线圈多路参数 (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; } // 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"); } /* 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); } } /*=========================================================================== * 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) { // === 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; 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); 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: misc_field = "\"passtime_ms5\""; misc_val = cs->misc.passtime_ms5; 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; 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; // Auth timeout check if (g_json_auth_state == JSON_STATE_WAIT_AUTH) { if (mstick() - g_json_auth_timer > TCP_JSON_AUTH_TIMEOUT_MS) { PRINT("JSON: Auth timeout, closing connection\n"); WCHNET_SocketClose(g_json_socket_listen, TCP_CLOSE_NORMAL); 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; } } // Check for pending Loop MCU deferred responses json_check_pending(); } /*=========================================================================== * 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, and data available if (g_json_socket_listen == 0xFF) return; if (g_json_auth_state != JSON_STATE_AUTHED) 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_SocketSend(g_json_socket_listen, (uint8_t *)out, &slen); free(out); } } // Clear after processing InitPkgUart(&g_pkg_uart_2); }