#include "ccu_config.h"
#include "interface.h"
#include "ocpp16.h"
#include "ocpp16_configkey.h"
#include "ocpp16_authorization.h"
#include "ocpp16_OfflineChargingRecords.h"
#include "ocpp16_ChargingProfiles.h"
#include "pcuhw.h"
#include "pcutc.h"
#include "ccu_bsp.h"
#include "gun_port.h"

static int ccu_SYS_send(ccu_port_t* ccu, uint8_t* data, int length)
{
    int ret = send(ccu->fd[1], data, length, 0);
    return ret;
}

static int ccu_SYS_recv(ccu_port_t* ccu, int timeout, uint8_t* buff, int length)
{
    fd_set rset;
    struct timeval tv;
    
    tv.tv_sec = timeout/1000;
    tv.tv_usec = (timeout%1000)*10000;
    FD_ZERO(&rset);
    FD_SET(ccu->fd[0], &rset);
    int ret = select(ccu->fd[0] + 1, &rset, NULL, NULL, &tv);
    if (ret > 0)
    {
        int FRAME_LENGTH = 8;
        modbusTCP_frame_t frame;
        modbusTCP_frame_t* pkg = (modbusTCP_frame_t*)&frame;
        ccu->rxbyte = 0;
        while(ccu->rxbyte < FRAME_LENGTH)
        {
            int ret = recv(ccu->fd[0], &ccu->rxbuff[ccu->rxbyte], (FRAME_LENGTH - ccu->rxbyte), 0);
            if (ret <= 0) { printe("ret:%d, errno:%d\n", ret, errno); return -1; }
            if (ret > 0) ccu->rxbyte += ret;
            if (ccu->rxbyte >= FRAME_LENGTH)
            {
                decode_FRAME(ccu->rxbuff, pkg);
                if (pkg->protoID == 0x0000)
                {
                    FRAME_LENGTH = 8 + pkg->datalen;
                    break;
                }
                memmove(&ccu->rxbuff[0], &ccu->rxbuff[1], ccu->rxbyte - 1);
                ccu->rxbyte = ccu->rxbyte - 1;
            }
        }
        while(ccu->rxbyte < FRAME_LENGTH)
        {
            int ret = recv(ccu->fd[0], &ccu->rxbuff[ccu->rxbyte], (FRAME_LENGTH - ccu->rxbyte), 0);
            if (ret <= 0) { printe("ret:%d, errno:%d\n", ret, errno); return -1; }
            if (ret > 0) ccu->rxbyte += ret;
            if (ccu->rxbyte >= FRAME_LENGTH)
            {
                // printd("command:%d, FRAME_LENGTH:%d\n", pkg->command, FRAME_LENGTH);
                return pkg->command;
            }
        }
        printe("ret:%d, errno:%d\n", ret, errno);
        return -1;
    }
    // printe("ret:%d, errno:%d\n", ret, errno);
    return ret;
}

void ccu_send_GUN_error_state(void* self, int connectorId, uint32_t GUN_Error, uint32_t GUN_State)
{
    ccu_port_t* ccu = (ccu_port_t*)self;
    uint8_t stream[256];
    int idx = 8;
    idx = encode_U32(stream, idx, 1, &connectorId);
    idx = encode_U32(stream, idx, 1, &GUN_Error);
    idx = encode_U32(stream, idx, 1, &GUN_State);
    idx = encode_HEADER(stream, ccu->transID++, 0, modbusTCP_CMD_0xA0, NULL, idx - 8);
    ccu_SYS_send((ccu_port_t*)ccu, stream, idx);
    // printd("step\n");
}

static int ccu_report_GUN_error_state(ccu_port_t* ccu)
{
    secc_port_t *seccA = (secc_port_t*)ccu->seccA;
    secc_port_t *seccB = (secc_port_t*)ccu->seccB;
    modbusTCP_QT_t *qttcu = (modbusTCP_QT_t*)ccu->qttcu;
    ocpp_client_t *ocpp0 = (ocpp_client_t *)ccu->ocpp0;
    ocpp_client_t *ocpp1 = (ocpp_client_t *)ccu->ocpp1;
    ccu_config_t *config = &ccu_config;
    uint8_t *stream = ccu->rxbuff;
    uint16_t datalen = (ccu->rxbuff[4]<<8) | ccu->rxbuff[5];
    int connectorId;
    uint32_t GUN_Error;
    uint32_t GUN_State;
    int idx = 8;
    idx = decode_U32(stream, idx, 1, &connectorId);
    idx = decode_U32(stream, idx, 1, &GUN_Error);
    idx = decode_U32(stream, idx, 1, &GUN_State);
    // idx = decode_HEADER(stream, transID, protoID, datalen, unitAddr, command);
    int FRAMELEN = 8 + datalen;
    memmove(ccu->rxbuff, &ccu->rxbuff[FRAMELEN], (ccu->rxbyte - FRAMELEN));

    modbusTCP_QT_cmd_0x90(qttcu, connectorId, GUN_Error, GUN_State);
    ocpp16_report_StatusNotification(ocpp0, connectorId, GUN_Error, GUN_State);
    return 0;
}

void ccu_send_GUN_MeterValues(void* self, int connectorId)
{
    ccu_port_t* ccu = (ccu_port_t*)self;
    uint8_t stream[256];
    int idx = 8;
    idx = encode_U32(stream, idx, 1, &connectorId);
    idx = encode_HEADER(stream, ccu->transID++, 0, modbusTCP_CMD_0xA1, NULL, idx - 8);
    ccu_SYS_send((ccu_port_t*)ccu, stream, idx);
    // printd("step\n");
}

static int ccu_report_GUN_MeterVaules(ccu_port_t* ccu)
{
    secc_port_t *seccA = (secc_port_t*)ccu->seccA;
    secc_port_t *seccB = (secc_port_t*)ccu->seccB;
    secc_port_t *secc = NULL;
    modbusTCP_QT_t *qttcu = (modbusTCP_QT_t*)ccu->qttcu;
    ocpp_client_t *ocpp0 = (ocpp_client_t *)ccu->ocpp0;
    ocpp_client_t *ocpp1 = (ocpp_client_t *)ccu->ocpp1;
    ccu_config_t *config = &ccu_config;
    uint8_t *stream = ccu->rxbuff;
    uint16_t datalen = (ccu->rxbuff[4]<<8) | ccu->rxbuff[5];
    int connectorId;
    int idx = 8;
    idx = decode_U32(stream, idx, 1, &connectorId);
    // idx = decode_HEADER(stream, transID, protoID, datalen, unitAddr, command);
    int FRAMELEN = 8 + datalen;
    memmove(ccu->rxbuff, &ccu->rxbuff[FRAMELEN], (ccu->rxbyte - FRAMELEN));

    modbusTCP_QT_cmd_0x91(qttcu, connectorId);
    ocpp16_report_MeterValues_Interval(ocpp0, connectorId);

    return 0;
}

void ccu_send_TransactionStart(void* self, int connectorId)
{
    ccu_port_t* ccu = (ccu_port_t*)self;
    uint8_t stream[256];
    int idx = 8;
    idx = encode_U32(stream, idx, 1, &connectorId);
    idx = encode_HEADER(stream, ccu->transID++, 0, modbusTCP_CMD_0xA2, NULL, idx - 8);
    ccu_SYS_send((ccu_port_t*)ccu, stream, idx);
    printd("step\n");
}

static int ccu_report_TransactionStart(ccu_port_t* ccu)
{
    secc_port_t *seccA = (secc_port_t*)ccu->seccA;
    secc_port_t *seccB = (secc_port_t*)ccu->seccB;
    secc_port_t *secc = NULL;
    modbusTCP_QT_t *qttcu = (modbusTCP_QT_t*)ccu->qttcu;
    ocpp_client_t *ocpp0 = (ocpp_client_t *)ccu->ocpp0;
    ocpp_client_t *ocpp1 = (ocpp_client_t *)ccu->ocpp1;
    ccu_config_t *config = &ccu_config;
    uint8_t *stream = ccu->rxbuff;
    uint16_t datalen = (ccu->rxbuff[4]<<8) | ccu->rxbuff[5];
    int connectorId;
    int idx = 8;
    idx = decode_U32(stream, idx, 1, &connectorId);
    // idx = decode_HEADER(stream, transID, protoID, datalen, unitAddr, command);
    printd("step, connectorId:%d\n", connectorId);
    int FRAMELEN = 8 + datalen;
    memmove(ccu->rxbuff, &ccu->rxbuff[FRAMELEN], (ccu->rxbyte - FRAMELEN));

    modbusTCP_QT_cmd_0x91(qttcu, connectorId);
    ocpp16_request_TransactionStart(ocpp0, connectorId);

    return 0;
}

void ccu_send_TransactionStop(void* self, int connectorId, int transactionId)
{
    ccu_port_t* ccu = (ccu_port_t*)self;
    uint8_t stream[256];
    int idx = 8;
    idx = encode_U32(stream, idx, 1, &connectorId);
    idx = encode_U32(stream, idx, 1, &transactionId);
    idx = encode_HEADER(stream, ccu->transID++, 0, modbusTCP_CMD_0xA3, NULL, idx - 8);
    ccu_SYS_send((ccu_port_t*)ccu, stream, idx);
    printd("step\n");
}

static int ccu_report_TransactionStop(ccu_port_t* ccu)
{
    secc_port_t *seccA = (secc_port_t*)ccu->seccA;
    secc_port_t *seccB = (secc_port_t*)ccu->seccB;
    secc_port_t *secc = NULL;
    modbusTCP_QT_t *qttcu = (modbusTCP_QT_t*)ccu->qttcu;
    ocpp_client_t *ocpp0 = (ocpp_client_t *)ccu->ocpp0;
    ocpp_client_t *ocpp1 = (ocpp_client_t *)ccu->ocpp1;
    ccu_config_t *config = &ccu_config;
    uint8_t *stream = ccu->rxbuff;
    uint16_t datalen = (ccu->rxbuff[4]<<8) | ccu->rxbuff[5];
    int connectorId;
    int transactionId;
    int idx = 8;
    idx = decode_U32(stream, idx, 1, &connectorId);
    idx = decode_U32(stream, idx, 1, &transactionId);
    // idx = decode_HEADER(stream, transID, protoID, datalen, unitAddr, command);
    int FRAMELEN = 8 + datalen;
    memmove(ccu->rxbuff, &ccu->rxbuff[FRAMELEN], (ccu->rxbyte - FRAMELEN));

    modbusTCP_QT_cmd_0x91(qttcu, connectorId);
    ocpp16_request_TransactionStop(ocpp0, connectorId, transactionId);

    return 0;
}


ccu_port_t ccu_port = 
{
    .config = &ccu_config,
    .ocpp0 = &ocpp0,
    .ocpp1 = &ocpp1,
    .qttcu = &qttcu,
    .priv = &ccu_port,
    .quit = 0,
};

static void gunA_IO_check(ccu_port_t* ccu)
{
    secc_port_t* seccA =(secc_port_t*)ccu->seccA;
    if (seccA->reserve_on)
    {
        secc_set_error(seccA, GUN_RESERVED);
    }
    if (gunA_get_FuseState(ccu) == 1)
    {
        secc_set_error(seccA, GUN_FUSE_ALARM);
    }
    if (gunA_get_PluginHomeState(ccu) == 1)
    {
        secc_set_state(seccA, GUN_NORMAL);
    }
    if (seccA->config->GUN_Type == GUN_TYPE_GBDC)
    {
        seccA->CC1_voltage = gunA_get_CC1_voltage(ccu);
        // printi("CC1_voltage:%f\n", seccA->CC1_voltage);
        if ((4.0f - 0.5f) < seccA->CC1_voltage  &&  seccA->CC1_voltage < (4.0f + 0.5f))
        {
            if (seccA->GUN_State < GUN_PLUGIN)
            {
                secc_init_can(seccA);
                secc_set_state(seccA, GUN_PLUGIN);
            }
        }else{
            if (seccA->GUN_State >= GUN_PLUGIN)
            {
                secc_exit_can(seccA);
                secc_set_state(seccA, GUN_PLUGOUT);
            } 
        }
    }
    if (seccA->config->GUN_Type == GUN_TYPE_NACS || seccA->config->GUN_Type == GUN_TYPE_CCS1 || seccA->config->GUN_Type == GUN_TYPE_NACS)
    {
        seccA->CP_voltage = gunA_get_CP_voltage(ccu, &seccA->CP_State);
        // printi("CP_voltage:%f\n", seccA->CP_voltage);
        if ((3.0f - 0.5f) < seccA->CP_voltage  &&  seccA->CP_voltage < (10.0f + 0.5f))
        {
            if (seccA->GUN_State < GUN_PLUGIN)
            {
                secc_init_v2g(seccA);
                secc_set_state(seccA, GUN_PLUGIN);
                
            }
        }
        if (seccA->CP_voltage >= (10.0f + 0.5f))
        {
            if (seccA->CP_State >= GUN_PLUGIN)
            {
                secc_exit_v2g(seccA);
                secc_set_state(seccA, GUN_PLUGOUT);
            }
        }
    }

    {
        float GUNTemp[2];
        GUNTemp[0] = gunA_get_GunLine_temp(seccA, 0);
        GUNTemp[1] = gunA_get_GunLine_temp(seccA, 2);
        // printi("GUNA_Temp:%f, %f\n", GUNTemp[0], GUNTemp[1]);
        if (GUNTemp[0] > 100 || GUNTemp[1] > 100)
        {
            secc_set_error(seccA, GUN_LINE_OVERTEMP);
        }
    }

    printd("Error:%08x, State:%d, VGround:%3.6f, VMeters:%3.6f, IMeters:%3.6f\n", seccA->GUN_Error, seccA->GUN_State, seccA->VGround, seccA->VMeters, seccA->IMeters);
}

static void gunB_IO_check(ccu_port_t* ccu)
{
    secc_port_t* seccB =(secc_port_t*)ccu->seccB;
    if (seccB->reserve_on)
    {
        secc_set_error(seccB, GUN_RESERVED);
    }
    if (gunB_get_FuseState(ccu) == 1)
    {
        secc_set_error(seccB, GUN_FUSE_ALARM);
    }
    if (gunB_get_PluginHomeState(ccu) == 1)
    {
        secc_set_state(seccB, GUN_NORMAL);
    }  
    if (seccB->config->GUN_Type == GUN_TYPE_GBDC)
    {
        seccB->CC1_voltage = gunB_get_CC1_voltage(ccu);
        // printi("CC1_voltage:%f\n", seccB->CC1_voltage);
        if ((4.0f - 0.5f) < seccB->CC1_voltage  &&  seccB->CC1_voltage < (4.0f + 0.5f))
        {
            if (seccB->GUN_State < GUN_PLUGIN)
            {
                secc_init_can(seccB);
                secc_set_state(seccB, GUN_PLUGIN);
            }
        }else{
            if (seccB->GUN_State >= GUN_PLUGIN)
            {
                secc_exit_can(seccB);
                secc_set_state(seccB, GUN_PLUGOUT);
            } 
        }
    }
    if (seccB->config->GUN_Type == GUN_TYPE_NACS || seccB->config->GUN_Type == GUN_TYPE_CCS1 || seccB->config->GUN_Type == GUN_TYPE_NACS)
    {
        seccB->CP_voltage = gunB_get_CP_voltage(ccu, &seccB->CP_State);
        // printi("CP_voltage:%f\n", seccB->CP_voltage);
        if ((3.0f - 0.5f) < seccB->CP_voltage  &&  seccB->CP_voltage < (10.0f + 0.5f))
        {
            if (seccB->GUN_State < GUN_PLUGIN)
            {
                secc_init_v2g(seccB);
                secc_set_state(seccB, GUN_PLUGIN);
            }
        }
        if (seccB->CP_voltage >= (10.0f + 0.5f))
        {
            if (seccB->CP_State >= GUN_PLUGIN)
            {
                secc_exit_v2g(seccB);
                secc_set_state(seccB, GUN_PLUGOUT);
            }
        }
    }
    {
        float GUNTemp[2];
        GUNTemp[0] = gunB_get_GunLine_temp(seccB, 0);
        GUNTemp[1] = gunB_get_GunLine_temp(seccB, 2);
        // printi("GUNB_Temp:%f, %f\n", GUNTemp[0], GUNTemp[1]);
        if (GUNTemp[0] > 100 || GUNTemp[1] > 100)
        {
            secc_set_error(seccB, GUN_LINE_OVERTEMP);
        }
    }
    // printd("Error:%08x, State:%d, VGround:%3.6f, VMeters:%3.6f, IMeters:%3.6f\n", seccB->GUN_Error, seccB->GUN_State, seccB->VGround, seccB->VMeters, seccB->IMeters);
}

static void ccu_IO_check(ccu_port_t* ccu)
{
    ccu_config_t *config = &ccu_config;
    secc_port_t* seccA =(secc_port_t*)ccu->seccA;
    secc_port_t* seccB =(secc_port_t*)ccu->seccB;

    {
        float BOXTemp[4];
        BOXTemp[0] = ccu_Get_BOX_Temp(ccu, 0);
        BOXTemp[1] = ccu_Get_BOX_Temp(ccu, 1);
        BOXTemp[2] = ccu_Get_BOX_Temp(ccu, 2);
        BOXTemp[3] = ccu_Get_BOX_Temp(ccu, 3);
        ccu->BOX_Temp = fmax(fmax(BOXTemp[0], BOXTemp[1]), fmax(BOXTemp[2], BOXTemp[3]));
        if (ccu->BOX_Temp > 1000)
        {
            secc_set_error(seccA, CCU_BOX_OVERTEMP);
            secc_set_error(seccB, CCU_BOX_OVERTEMP);
        }
    }
    //CCU_SMOKE_ALARM
    if (ccu_Get_LGPTFA(ccu) == 1)
    {
        secc_set_error(seccA, CCU_LGPTA_ALARM);
        secc_set_error(seccB, CCU_LGPTA_ALARM);
    }
    if (ccu_Get_LGPTFB(ccu) == 1)
    {
        secc_set_error(seccA, CCU_LGPTB_ALARM);
        secc_set_error(seccB, CCU_LGPTB_ALARM);
    }
    if (ccu_Get_FanFBA(ccu) == 1)
    {
        secc_set_error(seccA, CCU_FANA_ALARM);
        secc_set_error(seccB, CCU_FANA_ALARM);
    }
    if (ccu_Get_FanFBB(ccu) == 1)
    {
        secc_set_error(seccA, CCU_FANB_ALARM);
        secc_set_error(seccB, CCU_FANB_ALARM);
    }
    if (ccu_Get_FanFBC(ccu) == 1)
    {
        secc_set_error(seccA, CCU_FANC_ALARM);
        secc_set_error(seccB, CCU_FANC_ALARM);
    }
    //CCU_LIQC_ALARM
    if (ccu_Get_EMGFB(ccu) == 1)
    {
        secc_set_error(seccA, CCU_EMG_STOP);
        secc_set_error(seccB, CCU_EMG_STOP);
    }
    if (ccu_Get_WATFB(ccu) == 1)
    {
        secc_set_error(seccA, CCU_WAT_STOP);
        secc_set_error(seccB, CCU_WAT_STOP);
    }
    if (ccu_Get_GATFB(ccu) == 1)
    {
        secc_set_error(seccA, CCU_GAT_STOP);
        secc_set_error(seccB, CCU_GAT_STOP);
    }
    //CCU_HPUMP_ALARM
    if (config->CCU_Available == false)
    {
        secc_set_error(seccA, GUN_INOPERAVIVE);
        secc_set_error(seccB, GUN_INOPERAVIVE);
    }
    if (config->GUN_config[0].GUN_Available == 0)
    {
        secc_set_error(seccA, GUN_INOPERAVIVE);
    }
    if (config->GUN_config[1].GUN_Available == 0)
    {
        secc_set_error(seccB, GUN_INOPERAVIVE);
    }
}

static void ccu_DCMeter_check(ccu_port_t* ccu)
{
    secc_port_t* seccA =(secc_port_t*)ccu->seccA;
    secc_port_t* seccB =(secc_port_t*)ccu->seccB;
    ccu_Read_DCMeterA(ccu, &seccA->VMeters, &seccA->IMeters, &seccA->PMeters, &seccA->EMeters);
    ccu_Read_DCMeterB(ccu, &seccB->VMeters, &seccB->IMeters, &seccB->PMeters, &seccB->EMeters);
}

static void ccu_DCGround_check(ccu_port_t* ccu)
{
    ccu_config_t *config = &ccu_config;
    secc_port_t* seccA =(secc_port_t*)ccu->seccA;
    secc_port_t* seccB =(secc_port_t*)ccu->seccB;
    pcu_port_t* pcuA = (pcu_port_t*)ccu->pcuA;
    pcu_port_t* pcuB = (pcu_port_t*)ccu->pcuB;
    ccu_Read_DCGroundA(ccu, &seccA->VGround, &seccA->RGround);
    ccu_Read_DCGroundB(ccu, &seccB->VGround, &seccB->RGround);

    if (config->pcuhw.enable)
    {
        pcuA->update_K1K2_voltate(pcuA, seccA->VGround);
        pcuB->update_K1K2_voltate(pcuB, seccB->VGround);
    }
}

void* ccu_SYS_loop(void* argv)
{
    ccu_config_t *config = &ccu_config;
    ccu_port_t* ccu = (ccu_port_t*)argv;
    secc_port_t* seccA =(secc_port_t*)ccu->seccA;
    secc_port_t* seccB =(secc_port_t*)ccu->seccB;

    printd("ENTER.\n");
    while(ccu->quit == 0)
    {
        seccA->GUN_Error = 0;
        seccB->GUN_Error = 0;  
        ccu_IO_check(ccu);
        gunA_IO_check(ccu);
        gunB_IO_check(ccu);
        ccu_DCMeter_check(ccu);
        ccu_DCGround_check(ccu);
    }
    printd("LEAVE.\n");

    return NULL;
}

void* ccu_SYS_cmdloop(void* argv)
{
    ccu_config_t *config = &ccu_config;
    ccu_port_t* ccu = (ccu_port_t*)argv;

    printd("ENTER.\n");
    while(ccu->quit == 0)
    {
        int ret = ccu_SYS_recv(ccu, 100, &ccu->rxbuff[ccu->rxbyte], 1024 - ccu->rxbyte);
        switch (ret)
        {
            case modbusTCP_CMD_0xA0: ccu_report_GUN_error_state(ccu); break;
            case modbusTCP_CMD_0xA1: ccu_report_GUN_MeterVaules(ccu); break;
            case modbusTCP_CMD_0xA2: ccu_report_TransactionStart(ccu); break;
            case modbusTCP_CMD_0xA3: ccu_report_TransactionStop(ccu); break;
            default: ccu->rxbyte = 0;break;
        }
    }
    printd("LEAVE.\n");

    return NULL;
}

int ccu_SYS_init(void)
{
    printd("ENTER.\n");
    ccu_port_t* ccu = (ccu_port_t*)&ccu_port;
    ccu_config_t *config = &ccu_config;
    socketpair(AF_UNIX, SOCK_STREAM, 0, ccu->fd);

    ccu_BSP_init();
    gunA_BSP_init();
    gunB_BSP_init();
    qttcu.ccu = &ccu_port;
    ocpp0.ccu = &ccu_port;
    ocpp1.ccu = &ccu_port;

    // return 0;

    printd("step, config->pcuhw.enable:%d\n", config->pcuhw.enable);
    if (config->pcuhw.enable == 1)
    {
        ccu->pcuA = (pcu_port_t*)&pcuhwA;
        ccu->pcuB = (pcu_port_t*)&pcuhwB;
        pcuhw_Stub_init();
    }
    printd("step, config->pcutc.enable:%d\n", config->pcutc.enable);
    if (config->pcutc.enable == 1){
        ccu->pcuA = (pcu_port_t*)&pcutcA;
        ccu->pcuB = (pcu_port_t*)&pcutcB;
        pcutc_Stub_init();
    }

    secc_portA_bind(ccu);
    secc_portB_bind(ccu);

    printd("step, config->qttcu.enable:%d\n", config->qttcu.enable);
    if (config->qttcu.enable)
    {
        modbusTCP_QT_init(&qttcu);
    }

    printd("step, config->ocpp16[0].enable:%d\n", config->ocpp16[0].enable);
    if (config->ocpp16[0].enable)
    {
        ocpp16_client_init(&ocpp0, &config->ocpp16[0]);
    }

    printd("step, config->ocpp16[1].enable:%d\n", config->ocpp16[1].enable);
    if (config->ocpp16[1].enable)
    {
        ocpp16_client_init(&ocpp1, &config->ocpp16[1]);
    }

    ccu->quit = 0;
    mthread_init(&ccu->threadIO, ccu_SYS_loop, ccu);
    mthread_init(&ccu->threadCmd, ccu_SYS_cmdloop, ccu);
    printd("LEAVE.\n");

    return 0;
}

void ccu_SYS_exit(void)
{
    ccu_port_t* ccu = (ccu_port_t*)&ccu_port;
    ocpp_client_t *ocpp0 = (ocpp_client_t *)ccu->ocpp0;
    ocpp_client_t *ocpp1 = (ocpp_client_t *)ccu->ocpp1;
    modbusTCP_QT_t *qttcu = (modbusTCP_QT_t*)ccu->qttcu;
    ccu_config_t *config = &ccu_config;

    ccu->quit = 1;
    mthread_exit(&ccu->threadIO);
    mthread_exit(&ccu->threadCmd);

    if (config->ocpp16[1].enable)
    {
        ocpp16_client_exit(ocpp1);
    }
    
    if (config->ocpp16[0].enable)
    {
        ocpp16_client_exit(ocpp0);
    }

    if (config->qttcu.enable)
    {
        qttcu->quit = 1;
    }
    
    if (config->pcuhw.enable == 1)
    {
        pcuhw_Stub_exit();
    }

    if (config->pcutc.enable == 1)
    {
        pcutc_Stub_exit();
    }
    ccu->pcuA = NULL;
    ccu->pcuB = NULL;
    gunA_BSP_exit();
    gunB_BSP_exit();
    ccu_BSP_exit();
}
