#include "lc.h"
#include "../tag.h"
#include "../common.h"
#include "../ems/main.h"
#include "../opcua/ua_client.h"
#include "../ems/ems_ua.h"
#include "../business_log/business_log.h"
#include "../cc_lc_com/define.h"
#define LC_BUSINESS_LOG(_lev, _id, _no, _fmt, ...) BUSINESS_LOG(_lev, _id, _no, "[主从]"_fmt, ##__VA_ARGS__)
static lc_info_t lcs;
//
static char *lc_status_string[LC_STATUS_MAX + 1] = {
    "正常",
    "禁用",
    "离线",
    "系统错误",
    "保护",
    "MAX"};

#define LC_STATUS_2_STRING(e) (lc_status_string[e])

slave_lc_frpcaddr_t g_slave_lc_frpcaddr_list[MAX_LC_SUM_SUPPORT];
int g_slave_lc_frpcaddr_count = 0;

extern key_info_t key_info;
static LC_STATUS_ENUM lc_charge_valid(single_lc_info_t *lc)
{
    if (nod_cmp_int(lc->no, STATE_ONLINE, 0) == 0)
    {
        return LC_STATUS_OFFLINE;
    }
    //
    if (nod_cmp_int(lc->no, SYSTEM_STATUS, 0) != 0)
    {
        return LC_STATUS_SYSTEM_ERR;
    }
    //
    if (nod_cmp_int(lc->no, PROHIBITE_CHARGE, 0) != 0)
    {
        return LC_STATUS_PROTECT;
    }
    // 策略DOD
    if (g_usercfg_variant.ControlMode == EMS_AUTO_MODE && g_usercfg_variant.StratagyType == STRATAGY_TYPE_PERIOD_CTRL){ // --自动模式+削峰填谷，策略中的DOD保护才会生效
        double soc_cur = dev_get_dev_tag_float(lc->no, CURRENT_SOC);
        if (logic_dod_protect(&key_info, &(lc->chadischapro), soc_cur) == 1) {
            LC_BUSINESS_LOG(BLOG_WARNING, MS_ID_0001, lc->no, "%s策略DOD禁充保护->触发", lc->no);
            return LC_STATUS_PROTECT;            
        } else {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0001, lc->no, "%s策略DOD禁充保护->解除", lc->no);
        }
    }

    return LC_STATUS_OK;
}

static LC_STATUS_ENUM lc_discharge_valid(single_lc_info_t *lc)
{
    if (nod_cmp_int(lc->no, STATE_ONLINE, 0) == 0)
    {
        return LC_STATUS_OFFLINE;
    }
    //
    if (nod_cmp_int(lc->no, SYSTEM_STATUS, 0) != 0)
    {
        return LC_STATUS_SYSTEM_ERR;
    }
    //
    if (nod_cmp_int(lc->no, PROHIBITE_DISCHARGE, 0) != 0)
    {
        return LC_STATUS_PROTECT;
    }
    // 策略DOD
    if (g_usercfg_variant.ControlMode == EMS_AUTO_MODE && g_usercfg_variant.StratagyType == STRATAGY_TYPE_PERIOD_CTRL){ // --自动模式+削峰填谷，策略中的DOD保护才会生效
        double soc_cur = dev_get_dev_tag_float(lc->no, CURRENT_SOC);
        if (logic_dod_protect(&key_info, &(lc->chadischapro), soc_cur) == 2){
            LC_BUSINESS_LOG(BLOG_WARNING, MS_ID_0002, lc->no, "%s策略DOD禁放保护->触发", lc->no);
            return LC_STATUS_PROTECT;           
        } else {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0002, lc->no, "%s策略DOD禁放保护->解除", lc->no);
        }
    }       

    return LC_STATUS_OK;
}


int set_ChDischStatus(const char *no)
{
    double active_power = dev_get_dev_tag_float(no, ACTIVE_POWER);
    if (active_power > -0.2 && active_power < 0.2)
    {
        active_power = 0.0;
    }
    if (active_power < 0)
    {
        dev_set_dev_tag_int(no, CH_DISCH_STATUS, 1);
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0003, no, "%s:充放电状态改变->充电", no);
    }
    else if(active_power > 0)
    {
        dev_set_dev_tag_int(no, CH_DISCH_STATUS, 2);
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0003, no, "%s:充放电状态改变->放电", no);
    }
    else
    {
        dev_set_dev_tag_int(no, CH_DISCH_STATUS, 0);
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0003, no, "%s:充放电状态改变->静置", no);
    }
    return 0;
}

int check_charge_discharge_status()
{
    int cur_sum = get_pcs_ctrl_var()->lc_list_len;
    lc_it *lc_cfg = get_pcs_ctrl_var()->lc_list;

    for(int i = 0; i < cur_sum; i++)
    {
        set_ChDischStatus(lc_cfg[i].no);
    }

    set_ChDischStatus(DEV_NO_EMS);
    return 0;
}

static int update_lc_available(lc_info_t *lc, double power, double repower)
{
    int available_sum = 0;
    int cur_sum = lc->lc_sum = get_pcs_ctrl_var()->lc_list_len;
    lc_it *lc_cfg = get_pcs_ctrl_var()->lc_list;
    //
    // 0正常  1上限（禁充）  2下限（禁放）单柜(lc)充放保护标记在结构体中定义
    if (key_info.index != key_info.last_index || (get_pcs_ctrl_var()->reflush_flag == 1)) {
        get_pcs_ctrl_var()->reflush_flag = 0;
        key_info.last_index = key_info.index;
        for (int i = 0; i < cur_sum; i++){
            lc->lc[i].chadischapro = 0;
        }
        ems_syslog(LOG_WARNING, "策略时间段切换,强制清除策略DOD禁充、禁放标记...");
    }
    //
    LC_STATUS_ENUM (*check_lc_valid)(single_lc_info_t *) = lc_charge_valid;
    if (IS_CHARGE(power))
    {
        check_lc_valid = lc_charge_valid;
    }
    else if (IS_DISCHARGE(power))
    {
        check_lc_valid = lc_discharge_valid;
    }
    //装机功率系数
    for (int n = 0; n < cur_sum; n++)
    { 
        lc->lc[n].no = lc_cfg[n].no;
        lc->lc[n].is_vlc = lc_cfg[n].type == eLC_TYPE_VLC ? 1 : 0;
        lc->lc[n].installed_power_coef = dev_get_dev_tag_int(lc_cfg[n].no, INSPOW_COEF);
        if(lc->lc[n].installed_power_coef < 0){
            lc->lc[n].installed_power_coef = 0;
            LC_BUSINESS_LOG(BLOG_WARNING, MS_ID_0007, lc_cfg[n].no, "%s:装机功率系数异常,强制按0计算", lc_cfg[n].no);
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0007, lc_cfg[n].no, "%s:装机功率系数->%d", lc_cfg[n].no, lc->lc[n].installed_power_coef);
        }
        lc->lc[n].status = (lc_cfg[n].disabled == 0) ? check_lc_valid(&(lc->lc[n])) : LC_STATUS_DISABLE;
        if (lc->lc[n].status == LC_STATUS_OK)
        {
            available_sum++;
        }
    }

    lc->valid_sum = available_sum;
    if (nod_cmp_int(DEV_NO_EMS, SYSTEM_STATUS, 0) != 0)
    {
        lc->valid_sum = 0;
        return -1;
    }
    return available_sum;
}


static double bms_limit_power(double power, int index, lc_info_t *lc)
{
    double limit = 0;
    if (g_usercfg_variant.en_bms_power_limit != 0)
    {
        const char *cab_no = lc->lc[index].no;

        if (IS_CHARGE(power))
        {
            limit = 0 - dev_get_dev_tag_float(cab_no, MAX_CHARGE_POWER);
            if (power < limit)
            {
                power = limit;
                LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0004, cab_no, "%s:BMS最大充电功率限制->%.2f", cab_no, power);
            }
            else
            {
                LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0004, cab_no, "%s:BMS最大充电功率限制->解除", cab_no);
            }
        }
        else
        {
            limit = dev_get_dev_tag_float(cab_no, MAX_DISCHA_POWER);
            if (power > limit)
            {
                power = limit;
                LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0004, cab_no, "%s:BMS最大放电功率限制->%.2f", cab_no, power);
            }
            else
            {
                LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0004, cab_no, "%s:BMS最大放电功率限制->解除", cab_no);
            }
        }
    }
    return power;
}

double get_lc_installed_power_coefficient(lc_info_t *lc, int index) {
    double total_installed_power = 0.0;
    double my_power = 0.0;

    for (int i = 0; i < lc->lc_sum; i++) {
        if (lc->lc[i].status == LC_STATUS_OK && lc->lc[i].installed_power_coef >= 0) {
            total_installed_power += lc->lc[i].installed_power_coef;
        }
    }
    if (lc->lc[index].status == LC_STATUS_OK) {
        my_power = lc->lc[index].installed_power_coef;
    } else {
        return 0.0;
    }

    if (total_installed_power <= 0.0) {
        return 0.0;
    }
    LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0005, lc->lc[index].no, "%s:装机功率->%d, 总装机功率%.2f", lc->lc[index].no, lc->lc[index].installed_power_coef, total_installed_power);
    return my_power / total_installed_power;
}

static int update_lc_expect_power(lc_info_t *lc, double power, double repower)
{
    double power_sum = 0, repower_sum = 0;
    int valid_sum = lc->valid_sum;
    ems_syslog(LOG_NOTICE, "cur valid lc sum:%d", valid_sum);

    LC_STATUS_ENUM status = LC_STATUS_OK;
    for (int i = 0; i < lc->lc_sum; i++)
    {
        status = lc->lc[i].status;
        if (status == LC_STATUS_OK && valid_sum > 0)
        {
            double coeff = get_lc_installed_power_coefficient(lc, i);
            // 有功
            double inst_power = power * coeff;
            lc->lc[i].exp_powert = bms_limit_power(inst_power, i, lc);
            power_sum += lc->lc[i].exp_powert;
            // 无功
            double inst_repower = repower * coeff;
            lc->lc[i].exp_repowert = inst_repower;
            repower_sum += lc->lc[i].exp_repowert;
        }
        else
        {
            lc->lc[i].exp_powert = 0;
            lc->lc[i].exp_repowert = 0;
        }
        ems_syslog(LOG_NOTICE, "lc: %s status:%d[%s], expect power: %f repower: %f", lc->lc[i].no, status, LC_STATUS_2_STRING(status), lc->lc[i].exp_powert, lc->lc[i].exp_repowert);
    }
    lc->exp_powert = power_sum;
    lc->exp_repowert = repower_sum;
    dev_set_dev_tag_float(DEV_NO_EMS, EXPECT_ACT_POWER, lc->exp_powert);
    // -- 期望无功功率暂不添加点位
    return 0;
}

static int sync_lc_power(lc_info_t *lc)
{
    const char *dev_no = NULL;
    LC_STATUS_ENUM lc_s = LC_STATUS_OK;

    // 写入功率
    for (int i = 0; i < lc->lc_sum; i++)
    {
        lc_s = lc->lc[i].status;
        dev_no = lc->lc[i].no;
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0006, dev_no, "%s:状态->[%d]%s", dev_no, lc_s, LC_STATUS_2_STRING(lc_s));
        if (lc_s >= LC_STATUS_MAX)
        {
            continue;
        }

        if (lc_s == LC_STATUS_OFFLINE)
        {
            continue;
        }

        if (lc_s == LC_STATUS_DISABLE)
        {
            continue;
        }

        //
        dev_set_dev_tag_float(dev_no, SET_ACT_POWER, lc->lc[i].exp_powert);    // 设置有功
        dev_set_dev_tag_float(dev_no, SET_REACTPOWER, lc->lc[i].exp_repowert); // 设置无功
        // vlc_sync_power(lc);// 为了速度考虑，没有单独使用线程实现，直接做在这儿，后期有必要可以将下面的逻辑放在vlc_deviceLater.c中
    }
    return 0;
}


double ems_limit_power(double power)
{
    int limit = 0;
    if(IS_CHARGE(power))
    {
        limit = 0 - dev_get_dev_tag_float(DEV_NO_EMS, CHARGE_MAX_POWER);
        if (power < limit)
        {
            power = limit;
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0006, NULL, "EMS最大充电功率限制->%.2f", power);
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0006, NULL, "EMS最大充电功率限制->解除");
        }
    }
    else
    {
        limit = dev_get_dev_tag_float(DEV_NO_EMS, DISCHARGE_THRES_HOLD);
        if (power > limit)
        {
            power = limit;
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0006, NULL, "EMS最大放电功率限制->%.2f", power);
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0006, NULL, "EMS最大放电功率限制->解除");
        }
    
    }
    return power;
}


//
int update_lc_info(void)
{
    double power = get_lc_power();
    double repower = get_lc_repower();
    ems_syslog(LOG_NOTICE, "local power: %f repower: %f", power, repower);
    double active_power = ems_limit_power(power); //EMS最大可充可放功率限制
    update_lc_available(&lcs, active_power, repower);
    update_lc_expect_power(&lcs, active_power, repower);
    sync_lc_power(&lcs);
    return 0;
}

int check_lc_lang(void)
{
    char url_buff[128] = {0};
    static char *local_lang = NULL;
    static char *lang_sync_ok = NULL; // 用于同步标记，如果检测到语言一致的置1，否则置0，并继续尝试同步
    if (local_lang == NULL)
    {
        char *tmp = read_file_data(ENV_LANG_CFG_FILE);
        if (tmp == NULL)
        {
            return -1;
        }
        local_lang = tmp;
    }

    int cur_sum = get_pcs_ctrl_var()->lc_list_len;
    lc_it *lcs = get_pcs_ctrl_var()->lc_list;

    if (lang_sync_ok == NULL)
    {
        lang_sync_ok = calloc(cur_sum, sizeof(char));
        if (lang_sync_ok == NULL)
        {
            ems_syslog(LOG_ERR, "calloc failed");
            return -3;
        }
    }
    for (int i = 0; i < cur_sum; i++)
    {
        if ((lcs[i].type != eLC_TYPE_LC) || (lcs[i].disabled != 0) || (lang_sync_ok[i] != 0))
        {
            ems_syslog(LOG_NOTICE, "%s:[type:%d, disable:%d, lang_sync_ok:%d];local_lang:%s", lcs[i].no, lcs[i].type, lcs[i].disabled, lang_sync_ok[i], local_lang);
            continue;
        }
        snprintf(url_buff, sizeof(url_buff), "opc.tcp://%s:4841", lcs[i].ipaddr);
        char *ret = ua_client_call_read_cfg(url_buff, ENV_LANG);
        if (ret == NULL)
        {
            LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0011, lcs[i].no, "%s:读取语言配置失败", lcs[i].no);
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0011, lcs[i].no, "%s:读取语言配置成功", lcs[i].no);
            ems_syslog(LOG_ERR, "%s:%s;local_lang:%s", url_buff, ret, local_lang);
            if (strcmp(ret, local_lang) == 0)
            {
                LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0012, lcs[i].no, "%s:同步语言成功", lcs[i].no);
                lang_sync_ok[i] = 1;
            }
            else
            {
                LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0012, lcs[i].no, "%s:开始同步语言", lcs[i].no);
                ret = ua_client_call_write_cfg(url_buff, ENV_LANG, local_lang, strlen(local_lang));
                if (ret == NULL)
                {
                    LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0012, lcs[i].no, "%s:同步语言写入成功", lcs[i].no);
                }
                else
                {
                    LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0012, lcs[i].no, "%s:同步语言写入失败", lcs[i].no);
                }
            }
            free(ret);
        }
    }

    return 0;
}

int check_lc_ms_mode(void) {
    if (get_cabinet_info()->is_slave != 0)
    {
        return 0; // 自己本身是从机就不要把自己的模式同步下去
    }
    //
  static char *ms_mode_sync_ok = NULL;

  long master_ms_mode = get_cabinet_info()->ms_mode;

  int cur_sum = get_pcs_ctrl_var()->lc_list_len;
  lc_it *lcs = get_pcs_ctrl_var()->lc_list;

  if (ms_mode_sync_ok == NULL) {
    ms_mode_sync_ok = calloc(cur_sum, sizeof(char));
    if (ms_mode_sync_ok == NULL) {
      ems_syslog(LOG_ERR, "[MS_MODE_SYNC] ms_mode_sync_ok calloc failed");
      return -3;
    }
  }

  for (int i = 0; i < cur_sum; i++) {
    if ((lcs[i].type != eLC_TYPE_EMS) || (ms_mode_sync_ok[i] != 0))
      continue;

    char url[64] = {0};
    snprintf(url, sizeof(url), "opc.tcp://%s:4840", lcs[i].ipaddr);

    UA_Client *client = ua_connect_opcua(url);
    if (client == NULL)
      continue;

    long slave_ms_mode = ua_client_read_node_value_int(client, "ms_mode");

    if (slave_ms_mode < 0) {
      ua_client_delete(client);
      LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0013, lcs[i].no, "%s:主从模式获取失败", lcs[i].no);
      continue;
    }
    else
    {
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0013, lcs[i].no, "%s:主从模式获取成功", lcs[i].no);
    }

    if (slave_ms_mode == master_ms_mode)
    {
        ms_mode_sync_ok[i] = 1;
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0014, lcs[i].no, "%s:主从模式已同步", lcs[i].no);
    }
    else
    {
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0014, lcs[i].no, "%s:主从模式开始同步", lcs[i].no);
        UA_StatusCode write_ret = ua_client_write_node_value_int(client, "ms_mode", master_ms_mode);
        if (write_ret == UA_STATUSCODE_GOOD)
        {
            ms_mode_sync_ok[i] = 1;
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0014, lcs[i].no, "%s:主从模式同步写入成功", lcs[i].no);
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0014, lcs[i].no, "%s:主从模式同步写入失败", lcs[i].no);
        }
    }
    ua_client_delete(client);
  }
  return 0;
}

static char *find_lc_ems_dev_group_cfg_info_by_no(cJSON *root, const char *no)
{
    cJSON *dev_tree = cJSON_GetObjectItem(root, "dev_tree");
    if (dev_tree == NULL)
    {
        ems_syslog(LOG_ERR, "dev_tree is null");
        return NULL;
    }
    int cnt = cJSON_GetArraySize(dev_tree);
    cJSON *target = NULL;
    for (int i = 0; i < cnt; i++)
    {
        cJSON *item = cJSON_GetArrayItem(dev_tree, i);
        if (item == NULL)
        {
            ems_syslog(LOG_ERR, "GetArrayItem error");
            continue;
        }

        cJSON *no_item = cJSON_GetObjectItem(item, "no");
        if ((no_item != NULL) && (strcmp(no_item->valuestring, no) == 0))
        {
            target = item;
        }
    }

    if (target != NULL)
    {
        cJSON *obj = cJSON_CreateObject();
        if (obj !=  NULL)
        {
            dev_tree = cJSON_DetachItemFromObject(target, "dev_tree");
            cJSON_AddItemToObject(obj, "dev_tree", dev_tree);
            char *ret = cJSON_PrintUnformatted(obj);
            cJSON_Delete(obj);
            return ret;
        }
    }
    //
    return NULL;
}

int add_group_to_dev_tree(char **dev_tree)
{
    if (dev_tree == NULL || *dev_tree == NULL)
    {
        return -1;
    }

    cJSON *root = cJSON_Parse(*dev_tree);
    if (root == NULL)
    {
        return -2;
    }

    cJSON *dev_tree_arr = cJSON_GetObjectItem(root, "dev_tree");
    if (!cJSON_IsArray(dev_tree_arr))
    {
        cJSON_Delete(root);
        return -3;
    }

    int arr_size = cJSON_GetArraySize(dev_tree_arr);

    for (int i = 0; i < arr_size; i++)
    {
        cJSON *item = cJSON_GetArrayItem(dev_tree_arr, i);
        if (!cJSON_IsObject(item))
        {
            continue;
        }

        cJSON *type = cJSON_GetObjectItem(item, "type");
        cJSON *no = cJSON_GetObjectItem(item, "no");
        cJSON *sub_dev_tree = cJSON_GetObjectItem(item, "dev_tree");

        if (type && no && strcmp(type->valuestring, "LC") == 0 && cJSON_IsArray(sub_dev_tree))
        {

            int sub_size = cJSON_GetArraySize(sub_dev_tree);

            for (int j = 0; j < sub_size; j++)
            {
                cJSON *sub_item = cJSON_GetArrayItem(sub_dev_tree, j);
                if (!cJSON_IsObject(sub_item))
                {
                    continue;
                }

                cJSON *group = cJSON_GetObjectItem(sub_item, "group");
                if (group)
                {
                    cJSON_ReplaceItemInObject(sub_item, "group", cJSON_CreateString(no->valuestring));
                }
                else
                {
                    cJSON_AddStringToObject(sub_item, "group", no->valuestring);
                }
            }
        }
    }

    char *new_str = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);

    if (new_str)
    {
        free(*dev_tree);
        *dev_tree = new_str;
        return 0;
    }

    return -4;
}

typedef struct {
    char group[32];
    char mp[32];
    char pno[32];
    int found;
    int is_subdevice;
} FindGroupResult;

FindGroupResult find_group_for_no(cJSON *cfg_array, const char *no, const char *host_no) {
    FindGroupResult res = {0};
    int gcount = cJSON_GetArraySize(cfg_array);
    for (int i = 0; i < gcount; i++) {
        cJSON *group_obj = cJSON_GetArrayItem(cfg_array, i);
        cJSON *group_name = cJSON_GetObjectItem(group_obj, "group");
        cJSON *devices = cJSON_GetObjectItem(group_obj, "devices");
        if (!group_name || !devices) continue;
        int dev_count = cJSON_GetArraySize(devices);
        for (int j = 0; j < dev_count; j++) {
            cJSON *dev = cJSON_GetArrayItem(devices, j);
            cJSON *no_item = cJSON_GetObjectItem(dev, "no");
            cJSON *mp_item = cJSON_GetObjectItem(dev, "mp");
            if (no_item && strcmp(no_item->valuestring, no) == 0 &&
                mp_item && strcmp(mp_item->valuestring, host_no) == 0) {
                snprintf(res.group, sizeof(res.group), "%s", group_name->valuestring);
                snprintf(res.mp, sizeof(res.mp), "%s", mp_item->valuestring);
                snprintf(res.pno, sizeof(res.pno), "%s", no_item->valuestring);
                res.found = 1;
                res.is_subdevice = 0;
                return res;
            }
            cJSON *subdevs = cJSON_GetObjectItem(dev, "devices");
            if (subdevs && subdevs->type == cJSON_Array) {
                int sub_count = cJSON_GetArraySize(subdevs);
                for (int k = 0; k < sub_count; k++) {
                    cJSON *subdev = cJSON_GetArrayItem(subdevs, k);
                    cJSON *sub_no = cJSON_GetObjectItem(subdev, "no");
                    cJSON *sub_mp = cJSON_GetObjectItem(subdev, "mp");
                    if (sub_no && strcmp(sub_no->valuestring, no) == 0 &&
                        sub_mp && strcmp(sub_mp->valuestring, host_no) == 0) {
                        snprintf(res.group, sizeof(res.group), "%s", group_name->valuestring);
                        snprintf(res.mp, sizeof(res.mp), "%s", sub_mp->valuestring);
                        snprintf(res.pno, sizeof(res.pno), "%s", no_item->valuestring);
                        res.found = 1;
                        res.is_subdevice = 1;
                        return res;
                    }
                }
            }
        }
    }
    return res;
}

int bau_mode_add_group_to_dev_tree(char **dev_tree, const char *config_path) {
    char *cfg_str = read_file_data(config_path);
    if (!cfg_str) return -1;
    cJSON *cfg_array = cJSON_Parse(cfg_str);
    free(cfg_str);
    if (!cfg_array) return -2;

    cJSON *root = cJSON_Parse(*dev_tree);
    if (!root) {
        cJSON_Delete(cfg_array);
        return -3;
    }

    cJSON *dt_array = cJSON_GetObjectItem(root, "dev_tree");
    if (!dt_array || dt_array->type != cJSON_Array) {
        cJSON_Delete(cfg_array);
        cJSON_Delete(root);
        return -4;
    }

    int outer_sz = cJSON_GetArraySize(dt_array);
    for (int i = 0; i < outer_sz; ++i) {
        cJSON *outer_dev = cJSON_GetArrayItem(dt_array, i);
        cJSON *host_no = cJSON_GetObjectItem(outer_dev, "no"); // 主机no
        const char *host_no_str = host_no ? host_no->valuestring : "";

        cJSON *inner_dt = cJSON_GetObjectItem(outer_dev, "dev_tree");
        if (!inner_dt || inner_dt->type != cJSON_Array) continue;

        int inner_sz = cJSON_GetArraySize(inner_dt);
        for (int j = 0; j < inner_sz; ++j) {
            cJSON *dev = cJSON_GetArrayItem(inner_dt, j);
            cJSON *no = cJSON_GetObjectItem(dev, "no");
            if (!no) continue;

            FindGroupResult res = find_group_for_no(cfg_array, no->valuestring, host_no_str);

            if (!res.found && host_no_str) {
                strncpy(res.group, host_no_str, sizeof(res.group) - 1);
            }

            cJSON *group_item = cJSON_CreateString(res.group);
            if (cJSON_GetObjectItem(dev, "group"))
                cJSON_ReplaceItemInObject(dev, "group", group_item);
            else
                cJSON_AddItemToObject(dev, "group", group_item);

            // parent
            if (res.found && res.is_subdevice) {
                if (strlen(res.mp) > 0 && strlen(res.pno) > 0) {
                    if (strcmp(host_no_str, res.mp) != 0) {
                        ems_syslog(LOG_ERR, "parent_no %s not equal to mp %s", res.pno, res.mp);
                    } else {
                        cJSON *parent_item = cJSON_CreateString(res.pno);
                        if (cJSON_GetObjectItem(dev, "parent"))
                            cJSON_ReplaceItemInObject(dev, "parent", parent_item);
                        else
                            cJSON_AddItemToObject(dev, "parent", parent_item);
                    }
                }
            }
        }
    }

    char *new_str = cJSON_PrintUnformatted(root);
    cJSON_Delete(cfg_array);
    cJSON_Delete(root);

    if (!new_str) return -5;
    free(*dev_tree);
    *dev_tree = new_str;
    return 0;
}

int flush_dev_group_info_file(void)
{
    char *dev_tree = get_all_dev_tree();
    if (dev_tree == NULL)
    {
        ems_syslog(LOG_ERR, "get all dev tree failed");
        return -1;
    }

    int result = bau_mode_add_group_to_dev_tree(&dev_tree, DEV_GROUP_CFG_PATH);
    if(result != 0)
    {
        result = add_group_to_dev_tree(&dev_tree);
    }
    if (result != 0)
    {
        ems_syslog(LOG_WARNING, "add group info to devices failed");
        return -2;
    }

    char *old_dev_tree = read_file_data(CONFIG_PATH "/" DEV_GROUP_INFO_NAME".json");
    if ((old_dev_tree == NULL) || (strcmp(old_dev_tree, dev_tree) != 0)){
        write_file_data(CONFIG_PATH "/" DEV_GROUP_INFO_NAME".json", dev_tree, strlen(dev_tree));
        ems_syslog(LOG_INFO, "flush dev tree to %s", CONFIG_PATH "/" DEV_GROUP_INFO_NAME".json");
    }
    if (old_dev_tree != NULL)
    {
        free(old_dev_tree);
    }

    free(dev_tree);
    return 0;
}
int check_lc_group_info(void)
{
    static struct lc_group_info
    {
        // int ok;
        char *group_info;
    } *lc_group_cfg_info = NULL;
    static int lc_group_info_is_ok = 0;

    int cur_sum = get_pcs_ctrl_var()->lc_list_len;
    lc_it *lcs = get_pcs_ctrl_var()->lc_list;

    if (lc_group_info_is_ok == 0)
    {
        if (lc_group_cfg_info == NULL)
        {
            lc_group_cfg_info = calloc(cur_sum, sizeof(struct lc_group_info));
            if (lc_group_cfg_info == NULL)
            {
                return -1;
            }
        }
        //
        char *cfg_name = DEV_GROUP_INFO_PATH;
        char *tmp = read_file_data(cfg_name);
        if (tmp == NULL)
        {
            free(tmp);
            return -2;
        }
        cJSON *dev_group_info = cJSON_Parse(tmp);
        free(tmp);
        if (dev_group_info == NULL)
        {
            return -3;
        }
        //
        for (int i = 0; i < cur_sum; i++)
        {
            if (lcs[i].type == eLC_TYPE_EMS)
            {
                lc_group_cfg_info[i].group_info = find_lc_ems_dev_group_cfg_info_by_no(dev_group_info, lcs[i].no);
                if (lc_group_cfg_info[i].group_info == NULL)
                {
                    LC_BUSINESS_LOG(BLOG_WARNING, MS_ID_0008, lcs[i].no, "没有获取到从机的分组配置信息:%s", lcs[i].no);
                }
                else
                {
                    LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0008, lcs[i].no, "%s -> 分组配置信息:%s", lcs[i].no, lc_group_cfg_info[i].group_info);
                }
            }
        }
        lc_group_info_is_ok = 1;
        cJSON_Delete(dev_group_info);
    }
    //
    for (int i = 0; i < cur_sum; i++)
    {
        if (lcs[i].type != eLC_TYPE_EMS)
        {
            ems_syslog(LOG_NOTICE, "%s:[type:%d, disable:%d", lcs[i].no, lcs[i].type, lcs[i].disabled);
            continue;
        }
        //
        char *lc_cur_group_info = slave_ems_get_dev_group_info(lcs[i].slave);
        if (lc_cur_group_info == NULL)
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0009, lcs[i].no, "%s: 从机当前分组信息为空", lcs[i].no);
        }
        else if (lc_group_cfg_info[i].group_info == NULL)
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0009, lcs[i].no, "%s: 从机配置的分组信息为空", lcs[i].no);
        }
        else if (strcmp(lc_cur_group_info, lc_group_cfg_info[i].group_info) == 0)
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0009, lcs[i].no, "%s: 从机的分组信息没有一致", lcs[i].no);
        }
        else
        {
            // if (slave_ems_set_dev_group_info(lcs[i].slave, lc_cur_group_info) != 0)
            char url[128] = {0};
            snprintf(url, sizeof(url), "opc.tcp://%s:4840", lcs[i].ipaddr);
            char *ret = ua_client_call_write_cfg(url, DEV_GROUP_INFO_NAME, lc_group_cfg_info[i].group_info, strlen(lc_group_cfg_info[i].group_info));
            if (ret == NULL)
            {
                LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0010, lcs[i].no, "给从机设置分组信息失败,lc:%s", lcs[i].no);
            }
            else if (strstr(ret, "ok") != NULL)
            {
                LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0010, lcs[i].no, "给从机设置分组信息成功,lc:%s", lcs[i].no);
            }
            else
            {
                LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0010, lcs[i].no, "给从机设置分组信息失败,lc:%s,ret:%s", lcs[i].no, ret);
            }
        }
    }

    return 0;
}

#if 1 // LC点位汇集逻辑
static const char *lc_node_ave_sync_tab[] = {
    CURRENT_SOC,
    CURRENT_SOH,
};


static const char *lc_node_sum_sync_tab[] = {
    ACTIVE_POWER,
    REACTIVE_POWER,
    MAX_CHARGE_POWER,
    MAX_DISCHA_POWER,
};

static const char *lc_node_status_sync_tab[] = {
    ON_OFF_STATUS,
    CONNECT_STATUS,
};


typedef struct lc_contex
{
    char no[DEV_NO_LEN];
} lc_contex;
int lc_sum = 0;

static lc_contex g_lc_contex[MAX_LC_SUM_SUPPORT] = {0};

static int lc_collector_variant_monitor_add(pcs_ctrl_var_t *var, const char **node, size_t size, ems_data_par_cb cb)
{
    int err = 0, ret;
    int cur_sum = var->lc_list_len;
    lc_it *lc_cfg = var->lc_list;
    char node_name[TAG_NAME_LEN] = {};

    for (int i = 0; i < cur_sum; i++)
    {
		ret = snprintf(g_lc_contex[i].no, sizeof(g_lc_contex[i].no), "%s", lc_cfg[i].no);
		if (ret < 0 || ret >= sizeof(g_lc_contex[i].no)) {
			ems_syslog(LOG_WARNING, "lc no truncated: %s", lc_cfg[i].no);
		}
        g_lc_contex[i].no[sizeof(g_lc_contex[i].no) - 1] = '\0';

        for (int j = 0; j < size; j++)
        {
			ret = snprintf(node_name, sizeof(node_name), "%s.%s", lc_cfg[i].no, node[j]);
			if (ret < 0 || ret >= sizeof(node_name)) {
				ems_syslog(LOG_WARNING, "node_name too long: %s.%s", lc_cfg[i].no, node[j]);
			}

            if (0 != collector_variant_monitor_add_one_with_par(var, node_name, cb, &g_lc_contex[i], 1))
            {
                ems_syslog(LOG_ERR, "vlc add monitor fail:%s", node_name);
            }
            else
            {
                ems_syslog(LOG_NOTICE, "vlc add monitor success:%s", node_name);
            }
        }
    }

    return err;
}
char* extract_tag_name(const char* input) {
    const char* dot_pos = strchr(input, '.');
    
    if (dot_pos != NULL && dot_pos > input) {
        size_t len = strlen(dot_pos + 1);
        char* result = (char*)malloc(len + 1);
        if (result == NULL) return NULL; 

        strcpy(result, dot_pos + 1);
        return result;
    }

    return strdup(input);
}
static void lc_data_sum_tab_cb(char *nod, tag_value *data_value, void *par)
{
    char* set_tag_name = extract_tag_name(nod);
    double sum = 0.0;
    pcs_ctrl_var_t *var = get_pcs_ctrl_var();

    int cur_sum = var->lc_list_len;
    lc_it *lc_cfg = var->lc_list;
    MS_MODE_ENUM ms_mode = get_cabinet_info()->ms_mode;

    if (ms_mode == MS_MODE_STD) {
        for (int i = 0; i < cur_sum; i++) {
            if(dev_get_dev_tag_int(lc_cfg[i].no, STATE_ONLINE) == 1)
            {
                sum += dev_get_dev_tag_float(lc_cfg[i].no, set_tag_name);
            }
            else
            {
                ems_syslog(LOG_NOTICE, "lc:%s is offline", lc_cfg[i].no);
            }
        }
    } else if (ms_mode == MS_MODE_DC) {
        for (int i = 0; i < cur_sum; i++) {
            if (lc_cfg[i].type == eLC_TYPE_VLC)
            {
                sum += dev_get_dev_tag_float(lc_cfg[i].no, set_tag_name);
            }
        }
    }

    if (data_value->type == 0) {
        dev_set_dev_tag_int(DEV_NO_EMS, set_tag_name, sum);
    } else {
        dev_set_dev_tag_float(DEV_NO_EMS, set_tag_name, sum);
    }
    free(set_tag_name);
}


static void lc_data_ave_tab_cb(char *nod, tag_value *data_value, void *par)
{
    char* set_tag_name = extract_tag_name(nod);
    double num = 0.0, sum = 0.0, ave = 0.0;
    int valid_count = 0;
    pcs_ctrl_var_t *var = get_pcs_ctrl_var();
    int cur_sum = var->lc_list_len;
    lc_it *lc_cfg = var->lc_list;

    for(int i = 0; i < cur_sum; i++) {
        if(dev_get_dev_tag_int(lc_cfg[i].no, STATE_ONLINE) == 1)
        {
            num = dev_get_dev_tag_float(lc_cfg[i].no, set_tag_name);
            if (num > 0.0) {
                sum += num;
                valid_count++;
            }
        }
    }

    if (valid_count > 0) {
        ave = sum / valid_count;
    } else {
        ave = 0.0;
    }

    if (data_value->type == 0)
    {
        dev_set_dev_tag_int(DEV_NO_EMS, set_tag_name, ave);
    }
    else
    {
        dev_set_dev_tag_float(DEV_NO_EMS, set_tag_name, ave);
    }
    free(set_tag_name);
}

static void lc_data_status_tab_cb(char *nod, tag_value *data_value, void *par)
{
    char* set_tag_name = extract_tag_name(nod);
    int result = 0;
    pcs_ctrl_var_t *var = get_pcs_ctrl_var();
    int cur_sum = var->lc_list_len;
    lc_it *lc_cfg = var->lc_list;

    for (int i = 0; i < cur_sum; i++) {
        if(dev_get_dev_tag_int(lc_cfg[i].no, STATE_ONLINE) == 1)
        {
            result |= dev_get_dev_tag_int(lc_cfg[i].no, set_tag_name);
        }
    }

    dev_set_dev_tag_int(DEV_NO_EMS, set_tag_name, result ? 1 : 0);
    free(set_tag_name);
}

int lc_variant_cb_register(void *var)
{
    lc_sum = ((pcs_ctrl_var_t *)var)->lc_list_len;
    lc_collector_variant_monitor_add((pcs_ctrl_var_t *)var, lc_node_ave_sync_tab, sizeof(lc_node_ave_sync_tab) / sizeof(char *), lc_data_ave_tab_cb);
    lc_collector_variant_monitor_add((pcs_ctrl_var_t *)var, lc_node_sum_sync_tab, sizeof(lc_node_sum_sync_tab) / sizeof(char *), lc_data_sum_tab_cb);
    lc_collector_variant_monitor_add((pcs_ctrl_var_t *)var, lc_node_status_sync_tab, sizeof(lc_node_status_sync_tab) / sizeof(char *), lc_data_status_tab_cb);
    return 0;
}

#endif


int copy_remote_template_to_local(const char *ipaddr, const char *lc_no) {
    char local_path[256];
    snprintf(local_path, sizeof(local_path), LOCAL_TEMPLATE_FILE_PATH_FMT, lc_no);
    char cmd[1024];
    snprintf(cmd, sizeof(cmd),
             "sshpass -p '%s' scp -o StrictHostKeyChecking=no root@%s:%s %s",
             SSH_PASSWORD, ipaddr, REMOTE_TEMPLATE_FILE_PATH, local_path);
    int ret = system(cmd);
    if (ret != 0) {
        ems_syslog(LOG_ERR, "SCP failed: %s", cmd);
        return -1;
    }
    return 0;
}

static char *get_remote_file_md5(const char *ipaddr, const char *remote_file_path)
{
    char cmd[256] = {0};
    snprintf(cmd, sizeof(cmd), "sshpass -p lnxall123 ssh -o StrictHostKeyChecking=no root@%s \"md5sum %s | cut -d' ' -f1 | tr -d '\n'\"", ipaddr, remote_file_path);
    char re_str[256] = {0};
    int ret = getCommandReturn(cmd, re_str, 256);
    ems_syslog(LOG_ERR, "exc_cmd: %s, ret[%d]:%s", cmd, ret, (re_str == NULL ? "NULL" : re_str));
    if ((ret == 0) && (strlen(re_str) == 32))
    {
        return strdup(re_str);
    }
    return NULL;
}

static int copy_remote_file_to_local(const char *ipaddr, const char *remote_file_path, const char *local_file_path)
{
    char cmd[1024] = {0};
    snprintf(cmd, sizeof(cmd),
             "sshpass -p '%s' scp -o StrictHostKeyChecking=no root@%s:%s %s",
             SSH_PASSWORD, ipaddr, remote_file_path, local_file_path);
    int ret = system(cmd);
    if (ret != 0)
    {
        ems_syslog(LOG_ERR, "scp failed: %s", cmd);
        return -1;
    }
    return 0;
}

static int remote_file_sync(const char *ipaddr, const char *remote_file_path, const char *local_file_path)
{
    int ret = -1;
    char *local_md5 = cal_file_md5(local_file_path);
    if (local_md5 != NULL)
    {
        char *remote_md5 = get_remote_file_md5(ipaddr, remote_file_path);
        ems_syslog(LOG_WARNING, "%s md5:%s", local_file_path, local_md5);
        if (remote_md5 != NULL)
        {
            if (strcmp(local_md5, remote_md5) != 0)
            {
                ems_syslog(LOG_WARNING, "%s md5:%s, %s:%s md5:%s", local_file_path, local_md5, ipaddr, remote_file_path, remote_md5);
                copy_remote_file_to_local(ipaddr, remote_file_path, local_file_path);
                ret = 1;
            }
            else
            {
                ems_syslog(LOG_INFO, "%s md5:%s, %s:%s md5:%s", local_file_path, local_md5, ipaddr, remote_file_path, remote_md5);
                ret = 0;
            }
            free(remote_md5);
        }
        free(local_md5);
    }
    return ret;
}
int check_and_sync_lc_template() {
    int cur_sum = get_pcs_ctrl_var()->lc_list_len;
    lc_it *lc_list = get_pcs_ctrl_var()->lc_list;
    static char *template_sync_ok = NULL;
    char lc_template[256] = {0};
    int need_restart = 0;
    int ret = 0;
    if (template_sync_ok == NULL) {
        template_sync_ok = calloc(cur_sum, sizeof(char));
        if (template_sync_ok == NULL) {
            ems_syslog(LOG_ERR, "calloc failed for template_sync_ok");
            return -1;
        }
    }

    ems_syslog(LOG_NOTICE, "check_and_sync_lc_template: lc_sum=%d", cur_sum);

    for (int i = 0; i < cur_sum; i++) {
        if ((lc_list[i].type != eLC_TYPE_LC)) {
            ems_syslog(LOG_NOTICE, "skip main LC: %s", lc_list[i].no);
            continue;
        }

        if (template_sync_ok[i]) {
            ems_syslog(LOG_NOTICE, "LC %s already synced, skip", lc_list[i].no);
            continue;
        }
        snprintf(lc_template, sizeof(lc_template), "/app/config/lc_devs/%s/lc_template.json", lc_list[i].no);
        ret = remote_file_sync(lc_list[i].ipaddr, "/app/template/lc_template.json", lc_template);
        if (ret > 0)
        {
            ret = 0;
            need_restart++;
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0015, lc_list[i].no, "%s:同步模板文件", lc_list[i].no);
        }
        else if (ret == 0)
        {
            template_sync_ok[i] = 1;
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0015, lc_list[i].no, "%s:模板文件同步完成", lc_list[i].no);
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0015, lc_list[i].no, "%s:同步模板发生异常", lc_list[i].no);
        }
        //

    }
    if (need_restart) {
        LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0016, NULL, "有从机的模板更新,应用重启......");
        exit(25);
    }
    return 0;
}

int get_slave_frpcaddr_by_ua(void)
{
    pcs_ctrl_var_t *var = get_pcs_ctrl_var();
    int lc_list_len = var->lc_list_len;
    lc_it *it_arr = var->lc_list;
    static int lc_frpcaddr_count_tmp = 0;
    char url_buff[128] = {0};
    static char *frpcaddr_sync_ok = NULL;

    if (frpcaddr_sync_ok == NULL)
    {
        frpcaddr_sync_ok = calloc(lc_list_len, sizeof(char));
        if (frpcaddr_sync_ok == NULL)
        {
            ems_syslog(LOG_ERR, "calloc failed");
            return -3;
        }
    }

    for (int i = 0; i < lc_list_len; i++)
    {
        if (it_arr[i].type != eLC_TYPE_EMS || frpcaddr_sync_ok[i] != 0)
            continue;
        snprintf(url_buff, sizeof(url_buff), "opc.tcp://%s:4840", it_arr[i].ipaddr);
        char *ret = ua_client_call_read_cfg(url_buff, "subdomain_read");
        if (!ret)
        {
            LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0017, it_arr[i].no, "%s:获取frpc地址失败", it_arr[i].no);
            continue;
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0017, it_arr[i].no, "%s:获取frpc地址成功", it_arr[i].no);
        }
        cJSON *root = cJSON_Parse(ret);
        free(ret);
        if (!root)
        {
            LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0018, it_arr[i].no, "%s:解析frpc地址失败", it_arr[i].no);
            continue;
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0018, it_arr[i].no, "%s:解析frpc地址成功", it_arr[i].no);
        }
        cJSON *subdomain = cJSON_GetObjectItem(root, "subdomain");
        if (subdomain && cJSON_IsString(subdomain))
        {
            strncpy(g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].lc_no, it_arr[i].no, sizeof(g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].lc_no) - 1);
            g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].lc_no[sizeof(g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].lc_no) - 1] = '\0';

            strncpy(g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].frpcaddr, subdomain->valuestring, sizeof(g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].frpcaddr) - 1);
            g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].frpcaddr[sizeof(g_slave_lc_frpcaddr_list[lc_frpcaddr_count_tmp].frpcaddr) - 1] = '\0';

            lc_frpcaddr_count_tmp++;
            frpcaddr_sync_ok[i] = 1;
            LC_BUSINESS_LOG(BLOG_NOTICE, MS_ID_0019, it_arr[i].no, "%s:获取frpc地址成功", it_arr[i].no);
        }
        else
        {
            LC_BUSINESS_LOG(BLOG_ERR, MS_ID_0019, it_arr[i].no, "%s:获取frpc地址失败", it_arr[i].no);
        }
        cJSON_Delete(root);
    }
    g_slave_lc_frpcaddr_count = lc_frpcaddr_count_tmp;

    return g_slave_lc_frpcaddr_count;
}

slave_lc_frpcaddr_t* get_lc_list_frpcaddr(void)
{
    return g_slave_lc_frpcaddr_list;
}

int get_lc_list_frpcaddr_count(void)
{
    return g_slave_lc_frpcaddr_count;
}

void add_frpcaddr_info_to_lc_list(char* all_info, int all_info_len)
{
    slave_lc_frpcaddr_t* frpcaddr_arr = get_lc_list_frpcaddr();
    int frpcaddr_count = get_lc_list_frpcaddr_count();

    cJSON *root = cJSON_Parse(all_info);
    if (!root) {
        return;
    }
    cJSON *json_arr = cJSON_GetObjectItem(root, "devices");
    if (!json_arr || !cJSON_IsArray(json_arr)) {
        cJSON_Delete(root);
        return;
    }
    int json_len = cJSON_GetArraySize(json_arr);

    // 添加本机柜名称
    for(int n = 0; n < json_len; n++){
        cJSON *obj = cJSON_GetArrayItem(json_arr, n);
        cJSON *type_item = cJSON_GetObjectItem(obj, "type");
        if(type_item && cJSON_IsString(type_item)){
            const char* json_type = type_item->valuestring;
            if(json_type && strcmp(json_type, "vlc") == 0){
                cJSON *cabname_item = cJSON_CreateString(cabinet_info.name);
                cJSON_AddItemToObject(obj, "cabname", cabname_item);
                break;
            }
        }
    }

    for (int i = 0; i < frpcaddr_count; i++) {
        const char* lc_no = frpcaddr_arr[i].lc_no;
        const char* frpcaddr = frpcaddr_arr[i].frpcaddr;

        for (int j = 0; j < json_len; j++) {
            cJSON *obj = cJSON_GetArrayItem(json_arr, j);
            cJSON *no_item = cJSON_GetObjectItem(obj, "no");
            if (no_item && cJSON_IsString(no_item)) {
                const char* json_no = no_item->valuestring;
                if (json_no && strcmp(json_no, lc_no) == 0) {
                    cJSON *frpcaddr_item = cJSON_CreateString(frpcaddr ? frpcaddr : "");
                    cJSON_AddItemToObject(obj, "frpcaddr", frpcaddr_item);
                    break;
                }
            }
        }
    }
    char *out_str = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);
    if (out_str != NULL) {
        strncpy(all_info, out_str, all_info_len - 1);
        all_info[all_info_len - 1] = '\0';
        free(out_str);
    }
}

char* make_slave_alarm_info_json(void)
{
    int lc_list_len = get_pcs_ctrl_var()->lc_list_len;
    lc_it *it_arr = get_pcs_ctrl_var()->lc_list;
    cJSON *root = cJSON_CreateObject();
    cJSON *arr_json = cJSON_CreateArray();

    for (int i = 0; i < lc_list_len; i++) {
        if (it_arr[i].type != eLC_TYPE_EMS) {
            continue;
        }
        cJSON *item = cJSON_CreateObject();
        cJSON_AddStringToObject(item, "no", it_arr[i].no);
        cJSON_AddStringToObject(item, "name", it_arr[i].name);
        cJSON_AddNumberToObject(item, "cur_alarmcnt", dev_get_dev_tag_int(it_arr[i].no, ALARMCNT));
        cJSON_AddNumberToObject(item, "his_alarmcnt", dev_get_dev_tag_int(it_arr[i].no, ALARMHISCNT));
        cJSON_AddItemToArray(arr_json, item);
    }
    cJSON_AddItemToObject(root, "lc_alarmcnt", arr_json);
    char *json_str = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);
    return json_str;
}


static char * fault_status_sync_tab[] = {
    PROHIBITE_CHARGE,
    PROHIBITE_DISCHARGE,
    DOD_LIMITED,
    DOD_LIMITED_CH,
    DOD_LIMITED_DC,
    SINGLE_CELL_VOL_THRESHOLD_ALARM,
    IO_PROTECT,
    FIRE
};

int check_ems_and_fault_word_tag_status(void) {
  pcs_ctrl_var_t *var = get_pcs_ctrl_var();
  int lc_cnt = var->lc_list_len;
  lc_it *lc_list_tmp = var->lc_list;

  int tab_cnt = sizeof(fault_status_sync_tab) / sizeof(char *);

  for (int i = 0; i < tab_cnt; i++) {
    const char *tag_name = fault_status_sync_tab[i];
    int status_ems = 1;

    for (int j = 0; j < lc_cnt; j++) {
      if (lc_list_tmp[j].disabled != 0) {
        continue;
      }
      const char *lc_no = lc_list_tmp[j].no;
      int status = dev_get_dev_tag_int(lc_no, tag_name);

      if (status == 0) {
        status_ems = 0;
        break;
      }
    }

    if (lc_cnt == 0) {
      status_ems = 0;
    }

    int old_status = dev_get_dev_tag_int(DEV_NO_EMS, tag_name);
    if (old_status != status_ems) {
      dev_set_dev_tag_int(DEV_NO_EMS, tag_name, status_ems);
    }
  }

  return 0;
}
