/*
 * @Author: ybzhou yibo.zhou@lnxall.com
 * @Date: 2025-07-16 12:48:28
 * @LastEditors: ybzhou yibo.zhou@lnxall.com
 * @LastEditTime: 2025-11-24 13:56:09
 * @FilePath: /2025.07.09/proto_forward/src/automatic_north/modbus_create.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <math.h>
#include <time.h>

#include "xlsx_api.h"
#include "modbus_create.h"

static home_page_t _mo_home_page[] = {
	{"EMS",  1, 1, ""},
	{"PCS",  2,  33, "the maximum support is 32"},
	{"BMS", 34, 97, "the maximum support is 64"},
	{"CELL", 98, 161, "the maximum support is 64"},
	{"GRID_METER", 162, 162, "the maximum support is 64"},
	{"PCS_METER", 163, 163, "the maximum support is 64"},
	{"TRAS_LV_METER", 164, 164, "the maximum support is 64"},
	{"LOAD_METER", 165, 165, "the maximum support is 64"},
	{"PV_METER", 166, 166, "the maximum support is 64"},
	{"DG_METER", 167, 167, "the maximum support is 64"},
	{"OTHERS_METER", 168, 168, "the maximum support is 64"},
	{"DIDO", 169, 169, "the maximum support is 64"},
	{"AIRCOND", 170, 170, "the maximum support is 64"},
	{"TMS", 171, 171, "the maximum support is 64"},
	{"PV", 172, 172, "the maximum support is 64"},
	{"DG", 173, 173, "the maximum support is 64"},
	{"FIRE", 174, 174, "the maximum support is 64"},
	{"OTHERS", 175, 175, "the maximum support is 64"},
};
static int num_mo = sizeof(_mo_home_page) / sizeof(_mo_home_page[0]);

/*
       本协议适用于简捷物联科技有限公司EMS北向与Modbus主站之间的通信。采用 Ethernet(Modbus TCP)通信规约。				
设备名称	起始地址码	最大地址码	设备数量	                     备注
EMS        	 1	          1	        1	
PCS	         2	          17	    最大支持16个	
主控        	 18	          49	    最大支持32个		
电芯	         50	          81	    最大支持32个		
关口表	         82	          82	    ...	
储能电表	     83	          83		...    	
干接点          84	          84	    ...
空调           85           85        ...
其他           86           86	    最大支持32个
*/
static mo_proto_info_t modbus_proto_info = {
    .ver = 2,
	.server_port = 502,
	.start_bits = 1,	
	.start_input_bits = 10001,
	.start_registers = 40001,
	.start_input_registers = 30001,
	.mo_class_info = {
	    {ems_compatible,  1, 1, 0, 0, 0, 0},
	    {pcs_compatible,  2, 33, 0, 0, 0, 0},
		{bms_compatible,  34, 97, 0, 0, 0, 0},
		{cell_compatible, 98, 161, 0, 0, 0, 0},
		{grid_meter_compatible, 162, 162, 0, 0, 0, 0},
		{pcs_meter_compatible, 163, 163, 0, 0, 0, 0},
		{tras_lv_meter_compatible, 164, 164, 0, 0, 0, 0},
		{load_meter_compatible, 165, 165, 0, 0, 0, 0},
		{pv_meter_compatible, 166, 166, 0, 0, 0, 0},
		{dg_meter_compatible, 167, 167, 0, 0, 0, 0},
		{other_meter_compatible, 168, 168, 0, 0, 0, 0},
		{dido_compatible, 169, 169, 0, 0, 0, 0},
		{ac_compatible, 170, 170, 0, 0, 0, 0},
		{tms_compatible, 171, 171, 0, 0, 0, 0},
		{pv_compatible, 172, 172, 0, 0, 0, 0},
		{dg_compatible, 173, 173, 0, 0, 0, 0},
		{fire_compatible, 174, 174, 0, 0, 0, 0},
		{others_compatible, 175, 175, 0, 0, 0, 0}
	}
};

mo_proto_info_t *get_mo_proto_info(void){
    return &modbus_proto_info;
}

int mo_match_by_class_type(char **class_type, mo_class_info_t *mo_class_info, int *p_compatible_id, int log_flag) // -1，匹配失败，其他匹配成功
{
    int ret = -1;

	for (int i = 0; i < CLASS_NUM; i++) {
        char **pp = mo_class_info[i].compatible;
        if (pp == NULL) {
            continue;
        }
		
        ret = i;
		while (*pp != NULL)
		{
			if (log_flag) ems_syslog(LOG_DEBUG, "compatible = %s", *pp);
			if (strcmp(*pp, *class_type) == 0)
			{
				*p_compatible_id = pp - mo_class_info[i].compatible;
				snprintf(*class_type, 32, "%s", _class_name[i].compatible[0]);
				return ret;
			}
			pp++;
		}
	}
    return -1;
}

static void mo_write_worksheet_item(lxw_worksheet *worksheet, int row, lxw_format *format, mq_north_tag_t *mq_north_tag) {

	mo_north_tag_t *mo_north_tag = mq_north_tag->proto_ptr;

    write_number_cell(worksheet, row, 0, (double)mo_north_tag->function_code, format);
    write_number_cell(worksheet, row, 1, (double)mo_north_tag->addr, format);
    write_string_cell(worksheet, row, 2, mq_north_tag->tag->disp_name, format);
    write_string_cell(worksheet, row, 3, (mq_north_tag->limit == 1) ? "RW" : "RO", format);
    write_string_cell(worksheet, row, 4, xlsx_data_type[mo_north_tag->data_type], format);
    write_number_cell(worksheet, row, 5, mq_north_tag->tag->scale, format);
    write_string_cell(worksheet, row, 6, property_name[mq_north_tag->tag->property], format);
	if (mq_north_tag->tag->desc != NULL && strlen(mq_north_tag->tag->desc) > 0){
		write_string_cell(worksheet, row, 7, mq_north_tag->tag->desc, format);
	}
	else
	{
		char *dynamic_desc = add_dynamic_desc(mq_north_tag->tag->name);
		if (dynamic_desc != NULL) {
			write_string_cell(worksheet, row, 7, dynamic_desc, format);
			free(dynamic_desc);
		}
	}
	if (mq_north_tag->tag->active){ // 无效
		write_string_cell(worksheet, row, 8, _("Invalid"), format);
		write_string_cell(worksheet, row, 2, _("Reserved"), format);
	}
}

static void mo_write_worksheet_title(lxw_worksheet *worksheet, int row, lxw_format *format) {

	write_string_cell(worksheet, row, 0, _("Function code"), format); // 功能码
	write_string_cell(worksheet, row, 1, _("Address"), format); // 地址
	write_string_cell(worksheet, row, 2, _("Name"),   format);  // 名称
	write_string_cell(worksheet, row, 3, _("Permission"), format);  // 权限
	write_string_cell(worksheet, row, 4, _("Data type"), format);   // 数据类型
	write_string_cell(worksheet, row, 5, _("Precision"), format);   // 精度
	write_string_cell(worksheet, row, 6, _("Attribute"), format);   // 属性
	write_string_cell(worksheet, row, 7, _("Description"), format); // 描述
	write_string_cell(worksheet, row, 8, _("Is it valid (default: valid)"), format); // 是否有效（默认：有效）
}

static int create_mo_external_xlsx(mq_xlsx_book_t *mq_xlsx_book)
{
    chdir(NORTH_EXCEL_FILE_DOWNLOAD_PATH);

    int i = 0, j = 0;
	char dev_info[128] = "";
    mq_xlsx_book->workbook = workbook_new(mq_xlsx_book->name);
    if (mq_xlsx_book->workbook == NULL) {
		ems_syslog(LOG_ERR, "Error in workbook_new()"
               "Error = %s\n", "Failed to create the workbook!!!");
		return -1;
	}

    north_info_t *north_info = get_north_info();
	xlsx_home_page(mq_xlsx_book->workbook, north_info->protocol_type, _mo_home_page, num_mo); // 首表

	lxw_format *merge_format = create_center_bold_bg_format(mq_xlsx_book->workbook, LXW_COLOR_CYAN, 12);
    lxw_format *format1 = create_left_bold_format(mq_xlsx_book->workbook, 12);
	lxw_format *format2 = create_left_format(mq_xlsx_book->workbook, 12);
	for (i = 0; i < mq_xlsx_book->num; i++) {
		mq_xlsx_sheet_t *mq_xlsx_sheet = mq_xlsx_book->mq_xlsx_sheet[i];

	    mq_xlsx_sheet->worksheet = workbook_add_worksheet(mq_xlsx_book->workbook, _(mq_xlsx_sheet->name));
		if (mq_xlsx_sheet->worksheet == NULL){
			ems_syslog(LOG_ERR, "Error in workbook_add_worksheet()"
			        "Name %s，Error = %s\n", mq_xlsx_sheet->name, "Failed to create the worksheet!!!");
			continue;
		}
		/* Make the columns wider for clarity. */
		set_column_width(mq_xlsx_sheet->worksheet, 0, 0, 20);
		set_column_width(mq_xlsx_sheet->worksheet, 1, 1, 10);
		set_column_width(mq_xlsx_sheet->worksheet, 2, 2, 40);
		set_column_width(mq_xlsx_sheet->worksheet, 3, 3, 20);
		set_column_width(mq_xlsx_sheet->worksheet, 4, 4, 20);
		set_column_width(mq_xlsx_sheet->worksheet, 5, 5, 20);
		set_column_width(mq_xlsx_sheet->worksheet, 6, 6, 20);
		set_column_width(mq_xlsx_sheet->worksheet, 7, 7, 50);
		set_column_width(mq_xlsx_sheet->worksheet, 8, 8, 50);
		
		for (j = 0; j < mq_xlsx_sheet->num; j++) {
			mq_xlsx_dev_t *mq_xlsx_dev = mq_xlsx_sheet->mq_xlsx_dev[j];
			mo_xlsx_dev_t *mo_xlsx_dev = mq_xlsx_dev->proto_ptr;

		    snprintf(dev_info, 128, "no:%s  name:%s  Addr:%d", mq_xlsx_dev->no, mq_xlsx_dev->disp_name, mo_xlsx_dev->addr);
	        merge_cells(mq_xlsx_sheet->worksheet, mq_xlsx_sheet->cuurent_row, 0, mq_xlsx_sheet->cuurent_row, 7, dev_info, merge_format);
			mq_xlsx_sheet->cuurent_row++;
			
			mo_write_worksheet_title(mq_xlsx_sheet->worksheet, mq_xlsx_sheet->cuurent_row, format1);
			mq_xlsx_sheet->cuurent_row++;
			
			for (int m = 0; m < mo_xlsx_dev->nb_coil; m++) {
                 mq_north_tag_t *mq_north_tag = mo_xlsx_dev->coil_tag[m];
                 mo_write_worksheet_item(mq_xlsx_sheet->worksheet, mq_xlsx_sheet->cuurent_row, format2, mq_north_tag);
			     mq_xlsx_sheet->cuurent_row++;
			}
			for (int m = 0; m < mo_xlsx_dev->nb_inputb; m++) {
                 mq_north_tag_t *mq_north_tag = mo_xlsx_dev->inputb_tag[m];
                 mo_write_worksheet_item(mq_xlsx_sheet->worksheet, mq_xlsx_sheet->cuurent_row, format2, mq_north_tag);
			     mq_xlsx_sheet->cuurent_row++;
			}
			for (int m = 0; m < mo_xlsx_dev->nb_hold; m++) {
                 mq_north_tag_t *mq_north_tag = mo_xlsx_dev->hold_tag[m];
                 mo_write_worksheet_item(mq_xlsx_sheet->worksheet, mq_xlsx_sheet->cuurent_row, format2, mq_north_tag);
			     mq_xlsx_sheet->cuurent_row++;
			}
			for (int m = 0; m < mo_xlsx_dev->nb_inputr; m++) {
                 mq_north_tag_t *mq_north_tag = mo_xlsx_dev->inputr_tag[m];
                 mo_write_worksheet_item(mq_xlsx_sheet->worksheet, mq_xlsx_sheet->cuurent_row, format2, mq_north_tag);
			     mq_xlsx_sheet->cuurent_row++;
			}
				
			mq_xlsx_sheet->cuurent_row += DEV_BETWEEN_ROW;
		}
	}   

    /* Close the workbook, save the file and free any memory. */
    lxw_error error = workbook_close(mq_xlsx_book->workbook);

    /* Check if there was any error creating the xlsx file. */
    if (error){
        ems_syslog(LOG_ERR, "Error in workbook_close().\n"
               "Error %d = %s\n", error, lxw_strerror(error));
        return error;
    }

	return 0;
}

//static const char *data_order[] = {"A", "AB", "BA", "ABCD", "DCBA", "BADC", "CDAB", "ABCDEFGH", "GHEFCDAB", "BADCFEHG", "HGFEDCBA"};
static void mo_write_inside_taginfo(mq_north_tag_t *mq_north_tag, int find, cJSON *g_pointInfo, cJSON *pointInfo) {

    if ((find == 1 && pointInfo == NULL) || (find == 0 && g_pointInfo == NULL))
		return;
	
    char buff[32] = {0};
	mo_north_tag_t *mo_north_tag = mq_north_tag->proto_ptr;	
	
	cJSON *tag = cJSON_CreateArray();

    cJSON_AddItemToArray(tag, cJSON_CreateString(mq_north_tag->tag->dev_tag_name));
    cJSON_AddItemToArray(tag, cJSON_CreateNumber(mo_north_tag->function_code));
	snprintf(buff, 32, "%d", mo_north_tag->addr);
	cJSON_AddItemToArray(tag, cJSON_CreateString(buff));
	cJSON_AddItemToArray(tag, cJSON_CreateString(type_string[mo_north_tag->data_type]));				
	cJSON_AddItemToArray(tag, cJSON_CreateString(order_string[mo_north_tag->data_order]));
	cJSON_AddItemToArray(tag, cJSON_CreateNumber(mq_north_tag->coefficient));
	if (find)
		cJSON_AddItemToArray(pointInfo, tag);
	else
		cJSON_AddItemToArray(g_pointInfo, tag);
}

static char *create_mo_inside_json(mq_north_inside_t *mq_north_inside)
{
	cJSON *root = NULL;
	char *string = NULL;
	int i = 0, j = 0;
	root = cJSON_CreateObject();
	if (root == NULL)
	{
		ems_syslog(LOG_NOTICE, "cJSON_CreateObject root NULL");
		goto end;
	}
	char buff[32] = {0};
	mo_proto_info_t *mo_proto_info = get_mo_proto_info();
	
	// Add version field only if needed
	north_info_t *north_info = get_north_info();
	if (north_info->need_add_version) {
		cJSON_AddNumberToObject(root, "version", 2);
	}
	// Keep ver field for backward compatibility
	cJSON_AddNumberToObject(root, "ver", mo_proto_info->ver);
	cJSON_AddNumberToObject(root, "server_port", mo_proto_info->server_port);
	cJSON *pts = cJSON_CreateArray();
	cJSON_AddItemToObject(root, "pts", pts);

	for (i = 0; i < mq_north_inside->num; i++) {
		mq_north_class_t *mq_north_class = mq_north_inside->mq_north_class[i];

	    cJSON *g_pointInfo = NULL;
	    if ((strcmp(mq_north_class->class_type, "PCS") != 0) && (strcmp(mq_north_class->class_type, "BMS") != 0) && (strcmp(mq_north_class->class_type, "CELL") != 0) && (strcmp(mq_north_class->class_type, "EMS") != 0)){
			cJSON *domain = cJSON_CreateObject();

            mo_class_info_t *mo_class_info = NULL;
			int p_compatible_id = 0;
			int index = mo_match_by_class_type(&(mq_north_class->class_type), mo_proto_info->mo_class_info, &p_compatible_id, 0);
			if (index == -1){
                continue;
			}else
				mo_class_info = &(mo_proto_info->mo_class_info[index]);

		    mo_north_class_t *mo_north_class = (mo_north_class_t *)mq_north_class->proto_ptr;
			cJSON_AddNumberToObject(domain, "addr", mo_north_class->mo_class_info->str_addr); // 非PCS\BMS\CELL类型 则设备地址码固定			
			snprintf(buff, 32, "%d", mo_proto_info->start_bits);
			cJSON_AddStringToObject(domain, "start_bits", buff);
			cJSON_AddNumberToObject(domain, "nb_bits", mo_class_info->nb_bits); // 线圈寄存器个数
			snprintf(buff, 32, "%d", mo_proto_info->start_input_bits);
			cJSON_AddStringToObject(domain, "start_input_bits", buff);
			cJSON_AddNumberToObject(domain, "nb_input_bits", mo_class_info->nb_input_bits); // 离散量输入寄存器个数
			snprintf(buff, 32, "%d", mo_proto_info->start_registers);
			cJSON_AddStringToObject(domain, "start_registers", buff);
			cJSON_AddNumberToObject(domain, "nb_registers", mo_class_info->nb_registers); // 保持寄存器个数
			snprintf(buff, 32, "%d", mo_proto_info->start_input_registers);
			cJSON_AddStringToObject(domain, "start_input_registers", buff);
			cJSON_AddNumberToObject(domain, "nb_input_registers", mo_class_info->nb_input_registers); // 输入寄存器个数
			
			g_pointInfo = cJSON_CreateArray();
			cJSON_AddItemToObject(domain, "pointInfo", g_pointInfo);
			cJSON_AddItemToArray(pts, domain);
		}

		for (j = 0; j < mq_north_class->num; j++) {
			mq_north_dev_t *mq_north_dev = mq_north_class->mq_north_dev[j];
			mo_north_dev_t *mo_north_dev = (mo_north_dev_t *)mq_north_dev->proto_ptr;
		    int find = 0;
			if ((strcmp(mq_north_dev->class_type, "PCS") == 0) || (strcmp(mq_north_dev->class_type, "BMS") == 0) || (strcmp(mq_north_dev->class_type, "CELL") == 0) || (strcmp(mq_north_dev->class_type, "EMS") == 0)){
                find = 1;
			}    

            cJSON *pointInfo = NULL;
		    if (find){
				cJSON *domain = cJSON_CreateObject();
				
				cJSON_AddNumberToObject(domain, "addr", mo_north_dev->addr);				
				snprintf(buff, 32, "%d", mo_proto_info->start_bits);
				cJSON_AddStringToObject(domain, "start_bits", buff);
				cJSON_AddNumberToObject(domain, "nb_bits", 200); //
				snprintf(buff, 32, "%d", mo_proto_info->start_input_bits);
				cJSON_AddStringToObject(domain, "start_input_bits", buff);
				cJSON_AddNumberToObject(domain, "nb_input_bits", 500); //
				snprintf(buff, 32, "%d", mo_proto_info->start_registers);
				cJSON_AddStringToObject(domain, "start_registers", buff);
				cJSON_AddNumberToObject(domain, "nb_registers", 2000); //
				snprintf(buff, 32, "%d", mo_proto_info->start_input_registers);
				cJSON_AddStringToObject(domain, "start_input_registers", buff);
				cJSON_AddNumberToObject(domain, "nb_input_registers", 2000); //
				
				pointInfo = cJSON_CreateArray();
				cJSON_AddItemToObject(domain, "pointInfo", pointInfo);
				cJSON_AddItemToArray(pts, domain);
			}

			for (int m = 0; m < mo_north_dev->nb_coil; m++) {
				mq_north_tag_t *mq_north_tag = mo_north_dev->coil_tag[m];		
				mo_write_inside_taginfo(mq_north_tag, find, g_pointInfo, pointInfo);
			}
			for (int m = 0; m < mo_north_dev->nb_inputb; m++) {
				mq_north_tag_t *mq_north_tag = mo_north_dev->inputb_tag[m];			
				mo_write_inside_taginfo(mq_north_tag, find, g_pointInfo, pointInfo);
			}
			for (int m = 0; m < mo_north_dev->nb_hold; m++) { //ABCD
				mq_north_tag_t *mq_north_tag = mo_north_dev->hold_tag[m];			
				mo_write_inside_taginfo(mq_north_tag, find, g_pointInfo, pointInfo);
			}
			for (int m = 0; m < mo_north_dev->nb_inputr; m++) {  //ABCD
				mq_north_tag_t *mq_north_tag = mo_north_dev->inputr_tag[m];
				mo_write_inside_taginfo(mq_north_tag, find, g_pointInfo, pointInfo);
			}
		}
	}

	string = cJSON_Print(root);
end:
	if (root != NULL)
	{
		cJSON_Delete(root);
	}
	//ems_syslog(LOG_NOTICE,"%s",string);
	return string;
}

char *create_mo_relate_file(mq_north_inside_t *mq_north_inside, mq_xlsx_book_t *mq_xlsx_book){
    //int ret = 0;
	char *mo_inside_json = NULL;

	mo_inside_json = create_mo_inside_json(mq_north_inside);
	if (mo_inside_json)
	{
		if (check_file_exist(MODBUSTCP_NORTH_MAP_FILE) == 0)
			write_file_data(NORTH_CONFIG_PATH"/"MODBUSTCP_NORTH_MAP_FILE, mo_inside_json, strlen(mo_inside_json));
	}
	else
	{
		ems_syslog(LOG_ERR, "create modbustcp_north.json error!!!!");
	}

    if (check_file_exist(mq_xlsx_book->name) == 0)
		create_mo_external_xlsx(mq_xlsx_book);

	return mo_inside_json;	
}

void init_mo_north_dev(mo_north_dev_t *dev) {
    for (int i = 0; i < TAG_NUM; i++) {
        dev->coil_tag[i] = NULL;
        dev->hold_tag[i] = NULL;
    }
    for (int i = 0; i < TAG_NUM; i++) {
        dev->inputb_tag[i] = NULL;
        dev->inputr_tag[i] = NULL;
    }
}

void init_mo_xlsx_dev(mo_xlsx_dev_t *dev) {
    for (int i = 0; i < TAG_NUM; i++) {
        dev->coil_tag[i] = NULL;
        dev->hold_tag[i] = NULL;
    }
    for (int i = 0; i < TAG_NUM; i++) {
        dev->inputb_tag[i] = NULL;
        dev->inputr_tag[i] = NULL;
    }
}

void clear_mo_class_nb(mo_class_info_t *mo_class_info){ // modbus清零类，线圈、离散量输入、保持寄存器、输入寄存器个数
	for (int i = 0; i < CLASS_NUM; i++) {
		mo_class_info[i].nb_bits = 0;
		mo_class_info[i].nb_input_bits = 0;
        mo_class_info[i].nb_registers = 0;
		mo_class_info[i].nb_input_registers = 0;
	}
}