/**
 ******************************************************************************
 * @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 <string.h>
#include <stdlib.h>
#include <stdio.h>

/*===========================================================================
 * 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=主动上报传感器数据

/*===========================================================================
 * 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;
    }

    // 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_srv_init(void) {
    uint8_t ret;
    SOCK_INF sock_inf;
    static uint8_t _init_done = 0;

    if (_init_done) return;  // 防止 net_srv_init 反复调用
    _init_done = 1;

    // 注册传感器回调 — 必须在 socket 操作之前，
    // 避免 socket 失败导致 return 而漏掉注册
    lup_set_sensor_callback(json_sensor_callback);

    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, 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);
}
