#include "Module_PsuComm.h"
#include "Config.h"
#include "Common.h"

#define DERATING_COUNT		30
#define DERATING_GAP		30
#define ELEMENT_NOT_FIND	255
#define CHK_VOL_RANGE		50
#define CHK_CUR_RANGE		10
#define DERATING_RANGE		100
#define ZERO_CURRENT		10			// 該值須保持最小為 1A
#define ZERO_VOLTAGE		50
#define STOP_CURRENT		30
#define PSU_MAX_CUR			1000
#define PSU_MIN_VOL			1500
#define PRE_CHARG_STEP_CUR	30
#define PRE_CHARG_RANGE		50
#define CMD_DELAY_TIME 		25000                   // 25us
#define PSU_TASK_CHECK_TIME 1
#define PSU_COMM_CHECK_TIME 1

#define GET_PSU_COUNT_INTERVAL          1           // unit: second
#define GET_PSU_COUNT_TIME              13          // unit: second
#define GET_PSU_LOCATION_INTERVAL       200         // unit: millisecond
#define PSU_COUNT_CONFIRM_INTERVAL      200         // unit: millisecond
#define PSU_COUNT_CONFIRM_TIME          1           // unit: second
#define GET_PSU_VERSION_INTERVAL        200         // unit: millisecond
#define GET_PSU_CAP_INTERVAL            200         // unit: millisecond
#define GET_PSU_CAP_TIME                1           // unit: second
#define STABLE_CURRENT_TOLERANCE        10          // unit: 0.1A, 1A
#define MAX_STABLE_COUNT                24

#define WAIT_EV_DERATING_TIMEOUT        5           // unit: second
#define WAIT_SLAVE_POWER_OFF_TIMEOUT    5           // unit: second
#define WAIT_PARALLEL_RELAY_DELAY       1           // unit: second
#define WAIT_RELAY_CONFIRMED_TIME       3           // unit: second
#define MAX_PREPARE_SWITCH_OFF_TIME     10          // unit: second
#define MIN_POWER_OFF_TIME              1           // unit: second
#define WAIT_SLAVE_TO_CHARGING          1           // unit: second
#define WAIT_SLAVE_TIMEOUT              5           // unit: second
#define WAIT_GRAB_TIME                  15          // unit: second
#define MAX_PSU_POWER_OFF_CURRENT       50          // unit: 0.1A, 5A
#define MAX_PSU_POWER_OFF_VOLTAGE       100         // unit: 0.1A, 10V
#define PRECHARGE_OFFSET_VOLTAGE        20          // unit: 0.1V, 2V
#define PRECHARGE_RANGE_VOLTAGE         50          // unit: 0.1V, 5V
#define WAIT_PRECHARGE_TIME             10          // unit: second
#define EXTEND_CAPABILITY_DELAY         60          // unit: second
#define EXTEND_LOADING                  8000        // unit: 0.01%, 80%
#define RELEASE_LOADING                 5000        // unit: 0.01%, 50%
#define RELEASE_LOADING_OFFSET          1000        // unit: 0.01%, 10%
#define BALANCE_CURRENT_INTERVAL        100         // unit: 1ms, 100ms
#define CURRENT_REACH_TARGET_TIME       5           // unit: 1s, 5s
#define CURRENT_STABLE_TIME             10          // unit: 1s, 10s
#define REACH_CURRENT_TOLERANCE         10          // unit: 0.1A, 1A
#define MAX_ADJ_BALANCE_CURRENT         30          // unit: 0.1A, 3A
#define START_BALANCE_CRITERIA          100         // unit: 0.01%, 1%
#define STOP_BALANCE_CRITERIA           10          // unit: 0.01%, 0.1%
#define EV_MAX_CURRENT_DELAY            120         // unit: second
#define WAIT_SLAVE_READY_TIME           15          // unit: second
#define WAIT_SLAVE_DELAY                1           // unit: second
#define SHOW_OUTPUT_DELAY               1
#define CURRENT_BALANCE_CRITERIA        400         // unit: 0.1A, 40A
#define SAFETY_PRECHARGE_OFFSET         200         // unit: 0.1V, 20V
#define CHARGING_DELAY_INTERVAL         200         // unit: 1ms, 400ms
#define MAX_CHARGING_DELAY_COUNT        15
#define VOLTAGE_RESUME_STEP             10          // unit: 0.1V, 1V
#define POWER_ONOFF_RESEND_INTERVAL     1           // unit: 1s, 1s
#define COMM_LOST_CRITICAL              0
#define LOST_CNT_CRITICAL               10

#if SAFETY_TEST_ENABLE
#define PRECHARGE_OFFSET                1           // 0: normal output, 1: precharge voltage offset enable
#define ONE_MODULE_OUTPUT               1           // 0: normal output, 1: only one module output enable
#define MASTER_OUTPUT_FIRST             0           // 0: normal output, 1: master output first enable
#else
#define PRECHARGE_OFFSET                0           // 0: normal output, 1: precharge voltage offset enable
#define ONE_MODULE_OUTPUT               0           // 0: normal output, 1: only one module output enable
#define MASTER_OUTPUT_FIRST             0           // 0: normal output, 1: master output first enable
#endif

struct SysConfigAndInfo			*ShmSysConfigAndInfo;
struct StatusCodeData 			*ShmStatusCodeData;
struct PsuData 					*ShmPsuData;
ChargerInfoData                 *ShmChargerInfo;
PsuPositionInfoData             *ShmPsuPosition;
PsuGroupingInfoData             *ShmPsuGrouping;

bool libInitialize = false;
byte _gunCount = 0;
byte _maxGroupCount = 0;
byte getAvailableCapOffset = 5;
byte deratingKeepCount = 0;
byte psuCmdSeq = _PSU_CMD_CAP;

byte startModuleFlag = false;
bool psuReceiveRecovery = false;
bool isCommStart = false;
int lostCnt = 0;

unsigned short evseOutVol[CONNECTOR_QUANTITY] = {0, 0, 0, 0};
unsigned short evseOutCur[CONNECTOR_QUANTITY] = {0, 0, 0, 0};
unsigned short evseOutputDelay[CONNECTOR_QUANTITY] = {0, 0, 0, 0};
struct timespec _PsuReceiveRecoveryCheck_time;
struct timespec _PsuWorkStep_time;
struct timespec _cmdSubPriority_time;
struct timespec _PsuGroupRole_time[CONNECTOR_QUANTITY];
struct timespec _ChargingRequest_time[CONNECTOR_QUANTITY];
struct timespec _PsuGroupDerating_time[CONNECTOR_QUANTITY];
struct timespec _StopCharging_time[CONNECTOR_QUANTITY];
struct timespec _ExtendCapability_time[CONNECTOR_QUANTITY];
struct timespec _ReachCurrent_time[CONNECTOR_QUANTITY];
struct timespec _BalanceCurrent_time[CONNECTOR_QUANTITY];
struct timespec _MaxCurrent_time[CONNECTOR_QUANTITY];
struct timespec _StageCurrent_time[CONNECTOR_QUANTITY];
struct timespec _CheckSlaveReady_time[CONNECTOR_QUANTITY];
struct timespec _ChargingDelay_time[CONNECTOR_QUANTITY];
struct timespec _PoweOnOff_time[CONNECTOR_QUANTITY];
struct timespec _PsuCommCheck_time;

unsigned short  GCTargetVoltage[CONNECTOR_QUANTITY];
unsigned short  GCTargetCurrent[CONNECTOR_QUANTITY];
unsigned short  StableOutputCurrent[CONNECTOR_QUANTITY];
unsigned short  MaxCurrentDemand[CONNECTOR_QUANTITY];
unsigned short  StageMaxCurrent[CONNECTOR_QUANTITY];

GroupOutputConfigInfo PreGroupOutput[MAX_GROUP_QUANTITY];
unsigned char OutputConfigStep[MAX_GROUP_QUANTITY];
unsigned char _preOutputConfigStep[MAX_GROUP_QUANTITY];

unsigned char _GfdStep[MAX_GROUP_QUANTITY];
unsigned char _VoltageResumeCnt[MAX_GROUP_QUANTITY];

//=================================
// Common routine
//=================================

// return psu group number
// return -1 when out of TotalPsuQuantity
int FindTargetGroup(byte address)
{
	return address < ShmPsuPosition->TotalPsuQuantity ? ShmPsuPosition->PsuAddressInfo[address].GroupNo : -1;
}

// return psu index in the group
// return -1 when out of TotalPsuQuantity
int FindGroupIndex(byte address)
{
    return address < ShmPsuPosition->TotalPsuQuantity ? ShmPsuPosition->PsuAddressInfo[address].GIndex : -1;
}

//=================================
// Save data to share memory Function
//=================================
bool FindChargingInfoData(byte target, struct ChargingInfoData **chargingData)
{
	if(GENERAL_GUN_QUANTITY > 0 && target < GENERAL_GUN_QUANTITY)
	{
		chargingData[target] = &ShmSysConfigAndInfo->SysInfo.ConnectorInfo[target].GeneralChargingData;
		return true;
	}

	return false;
}

//=================================
// Alarm code mapping to share memory Function
//=================================
// 檢查 Byte 中某個 Bit 的值
// _byte : 欲改變的 byte
// _bit : 該 byte 的第幾個 bit
unsigned char mask_table[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
unsigned char DetectBitValue(unsigned char _byte, unsigned char _bit)
{
	return ( _byte & mask_table[_bit] ) != 0x00;
}

void AbnormalStopAnalysis(byte gun_index, int errCode)
{
	for (char i = 0; i < 4; i++)
	{
		unsigned char byteIndex = (errCode >> (8 * i)) & 0xff;

		for (char bitIndex = 0; bitIndex < 8; bitIndex++)
		{
			if(DetectBitValue(byteIndex , bitIndex) == 1)
			{
				switch(i)
				{
					case 0:
					{
					    // err 1
						if (bitIndex == 2)
						{
						    // 012307
						    // fuse burn-out
						    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFuseBurnOut = YES;
						}
						else if (bitIndex == 3)
						{
						    // 012308
						    // Communication fault between PFC and DCDC
						    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuPfcAndDcdcCommFault = YES;
						}
                        else if (bitIndex == 6)
                        {
                            // 012309
                            // Unbalance positive and negative BUS voltage
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusVoltageUnbalance = YES;
                        }
                        else if (bitIndex == 7)
                        {
                            // 012310
                            // BUS over voltage
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusOverVoltage = YES;
                        }
					}
						break;
				case 1:
					{
					    // err 2
						if (bitIndex == 0)
						{
						    // 012311
						    // BUS voltage abnormal
						    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusVoltageAbnormal = YES;
						}
						else if (bitIndex == 1)
                        {
						    // 012270
						    // Over voltage of any phase
						    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputOVP = YES;
                        }
                        else if (bitIndex == 2)
                        {
                            // 012263
                            // ID number repetition
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDuplicateID = YES;
                        }
                        else if (bitIndex == 3)
                        {
                            // 012312
                            // BUS under voltage
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusUnderVoltage = YES;
                        }
                        else if (bitIndex == 4)
                        {
                            // 012313
                            // Phase loss
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputPhaseLoss = YES;
                        }
                        else if (bitIndex == 6)
                        {
                            // 012271
                            // Under voltage of any phase
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputUVP = NO;
                        }
					}
					break;
				case 2:
					{
					    // err3
						if (bitIndex == 0)
						{
						    // 012240
						    // CAN communication fault
						    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuCommunicationFail = YES;
						}
						else if (bitIndex == 1)
						{
						    // 012275
						    // DCDC uneven current sharing
						    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuSevereUnevenCurrent = YES;
						}
                        else if (bitIndex == 3)
                        {
                            // 012278
                            // PFC power off
                            //ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFfcSideShutDown = YES;
                        }
                        else if (bitIndex == 5)
                        {
                            // 012314
                            // Full speed of fan
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFanFullSpeed = YES;
                        }
                        else if (bitIndex == 6)
                        {
                            // 012266
                            // DCDC power off
                            //ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcSideShutDown = YES;
                        }
                        else if (bitIndex == 7)
                        {
                            // 012273
                            // Module unders power limiting status
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuPowerLimitedState = YES;
                        }
					}
					break;
				case 3:
                    {
                        // err 4
                        if (bitIndex == 0)
                        {
                            // 012315
                            // Temperature power limiting
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuTemperaturePowerLimit = YES;
                        }
                        else if (bitIndex == 1)
                        {
                            // 012316
                            // AC power limiting
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuAcPowerLimit = YES;
                        }
                        else if (bitIndex == 2)
                        {
                            // 012317
                            // DCDC eeprom faults
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcdcEepromFault = YES;
                        }
                        else if (bitIndex == 3)
                        {
                            // 012269
                            // Fan faults
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFanFailureAlarm = YES;
                        }
                        else if (bitIndex == 4)
                        {
                            // 012264
                            // DCDC short circuit
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuOutputShortCircuit = YES;
                        }
                        else if (bitIndex == 5)
                        {
                            // 012318
                            // PFC eeprom faults
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuPfcEepromFault = YES;
                        }
                        else if (bitIndex == 6)
                        {
                            // 012226
                            // DCDC over temperature
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuCriticalPointOTP = YES;
                        }
                        else if (bitIndex == 7)
                        {
                            // 012319
                            // DCDC output over voltage
                            ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcdcOverVoltage = YES;
                        }
                    }
                    break;
				}
			}
//			else
//			{
//				switch (byteIndex) {
//				case 0: {
//					if (bitIndex == 0)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuOutputShortCircuit = NO;
//					else if (bitIndex == 5)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcSideShutDown = NO;
//				}
//					break;
//				case 1: {
//					if (bitIndex == 1)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFailureAlarm = NO;
//					else if (bitIndex == 2)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuProtectionAlarm = NO;
//					else if (bitIndex == 3)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFanFailureAlarm = NO;
//					else if (bitIndex == 4)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuCriticalPointOTP = NO;
//					else if (bitIndex == 5)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcSideShutDown = NO;
//				}
//					break;
//				case 2: {
//					if (bitIndex == 1)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDuplicateID = NO;
//					if (bitIndex == 2)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuThreePhaseOnputImbalance = NO;
//					else if (bitIndex == 3)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuThreePhaseInputInadequate = NO;
//					else if (bitIndex == 4)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuThreePhaseInputInadequate = NO;
//					else if (bitIndex == 5)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputUVP = NO;
//					else if (bitIndex == 6)
//						ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputOVP = NO;
//				}
//					break;
//				}
//			}
		}
	}
}

//=================================
// Callback Function
//=================================
// 0x04: PSU_RCmd_ModuleStatus
void GetStatusCallback(byte group, byte SN, byte temp, int alarm)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if(ShmPsuData->Work_Step != Get_PSU_LOCATION)
    {
        return;
    }

    if(group >= _maxGroupCount || SN >= MAX_MODULE_PER_GROUP)
    {
        return;
    }

    ShmPsuPosition->PsuAddressInfo[SN].Address = SN;
    ShmPsuPosition->PsuAddressInfo[SN].GroupNo = group;

    if(!ShmPsuPosition->PsuAddressInfo[SN].CheckIn)
    {
        ShmPsuPosition->PsuAddressInfo[SN].CheckIn = true;
        ShmPsuPosition->TotalPsuQuantity++;
        //LOG_INFO("SN = %d At Group %02X", SN, group);
    }

    if(ShmPsuPosition->TotalPsuQuantity == ShmPsuData->SystemPresentPsuQuantity)
    {
        byte group = 0, quantity = 0;

        if(!ShmPsuPosition->PsuLocationInit)
        {
            memset(ShmPsuPosition->GroupLocationInfo, 0x00, sizeof(ShmPsuPosition->GroupLocationInfo));

            for(int index = 0; index < MAX_PSU_MODULE_QUANTITY; index++)
            {
                if(ShmPsuPosition->PsuAddressInfo[index].CheckIn)
                {
                    group = ShmPsuPosition->PsuAddressInfo[index].GroupNo;
                    quantity = ShmPsuPosition->GroupLocationInfo[group].GroupPsuQuantity;
                    ShmPsuPosition->PsuAddressInfo[index].GIndex = quantity;
                    ShmPsuPosition->GroupLocationInfo[group].PsuSN[quantity] = ShmPsuPosition->PsuAddressInfo[index].Address;
                    ShmPsuPosition->GroupLocationInfo[group].GroupPsuQuantity++;
                    //LOG_INFO("Psu %d Assign To Group %02X At %d", index, group, ShmPsuPosition->PsuAddressInfo[index].GIndex);
                }
            }

            bool match = true;
            for(int index = 0; index < _maxGroupCount; index++)
            {
                if(ShmPsuPosition->GroupLocationInfo[index].GroupPsuQuantity != ShmPsuData->PsuGroup[index].GroupPresentPsuQuantity)
                {
                    match = false;
                }
            }

            if(match)
            {
                ShmPsuPosition->PsuLocationInit = true;
            }
            else
            {
                // try to re-initial psu location
                ShmPsuPosition->ReInitPsuLocation = true;
            }
        }
        else
        {
            ShmPsuData->PsuGroup[group].PsuModule[ShmPsuPosition->PsuAddressInfo[SN].GIndex].CriticalTemp1 = temp;
        }
    }
}

// 0x02: PSU_RCmd_SysModuleCount
void GetModuleCountCallback(byte group, byte count)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

	if(group == SYSTEM_CMD)
	{
		ShmPsuData->SystemPresentPsuQuantity = count;
	}
	else
	{
	    if(group < _maxGroupCount)
	    {
	        ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity = count;
	        ShmPsuGrouping->GroupCollection[group].GunPsuQuantity = count;
	    }
	}
}

void UpdateGunAvailableCapability(unsigned char group)
{
    int _availableCurrent = 0, _availablePower = 0, _realPower = 0;
    unsigned char master = 0;

    master = ShmChargerInfo->PsuGrouping.GroupCollection[group].TargetGroup;

    // calculate output group capability
    if(master == 0)
    {
        chargingInfo[group]->AvailableChargingCurrent = ShmPsuData->PsuGroup[group].GroupAvailableCurrent;
        chargingInfo[group]->AvailableChargingPower = ShmPsuData->PsuGroup[group].GroupAvailablePower;
        chargingInfo[group]->RealRatingPower = ShmPsuData->PsuGroup[group].GroupRealOutputPower * 10;
    }
    else
    {
        if(ShmPsuGrouping->GroupCollection[master - 1].Role == _GROLE_MASTER)
        {
            _availableCurrent = ShmPsuData->PsuGroup[master - 1].GroupAvailableCurrent;
            _availablePower = ShmPsuData->PsuGroup[master - 1].GroupAvailablePower;
            _realPower = ShmPsuData->PsuGroup[master - 1].GroupRealOutputPower * 10;

            for(byte i = 0; i < ShmPsuGrouping->GroupCollection[master - 1].Partner.Quantity; i++)
            {
                byte slave = ShmPsuGrouping->GroupCollection[master - 1].Partner.Member[i];

                if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE ||
                    ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_PREPARE_SWITCH_OFF)
                {
                    _availableCurrent += ShmPsuData->PsuGroup[slave].GroupAvailableCurrent;
                    _availablePower += ShmPsuData->PsuGroup[slave].GroupAvailablePower;
                    _realPower += ShmPsuData->PsuGroup[slave].GroupRealOutputPower * 10;
                }
            }

            if(ShmPsuGrouping->GroupCollection[master - 1].GroupCtrl.bits.DeratingConfirmed)
            {
                chargingInfo[master - 1]->AvailableChargingCurrent = ShmPsuGrouping->GroupCollection[master - 1].ReAssignAvailableCurrent;
            }
            else
            {
                chargingInfo[master - 1]->AvailableChargingCurrent = _availableCurrent;
            }
            chargingInfo[master - 1]->AvailableChargingPower = _availablePower;
            chargingInfo[master - 1]->RealRatingPower = _realPower;

//            if((master - 1) != group)
//            {
//                chargingInfo[group]->AvailableChargingCurrent = 0;;
//                chargingInfo[group]->AvailableChargingPower = 0;
//                chargingInfo[group]->RealRatingPower = 0;
//            }
        }
    }
}

void UpdateSystemAvailable(void)
{
    int _sysCurrent = 0, _sysPower = 0;

    // summation of system current & power
    for(byte i = 0; i < _maxGroupCount; i++)
    {
        _sysCurrent += ShmPsuData->PsuGroup[i].GroupAvailableCurrent;
        _sysPower += ShmPsuData->PsuGroup[i].GroupAvailablePower;
    }
    ShmPsuData->SystemAvailableCurrent = _sysCurrent;
    ShmPsuData->SystemAvailablePower = _sysPower;
}

// maxVol, minVol unit: 0.1V
// maxCur unit: 0.1A
// totalPow unit: 0.01kW
// 0x0A: PSU_RCmd_ModuleCapability
void GetAvailableCapCallback(byte address, short maxVol, short minVol, short maxCur, short totalPow)
{
    int _groupCurrent = 0, _groupPower = 0;

    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step < GET_SYS_CAP || address >= ShmPsuData->SystemPresentPsuQuantity ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    byte master = 0;
    int group = FindTargetGroup(address);
    int gIndex = FindGroupIndex(address);

    if(group < 0 || gIndex < 0)
    {
        return;
    }

    // calculate psu module available power
    ShmPsuData->PsuGroup[group].PsuModule[gIndex].AvailablePower = totalPow;
    //if(ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage >= PSU_MIN_VOL)
    if(ShmPsuGrouping->GroupOutput[group].GTargetVoltage >= PSU_MIN_VOL)
    {
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].AvailableCurrent =
            ((ShmPsuData->PsuGroup[group].PsuModule[gIndex].AvailablePower * 100) * 10) / (ShmPsuGrouping->GroupOutput[group].GTargetVoltage / 10);
    }
    else
    {
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].AvailableCurrent = maxCur > PSU_MAX_CUR ? PSU_MAX_CUR : maxCur;
    }

    // summation of psu group current & power
    for(byte i = 0; i < ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity; i++)
    {
        _groupCurrent += ShmPsuData->PsuGroup[group].PsuModule[i].AvailableCurrent;
        _groupPower += ShmPsuData->PsuGroup[group].PsuModule[i].AvailablePower;
    }

    if(ShmPsuData->PsuGroup[group].StableIAvailableCurrent != 0)
    {
        ShmPsuData->PsuGroup[group].GroupAvailableCurrent = ShmPsuData->PsuGroup[group].StableIAvailableCurrent;
    }
    else
    {
        if(ShmPsuData->PsuGroup[group].GroupRealOutputPower != 0 &&
            ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage >= PSU_MIN_VOL)
        {
            ShmPsuData->PsuGroup[group].GroupAvailableCurrent = ((ShmPsuData->PsuGroup[group].GroupRealOutputPower * 1000) * 10) / (ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage / 10);
        }
        else
        {
            ShmPsuData->PsuGroup[group].GroupAvailableCurrent = _groupCurrent;
        }
    }
    ShmPsuData->PsuGroup[group].GroupAvailablePower = _groupPower;

    // update MaximumChargingVoltage
    master = ShmChargerInfo->PsuGrouping.GroupCollection[group].TargetGroup;
    if(master == 0)
    {
        chargingInfo[group]->MaximumChargingVoltage = maxVol;
    }
    else
    {
        if(ShmPsuGrouping->GroupCollection[master - 1].Role == _GROLE_MASTER)
        {
            chargingInfo[master - 1]->MaximumChargingVoltage = maxVol;

//            if((master - 1) != group)
//            {
//                chargingInfo[group]->MaximumChargingVoltage = 0;
//            }
        }
    }

    UpdateGunAvailableCapability(group);

    UpdateSystemAvailable();
    //LOG_INFO("address = %d, maxVol = %d, minVol = %d, maxCur = %d, totalPow = %d", address, maxVol, minVol, maxCur, totalPow);
}

// 0x07: PSU_RCmd_ModuleVersion
void GetFwCallback(byte address, short dcSwVer, short pfcSwVer, short hwVer)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step != Get_PSU_VERSION || address >= ShmPsuData->SystemPresentPsuQuantity ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    int group = FindTargetGroup(address);
    int gIndex = FindGroupIndex(address);

    if(group < 0 || gIndex < 0)
    {
        return;
    }

    sprintf((char *)ShmPsuData->PsuVersion[address].FwPrimaryVersion, "DC %d.%02d", (dcSwVer & 0xFF00) >> 8, dcSwVer & 0xFF);
    sprintf((char *)ShmPsuData->PsuVersion[address].FwSecondVersion, "PFC %d.%02d", (pfcSwVer & 0xFF00) >> 8, pfcSwVer & 0xFF);
    sprintf((char *)ShmPsuData->PsuGroup[group].PsuModule[gIndex].FwVersion, "DC %d.%02d", (dcSwVer & 0xFF00) >> 8, dcSwVer & 0xFF);
    //LOG_INFO("Psu %d %s %s", address, ShmPsuData->PsuVersion[address].FwPrimaryVersion, ShmPsuData->PsuVersion[address].FwSecondVersion);
}

// no using -- GetInputVoltageCallback
void GetInputVoltageCallback(byte address, unsigned short vol1, unsigned short vol2, unsigned short vol3)
{
}
// no using -- GetInputVoltageCallback End

// no using -- GetPresentOutputCallback
void GetPresentOutputCallback(byte group, unsigned short outVol, unsigned short outCur)
{
}
// no using -- GetPresentOutputCallback End

// type 1: FAN_SPEED_CMD
// type 2: TEMP_DC_CMD
// type 3: TEMP_PFC_CMD
// 0x0E: PSU_RCmd_ModuleMiscInfo
void GetMisCallback(byte address, unsigned int value, byte type)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step < GET_SYS_CAP || address >= ShmPsuData->SystemPresentPsuQuantity ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    int group = FindTargetGroup(address);
    int gIndex = FindGroupIndex(address);

    if(group < 0 || gIndex < 0)
    {
        return;
    }

    if (type == 1)
    {
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].FanSpeed_1 = value;
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].FanSpeed_2 = value;
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].FanSpeed_3 = value;
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].FanSpeed_4 = value;
    }
    else if (type == 2)
    {
        //ShmPsuData->PsuGroup[group].PsuModule[gIndex].CriticalTemp1 = value;
        //ShmPsuData->PsuGroup[group].PsuModule[gIndex].CriticalTemp2 = value;
        //ShmPsuData->PsuGroup[group].PsuModule[gIndex].CriticalTemp3 = value;
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].ExletTemp = value;
    }
    else if (type == 3)
    {
        //printf("PFC - group = %d, index = %d, value = %d \n", group, address, value);
        ShmPsuData->PsuGroup[group].PsuModule[gIndex].InletTemp = value;
    }
}

// Iavail unit: 0.1A
// Vext unit: 0.1V
// 0x0C: PSU_RCmd_ModuleIAvailable
// 0x02CCF0XX
void GetIavailableCallback(byte address, unsigned short Iavail, unsigned short Vext)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step < GET_SYS_CAP || address >= ShmPsuData->SystemPresentPsuQuantity ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    byte master = 0;
    unsigned short diffIAvailable = 0;
    int totalCurrent = 0;
    int group = FindTargetGroup(address);
    int gIndex = FindGroupIndex(address);

    if(group < 0 || gIndex < 0)
    {
        return;
    }

    ShmPsuData->PsuGroup[group].PsuModule[gIndex].IAvailableCurrent = Iavail;

    // summation of psu group i available current
    for (byte i = 0; i < ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity; i++)
    {
        totalCurrent += ShmPsuData->PsuGroup[group].PsuModule[i].IAvailableCurrent;
    }
    ShmPsuData->PsuGroup[group].TotalIAvailableCurrent = totalCurrent;

    diffIAvailable = totalCurrent >= ShmPsuData->PsuGroup[group].TempIAvailableCurrent ?
        totalCurrent - ShmPsuData->PsuGroup[group].TempIAvailableCurrent : ShmPsuData->PsuGroup[group].TempIAvailableCurrent - totalCurrent;

    if(diffIAvailable > STABLE_CURRENT_TOLERANCE)
    {
        ShmPsuData->PsuGroup[group].StableCurrentCounter = 0;
        ShmPsuData->PsuGroup[group].StableIAvailableCurrent = 0;
    }
    ShmPsuData->PsuGroup[group].TempIAvailableCurrent = ShmPsuData->PsuGroup[group].TotalIAvailableCurrent;
    ShmPsuData->PsuGroup[group].StableCurrentCounter++;

    if(ShmPsuData->PsuGroup[group].StableCurrentCounter >= MAX_STABLE_COUNT)
    {
        // get stable StableIAvailableCurrent
        ShmPsuData->PsuGroup[group].StableIAvailableCurrent = ShmPsuData->PsuGroup[group].TotalIAvailableCurrent;

        // get stable GroupRealOutputPower
        ShmPsuData->PsuGroup[group].GroupRealOutputPower = (ShmPsuData->PsuGroup[group].StableIAvailableCurrent * ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage) / 100 / 1000;

        ShmPsuData->PsuGroup[group].StableCurrentCounter = 0;
    }

    master = ShmChargerInfo->PsuGrouping.GroupCollection[group].TargetGroup;

    if(master == 0)
    {
        // unit: 0.1A
        chargingInfo[group]->DeratingChargingCurrent = ShmPsuData->PsuGroup[group].StableIAvailableCurrent;
        chargingInfo[group]->DeratingChargingPower = (chargingInfo[group]->DeratingChargingCurrent * chargingInfo[group]->PresentChargingVoltage) / 10 / 100;
    }
    else
    {
        // calculate DeratingChargingCurrent of master group
        totalCurrent = ShmPsuData->PsuGroup[master - 1].StableIAvailableCurrent;

        for(byte i = 0; i < ShmPsuGrouping->GroupCollection[master - 1].Partner.Quantity; i++)
        {
            byte slave = ShmPsuGrouping->GroupCollection[master - 1].Partner.Member[i];

            if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE)
            {
                totalCurrent += ShmPsuData->PsuGroup[slave].StableIAvailableCurrent;
            }
        }
        chargingInfo[master - 1]->DeratingChargingCurrent = totalCurrent;
        chargingInfo[master - 1]->DeratingChargingPower = (chargingInfo[master - 1]->DeratingChargingCurrent * chargingInfo[master - 1]->PresentChargingVoltage) / 10 / 100;

        if((master - 1) != group)
        {
            chargingInfo[group]->DeratingChargingCurrent = 0;
            chargingInfo[group]->DeratingChargingPower = 0;
        }
    }
}

// outVol unit: 1V
// outCur unit: 1A
// 0x01: PSU_RCmd_SysOutputVolCur_F
void GetPresentOutputFCallback(byte group, float outVol, float outCur)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step < GET_SYS_CAP || group >= _maxGroupCount ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    byte master = 0;
    unsigned short pVoltage = 0, pCurrent = 0;
    unsigned short gVoltage = 0, gCurrent = 0;

    pVoltage = isinf(outVol) == 0 ? (unsigned short)(outVol * 10) : 0;
    pCurrent = isinf(outCur) == 0 ? (unsigned short)(outCur * 10) : 0;
    ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage = pVoltage;
    ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent = pCurrent;
    ShmPsuData->PsuGroup[group].GroupPresentOutputPower = (pVoltage * pCurrent) / 100 / 100;

    master = ShmChargerInfo->PsuGrouping.GroupCollection[group].TargetGroup;

    if(master == 0)
    {
        // calculate PresentChargingVoltage and PresentChargingCurrent of self group
        chargingInfo[group]->PresentChargingVoltage = (float)ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage / 10;
        chargingInfo[group]->PresentChargingCurrent = (float)ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent / 10;
    }
    else
    {
        // calculate PresentChargingVoltage and PresentChargingCurrent of master group
        gVoltage = ShmPsuData->PsuGroup[master - 1].GroupPresentOutputVoltage;
        gCurrent = ShmPsuData->PsuGroup[master - 1].GroupPresentOutputCurrent;

        for(byte i = 0; i < ShmPsuGrouping->GroupCollection[master - 1].Partner.Quantity; i++)
        {
            byte slave = ShmPsuGrouping->GroupCollection[master - 1].Partner.Member[i];

            if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE ||
                ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_PREPARE_SWITCH_OFF ||
                ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE_POWER_OFF)
            {
                if(ShmPsuData->PsuGroup[slave].GroupPresentOutputVoltage > gVoltage)
                {
                    gVoltage = ShmPsuData->PsuGroup[slave].GroupPresentOutputVoltage;
                }

                gCurrent += ShmPsuData->PsuGroup[slave].GroupPresentOutputCurrent;
            }
        }

        chargingInfo[master - 1]->PresentChargingVoltage = (float)gVoltage / 10;
        chargingInfo[master - 1]->PresentChargingCurrent = (float)gCurrent / 10;

        if((master - 1) != group)
        {
            chargingInfo[group]->PresentChargingVoltage = 0;
            chargingInfo[group]->PresentChargingCurrent = 0;
        }
    }
}

//==========================================
// 特規用指令
//==========================================

// 0x1901: Nexton_PSU_DcOutputValue
void GetOutputAndTempCallback(byte address, unsigned short outputVol_s,
		unsigned short outputCur_s, unsigned short outputPower, unsigned char Temperature)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step < GET_SYS_CAP || address >= ShmPsuData->SystemPresentPsuQuantity ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    int group = FindTargetGroup(address);
    int gIndex = FindGroupIndex(address);

    if(group < 0 || gIndex < 0)
    {
        return;
    }

    //ShmPsuData->PsuGroup[group].PsuModule[gIndex].CriticalTemp1 = Temperature;
    ShmPsuData->PsuGroup[group].PsuModule[gIndex].CriticalTemp2 = Temperature;
    ShmPsuData->PsuGroup[group].PsuModule[gIndex].CriticalTemp3 = Temperature;
}

// 0x1902: Nexton_PSU_StatusEvent
void GetModuleStatusCallback(byte address, unsigned char isErr, unsigned char status,
		unsigned char err1, unsigned char err2, unsigned char err3, unsigned char err4)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step < GET_SYS_CAP || address >= ShmPsuData->SystemPresentPsuQuantity ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    int group = FindTargetGroup(address);
    int gIndex = FindGroupIndex(address);

    if(group < 0 || gIndex < 0)
    {
        return;
    }

    int alarm = (err4 << 24) | (err3 << 16) | (err2 << 8) | err1;

    ShmPsuData->PsuGroup[group].PsuModule[gIndex].AlarmCode = alarm;
    AbnormalStopAnalysis(group, alarm);

	if(isErr)
	{
	    // 012267
	    //ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFailureAlarm = YES;
	    ShmPsuData->PsuGroup[group].GroupErrorFlag.bits.PsuFailure = true;
	}
}

// 0x1903: Nexton_PSU_AcInputValue
void GetModuleInputCallback(byte address, unsigned short inputR,
		unsigned short inputS, unsigned short inputT)
{
    ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt++;

    if (ShmPsuData->Work_Step < GET_SYS_CAP || address >= ShmPsuData->SystemPresentPsuQuantity ||
        !ShmPsuPosition->PsuLocationInit)
    {
        return;
    }

    int group = FindTargetGroup(address);
    int gIndex = FindGroupIndex(address);

    if(group < 0 || gIndex < 0)
    {
        return;
    }

    ShmPsuData->PsuGroup[group].PsuModule[gIndex].InputVoltageL1 = inputR;
    ShmPsuData->PsuGroup[group].PsuModule[gIndex].InputVoltageL2 = inputS;
    ShmPsuData->PsuGroup[group].PsuModule[gIndex].InputVoltageL3 = inputT;
}
//==========================================
// Init all share memory
//==========================================
int InitShareMemory()
{
	int result = PASS;
	int MeterSMId;

	//creat ShmSysConfigAndInfo
	if ((MeterSMId = shmget(ShmSysConfigAndInfoKey, sizeof(struct SysConfigAndInfo),  0777)) < 0)
    {
		#ifdef SystemLogMessage
		LOG_ERROR("shmget ShmSysConfigAndInfo NG %d");
		#endif
		result = FAIL;
	}
    else if ((ShmSysConfigAndInfo = shmat(MeterSMId, NULL, 0)) == (void *) -1)
    {
    	#ifdef SystemLogMessage
    	LOG_ERROR("shmat ShmSysConfigAndInfo NG");
		#endif
    	result = FAIL;
   	 }
    else
    {}

   	//creat ShmStatusCodeData
   	if ((MeterSMId = shmget(ShmStatusCodeKey, sizeof(struct StatusCodeData),  0777)) < 0)
    {
		#ifdef SystemLogMessage
   		LOG_ERROR("shmget ShmStatusCodeData NG");
		#endif
   		result = FAIL;
	}
    else if ((ShmStatusCodeData = shmat(MeterSMId, NULL, 0)) == (void *) -1)
    {
    	#ifdef SystemLogMessage
    	LOG_ERROR("shmat ShmStatusCodeData NG");
		#endif
    	result = FAIL;
   	}
    else
    {}

   	//creat ShmPsuData
	if ((MeterSMId = shmget(ShmPsuKey, sizeof(struct PsuData),  0777)) < 0)
	{
		#ifdef SystemLogMessage
		LOG_ERROR("shmget ShmPsuData NG");
		#endif
		result = FAIL;
	}
	else if ((ShmPsuData = shmat(MeterSMId, NULL, 0)) == (void *) -1)
	{
		#ifdef SystemLogMessage
		LOG_ERROR("shmat ShmPsuData NG");
		#endif
		result = FAIL;
	}

    if ((MeterSMId = shmget(SM_ChargerInfoKey, sizeof(ChargerInfoData), 0777)) < 0)
    {
        #ifdef SystemLogMessage
        LOG_ERROR("shmat ChargerInfoData NG");
        #endif
        result = FAIL;
    }
    else if ((ShmChargerInfo = shmat(MeterSMId, NULL, 0)) == (void *) -1)
    {
        #ifdef SystemLogMessage
        LOG_ERROR("shmat ChargerInfoData NG");
        #endif
        result = FAIL;
    }
    if(result == PASS)
    {
        ShmPsuPosition = &ShmChargerInfo->PsuPosition;
        ShmPsuGrouping = &ShmChargerInfo->PsuGrouping;
    }

    return result;
}

//================================================
// Main process
//================================================
void InitialPsuData()
{
	ShmPsuData->SystemPresentPsuQuantity = 0;
	ShmPsuData->SystemAvailablePower = 0;

	LOG_INFO("************ psu Group = %d", ShmPsuData->GroupCount);
	for (byte _groupCount = 0; _groupCount < ShmPsuData->GroupCount; _groupCount++)
	{
		ShmPsuData->PsuGroup[_groupCount].GroupPresentPsuQuantity = 0;
		ShmPsuData->PsuGroup[_groupCount].GroupAvailablePower = 0;
		ShmPsuData->PsuGroup[_groupCount].GroupAvailableCurrent = 0;
		ShmPsuData->PsuGroup[_groupCount].GroupErrorFlag.PsuGroupErrorValue = 0;
	}

    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFuseBurnOut = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuPfcAndDcdcCommFault = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusVoltageUnbalance = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusOverVoltage = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusVoltageAbnormal = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputOVP = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDuplicateID = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuBusUnderVoltage = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputPhaseLoss = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuInputUVP = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuCommunicationFail = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuSevereUnevenCurrent = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFfcSideShutDown = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFanFullSpeed = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcSideShutDown = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuPowerLimitedState = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuTemperaturePowerLimit = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuAcPowerLimit = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcdcEepromFault = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuFanFailureAlarm = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuOutputShortCircuit = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuPfcEepromFault = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuCriticalPointOTP = NO;
    ShmStatusCodeData->AlarmCode.AlarmEvents.bits.PsuDcdcOverVoltage = NO;
}

void Initialization()
{
	bool isPass = false;
	while(!isPass)
	{
		isPass = true;
		for (byte _index = 0; _index < _maxGroupCount; _index++)
		{
			if (!FindChargingInfoData(_index, &chargingInfo[0]))
			{
				LOG_ERROR("Module_PsuComm : FindChargingInfoData false");
				isPass = false;
				break;
			}
		}
		sleep(1);
	}

    ShmPsuData->GroupCount = _maxGroupCount;
}

void Await()
{
	usleep(CMD_DELAY_TIME);
}

void PsuReceiveRecoveryCheck(void)
{
    char *ptrSave, *ptrToken;
    int fd, psuTaskCount = 0;
    char pathBuffer[64], psuTaskPidString[64];

    system("pidof Module_PsuComm > /tmp/Module_PsuTask");

    snprintf(pathBuffer, sizeof(pathBuffer), "/tmp/Module_PsuTask");
    fd = open(pathBuffer, O_RDWR);

    if(fd < 0)
    {
        return;
    }
    if(read(fd, psuTaskPidString, 64) < 0)
    {
        return;
    }

    ptrToken = strtok_r(psuTaskPidString, " ", &ptrSave);
    while(ptrToken != NULL)
    {
        int psuPid = atoi(ptrToken);

        if(psuPid > 0)
        {
            psuTaskCount++;
        }

        ptrToken = strtok_r(NULL, " ", &ptrSave);
    }
    close(fd);

    if(psuTaskCount == 1)
    {
        LOG_INFO("************ PSU Receive Task Need Recovery ************\n");
        InitialCommunication();

        psuReceiveRecovery = true;
    }
}

void ShowGroupMember(unsigned char group)
{
    if(group < MAX_GROUP_QUANTITY)
    {
        if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
        {
            for(byte psu = 0; psu < ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity; psu++)
            {
                LOG_INFO("Group %d - Number = %d, SN = %02X", group + 1, psu,
                    ShmPsuPosition->GroupLocationInfo[group].PsuSN[psu]);
            }
        }
    }
}

void ShowPsuVersion(unsigned char group)
{
    if(group < MAX_GROUP_QUANTITY)
    {
        if(ShmPsuPosition->GroupLocationInfo[group].GroupPsuQuantity > 0)
        {
            LOG_INFO("Group %d PSU Version Info", group + 1);
            for(int i = 0; i < ShmPsuPosition->GroupLocationInfo[group].GroupPsuQuantity; i++)
            {
                int psuIndex = ShmPsuPosition->GroupLocationInfo[group].PsuSN[i];
                LOG_INFO("PSU Primary: %s, Second: %s",
                    ShmPsuData->PsuVersion[psuIndex].FwPrimaryVersion,
                    ShmPsuData->PsuVersion[psuIndex].FwSecondVersion);
            }
        }
    }
}

void ShowGroupAvailableCurrentPower(unsigned char group)
{
    if(group < MAX_GROUP_QUANTITY)
    {
        LOG_INFO("Group %d Available Current = %3d.%dA, Power = %3d.%dkW", group + 1,
            (ShmPsuData->PsuGroup[group].GroupAvailableCurrent / 10), (ShmPsuData->PsuGroup[group].GroupAvailableCurrent % 10),
            (ShmPsuData->PsuGroup[group].GroupAvailablePower / 10), (ShmPsuData->PsuGroup[group].GroupAvailablePower % 10));
    }
}

unsigned char _TempType = 0;
void PsuGroupRoutineQuery(void)
{
    for(byte group = 0; group < GENERAL_GUN_QUANTITY; group++)
    {
        if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
        {
            if (psuCmdSeq == _PSU_CMD_CAP)
            {
                // 取系統總輸出能力
                GetModuleCap(group);
            }
            else if (psuCmdSeq == _PSU_CMD_OUTPUT)
            {
                // 取各群輸出電壓電流 (float)
                GetModuleOutputF(group);
            }
            else if (psuCmdSeq == _PSU_CMD_IVAILIABLE)
            {
                // 取得模塊輸出額定電流能力
                GetModuleIavailable(group);
            }
            else if (psuCmdSeq == _PSU_CMD_TEMP)
            {
                // 取得模塊溫度
                if(_TempType == _PSU_TMP_DCDC)
                {
                    GetDcTemperature(group);
                }
                else
                {
                    GetPfcTemperature(group);
                }
            }
        }
    }

    if(psuCmdSeq == _PSU_CMD_CAP)
    {
        psuCmdSeq = _PSU_CMD_OUTPUT;
    }
    else if(psuCmdSeq == _PSU_CMD_OUTPUT)
    {
        psuCmdSeq = _PSU_CMD_IVAILIABLE;
    }
    else if(psuCmdSeq == _PSU_CMD_IVAILIABLE)
    {
        psuCmdSeq = _PSU_CMD_TEMP;

        _TempType = _TempType == _PSU_TMP_DCDC ? _PSU_TMP_PFC : _PSU_TMP_DCDC;
    }
    else
    {
        psuCmdSeq = _PSU_CMD_CAP;
    }

    Await();
}

void SetPsuGroupRole(byte group, byte role)
{
    if(group < GENERAL_GUN_QUANTITY)
    {
        ShmPsuGrouping->GroupCollection[group].Role = role;
    }
}

void SetPsuGroupToIdle(byte group)
{
    if(group < GENERAL_GUN_QUANTITY)
    {
        SetPsuGroupRole(group, _GROLE_IDLE);
        memset(&ShmPsuGrouping->GroupCollection[group].Partner, 0x00, sizeof(PsuGroupPartner));
        memset(&ShmPsuGrouping->GroupCollection[group].PossibleMember, 0x00, sizeof(PsuGroupPartner));
        ShmPsuGrouping->GroupCollection[group].TargetGroup = 0;
        ShmPsuGrouping->GroupCollection[group].ReservedTarget = 0;
        //LOG_INFO("Reset Group[%02X] To Idle", group);
    }
}

// group: self group index
void SetPsuGroupToMaster(byte group)
{
    if(group < GENERAL_GUN_QUANTITY)
    {
        SetPsuGroupRole(group, _GROLE_MASTER);
        ShmPsuGrouping->GroupCollection[group].TargetGroup = group + 1;
        //LOG_INFO("Set Group[%02X] As Master To Gun %d", group, group + 1);
    }
}

// group: self group index
// target: target group index + 1
void SetPsuGroupToSlave(byte group, byte target)
{
    if(group < GENERAL_GUN_QUANTITY && target <= GENERAL_GUN_QUANTITY)
    {
        SetPsuGroupRole(group, _GROLE_SLAVE);
        ShmPsuGrouping->GroupCollection[group].TargetGroup = target;
        ShmPsuGrouping->GroupCollection[group].ReservedTarget = 0;
        memset(&ShmPsuGrouping->GroupCollection[group].PossibleMember, 0x00, sizeof(PsuGroupPartner));
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.SlaveCtrlValue = 0;
        //LOG_INFO("Set Group[%02X] As Slave To Gun %d", group, target);
    }
}

// target: target group index + 1
// tPartner: set member to power off
void PrepareToPowerOff(byte target, PsuGroupPartner *tPartner)
{
    unsigned char master = 0, slave = 0;
    char strMember[64];
    bool find = false;

    sprintf(strMember, "Set Member:");

    for(int i = 0; i < tPartner->Quantity; i++)
    {
        slave = tPartner->Member[i];
        master = ShmPsuGrouping->GroupCollection[slave].TargetGroup - 1;
        SetPsuGroupRole(slave, _GROLE_PREPARE_SWITCH_OFF);
        ShmPsuGrouping->GroupCollection[slave].ReservedTarget = target;
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedDerating = true;

        char strSlave[8];
        sprintf(strSlave, " [%02X]", slave);
        strcat(strMember, strSlave);
        find = true;
    }

    if(find)
    {
        char strTemp[32];
        if(target != 0)
        {
            sprintf(strTemp, " Prepare To Change To Gun %d", target);
        }
        else
        {
            sprintf(strTemp, " Prepare To Power Of");
        }
        strcat(strMember, strTemp);
        LOG_INFO("%s", strMember);
    }
}

// target: target group index + 1
// tPartner: set member to prepare to attach on
void PrepareToExtendCapability(byte target, PsuGroupPartner *tPartner)
{
    char strMember[64];

    sprintf(strMember, "Set Member:");
    for(int i = 0; i < tPartner->Quantity; i++)
    {
        SetPsuGroupRole(tPartner->Member[i], _GROLE_PREPARE_ATTACH_ON);
        ShmPsuGrouping->GroupCollection[tPartner->Member[i]].ReservedTarget = target;

        char strSlave[8];
        sprintf(strSlave, " [%02X]", tPartner->Member[i]);
        strcat(strMember, strSlave);
    }

    if(tPartner->Quantity > 0)
    {
        char strTemp[32];
        sprintf(strTemp, " Prepare To Attach To Gun %d", target);
        strcat(strMember, strTemp);
        LOG_INFO("%s", strMember);
    }
}

void FindPsuGroupPartner(byte master, byte quantity, PsuGroupPartner *tPartner)
{
    int slave = 0, location = 0;
    PsuGroupPartner partner;

    memset(&partner, 0x00, sizeof(PsuGroupPartner));

    // search from left
    location = ShmPsuGrouping->GroupCollection[master].Location - 1;
    for(int i = location; i >= 0; i--)
    {
        if(partner.Quantity >= quantity)
        {
            break;
        }

        slave = ShmPsuGrouping->Layout[i];
        if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_IDLE ||
            (ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_WAIT_SLAVE && (ShmPsuGrouping->GroupCollection[slave].ReservedTarget - 1) == master))
        {
            partner.Member[partner.Quantity++] = slave;
        }
        else
        {
            if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE && master == (ShmPsuGrouping->GroupCollection[slave].TargetGroup - 1))
            {
                continue;
            }
            break;
        }
    }

    // search from right
    location = ShmPsuGrouping->GroupCollection[master].Location + 1;
    for(int i = location; i < ShmChargerInfo->Control.MaxConnector; i++)
    {
        if(partner.Quantity >= quantity)
        {
            break;
        }

        slave = ShmPsuGrouping->Layout[i];
        if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_IDLE ||
            (ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_WAIT_SLAVE && (ShmPsuGrouping->GroupCollection[slave].ReservedTarget - 1) == master))
        {
            partner.Member[partner.Quantity++] = slave;
        }
        else
        {
            if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE && master == (ShmPsuGrouping->GroupCollection[slave].TargetGroup - 1))
            {
                continue;
            }
            break;
        }
    }

    memcpy(tPartner, &partner, sizeof(PsuGroupPartner));
}

int GetPsuGroupAvailable(byte group)
{
    PsuGroupPartner partner;

    FindPsuGroupPartner(group, MAX_GROUP_QUANTITY, &partner);

    return partner.Quantity;
}

// return grab success or fail
// return tPartner with possible member
bool PsuGroupGrabCheck(byte group, PsuGroupPartner *tPartner)
{
    int slave = 0, target = 0, location = 0, share = 0, average = 0, total = 0;

    if(ShmPsuGrouping->GroupCollection[group].Role != _GROLE_SLAVE && ShmPsuGrouping->GroupCollection[group].Role != _GROLE_REQUEST_TO_CHARGING)
    {
        return share > 0 ? true : false;
    }

    total = ShmPsuGrouping->GroupCollection[group].Role == _GROLE_REQUEST_TO_CHARGING ? GetPsuGroupAvailable(group) + 1 : 0;

    // search from left
    location = ShmPsuGrouping->GroupCollection[group].Location - 1;
    for(int i = location; i >= 0; i--)
    {
        slave = ShmPsuGrouping->Layout[i];
        if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE &&
            (ShmPsuGrouping->GroupCollection[group].Role == _GROLE_REQUEST_TO_CHARGING ||
            ShmPsuGrouping->GroupCollection[slave].TargetGroup == ShmPsuGrouping->GroupCollection[group].TargetGroup))
        {
            target = ShmPsuGrouping->GroupCollection[slave].TargetGroup - 1;
            if((ShmPsuGrouping->GroupCollection[target].Partner.Quantity + 1) > total)
            {
                average = (ShmPsuGrouping->GroupCollection[target].Partner.Quantity + 1 + total) / 2;
                share = average > 0 ? average - 1 : 0;
            }
            break;
        }
        else
        {
            break;
        }
    }

    if(share == 0)
    {
        // search from right
        location = ShmPsuGrouping->GroupCollection[group].Location + 1;
        for(int i = location; i < ShmChargerInfo->Control.MaxConnector; i++)
        {
            slave = ShmPsuGrouping->Layout[i];
            if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE &&
                (ShmPsuGrouping->GroupCollection[group].Role == _GROLE_REQUEST_TO_CHARGING ||
                ShmPsuGrouping->GroupCollection[slave].TargetGroup == ShmPsuGrouping->GroupCollection[group].TargetGroup))
            {
                target = ShmPsuGrouping->GroupCollection[slave].TargetGroup - 1;
                if((ShmPsuGrouping->GroupCollection[target].Partner.Quantity + 1) > total)
                {
                    average = (ShmPsuGrouping->GroupCollection[target].Partner.Quantity + 1 + total) / 2;
                    share = average > 0 ? average - 1 : 0;
                }
                break;
            }
            else
            {
                break;
            }
        }
    }

    tPartner->Quantity = share;
    tPartner->Member[0] = share > 0 ? slave : 0;

    return share > 0 ? true : false;
}

// return tPartner with possible member when group power off
void PsuGroupPowerOffCheck(byte group, PsuGroupPartner *tPartner)
{
    int master = 0, quantity = 0, location = 0;
    memset(tPartner, 0x00, sizeof(PsuGroupPartner));

    if(ShmPsuGrouping->GroupCollection[group].Role != _GROLE_SLAVE)
    {
        return;
    }

    master = ShmPsuGrouping->GroupCollection[group].TargetGroup - 1;
    quantity = ShmPsuGrouping->GroupCollection[master].Partner.Quantity;

    for(int i = 0; i < quantity; i++)
    {
        if(tPartner->Quantity == 0)
        {
            if(group == ShmPsuGrouping->GroupCollection[master].Partner.Member[i])
            {
                location = i;
                tPartner->Member[tPartner->Quantity] = group;
                tPartner->Quantity++;
            }
        }
        else
        {
            // find other group in the same direction
            if(ShmPsuGrouping->Location[ShmPsuGrouping->GroupCollection[master].Partner.Member[location]] < ShmPsuGrouping->Location[master])
            {
                if(ShmPsuGrouping->Location[ShmPsuGrouping->GroupCollection[master].Partner.Member[i]] < ShmPsuGrouping->Location[master])
                {
                    tPartner->Member[tPartner->Quantity] = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
                    tPartner->Quantity++;
                    //printf("\r\n Find Other Group %02X In The Same Direction", ShmPsuGrouping->GroupCollection[master].Partner.Member[i]);
                }
            }
            if(ShmPsuGrouping->Location[ShmPsuGrouping->GroupCollection[master].Partner.Member[location]] > ShmPsuGrouping->Location[master])
            {
                if(ShmPsuGrouping->Location[ShmPsuGrouping->GroupCollection[master].Partner.Member[i]] > ShmPsuGrouping->Location[master])
                {
                    tPartner->Member[tPartner->Quantity] = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
                    tPartner->Quantity++;
                    //printf("\r\n Find Other Group %02X In The Same Direction", ShmPsuGrouping->GroupCollection[master].Partner.Member[i]);
                }
            }
        }
    }
}

// group: group index, target: target index
// master: master group index
void AddMember(byte group, byte master)
{
    if(group < GENERAL_GUN_QUANTITY && master < GENERAL_GUN_QUANTITY)
    {
        if(ShmPsuGrouping->GroupCollection[master].Role != _GROLE_MASTER &&
            ShmPsuGrouping->GroupCollection[master].Role != _GROLE_REQUEST_TO_CHARGING)
        {
            return;
        }
        if(ShmPsuGrouping->GroupCollection[group].Role != _GROLE_IDLE &&
            ShmPsuGrouping->GroupCollection[group].Role != _GROLE_WAIT_SLAVE &&
            ShmPsuGrouping->GroupCollection[group].Role != _GROLE_PRECHARGE_READY)
        {
            return;
        }

        SetPsuGroupToSlave(group, master + 1);
        ShmPsuGrouping->GroupCollection[master].Partner.Member[ShmPsuGrouping->GroupCollection[master].Partner.Quantity++] = group;
        if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
        {
            ShmPsuGrouping->GroupCollection[master].Partner.RealQuantity++;
            ShmPsuGrouping->GroupCollection[master].GunPsuQuantity += ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity;
        }
        //printf("\r\n Add Group %02X To Gun %d (Quantity %d), Set Parallel Relay %d On", group, target + 1, ShmPsuGrouping->GroupCollection[target].Partner.Quantity, ParallelConfig);
    }
}

// master: master group index
void AddAvailableMember(unsigned char master)
{
    PsuGroupPartner partner;
    char strMember[64];

    FindPsuGroupPartner(master, MAX_GROUP_QUANTITY, &partner);

    sprintf(strMember, "Gun %d Add Available Member:", master + 1);
    for(int i = 0; i < partner.Quantity; i++)
    {
        AddMember(partner.Member[i], master);

        char strSlave[8];
        sprintf(strSlave, " [%02X]", partner.Member[i]);
        strcat(strMember, strSlave);
    }

    if(partner.Quantity > 0)
    {
        LOG_INFO("%s", strMember);
    }
}

// master: master group index
// slave: slave group index
void RemoveMember(unsigned char master, unsigned char slave)
{
    bool find = false;
    int location = 0;
    unsigned char other = 0;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        if(slave == ShmPsuGrouping->GroupCollection[master].Partner.Member[i])
        {
            ShmPsuGrouping->GroupCollection[master].Partner.Member[i] = 0;
            location = i;
            find = true;
            break;
        }
    }
    if(find)
    {
        for(int i = location + 1; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            other = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
            ShmPsuGrouping->GroupCollection[master].Partner.Member[i] = 0;
            ShmPsuGrouping->GroupCollection[master].Partner.Member[i - 1] = other;
        }
        ShmPsuGrouping->GroupCollection[master].Partner.Quantity--;
        if(ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity > 0)
        {
            ShmPsuGrouping->GroupCollection[master].Partner.RealQuantity--;
            ShmPsuGrouping->GroupCollection[master].GunPsuQuantity -= ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity;
        }
    }
}

// master: master group index
void RemoveNonGroupMember(unsigned char master)
{
    unsigned char slave = 0;
    char strMember[64];
    PsuGroupPartner partner;
    bool find = false;

    if(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER)
    {
        sprintf(strMember, "Gun %d Remove Member:", master + 1);

        memcpy(&partner, &ShmPsuGrouping->GroupCollection[master].Partner, sizeof(PsuGroupPartner));
        for(int i = 0; i < partner.Quantity; i++)
        {
            slave = partner.Member[i];

            if(ShmPsuGrouping->GroupCollection[slave].Role != _GROLE_SLAVE)
            {
                RemoveMember(master, slave);
                if(ShmPsuGrouping->GroupCollection[slave].ReservedTarget == 0)
                {
                    SetPsuGroupToIdle(slave);
                }
                else
                {
                    SetPsuGroupRole(slave, _GROLE_WAIT_SLAVE);
                }

                char strSlave[8];
                sprintf(strSlave, " [%02X]", slave);
                strcat(strMember, strSlave);
                find = true;
            }
        }

        if(find)
        {
            LOG_INFO("%s", strMember);
        }
    }
}

void SetPsuGroupPowerOnOff(unsigned char group, unsigned char power_on_off)
{
    if(group < CONNECTOR_QUANTITY)
    {
        enum PSU_POWER_CMD power_cmd = power_on_off == PSU_POWER_ON ? PSU_POWER_ON : PSU_POWER_OFF;
        enum PSU_FLASH_CMD led_cmd = power_on_off == PSU_POWER_ON ? PSU_FLASH_ON : PSU_FLASH_NORMAL;

        if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
        {
#if ONE_MODULE_OUTPUT
            // for safety test (inrush current)
            if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.InPrechargeMode)
            {
                SinglePsuPower(ShmPsuPosition->GroupLocationInfo[group].PsuSN[0], power_cmd);
            }
            else
            {
                SwitchPower(group, power_cmd);
            }
#else
            SwitchPower(group, power_cmd);
#endif
            Await();
            FlashLed(group, led_cmd);
            Await();
        }
        isStartOutputSwitch[group] = power_on_off == PSU_POWER_ON ? true : false;
    }
}

// master: master group index
unsigned short GetPresentTargetCurrent(unsigned char master, unsigned char role_condition)
{
    unsigned char slave = 0;
    unsigned short current = 0;

    current = ShmPsuGrouping->GroupOutput[master].GTargetCurrent;
    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
        if(role_condition == _GROLE_MASTER || ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE)
        {
            current += ShmPsuGrouping->GroupOutput[slave].GTargetCurrent;
        }
    }
    return current;
}

// group: group index
int GetPsuModuleQuantity(unsigned char group)
{
    int quantity = 0;
    unsigned char slave = 0;

    quantity = ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity;
    for(int i = 0; i < ShmPsuGrouping->GroupCollection[group].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[group].Partner.Member[i];
        quantity += ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity;
    }

    return quantity;
}

// group: group index
bool IsAvailableGroup(unsigned char group)
{
    return ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0 ? true : false;
}

// master: master group index
int GetMasterAvailableGroup(unsigned char master)
{
    int quantity = 0;
    unsigned char slave = 0;

    quantity = IsAvailableGroup(master) ? quantity + 1 : quantity;
    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
        quantity = IsAvailableGroup(slave) ? quantity + 1 : quantity;
    }

    return quantity;
}

// group: group index
void UpdateGunLoading(unsigned char group)
{
    if(ShmChargerInfo->PsuGrouping.GroupCollection[group].TargetGroup != 0 &&
            group == ShmChargerInfo->PsuGrouping.GroupCollection[group].TargetGroup - 1)
    {
        unsigned short TargetCurrent = (int)(chargingInfo[group]->EvBatterytargetCurrent * 10);

        ShmPsuGrouping->GroupCollection[group].GunLoading = chargingInfo[group]->AvailableChargingCurrent != 0 ?
            (TargetCurrent * 10000 / (int)chargingInfo[group]->AvailableChargingCurrent) : 0;
    }
    else
    {
        ShmPsuGrouping->GroupCollection[group].GunLoading = 0;
    }
}

// group: group index
void UpdatePsuGroupLoading(unsigned char group)
{
    ShmPsuGrouping->GroupOutput[group].OutputLoading = ShmPsuData->PsuGroup[group].GroupAvailableCurrent != 0 ?
        (ShmPsuGrouping->GroupOutput[group].GTargetCurrent * 10000 / ShmPsuData->PsuGroup[group].GroupAvailableCurrent) : 0;
}

// master: master group index
void UpdateMasterPsuGroupLoading(unsigned char master)
{
    unsigned char slave = 0;

    UpdatePsuGroupLoading(master);

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
        UpdatePsuGroupLoading(slave);
    }
}

void PsuGroupIncreaseCurrent(unsigned char group, unsigned short needIncrease, unsigned short *realIncreaseCurrent)
{
    unsigned short increase = needIncrease;
    if((ShmPsuGrouping->GroupOutput[group].GTargetCurrent + increase) <= ShmPsuData->PsuGroup[group].GroupAvailableCurrent)
    {
        ShmPsuGrouping->GroupOutput[group].GTargetCurrent += increase;
    }
    else
    {
        increase = ShmPsuData->PsuGroup[group].GroupAvailableCurrent - ShmPsuGrouping->GroupOutput[group].GTargetCurrent;
        ShmPsuGrouping->GroupOutput[group].GTargetCurrent = ShmPsuData->PsuGroup[group].GroupAvailableCurrent;
    }
    *realIncreaseCurrent += increase;
}

void PsuGroupDecreaseCurrent(unsigned char group, unsigned short needDecrease, unsigned short *realDecreaseCurrent)
{
    unsigned short decrease = needDecrease;
    if(ShmPsuGrouping->GroupOutput[group].GTargetCurrent >= (decrease + ZERO_CURRENT))
    {
        ShmPsuGrouping->GroupOutput[group].GTargetCurrent -= decrease;
    }
    else
    {
        decrease = ShmPsuGrouping->GroupOutput[group].GTargetCurrent - ZERO_CURRENT;
        ShmPsuGrouping->GroupOutput[group].GTargetCurrent = ZERO_CURRENT;
    }
    *realDecreaseCurrent += decrease;
}

void IncreaseCurrentByGunLoading(unsigned char group, unsigned short loading, unsigned short *realIncrease, unsigned short MaxIncrease)
{
    unsigned short target = 0, need = 0;

    if(*realIncrease >= MaxIncrease)
    {
        return;
    }

    target = (loading * ShmPsuData->PsuGroup[group].GroupAvailableCurrent) / 10000;
    need = target >= ShmPsuGrouping->GroupOutput[group].GTargetCurrent ?
        (target - ShmPsuGrouping->GroupOutput[group].GTargetCurrent) : 0;

    need = need > (MaxIncrease - *realIncrease) ? (MaxIncrease - *realIncrease) : need;

    //LOG_INFO("Group [%02X] Target Current %d.%d A, Increase Current: %d.%d A",
    //    group, (target / 10), (target % 10), (need / 10), (need % 10));
    if(need > 0)
    {
        PsuGroupIncreaseCurrent(group, need, realIncrease);
    }
}

void DecreaseCurrentByGunLoading(unsigned char group, unsigned short loading, unsigned short *realDecrease, unsigned short MaxDecrease)
{
    unsigned short target = 0, need = 0;

    if(*realDecrease >= MaxDecrease)
    {
        return;
    }

    target = (loading * ShmPsuData->PsuGroup[group].GroupAvailableCurrent) / 10000;
    need = ShmPsuGrouping->GroupOutput[group].GTargetCurrent >= target ?
        (ShmPsuGrouping->GroupOutput[group].GTargetCurrent - target) : 0;

    need = need > (MaxDecrease - *realDecrease) ? (MaxDecrease - *realDecrease) : need;

    //LOG_INFO("Group [%02X] Target Current %d.%d A, Decrease Current: %d.%d A",
    //    group, (target / 10), (target % 10), (need / 10), (need % 10));
    if(need > 0)
    {
        PsuGroupDecreaseCurrent(group, need, realDecrease);
    }
}

// master: master group index
void AverageIncreaseCurrent(unsigned char master, unsigned short NeedIncreaseCurrent)
{
    unsigned char availableGroup = 0, slave = 0;
    unsigned short avgGroupCurrent = 0, excessCurrent = 0, realIncrease = 0;

#if 0
    PSU_LOG("Gun %d Increase By GunLoading %d.%02d Percent",
        master + 1, (ShmPsuGrouping->GroupCollection[master].GunLoading / 100), (ShmPsuGrouping->GroupCollection[master].GunLoading % 100));
#endif
    IncreaseCurrentByGunLoading(master, ShmPsuGrouping->GroupCollection[master].GunLoading, &realIncrease, NeedIncreaseCurrent);
    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
        IncreaseCurrentByGunLoading(slave, ShmPsuGrouping->GroupCollection[master].GunLoading, &realIncrease, NeedIncreaseCurrent);
    }

    availableGroup = GetMasterAvailableGroup(master);

    if(NeedIncreaseCurrent > realIncrease && availableGroup > 0)
    {
        // increase current by group quantity
        avgGroupCurrent = (NeedIncreaseCurrent - realIncrease) / availableGroup;
#if 0
        PSU_LOG("Gun %d Increase By Group, AvgGroupCurrent %d.%d A",
            master + 1, (avgGroupCurrent / 10), (avgGroupCurrent % 10));
#endif
        PsuGroupIncreaseCurrent(master, avgGroupCurrent, &realIncrease);
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
            PsuGroupIncreaseCurrent(slave, avgGroupCurrent, &realIncrease);
        }
    }

    if(NeedIncreaseCurrent > realIncrease)
    {
        // increase excess current to master group
        excessCurrent = NeedIncreaseCurrent - realIncrease;
#if 0
        PSU_LOG("Gun %d Increase, ExcessCurrent %d.%d A", master + 1, (excessCurrent / 10), (excessCurrent % 10));
#endif
        PsuGroupIncreaseCurrent(master, excessCurrent, &realIncrease);
    }
}

// master: master group index
void AverageDecreaseCurrent(unsigned char master, unsigned short NeedDecreaseCurrent)
{
    unsigned char availableGroup = 0, slave = 0;
    unsigned short avgGroupCurrent = 0, excessCurrent = 0, realDecrease = 0;

#if 0
    PSU_LOG("Gun %d Decrease By GunLoading %d.%02d Percent",
        master + 1, (ShmPsuGrouping->GroupCollection[master].GunLoading / 100), (ShmPsuGrouping->GroupCollection[master].GunLoading % 100));
#endif
    DecreaseCurrentByGunLoading(master, ShmPsuGrouping->GroupCollection[master].GunLoading, &realDecrease, NeedDecreaseCurrent);
    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
        DecreaseCurrentByGunLoading(slave, ShmPsuGrouping->GroupCollection[master].GunLoading, &realDecrease, NeedDecreaseCurrent);
    }

    availableGroup = GetMasterAvailableGroup(master);

    if(NeedDecreaseCurrent > realDecrease)
    {
        // decrease current by group quantity
        avgGroupCurrent = (NeedDecreaseCurrent - realDecrease) / availableGroup;
#if 0
        PSU_LOG("Gun %d Decrease By Group, AvgGroupCurrent %d.%d A",
            master + 1, (avgGroupCurrent / 10), (avgGroupCurrent % 10));
#endif
        PsuGroupDecreaseCurrent(master, avgGroupCurrent, &realDecrease);
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
            PsuGroupDecreaseCurrent(slave, avgGroupCurrent, &realDecrease);
        }
    }

    if(NeedDecreaseCurrent > realDecrease)
    {
        // decrease excess current
        excessCurrent = NeedDecreaseCurrent - realDecrease;
#if 0
        PSU_LOG("Gun %d Decrease, ExcessCurrent %d.%d A", master + 1, (excessCurrent / 10), (excessCurrent % 10));
#endif
        PsuGroupDecreaseCurrent(master, excessCurrent, &realDecrease);
    }
}

void MasterIncreaseCurrent(unsigned char master, unsigned short NeedIncreaseCurrent)
{
    unsigned short excessCurrent = 0, realIncrease = 0;

    PsuGroupIncreaseCurrent(master, NeedIncreaseCurrent, &realIncrease);

    if(NeedIncreaseCurrent > realIncrease)
    {
        excessCurrent = NeedIncreaseCurrent - realIncrease;

        AverageIncreaseCurrent(master, excessCurrent);
    }
}

void MasterDecreaseCurrent(unsigned char master, unsigned short NeedDecreaseCurrent)
{
    unsigned char slave = 0;
    unsigned short avgGroupCurrent = 0, excessCurrent = 0, realDecrease = 0;


    if(ShmPsuGrouping->GroupCollection[master].Partner.RealQuantity > 0)
    {
        // decrease current by group quantity
        avgGroupCurrent = NeedDecreaseCurrent / ShmPsuGrouping->GroupCollection[master].Partner.RealQuantity;
#if 0
    PSU_LOG("Gun %d Decrease By Group, AvgGroupCurrent %d.%d A",
        master + 1, (avgGroupCurrent / 10), (avgGroupCurrent % 10));
#endif
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];

            if(ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity > 0)
            {
                PsuGroupDecreaseCurrent(slave, avgGroupCurrent, &realDecrease);
            }
        }
    }

    if(NeedDecreaseCurrent > realDecrease)
    {
        excessCurrent = NeedDecreaseCurrent - realDecrease;
        PsuGroupDecreaseCurrent(master, excessCurrent, &realDecrease);
    }
}

void UpdateMaxCurrent(unsigned char master, unsigned short current)
{
    int time = 0;

    // update max stage current
    if(current > StageMaxCurrent[master])
    {
        StageMaxCurrent[master] = current;
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxStageCurrent = false;
        GetClockTime(&_StageCurrent_time[master]);
    }

    if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxStageCurrent)
    {
        time = GetTimeoutValue(_StageCurrent_time[master]) / uSEC_VAL;
        if(time >= EV_MAX_CURRENT_DELAY)
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxStageCurrent = true;
            LOG_INFO("Gun %d Reach Max Stage Current", master + 1);
        }
    }

    // update max target current and reset max current time
    if(current > MaxCurrentDemand[master])
    {
        if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxCurrentDemand)
        {
            LOG_INFO("Gun %d Max Current Demand Timer Reset", master + 1);
        }
        MaxCurrentDemand[master] = current;
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxCurrentDemand = false;
        GetClockTime(&_MaxCurrent_time[master]);
    }

    if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxCurrentDemand)
    {
        time = GetTimeoutValue(_MaxCurrent_time[master]) / uSEC_VAL;
        if(time >= EV_MAX_CURRENT_DELAY)
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxCurrentDemand = true;
            LOG_INFO("Gun %d Reach Max Current Demand", master + 1);
        }
    }
}

// master: master group index
bool IsMasterOutputCurrentStable(unsigned char master)
{
    int time = 0;
    bool stable = false, reachTarget = false;
    unsigned short presentOutput = (int)chargingInfo[master]->PresentChargingCurrent * 10;
    unsigned short TargetCurrent = 0;

    TargetCurrent = (int)(chargingInfo[master]->EvBatterytargetCurrent * 10);

    reachTarget = presentOutput > (TargetCurrent - REACH_CURRENT_TOLERANCE) && presentOutput < (TargetCurrent + REACH_CURRENT_TOLERANCE);

    if((presentOutput >= StableOutputCurrent[master] && presentOutput - StableOutputCurrent[master] > REACH_CURRENT_TOLERANCE) ||
        (presentOutput < StableOutputCurrent[master] && StableOutputCurrent[master] - presentOutput > REACH_CURRENT_TOLERANCE))
    {
        GetClockTime(&_ReachCurrent_time[master]);
        StableOutputCurrent[master] = presentOutput;
    }

    time = GetTimeoutValue(_ReachCurrent_time[master]) / uSEC_VAL;

    if(reachTarget || time >= CURRENT_STABLE_TIME ||
        (ShmChargerInfo->Control.FCharging[master].FCtrl.bits.EnableForceCharging && time >= CURRENT_REACH_TARGET_TIME))
    {
        stable = true;
    }

    return stable;
}

// master: master group index
void MasterBalanceCurrent(unsigned char master)
{
    int time = 0;
    unsigned char slave = 0;
    unsigned short realBalance = 0, grabCurrent = 0;

    time = GetTimeoutValue(_BalanceCurrent_time[master]) / mSEC_VAL;

    if(time >= BALANCE_CURRENT_INTERVAL)
    {
        //LOG_INFO("Gun %d Start Balance Current", master + 1);

        DecreaseCurrentByGunLoading(master, ShmPsuGrouping->GroupCollection[master].GunLoading, &grabCurrent, MAX_ADJ_BALANCE_CURRENT);
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
            DecreaseCurrentByGunLoading(slave, ShmPsuGrouping->GroupCollection[master].GunLoading, &grabCurrent, MAX_ADJ_BALANCE_CURRENT);
        }
        //LOG_INFO("Gun %d Grab Balance Current %d.%d A", master + 1, (grabCurrent / 10), (grabCurrent % 10));

        IncreaseCurrentByGunLoading(master, ShmPsuGrouping->GroupCollection[master].GunLoading, &realBalance, grabCurrent);
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
            IncreaseCurrentByGunLoading(slave, ShmPsuGrouping->GroupCollection[master].GunLoading, &realBalance, grabCurrent);
        }
        //LOG_INFO("Gun %d Increase Balance Current %d.%d A", master + 1, (realBalance / 10), (realBalance % 10));

        UpdateMasterPsuGroupLoading(master);
        GetClockTime(&_BalanceCurrent_time[master]);
    }
}

unsigned short GetLoadingDiff(unsigned char master)
{
    unsigned char slave = 0;
    unsigned short maxLoading = 0, minLoading = 0, diffLoading = 0;

    maxLoading = ShmPsuGrouping->GroupOutput[master].OutputLoading;
    minLoading = ShmPsuGrouping->GroupOutput[master].OutputLoading;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];

        if(ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity == 0 || ShmPsuData->PsuGroup[slave].GroupAvailableCurrent == 0)
        {
            continue;
        }

        maxLoading = ShmPsuGrouping->GroupOutput[slave].OutputLoading > maxLoading ?
            ShmPsuGrouping->GroupOutput[slave].OutputLoading : maxLoading;

        minLoading = ShmPsuGrouping->GroupOutput[slave].OutputLoading < minLoading ?
            ShmPsuGrouping->GroupOutput[slave].OutputLoading : minLoading;
    }

    diffLoading = maxLoading - minLoading;

    return diffLoading;
}

void CheckCurrentBalance(unsigned char master)
{
    unsigned short diffLoading = 0;

    diffLoading = GetLoadingDiff(master);

    if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedCurrentBalance)
    {
        if(diffLoading >= START_BALANCE_CRITERIA)
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedCurrentBalance = true;
            PSU_LOG("Gun %d Output Current Is Unbalance, diffLoading = %d.%02d", master + 1, (diffLoading / 100), (diffLoading % 100));
            GetClockTime(&_BalanceCurrent_time[master]);
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedCurrentBalance)
    {
        MasterBalanceCurrent(master);

        diffLoading = GetLoadingDiff(master);
        if(diffLoading <= STOP_BALANCE_CRITERIA)
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedCurrentBalance = false;
            PSU_LOG("Gun %d Output Current Is Balance", master + 1);
        }
    }
}

bool IsOtherCharging(unsigned char group)
{
    bool find = false;
    int other = 0, selfTarget = 0, location = 0;

    if(ShmPsuGrouping->GroupCollection[group].TargetGroup == 0)
    {
        return find;
    }

    selfTarget = ShmPsuGrouping->GroupCollection[group].TargetGroup - 1;

    // search from left
    location = ShmPsuGrouping->GroupCollection[group].Location - 1;
    for(int i = location; i >= 0; i--)
    {
        other = ShmPsuGrouping->Layout[i];
        if(ShmPsuGrouping->GroupCollection[other].Role == _GROLE_MASTER)
        {
            find = other != selfTarget ? true : false;
            break;
        }
    }

    if(!find)
    {
        // search from right
        location = ShmPsuGrouping->GroupCollection[group].Location + 1;
        for(int i = location; i < ShmChargerInfo->Control.MaxConnector; i++)
        {
            other = ShmPsuGrouping->Layout[i];
            if(ShmPsuGrouping->GroupCollection[other].Role == _GROLE_MASTER)
            {
                find = other != selfTarget ? true : false;
                break;
            }
        }
    }

    return find;
}

// master: master group index
void MasterReleasePsuGroup(unsigned char master)
{
    bool findOtherInCharging = false;
    unsigned char slave = 0, member = 0, releaseTarget = 0;
    unsigned char otherCharging[MAX_GROUP_QUANTITY] = {0};
    unsigned char releaseList[MAX_GROUP_QUANTITY] = {0};
    unsigned short releaseLoading[MAX_GROUP_QUANTITY] = {0};
    PsuGroupPartner ReleaseMember;
    unsigned short target = 0, releaseAvailableCurrent = 0, originalAvailableCurrent = 0, originalLoading = 0, maxReleaseLoading = 0;

    if(ShmChargerInfo->PsuGrouping.GroupCollection[master].Role != _GROLE_MASTER ||
        ShmPsuGrouping->GroupCollection[master].Partner.Quantity == 0)
    {
        return;
    }

    target = (int)(chargingInfo[master]->EvBatterytargetCurrent * 10);
    originalAvailableCurrent = (int)chargingInfo[master]->AvailableChargingCurrent;
    originalLoading = ShmPsuGrouping->GroupCollection[master].GunLoading;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];

        releaseAvailableCurrent = 0;
        PsuGroupPowerOffCheck(slave, &ReleaseMember);

        otherCharging[i] = IsOtherCharging(slave);
        findOtherInCharging = otherCharging[i] ? otherCharging[i] : findOtherInCharging;
        releaseList[i] = slave;

        for(int j = 0; j < ReleaseMember.Quantity; j++)
        {
            member = ReleaseMember.Member[j];
            releaseAvailableCurrent += ShmPsuData->PsuGroup[member].GroupAvailableCurrent;
        }

        releaseLoading[i] = originalAvailableCurrent > releaseAvailableCurrent ?
            (target * 10000 / (originalAvailableCurrent - releaseAvailableCurrent)) : 0;

        //LOG_INFO("Gun %d Release [%02X], Total Release %d Group, Gun Loading %d.%02d >> %d.%02d", master + 1, slave, ReleaseMember.Quantity,
        //    (originalLoading / 100), (originalLoading % 100),
        //    (releaseLoading[i] / 100), (releaseLoading[i] % 100));
    }

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        if((otherCharging[i] || !findOtherInCharging) &&
            releaseLoading[i] > maxReleaseLoading && releaseLoading[i] < (EXTEND_LOADING - RELEASE_LOADING_OFFSET))
        {
            maxReleaseLoading = releaseLoading[i];
            releaseTarget = releaseList[i];
        }
    }

    if(maxReleaseLoading > 0)
    {
        ShmPsuGrouping->GroupCollection[releaseTarget].GroupCtrl.bits.SlavePowerOffRequest = true;
        LOG_INFO("Gun %d Set [%02X] Release(%s), Gun Loading %d.%02d >> %d.%02d",
            master + 1, releaseTarget, findOtherInCharging ? "Other In Charging" : "Normal",
            (originalLoading / 100), (originalLoading % 100), (maxReleaseLoading / 100), (maxReleaseLoading % 100));
    }
}

void CheckReleaseOrExtend(unsigned char master)
{
    if((ShmChargerInfo->Control.TestCtrl.bits.ChargingSimulation ||
        !ShmChargerInfo->Control.FCharging[master].FCtrl.bits.EnableForceCharging) &&
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxStageCurrent &&
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.DeratingCtrlValue == 0 &&
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue == 0)
    {
        if(ShmPsuGrouping->GroupCollection[master].Partner.Quantity > 0 &&
            ShmPsuGrouping->GroupCollection[master].GunLoading < RELEASE_LOADING)
        {
            MasterReleasePsuGroup(master);
        }
        else if(ShmPsuGrouping->GroupCollection[master].GunLoading >= EXTEND_LOADING)
        {
            int available = GetPsuGroupAvailable(master);

            if(available > 0)
            {
                if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendAvailable)
                {
                    LOG_INFO("Gun %d Extend Capability Available", master + 1);
                    GetClockTime(&_ExtendCapability_time[master]);
                }
                ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendAvailable = true;
            }
        }
    }
}

bool IsPsuGroupOutputConfigDifferent(unsigned char group)
{
    bool different = false;

    if(ShmPsuGrouping->GroupOutput[group].GTargetVoltage != PreGroupOutput[group].GTargetVoltage ||
        ShmPsuGrouping->GroupOutput[group].GTargetCurrent != PreGroupOutput[group].GTargetCurrent)
    {
        different = true;
    }
    PreGroupOutput[group].GTargetVoltage = ShmPsuGrouping->GroupOutput[group].GTargetVoltage;
    PreGroupOutput[group].GTargetCurrent = ShmPsuGrouping->GroupOutput[group].GTargetCurrent;

    return different;
}

// Gun X Demand Change -> Voltage: XXXX.X V, Current: XXXX.X A, Available: XXXX.X A, Loading: XXX.XX
//         Master [XX] -> Voltage: XXXX.X V, Current: XXXX.X A, Available: XXXX.X A, Loading: XXX.XX
//         Member [XX] -> Voltage: XXXX.X V, Current: XXXX.X A, Available: XXXX.X A, Loading: XXX.XX
void ShowPsuGroupOutputConfig(unsigned char master)
{
    unsigned char slave = 0;

    PSU_LOG("Gun %d Demand Change -> Voltage: %4d V, Current: %4d.%d A, Available: %4d.%d A, Loading: %3d.%02d",
        master + 1, (ShmPsuGrouping->GroupOutput[master].GTargetVoltage / 10),
        ((int)chargingInfo[master]->EvBatterytargetCurrent), (((int)(chargingInfo[master]->EvBatterytargetCurrent * 10)) % 10),
        ((int)chargingInfo[master]->AvailableChargingCurrent / 10), ((int)chargingInfo[master]->AvailableChargingCurrent % 10),
        (ShmPsuGrouping->GroupCollection[master].GunLoading / 100), (ShmPsuGrouping->GroupCollection[master].GunLoading % 100));
    PSU_LOG("        Master [%02X] -> Voltage: %4d V, Current: %4d.%d A, Available: %4d.%d A, Loading: %3d.%02d",
        master, (ShmPsuGrouping->GroupOutput[master].GTargetVoltage / 10),
        (ShmPsuGrouping->GroupOutput[master].GTargetCurrent / 10), (ShmPsuGrouping->GroupOutput[master].GTargetCurrent % 10),
        (ShmPsuData->PsuGroup[master].GroupAvailableCurrent / 10), (ShmPsuData->PsuGroup[master].GroupAvailableCurrent % 10),
        (ShmPsuGrouping->GroupOutput[master].OutputLoading / 100), (ShmPsuGrouping->GroupOutput[master].OutputLoading % 100));
    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
        PSU_LOG("        Member [%02X] -> Voltage: %4d V, Current: %4d.%d A, Available: %4d.%d A, Loading: %3d.%02d",
            slave, (ShmPsuGrouping->GroupOutput[slave].GTargetVoltage / 10),
            (ShmPsuGrouping->GroupOutput[slave].GTargetCurrent / 10), (ShmPsuGrouping->GroupOutput[slave].GTargetCurrent % 10),
            (ShmPsuData->PsuGroup[slave].GroupAvailableCurrent / 10), (ShmPsuData->PsuGroup[slave].GroupAvailableCurrent % 10),
            (ShmPsuGrouping->GroupOutput[slave].OutputLoading / 100), (ShmPsuGrouping->GroupOutput[slave].OutputLoading % 100));
    }
}

void PsuGroupOutputConfigCheck(unsigned char master)
{
    unsigned char slave = 0;
    bool show = false;

    show = IsPsuGroupOutputConfigDifferent(master);

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
        if(IsPsuGroupOutputConfigDifferent(slave))
        {
            show = true;
        }
    }
    if(show)
    {
        ShowPsuGroupOutputConfig(master);
    }
}

void StepRecognition(unsigned char master, unsigned short voltage)
{
    if(_GfdStep[master] == PREPARE_STEP_NONE && voltage > 0)
    {
        // gfd step
        _GfdStep[master] = PREPARE_STEP_CABLE_CHECK;
        LOG_INFO("Gun %d GFD Start Step", master + 1);
    }
    else if(_GfdStep[master] == PREPARE_STEP_CABLE_CHECK && voltage == 0)
    {
        // gfd done step
        _GfdStep[master] = PREPARE_STEP_GFD_DONE;
        LOG_INFO("Gun %d GFD Stop Step", master + 1);
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.CableCheckDone = true;
    }
    else if(_GfdStep[master] == PREPARE_STEP_GFD_DONE && voltage >= SAFETY_PRECHARGE_OFFSET)
    {
        // precharge step
        _GfdStep[master] = PREPARE_STEP_PRECHARGE;
        LOG_INFO("Gun %d PreCharge Step", master + 1);
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.InPrechargeMode = true;
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AlreadyInChargingMode)
    {
        // charging step
        if(_GfdStep[master] != PREPARE_STEP_CHARGING)
        {
            LOG_INFO("Gun %d Charging Step", master + 1);
        }
        _GfdStep[master] = PREPARE_STEP_CHARGING;

        if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.InPrechargeMode)
        {
            if(_VoltageResumeCnt[master] < MAX_CHARGING_DELAY_COUNT)
            {
                int time = GetTimeoutValue(_ChargingDelay_time[master]) / mSEC_VAL;
                if(time >= CHARGING_DELAY_INTERVAL)
                {
                    GetClockTime(&_ChargingDelay_time[master]);
                    _VoltageResumeCnt[master]++;
                }
            }
            else
            {
                ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.InPrechargeMode = false;
            }
        }
    }
}

// master: master group index
// group: self group index
void UpdatePsuGroupOutputConfig(unsigned char master)
{
    unsigned char slave = 0;
    unsigned short TargetVoltage = 0, TargetCurrent = 0, PresentTargetCurrent = 0;
    unsigned short current = 0;

    TargetVoltage = (int)(chargingInfo[master]->EvBatterytargetVoltage * 10);
    TargetCurrent = (int)(chargingInfo[master]->EvBatterytargetCurrent * 10);

    if(chargingInfo[master]->RelayK1K2Status || ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.CableCheckDone)
    {
        // update target voltage
        if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed)
        {
            StepRecognition(master, TargetVoltage);

#if PRECHARGE_OFFSET
            // for safety test (inrush current)
            if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AlreadyInChargingMode)
            {
                GCTargetVoltage[master] = _GfdStep[master] == PREPARE_STEP_PRECHARGE ?
                    TargetVoltage - SAFETY_PRECHARGE_OFFSET : TargetVoltage;
            }
            else
            {
                if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.InPrechargeMode)
                {
                    GCTargetVoltage[master] = TargetVoltage > (GCTargetVoltage[master] + VOLTAGE_RESUME_STEP) ?
                        (GCTargetVoltage[master] + VOLTAGE_RESUME_STEP) : TargetVoltage;
                }
                else
                {
                    GCTargetVoltage[master] = TargetVoltage;
                }
            }
#else
            GCTargetVoltage[master] = TargetVoltage;
#endif
            ShmPsuGrouping->GroupOutput[master].GTargetVoltage = GCTargetVoltage[master];
            for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
            {
                slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
#if ONE_MODULE_OUTPUT
                // for safety test (inrush current)
                if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.InPrechargeMode)
                {
                    ShmPsuGrouping->GroupOutput[slave].GTargetVoltage = 0;
                }
                else
                {
                    ShmPsuGrouping->GroupOutput[slave].GTargetVoltage = GCTargetVoltage[master];
                }
#else
                ShmPsuGrouping->GroupOutput[slave].GTargetVoltage = GCTargetVoltage[master];
#endif
            }
        }

        // update target current
        if(((ShmPsuGrouping->GroupCollection[master].Partner.RealQuantity + 1) * ZERO_CURRENT) > TargetCurrent ||
            !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AlreadyInChargingMode)
        {
            current = ZERO_CURRENT;
#if 0
            if(ShmPsuGrouping->GroupOutput[master].GTargetCurrent != current)
            {
                LOG_INFO("Gun %d Need Minimum Target Current = %d", master + 1, current);
            }
#endif
            ShmPsuGrouping->GroupOutput[master].GTargetCurrent = current;

            for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
            {
                slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
                if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE || ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_PREPARE_SWITCH_OFF)
                {
#if ONE_MODULE_OUTPUT
                    // for safety test (inrush current)
                    ShmPsuGrouping->GroupOutput[slave].GTargetCurrent = 0;
#else
                    ShmPsuGrouping->GroupOutput[slave].GTargetCurrent =
                        ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity > 0 ? ZERO_CURRENT : 0;
#endif
                }
            }
        }
        else
        {
            if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed)
            {
                // master in normal output mode
                PresentTargetCurrent = GetPresentTargetCurrent(master, _GROLE_MASTER);

                current = TargetCurrent <= (int)chargingInfo[master]->AvailableChargingCurrent ?
                    TargetCurrent : (int)chargingInfo[master]->AvailableChargingCurrent;
#if 0
                if(GCTargetCurrent[master] != current)
                {
                    LOG_INFO("Gun %d Need Normal Target Current = %d", master + 1, current);
                }
#endif
                GCTargetCurrent[master] = current;

                UpdateMaxCurrent(master, GCTargetCurrent[master]);

                if(GCTargetCurrent[master] > PresentTargetCurrent)
                {
                    // increase current
                    OutputConfigStep[master] = _CURRENT_MODE_INCREASE;

                    PSU_LOG("Gun %d Increase Current: %d.%d A, %d.%d A -> %d.%d A", master + 1,
                        ((GCTargetCurrent[master] - PresentTargetCurrent) / 10), ((GCTargetCurrent[master] - PresentTargetCurrent) % 10),
                        (PresentTargetCurrent / 10), (PresentTargetCurrent % 10), (GCTargetCurrent[master] / 10), (GCTargetCurrent[master] % 10));
#if MASTER_OUTPUT_FIRST
                    // for safety test
                    if(GCTargetCurrent[master] <= CURRENT_BALANCE_CRITERIA)
                    {
                        MasterIncreaseCurrent(master, GCTargetCurrent[master] - PresentTargetCurrent);
                    }
                    else
                    {
                        AverageIncreaseCurrent(master, GCTargetCurrent[master] - PresentTargetCurrent);
                    }
#else
                    AverageIncreaseCurrent(master, GCTargetCurrent[master] - PresentTargetCurrent);
#endif
                }
                else if(GCTargetCurrent[master] < PresentTargetCurrent)
                {
                    // decrease current
                    OutputConfigStep[master] = _CURRENT_MODE_DECREASE;

                    PSU_LOG("Gun %d Decrease Current: %d.%d A, %d.%d A -> %d.%d A", master + 1,
                        ((PresentTargetCurrent - GCTargetCurrent[master]) / 10), ((PresentTargetCurrent - GCTargetCurrent[master]) % 10),
                        (PresentTargetCurrent / 10), (PresentTargetCurrent % 10), (GCTargetCurrent[master] / 10), (GCTargetCurrent[master] % 10));
#if MASTER_OUTPUT_FIRST
                    // for safety test
                    if(GCTargetCurrent[master] <= ShmPsuGrouping->GroupCollection[master].Partner.RealQuantity * CURRENT_BALANCE_CRITERIA)
                    {
                        MasterDecreaseCurrent(master, PresentTargetCurrent - GCTargetCurrent[master]);
                    }
                    else
                    {
                        AverageDecreaseCurrent(master, PresentTargetCurrent - GCTargetCurrent[master]);
                    }
#else
                    AverageDecreaseCurrent(master, PresentTargetCurrent - GCTargetCurrent[master]);
#endif
                }
                else
                {
                    // balance current or do not change
                    OutputConfigStep[master] = _CURRENT_MODE_BALANCE;
                    if(OutputConfigStep[master] != _preOutputConfigStep[master])
                    {
                        GetClockTime(&_ReachCurrent_time[master]);
                        LOG_INFO("Gun %d In Balance Mode", master + 1);
                        StableOutputCurrent[master] = (int)chargingInfo[master]->PresentChargingCurrent * 10;
                        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.OutputCurrentStable = false;
                        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedCurrentBalance = false;
                    }

                    if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.OutputCurrentStable)
                    {
                        if(IsMasterOutputCurrentStable(master))
                        {
                            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.OutputCurrentStable = true;
                            LOG_INFO("Gun %d Output Current = %5.1f A Stable", master + 1, chargingInfo[master]->PresentChargingCurrent);
                        }
                    }

                    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.OutputCurrentStable)
                    {
#if MASTER_OUTPUT_FIRST
                        // for safety test
                        if(GCTargetCurrent[master] > ShmPsuGrouping->GroupCollection[master].Partner.RealQuantity * CURRENT_BALANCE_CRITERIA)
                        {
                            CheckCurrentBalance(master);
                        }
                        else
                        {
                            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedCurrentBalance = false;
                        }
#else
                        CheckCurrentBalance(master);
#endif

                        if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedCurrentBalance)
                        {
                            CheckReleaseOrExtend(master);
                        }
                    }
                }
            }
            else
            {
                // master in derating mode
                OutputConfigStep[master] = _CURRENT_MODE_DERATING;
                if(OutputConfigStep[master] != _preOutputConfigStep[master])
                {
                    LOG_INFO("Gun %d In Derating Mode", master + 1);
                }
            }
            _preOutputConfigStep[master] = OutputConfigStep[master];
        }
    }
    else
    {
        ShmPsuGrouping->GroupOutput[master].GTargetVoltage = 0;
        ShmPsuGrouping->GroupOutput[master].GTargetCurrent = 0;
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];
            ShmPsuGrouping->GroupOutput[slave].GTargetVoltage = 0;
            ShmPsuGrouping->GroupOutput[slave].GTargetCurrent = 0;
        }
    }
}

// group: self group index
void SetPsuGroupOutput(unsigned char group)
{
    int time = 0;

    if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
    {
#if ONE_MODULE_OUTPUT
        // for safety test (inrush current)
        if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.InPrechargeMode)
        {
            SingleOutputVol(ShmPsuPosition->GroupLocationInfo[group].PsuSN[0], ShmPsuGrouping->GroupOutput[group].GTargetVoltage, ShmPsuGrouping->GroupOutput[group].GTargetCurrent);
        }
        else
        {
            PresentOutputVol(group, ShmPsuGrouping->GroupOutput[group].GTargetVoltage, ShmPsuGrouping->GroupOutput[group].GTargetCurrent);
        }
#else
        PresentOutputVol(group, ShmPsuGrouping->GroupOutput[group].GTargetVoltage, ShmPsuGrouping->GroupOutput[group].GTargetCurrent);
#endif
        Await();
    }

    //UpdatePsuGroupLoading(group);
    UpdateGunLoading(group);

    if(ShmPsuGrouping->GroupOutput[group].GTargetVoltage >= PSU_MIN_VOL)
    {
        if(!isStartOutputSwitch[group])
        {
            SetPsuGroupPowerOnOff(group, PSU_POWER_ON);
            GetClockTime(&_PoweOnOff_time[group]);
        }
        time = GetTimeoutValue(_PoweOnOff_time[group]) / uSEC_VAL;
        if(time > POWER_ONOFF_RESEND_INTERVAL)
        {
            SetPsuGroupPowerOnOff(group, PSU_POWER_ON);
            GetClockTime(&_PoweOnOff_time[group]);
        }
    }
    else
    {
        if(isStartOutputSwitch[group])
        {
            SetPsuGroupPowerOnOff(group, PSU_POWER_OFF);
            GetClockTime(&_PoweOnOff_time[group]);
        }
        time = GetTimeoutValue(_PoweOnOff_time[group]) / uSEC_VAL;
        if(time > POWER_ONOFF_RESEND_INTERVAL)
        {
            SetPsuGroupPowerOnOff(group, PSU_POWER_OFF);
            GetClockTime(&_PoweOnOff_time[group]);
        }
    }
}

// master: master group index
bool CanMasterStartDerating(unsigned char master)
{
    bool start = false;
    unsigned short TargetCurrent = 0;

    TargetCurrent = (int)(chargingInfo[master]->EvBatterytargetCurrent * 10);

    if(TargetCurrent <= ShmPsuGrouping->GroupCollection[master].ReAssignAvailableCurrent)
    {
        start = true;
    }

    return start;
}

// master: master group index
// role_condition: when role_condition = _GROLE_MASTER, stop all member
void SetMemberStartPowerOff(unsigned char master, unsigned char role_condition)
{
    unsigned char slave = 0;
    char strMember[64];
    bool find = false;

    if(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER)
    {
        sprintf(strMember, "Gun %d Set Member:", master + 1);

        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];

            if(role_condition == _GROLE_MASTER || ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_PREPARE_SWITCH_OFF)
            {
                SetPsuGroupRole(slave, _GROLE_SLAVE_POWER_OFF);

                char strSlave[8];
                sprintf(strSlave, " [%02X]", slave);
                strcat(strMember, strSlave);
                find = true;
            }
        }

        if(find)
        {
            strcat(strMember, " Power Off");
            LOG_INFO("%s", strMember);
        }
    }
}

// master: master group index
bool IsMemberPowerOffOK(unsigned char master)
{
    bool done = true;
    unsigned char slave = 0;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];

        if(ShmPsuGrouping->GroupCollection[slave].Role != _GROLE_SLAVE && ShmPsuGrouping->GroupCollection[slave].Role != _GROLE_SWITCH_OFF_OK)
        {
            done = false;
        }
    }

    return done;
}

// master: master group index
void SetMemberPowerOffDone(unsigned char master)
{
    unsigned char slave = 0;

    if(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER)
    {
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].Partner.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].Partner.Member[i];

            if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SWITCH_OFF_OK)
            {
                SetPsuGroupRole(slave, _GROLE_WAIT_IDLE);
            }
            else if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE_POWER_OFF)
            {
                // can not power off normally
                SetPsuGroupRole(slave, _GROLE_TERMINATE);
            }
        }
    }
}

unsigned char _ParallelRelay[MAX_GROUP_QUANTITY];
void SetPCabinetParallelRelay(unsigned short parallelConfig)
{
    bool change = false;

    if(ShmChargerInfo->Control.CabinetRole == _CROLE_MASTER)
    {
        for(int i = 0; i < MAX_SLAVE_CABINET_QUANTITY; i++)
        {
            change = false;
            if(ShmChargerInfo->ParallelCabinet.PCabinet[i].LocalStatus == _DeviceStatus_Idle ||
                ShmChargerInfo->ParallelCabinet.PCabinet[i].LocalStatus == _DeviceStatus_Alarm ||
                ShmChargerInfo->ParallelCabinet.PCabinet[i].LocalStatus == _DeviceStatus_Charging)
            {
                for(int j = 0; j < GENERAL_GUN_QUANTITY; j++)
                {
                    _ParallelRelay[j] = (parallelConfig & (1 << j)) > 0 ? YES : NO;

                    if(_ParallelRelay[i] != ShmChargerInfo->ParallelCabinet.PCabinet[i].ParallelRelaySetting[i])
                    {
                        change = true;
                    }
                }

                if(change)
                {
                    char strParallel[128];
                    sprintf(strParallel, "Set PCabinet %d Parallel Relay:", i + 1);
                    for(int j = 0; j < GENERAL_GUN_QUANTITY; j++)
                    {
                        char strState[8];
                        sprintf(strState, " %3s", _ParallelRelay[j] ? "ON" : "OFF");
                        strcat(strParallel, strState);
                    }
                    LOG_INFO("%s", strParallel);
                }
                ShmChargerInfo->ParallelCabinet.PCabinet[i].ParallelRelaySetting[i] = _ParallelRelay[i];
            }
        }
    }
}

// master: master group index
// Yes_No: Yes: relay on, No: relay off
// role_condition: when role_condition = _GROLE_MASTER, parallel relay of all member do the action(on or off)
void SetParallelRelayOnOff(unsigned char master, unsigned char Yes_No, unsigned char role_condition)
{
    int ParallelConfig = 0;
    unsigned char slave = 0;
    PsuGroupPartner *partner;

    if(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER ||
        (ShmPsuGrouping->GroupCollection[master].Role == _GROLE_REQUEST_TO_CHARGING && Yes_No))
    {
        ShmPsuGrouping->GroupCollection[master].ParallelCheck = 0;

        partner = role_condition == _GROLE_PRECHARGE_READY ? &ShmPsuGrouping->GroupCollection[master].PossibleMember : &ShmPsuGrouping->GroupCollection[master].Partner;

        for(int i = 0; i < partner->Quantity; i++)
        {
            slave = partner->Member[i];

            if(role_condition == _GROLE_MASTER || ShmPsuGrouping->GroupCollection[slave].Role != _GROLE_SLAVE)
            {
                ParallelConfig = ShmPsuGrouping->GroupCollection[master].ParallelConfig[slave];
                if(ParallelConfig != 0)
                {
                    ShmPsuGrouping->GroupCollection[master].ParallelCheck |= (1 << (ParallelConfig - 1));
                }
            }
        }
        if(Yes_No)
        {
            ShmPsuGrouping->ParallelRelayConfig.CtrlValue |= ShmPsuGrouping->GroupCollection[master].ParallelCheck;
        }
        else
        {
            ShmPsuGrouping->ParallelRelayConfig.CtrlValue &= ~ShmPsuGrouping->GroupCollection[master].ParallelCheck;
        }

        SetPCabinetParallelRelay(ShmPsuGrouping->ParallelRelayConfig.CtrlValue);
    }
}

// master: master group index
bool IsParallelRelayConfirmed(unsigned char master)
{
    bool confirmed = true;

    for(int i = 0; i < CONNECTOR_QUANTITY; i++)
    {
        if((1 << i) & ShmPsuGrouping->GroupCollection[master].ParallelCheck)
        {
            if((ShmPsuGrouping->ParallelRelayConfig.CtrlValue & (1 << i)) != (ShmPsuGrouping->ParallelRelayConfirmed.CtrlValue & (1 << i)))
            {
                confirmed = false;
                break;
            }
        }
    }

    return confirmed;
}

// master: master group index
bool IsPossibleMemberReady(unsigned char master)
{
    bool ready = true;
    unsigned char slave = 0;

    if(ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity > 0)
    {
        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity; i++)
        {
            slave = ShmPsuGrouping->GroupCollection[master].PossibleMember.Member[i];

            if(ShmPsuGrouping->GroupCollection[slave].Role != _GROLE_WAIT_SLAVE)
            {
                ready = false;
            }
        }
    }

    return ready;
}

// master: master group index
bool IsExtendPrechargeReady(unsigned char master)
{
    bool ready = true;
    unsigned char slave = 0;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].PossibleMember.Member[i];

        if(ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity == 0 || ShmPsuData->PsuGroup[slave].GroupAvailableCurrent == 0)
        {
            continue;
        }

        if(ShmPsuGrouping->GroupCollection[slave].Role != _GROLE_PREPARE_ATTACH_ON ||
            ShmPsuData->PsuGroup[slave].GroupPresentOutputVoltage > (ShmPsuData->PsuGroup[master].GroupPresentOutputVoltage + PRECHARGE_RANGE_VOLTAGE) ||
            ShmPsuData->PsuGroup[slave].GroupPresentOutputVoltage < (ShmPsuData->PsuGroup[master].GroupPresentOutputVoltage - PRECHARGE_OFFSET_VOLTAGE- PRECHARGE_RANGE_VOLTAGE))
        {
            ready = false;
        }
    }

    return ready;
}

// master: master group index
void SetExtendPrechargeCompleted(unsigned char master)
{
    unsigned char slave = 0;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].PossibleMember.Member[i];

        SetPsuGroupRole(slave, _GROLE_PRECHARGE_READY);
    }
}


// master: master group index
void SetExtendPrechargeStop(unsigned char master)
{
    unsigned char slave = 0;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].PossibleMember.Member[i];

        SetPsuGroupRole(slave, _GROLE_EXTEND_STOP);
    }
    memset(&ShmPsuGrouping->GroupCollection[master].PossibleMember, 0x00, sizeof(PsuGroupPartner));
}

// master: master group index
void AddExtendMember(unsigned char master)
{
    unsigned char slave = 0;

    for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity; i++)
    {
        slave = ShmPsuGrouping->GroupCollection[master].PossibleMember.Member[i];

        if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_PRECHARGE_READY)
        {
            AddMember(slave, master);
        }
    }
}

// group: self group index
void CheckChargingRequest(unsigned char group)
{
    if(ShmPsuGrouping->GroupCollection[group].Role != _GROLE_IDLE)
    {
        return;
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ChargingRequest)
    {
        SetPsuGroupRole(group, _GROLE_REQUEST_TO_CHARGING);
    }
}

// group: self group index
void ChargingRequestProcess(unsigned char group)
{
    int time = 0;
    PsuGroupPartner partner;

    if(ShmPsuGrouping->GroupCollection[group].Role != _GROLE_REQUEST_TO_CHARGING)
    {
        return;
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ChargingRequest && !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ChargingRequestConfirmed)
    {
        GetClockTime(&_ChargingRequest_time[group]);
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ChargingRequest = false;
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ChargingRequestConfirmed = true;
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.GroupShareCheck = true;
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.GroupShareCheck && !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ShareConfirmed)
    {
        // check is there psu group to grab
        if(PsuGroupGrabCheck(group, &partner))
        {
            GetClockTime(&_ChargingRequest_time[group]);
            ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.GrabGroupWait = true;
            PrepareToPowerOff(group + 1, &partner);
            memcpy(&ShmPsuGrouping->GroupCollection[group].PossibleMember, &partner, sizeof(PsuGroupPartner));
        }
        else
        {
            ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ShareCheckDone = true;
            LOG_INFO("Gun %d Grab Nothing", group + 1);
        }
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ShareConfirmed = true;
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.GrabGroupWait && !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ShareCheckDone)
    {
        // wait until grab psu ready or timeout
        bool ready = IsPossibleMemberReady(group);
        time = GetTimeoutValue(_ChargingRequest_time[group]) / uSEC_VAL;

        if(ready || time >= WAIT_GRAB_TIME)
        {
            ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ShareCheckDone = true;
            LOG_INFO("Gun %d Grab %s", group + 1, ready ? "OK" : "Timeout");
        }
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ShareCheckDone && !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.FindGroupPartner)
    {
        GetClockTime(&_ChargingRequest_time[group]);
        AddAvailableMember(group);
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.FindGroupPartner = true;
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.FindGroupPartner && !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ParallelRelayOn)
    {
        // after a little delay, set parallel relay on
        time = GetTimeoutValue(_ChargingRequest_time[group]) / uSEC_VAL;

        if(time >= WAIT_PARALLEL_RELAY_DELAY)
        {
            unsigned short original = ShmPsuGrouping->ParallelRelayConfig.CtrlValue;
            GetClockTime(&_ChargingRequest_time[group]);
            SetParallelRelayOnOff(group, YES, _GROLE_MASTER);
            ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ParallelRelayOn = true;
            LOG_INFO("Gun %d Charging Request Set All Member Parallel Relay On %X -> %X", group + 1, original, ShmPsuGrouping->ParallelRelayConfig.CtrlValue);
        }
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ParallelRelayOn && !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ParallelRelayConfirmed)
    {
        // wait until parallel relay on confirmed
        bool confirmed = IsParallelRelayConfirmed(group);
        time = GetTimeoutValue(_ChargingRequest_time[group]) / uSEC_VAL;

        //if(confirmed || time >= WAIT_RELAY_CONFIRMED_TIME)
        if(time >= WAIT_RELAY_CONFIRMED_TIME)
        {
            GetClockTime(&_ChargingRequest_time[group]);
            ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ParallelRelayConfirmed = true;
            LOG_INFO("Gun %d Charging Request Parallel Relay Confirmed %s", group + 1, confirmed ? "OK" : "Timeout");
        }
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ParallelRelayConfirmed && !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.GroupingDone)
    {
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.GroupingDone = true;
    }

    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.GroupingDone)
    {
        SetPsuGroupToMaster(group);
        ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.IdleCtrlValue = 0;
        LOG_INFO("Gun %d Grouping Completed", group + 1);
    }
}

// master: master group index
void PsuGroupDeratingProcess(unsigned char master)
{
    int time = 0;

    // slave group set NeedDerating flag to start re-assign
    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedDerating)
    {
        // reduce output current capability start
        GetClockTime(&_PsuGroupDerating_time[master]);
        unsigned short ReAssignCurrent = GetPresentTargetCurrent(master, _GROLE_SLAVE);
        ShmPsuGrouping->GroupCollection[master].ReAssignAvailableCurrent = ReAssignCurrent;
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.NeedDerating = false;

        LOG_INFO("Gun %d DeratingConfirmed%s, ReAssignAvailableCurrent = %d.%d A",
                master + 1,
                ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed ? " Again" : "",
                (ReAssignCurrent / 10), (ReAssignCurrent % 10));

        if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed)
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.DeratingCtrlValue = 0;
        }
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed = true;
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingStart)
    {
        // wait until derating from ev or timeout
        bool start = CanMasterStartDerating(master);
        bool bypass = ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AlreadyInChargingMode ? false : true;
        time = GetTimeoutValue(_PsuGroupDerating_time[master]) / uSEC_VAL;

        if(start || time >= WAIT_EV_DERATING_TIMEOUT || bypass)
        {
            GetClockTime(&_PsuGroupDerating_time[master]);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingStart = true;
            LOG_INFO("Gun %d %s Start Derating%s", master + 1, start ? "Normal" : "Force", bypass ? " (Bypass)" : "");
            SetMemberStartPowerOff(master, _GROLE_PREPARE_SWITCH_OFF);

            if(!bypass)
            {
                // update stage max current and reset max current time
                StageMaxCurrent[master] = start ?
                    (int)(chargingInfo[master]->EvBatterytargetCurrent * 10) :
                    ShmPsuGrouping->GroupCollection[master].ReAssignAvailableCurrent;
                ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxStageCurrent = false;
                GetClockTime(&_StageCurrent_time[master]);
            }
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingStart && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingPowerOffDone)
    {
        // wait until psu member power off ok or timeout
        bool power_off_ok = IsMemberPowerOffOK(master);
        time = GetTimeoutValue(_PsuGroupDerating_time[master]) / uSEC_VAL;

        if(power_off_ok || time >= WAIT_SLAVE_POWER_OFF_TIMEOUT)
        {
            GetClockTime(&_PsuGroupDerating_time[master]);
            SetMemberPowerOffDone(master);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingPowerOffDone = true;
            LOG_INFO("Gun %d Set Derating Member Power Off %s", master + 1, power_off_ok ? "OK" : "Timeout");
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingPowerOffDone && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingRelayOff)
    {
        // after a little delay, set parallel relay off
        time = GetTimeoutValue(_PsuGroupDerating_time[master]) / uSEC_VAL;

        if(time >= WAIT_PARALLEL_RELAY_DELAY)
        {
            unsigned short original = ShmPsuGrouping->ParallelRelayConfig.CtrlValue;
            GetClockTime(&_PsuGroupDerating_time[master]);
            SetParallelRelayOnOff(master, NO, _GROLE_SWITCH_OFF_OK);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingRelayOff = true;
            LOG_INFO("Gun %d Set Parallel Relay Off %X -> %X", master + 1, original, ShmPsuGrouping->ParallelRelayConfig.CtrlValue);
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingRelayOff && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingRelayConfirmed)
    {
        // wait until parallel relay off confirmed
        bool confirmed = IsParallelRelayConfirmed(master);
        time = GetTimeoutValue(_PsuGroupDerating_time[master]) / uSEC_VAL;

        //if(confirmed || time >= WAIT_RELAY_CONFIRMED_TIME)
        if(time >= WAIT_RELAY_CONFIRMED_TIME)
        {
            GetClockTime(&_PsuGroupDerating_time[master]);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingRelayConfirmed = true;
            LOG_INFO("Gun %d Parallel Relay Confirmed %s", master + 1, confirmed ? "OK" : "Timeout");
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingRelayConfirmed && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingCompleted)
    {
        // remove all non group member
        RemoveNonGroupMember(master);
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingCompleted = true;
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingCompleted)
    {
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.DeratingCtrlValue = 0;
        UpdateGunAvailableCapability(master);
        LOG_INFO("Gun %d Derating Completed", master + 1);
    }
}

void MasterStopChargingProcess(unsigned char master)
{
    int time = 0;

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingRequest && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingConfirmed)
    {
        // set all member to power off
        GetClockTime(&_StopCharging_time[master]);
        SetMemberStartPowerOff(master, _GROLE_MASTER);
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingConfirmed = true;
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingConfirmed && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllPowerOffDone)
    {
        // wait until psu member power off ok or timeout
        bool power_off_ok = IsMemberPowerOffOK(master);
        time = GetTimeoutValue(_StopCharging_time[master]) / uSEC_VAL;

        if(power_off_ok || time >= WAIT_SLAVE_POWER_OFF_TIMEOUT)
        {
            GetClockTime(&_StopCharging_time[master]);
            SetMemberPowerOffDone(master);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllPowerOffDone = true;
            LOG_INFO("Gun %d Set All Member Power Off %s", master + 1, power_off_ok ? "OK" : "Timeout");
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllPowerOffDone && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllParallelRelayOff)
    {
        // after a little delay, set all member parallel relay off
        time = GetTimeoutValue(_StopCharging_time[master]) / uSEC_VAL;

        if(time >= WAIT_PARALLEL_RELAY_DELAY)
        {
            unsigned short original = ShmPsuGrouping->ParallelRelayConfig.CtrlValue;
            GetClockTime(&_StopCharging_time[master]);
            SetParallelRelayOnOff(master, NO, _GROLE_MASTER);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllParallelRelayOff = true;
            LOG_INFO("Gun %d Set All Member Parallel Relay Off %X -> %X", master + 1, original, ShmPsuGrouping->ParallelRelayConfig.CtrlValue);
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllParallelRelayOff && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllParallelRelayConfirmed)
    {
        // wait until parallel relay off confirmed
        bool confirmed = IsParallelRelayConfirmed(master);
        time = GetTimeoutValue(_StopCharging_time[master]) / uSEC_VAL;

        //if(confirmed || time >= WAIT_RELAY_CONFIRMED_TIME)
        if(time >= WAIT_RELAY_CONFIRMED_TIME)
        {
            GetClockTime(&_StopCharging_time[master]);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllParallelRelayConfirmed = true;
            LOG_INFO("Gun %d All Member Parallel Relay Confirmed %s", master + 1, confirmed ? "OK" : "Timeout");
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllParallelRelayConfirmed && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllMemberStopCompleted)
    {
        // remove all non group member
        RemoveNonGroupMember(master);
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllMemberStopCompleted = true;
        LOG_INFO("Gun %d All Member Stop Completed", master + 1);
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.AllMemberStopCompleted && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingCompleted)
    {
        // check self group power down or not
        if(ShmPsuData->PsuGroup[master].GroupPresentOutputCurrent <= MAX_PSU_POWER_OFF_CURRENT)
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingCompleted = true;
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingCompleted)
    {
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.MasterCtrlValue = 0;
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.StopChargingCtrlValue = 0;
        SetPsuGroupToIdle(master);
        LOG_INFO("Gun %d Stop Charging Completed", master + 1);
    }
}

void SlaveStopChargingProcess(unsigned char slave)
{
    int time = 0;
    unsigned char master = 0;
    PsuGroupPartner PowerOffMember, GrabMember;

    if(ShmPsuGrouping->GroupCollection[slave].Role != _GROLE_SLAVE)
    {
        return;
    }

    master = ShmPsuGrouping->GroupCollection[slave].TargetGroup - 1;

    if(ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlaveChargingRequest && !ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.CheckSlaveReady)
    {
        ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.CheckSlaveReady = true;

        if(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_REQUEST_TO_CHARGING)
        {
            LOG_INFO("Gun %d Need Wait Gun %d Grouping Completed", slave + 1, master + 1);
            GetClockTime(&_CheckSlaveReady_time[slave]);
        }
        else if(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER && ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed)
        {
            LOG_INFO("Gun %d Need Wait Gun %d Derating Completed", slave + 1, master + 1);
            GetClockTime(&_CheckSlaveReady_time[slave]);
        }
        else
        {
            ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlavePowerOffRequest = true;
            ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.WaitSlaveReady = true;
            LOG_INFO("Gun %d Need Power Off And Request To Charging", slave + 1);
        }
    }

    // wait until master derating completed
    if(ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.CheckSlaveReady && !ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.WaitSlaveReady)
    {
        time = GetTimeoutValue(_CheckSlaveReady_time[slave]) / uSEC_VAL;
        if(time >= WAIT_SLAVE_READY_TIME ||
            (ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed))
        {
            if(!(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.DeratingConfirmed))
            {
                ShmPsuGrouping->GroupCollection[slave].GroupCtrl.RoleCtrl.SlaveCtrlValue = 0;
                LOG_INFO("Gun %d Wait Gun %d Timeout", slave + 1);
            }
            else
            {
                ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.WaitSlaveReady = true;
                LOG_INFO("Gun %d Is Ready", slave + 1);
                GetClockTime(&_CheckSlaveReady_time[slave]);
            }
        }
    }

    if(ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.WaitSlaveReady && !ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlavePowerOffRequest)
    {
        time = GetTimeoutValue(_CheckSlaveReady_time[slave]) / uSEC_VAL;

        if(time >= WAIT_SLAVE_DELAY)
        {
            ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlavePowerOffRequest = true;
        }
    }

    // self group, other slave or master set SlavePowerOffRequest flag to power off
    if(ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlavePowerOffRequest && !ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlavePowerOffConfirmed)
    {
        PsuGroupPowerOffCheck(slave, &PowerOffMember);

        if(PowerOffMember.Quantity == 1 && ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlaveChargingRequest)
        {
            // grab here
            if(PsuGroupGrabCheck(slave, &GrabMember))
            {
                for(int i = 0; i < GrabMember.Quantity; i++)
                {
                    PowerOffMember.Member[PowerOffMember.Quantity++] = GrabMember.Member[i];
                }
            }
        }

        if(PowerOffMember.Quantity > 0)
        {
            unsigned char target = ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlaveChargingRequest ? (slave + 1) : 0;
            PrepareToPowerOff(target, &PowerOffMember);
            if(PowerOffMember.Quantity > 1 && ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlaveChargingRequest)
            {
                ShmPsuGrouping->GroupCollection[slave].PossibleMember.Quantity = PowerOffMember.Quantity - 1;
                memcpy(ShmPsuGrouping->GroupCollection[slave].PossibleMember.Member, &PowerOffMember.Member[1], PowerOffMember.Quantity - 1);
            }
            ShmPsuGrouping->GroupCollection[slave].GroupCtrl.bits.SlavePowerOffConfirmed = true;
        }
        else
        {
            ShmPsuGrouping->GroupCollection[slave].GroupCtrl.RoleCtrl.SlaveCtrlValue = 0;
        }
    }
}

void MasterExtendCapabilityProcess(unsigned char master)
{
    int time = 0;

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.DeratingCtrlValue != 0 ||
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.StopChargingRequest)
    {
        if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.MorePowerConfirmed)
        {
            SetExtendPrechargeStop(master);
        }
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue = 0;
        return;
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.MorePowerRequest && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.MorePowerConfirmed)
    {
        GetClockTime(&_ExtendCapability_time[master]);
        memset(&ShmPsuGrouping->GroupCollection[master].PossibleMember, 0x00, sizeof(PsuGroupPartner));

        FindPsuGroupPartner(master, MAX_GROUP_QUANTITY, &ShmPsuGrouping->GroupCollection[master].PossibleMember);
        if(ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity > 0)
        {
            PrepareToExtendCapability(master + 1, &ShmPsuGrouping->GroupCollection[master].PossibleMember);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.MorePowerConfirmed = true;
        }
        else
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue = 0;
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.MorePowerConfirmed && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendPrechargeDone)
    {
        unsigned short voltage = ShmPsuData->PsuGroup[master].GroupPresentOutputVoltage >= PRECHARGE_OFFSET_VOLTAGE ?
                ShmPsuData->PsuGroup[master].GroupPresentOutputVoltage - PRECHARGE_OFFSET_VOLTAGE : 0;

        for(int i = 0; i < ShmPsuGrouping->GroupCollection[master].PossibleMember.Quantity; i++)
        {
            unsigned char slave = ShmPsuGrouping->GroupCollection[master].PossibleMember.Member[i];

            if(ShmPsuData->PsuGroup[slave].GroupPresentPsuQuantity > 0 && ShmPsuData->PsuGroup[slave].GroupAvailableCurrent > 0)
            {
                ShmPsuGrouping->GroupOutput[slave].GTargetVoltage = voltage;
                ShmPsuGrouping->GroupOutput[slave].GTargetCurrent = ZERO_CURRENT;
            }
        }
        if(!ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendPrecharge)
        {
            GetClockTime(&_ExtendCapability_time[master]);
            LOG_INFO("Gun %d Set ExtendPrecharge Voltage %d.%d V", master + 1, (voltage / 10), (voltage % 10));
        }
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendPrecharge = true;
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendPrecharge && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendPrechargeDone)
    {
        bool ready = IsExtendPrechargeReady(master);
        time = GetTimeoutValue(_ExtendCapability_time[master]) / uSEC_VAL;

        if(ready || time >= WAIT_PRECHARGE_TIME)
        {
            LOG_INFO("Gun %d ExtendPrecharge %s", master + 1, ready ? "Ready" : "Timeout");
            if(ready)
            {
                GetClockTime(&_ExtendCapability_time[master]);
                SetExtendPrechargeCompleted(master);
                ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendPrechargeDone = true;
            }
            else
            {
                SetExtendPrechargeStop(master);
                ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue = 0;
            }
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendPrechargeDone && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendRelayOn)
    {
        // after a little delay, set extend parallel relay on
        time = GetTimeoutValue(_ExtendCapability_time[master]) / uSEC_VAL;

        if(time >= WAIT_PARALLEL_RELAY_DELAY)
        {
            unsigned short original = ShmPsuGrouping->ParallelRelayConfig.CtrlValue;
            GetClockTime(&_ExtendCapability_time[master]);
            SetParallelRelayOnOff(master, YES, _GROLE_PRECHARGE_READY);
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendRelayOn = true;
            LOG_INFO("Gun %d Set Extend Parallel Relay On %X -> %X", master + 1, original, ShmPsuGrouping->ParallelRelayConfig.CtrlValue);
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendRelayOn && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendRelayConfirmed)
    {
        // wait until extend parallel relay on confirmed
        bool confirmed = IsParallelRelayConfirmed(master);
        time = GetTimeoutValue(_ExtendCapability_time[master]) / uSEC_VAL;

        //if(confirmed || time >= WAIT_RELAY_CONFIRMED_TIME)
        if(time >= WAIT_RELAY_CONFIRMED_TIME)
        {
            ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendRelayConfirmed = true;
            LOG_INFO("Gun %d Extend Parallel Relay Confirmed %s", master + 1, confirmed ? "OK" : "Timeout");
        }
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendRelayConfirmed && !ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendCompleted)
    {
        AddExtendMember(master);
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendCompleted = true;
    }

    if(ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ExtendCompleted)
    {
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue = 0;
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.OutputCurrentStable = false;
        GetClockTime(&_ReachCurrent_time[master]);
        UpdateGunAvailableCapability(master);
        ShmPsuGrouping->GroupCollection[master].GroupCtrl.bits.ReachMaxStageCurrent = false;
        GetClockTime(&_StageCurrent_time[master]);
        LOG_INFO("Gun %d Extend Capability Completed", master + 1);
    }
}

void ShowGunVoltageCurrent(unsigned char master)
{
    unsigned short voltage = 0, current = 0, fireVoltage = 0;
    unsigned short diffVoltage = 0, diffCurrent = 0;

    if(ShmPsuGrouping->GroupCollection[master].Role == _GROLE_MASTER)
    {
        voltage = (int)(chargingInfo[master]->PresentChargingVoltage * 10);
        current = (int)(chargingInfo[master]->PresentChargingCurrent * 10);
        fireVoltage = chargingInfo[master]->FireChargingVoltage;

        diffVoltage = voltage >= evseOutVol[master] ? voltage - evseOutVol[master] : evseOutVol[master] - voltage;
        diffCurrent = current >= evseOutCur[master] ? current - evseOutCur[master] : evseOutCur[master] - current;

        if(diffVoltage >= 10 || diffCurrent >= 10)
        {
            evseOutputDelay[master] = SHOW_OUTPUT_DELAY;
            evseOutVol[master] = voltage;
            evseOutCur[master] = current;
        }

        if(evseOutputDelay[master] != 0)
        {
            LOG_INFO("Gun %d Need Voltage: %4d V, Current: %3d A, Output Voltage: %4d.%d V, Current: %3d.%d A, Fire Voltage: %4d V",
                master + 1, (int)chargingInfo[master]->EvBatterytargetVoltage, (int)chargingInfo[master]->EvBatterytargetCurrent,
                (voltage / 10), (voltage % 10), (current / 10), (current % 10), fireVoltage / 10);
            evseOutputDelay[master]--;
        }
    }
}

void PsuGroupControlProcess(void)
{
    int time = 0;
    unsigned char role = 0;
    //unsigned short TargetVoltage = 0, TargetCurrent = 0;

    for(byte group = 0; group < GENERAL_GUN_QUANTITY; group++)
    {
        role = ShmPsuGrouping->GroupCollection[group].Role;
        switch(role)
        {
            case _GROLE_IDLE:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.IdleCtrlValue = 0;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.MasterCtrlValue = 0;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.StopChargingCtrlValue = 0;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.DeratingCtrlValue = 0;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue = 0;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.SlaveCtrlValue = 0;
                    PSU_LOG("===== PSU Group[%02X] ===== Idle", group);
                    GetClockTime(&_PsuGroupRole_time[group]);

                    ShmPsuGrouping->GroupOutput[group].GTargetVoltage = 0;
                    ShmPsuGrouping->GroupOutput[group].GTargetCurrent = 0;
                    PreGroupOutput[group].GTargetVoltage = 0;
                    PreGroupOutput[group].GTargetCurrent = 0;
                    SetPsuGroupOutput(group);
                }

                if(isStartOutputSwitch[group])
                {
                    SetPsuGroupPowerOnOff(group, PSU_POWER_OFF);
                }

                if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.IdleCtrlValue != 0 &&
                    ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
                {
                    CheckChargingRequest(group);
                }
                break;

            case _GROLE_MASTER:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.MasterCtrlValue = 0;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.StopChargingCtrlValue = 0;
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.DeratingCtrlValue = 0;
                    PSU_LOG("===== PSU Group[%02X] ===== Master", group);
                    GetClockTime(&_PsuGroupRole_time[group]);

                    OutputConfigStep[group] = _CURRENT_MODE_NONE;
                    _preOutputConfigStep[group] = _CURRENT_MODE_NONE;
                    MaxCurrentDemand[group] = 0;
                    StageMaxCurrent[group] = 0;
                    _GfdStep[group] = PREPARE_STEP_NONE;
                    _VoltageResumeCnt[group] = 0;
                }

                if(!ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.AlreadyInChargingMode)
                {
                    if(chargingInfo[group]->SystemStatus == S_CHARGING && chargingInfo[group]->RelayK1K2Status)
                    {
                        LOG_INFO("Gun %d Enter Charging Mode", group + 1);
                        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.AlreadyInChargingMode = true;
                        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ReachMaxCurrentDemand = false;
                        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ReachMaxStageCurrent = false;
                        GetClockTime(&_MaxCurrent_time[group]);
                        GetClockTime(&_StageCurrent_time[group]);
                        GetClockTime(&_ChargingDelay_time[group]);
                    }
                }

                if((chargingInfo[group]->SystemStatus <= S_IDLE) ||
                    (chargingInfo[group]->SystemStatus >= S_TERMINATING && chargingInfo[group]->SystemStatus <= S_FAULT))
                {
                    if(!ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.StopChargingRequest)
                    {
                        LOG_INFO("Gun %d SystemStatus(%d) Need Stop Charging", group + 1, chargingInfo[group]->SystemStatus);
                    }
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.StopChargingRequest = true;
                }

                if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue == 0)
                {
                    if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ExtendAvailable)
                    {
                        time = GetTimeoutValue(_ExtendCapability_time[group]) / uSEC_VAL;

                        if(time >= EXTEND_CAPABILITY_DELAY)
                        {
                            int available = GetPsuGroupAvailable(group);

                            if(available > 0)
                            {
                                ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.MorePowerRequest = true;
                                LOG_INFO("Gun %d Start Extend Capability", group + 1);
                            }
                            ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ExtendAvailable = false;
                        }
                    }
                }

                if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.ExtendCapabilityCtrlValue != 0)
                {
                    MasterExtendCapabilityProcess(group);
                }

                // need derating
                if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.DeratingCtrlValue != 0 &&
                    !ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.StopChargingRequest)
                {
                    PsuGroupDeratingProcess(group);
                }

                if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.StopChargingRequest)
                {
                    ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.DeratingCtrlValue = 0;
                    ShmPsuGrouping->GroupOutput[group].GTargetVoltage = 0;
                    ShmPsuGrouping->GroupOutput[group].GTargetCurrent = 0;
                    MasterStopChargingProcess(group);
                }
                else
                {
                    UpdateGunLoading(group);
                    UpdatePsuGroupOutputConfig(group);
                    UpdateMasterPsuGroupLoading(group);
                    PsuGroupOutputConfigCheck(group);
                }
                ShowGunVoltageCurrent(group);
                SetPsuGroupOutput(group);
                break;

            case _GROLE_SLAVE:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Slave", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.SlaveCtrlValue != 0)
                {
                    SlaveStopChargingProcess(group);
                }
                else
                {
                    SetPsuGroupOutput(group);
                }
                break;

            case _GROLE_PREPARE_SWITCH_OFF:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Prepare Off", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                time = GetTimeoutValue(_PsuGroupRole_time[group]) / uSEC_VAL;

                if(time >= MAX_PREPARE_SWITCH_OFF_TIME)
                {
                    // timeout shall not happen
                    SetPsuGroupRole(group, _GROLE_SLAVE_POWER_OFF);
                }
                else
                {
                    SetPsuGroupOutput(group);
                }
                break;

            case _GROLE_SLAVE_POWER_OFF:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Slave Power Off", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                time = GetTimeoutValue(_PsuGroupRole_time[group]) / uSEC_VAL;

                if(time >= MIN_POWER_OFF_TIME &&
                    ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent <= MAX_PSU_POWER_OFF_CURRENT)
                {
                    SetPsuGroupRole(group, _GROLE_SWITCH_OFF_OK);
                }
                else
                {
                    ShmPsuGrouping->GroupOutput[group].GTargetVoltage = 0;
                    ShmPsuGrouping->GroupOutput[group].GTargetCurrent = 0;
                    SetPsuGroupOutput(group);
                }
                break;

            case _GROLE_SWITCH_OFF_OK:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Switch Off OK, %d.%d V, %d.%d A", group,
                        (ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage / 10), (ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage % 10),
                        (ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent / 10), (ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent % 10));
                    GetClockTime(&_PsuGroupRole_time[group]);
                }
                break;

            case _GROLE_WAIT_IDLE:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Wait Idle, %d.%d V, %d.%d A", group,
                        (ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage / 10), (ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage % 10),
                        (ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent / 10), (ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent % 10));
                    GetClockTime(&_PsuGroupRole_time[group]);
                }
                break;

            case _GROLE_WAIT_SLAVE:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Wait Slave", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                time = GetTimeoutValue(_PsuGroupRole_time[group]) / uSEC_VAL;

                if(ShmPsuGrouping->GroupCollection[group].ReservedTarget != 0)
                {
                    if(time >= WAIT_SLAVE_TO_CHARGING && (ShmPsuGrouping->GroupCollection[group].ReservedTarget - 1) == group)
                    {
                        SetPsuGroupRole(group, _GROLE_REQUEST_TO_CHARGING);
                        ShmPsuGrouping->GroupCollection[group].GroupCtrl.bits.ChargingRequest = true;
                        ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.SlaveCtrlValue = 0;
                        break;
                    }
                }
                if(time >= WAIT_SLAVE_TIMEOUT)
                {
                    PSU_LOG("Group[%02X] Wait Slave Timeout", group);
                    SetPsuGroupToIdle(group);
                }
                break;

            case _GROLE_PREPARE_ATTACH_ON:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Prepare Attach On", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                SetPsuGroupOutput(group);
                break;

            case _GROLE_PRECHARGE_READY:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Precharge %d.%d V Ready",
                        group, (ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage / 10), (ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage % 10));
                    GetClockTime(&_PsuGroupRole_time[group]);
                }
                break;

            case _GROLE_EXTEND_STOP:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Extend Stop", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                time = GetTimeoutValue(_PsuGroupRole_time[group]) / uSEC_VAL;

                if(time >= MIN_POWER_OFF_TIME ||
                    (ShmPsuData->PsuGroup[group].GroupPresentOutputCurrent <= MAX_PSU_POWER_OFF_CURRENT &&
                    ShmPsuData->PsuGroup[group].GroupPresentOutputVoltage <= MAX_PSU_POWER_OFF_VOLTAGE))
                {
                    SetPsuGroupToIdle(group);
                }
                else
                {
                    ShmPsuGrouping->GroupOutput[group].GTargetVoltage = 0;
                    ShmPsuGrouping->GroupOutput[group].GTargetCurrent = 0;
                    SetPsuGroupOutput(group);
                }
                break;

            case _GROLE_REQUEST_TO_CHARGING:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Request To Charging", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                if(ShmPsuGrouping->GroupCollection[group].GroupCtrl.RoleCtrl.IdleCtrlValue != 0)
                {
                    ChargingRequestProcess(group);
                }
                break;

            case _GROLE_TERMINATE:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Terminate", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }
                break;

            case _GROLE_WAIT_TERMINATED:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Wait Terminated", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }
                break;

            case _GROLE_NONE:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== None", group);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }
                break;

            default:
                if(ShmPsuGrouping->GroupCollection[group].PreRole != role)
                {
                    ShmPsuGrouping->GroupCollection[group].PreRole = role;
                    PSU_LOG("===== PSU Group[%02X] ===== Unknown Role = %d", group, role);
                    GetClockTime(&_PsuGroupRole_time[group]);
                }

                SetPsuGroupRole(group, _GROLE_NONE);
                break;
        }
    }
}

// only for test & debug purpose
void PsuGroupSimulation(void)
{
    unsigned char master = 0;
    int _groupCurrent = 0, _groupPower = 0;

    for(int i = 0; i < ShmPsuData->GroupCount; i++)
    {
        master = ShmChargerInfo->PsuGrouping.GroupCollection[i].TargetGroup;

        // calculate output group capability
        if(master == 0)
        {
            chargingInfo[i]->AvailableChargingCurrent = ShmPsuData->PsuGroup[i].GroupAvailableCurrent;
            chargingInfo[i]->AvailableChargingPower = ShmPsuData->PsuGroup[i].GroupAvailablePower;
        }
        else
        {
            _groupCurrent = ShmPsuData->PsuGroup[master - 1].GroupAvailableCurrent;
            _groupPower = ShmPsuData->PsuGroup[master - 1].GroupAvailablePower;

            for(byte j = 0; j < ShmPsuGrouping->GroupCollection[master - 1].Partner.Quantity; j++)
            {
                byte slave = ShmPsuGrouping->GroupCollection[master - 1].Partner.Member[j];

                if(ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_SLAVE ||
                    ShmPsuGrouping->GroupCollection[slave].Role == _GROLE_PREPARE_SWITCH_OFF)
                {
                    _groupCurrent += ShmPsuData->PsuGroup[slave].GroupAvailableCurrent;
                    _groupPower += ShmPsuData->PsuGroup[slave].GroupAvailablePower;
                }
            }

            if(ShmPsuGrouping->GroupCollection[master - 1].GroupCtrl.bits.DeratingConfirmed)
            {
                chargingInfo[master - 1]->AvailableChargingCurrent = ShmPsuGrouping->GroupCollection[master - 1].ReAssignAvailableCurrent;
            }
            else
            {
                chargingInfo[master - 1]->AvailableChargingCurrent = _groupCurrent;
            }
            chargingInfo[master - 1]->AvailableChargingPower = _groupPower;

            if((master - 1) != i)
            {
                chargingInfo[i]->AvailableChargingCurrent = 0;;
                chargingInfo[i]->AvailableChargingPower = 0;
                chargingInfo[i]->RealRatingPower = 0;
                chargingInfo[i]->MaximumChargingVoltage = 0;
            }
        }
    }
}

void PsuGroupingInitial(void)
{
    for(int i = 0; i < ShmPsuData->GroupCount; i++)
    {
        ShmPsuGrouping->GroupCollection[i].Role = 0;
        ShmPsuGrouping->GroupCollection[i].PreRole = 0xFF;
        memset(&ShmPsuGrouping->GroupCollection[i].Partner, 0x00, sizeof(PsuGroupPartner));
        memset(&ShmPsuGrouping->GroupCollection[i].PossibleMember, 0x00, sizeof(PsuGroupPartner));
        ShmPsuGrouping->GroupCollection[i].TargetGroup = 0;
        ShmPsuGrouping->GroupCollection[i].ReservedTarget = 0;
        memset(&ShmPsuGrouping->GroupCollection[i].GroupCtrl, 0x00, sizeof(PsuGroupControl));
        ShmPsuGrouping->GroupCollection[i].ReAssignAvailableCurrent = 0;
        ShmPsuGrouping->GroupCollection[i].ParallelCheck = 0;
        ShmPsuGrouping->GroupCollection[i].GunLoading = 0;

        memset(&ShmPsuGrouping->GroupOutput[i], 0x00, sizeof(GroupOutputConfigInfo));
    }
}

void PsuCommLostCheck(void)
{
    if(ShmPsuData->Work_Step != INITIAL_START &&
        ShmPsuData->Work_Step != _NO_WORKING &&
        ShmPsuData->Work_Step != _INIT_PSU_STATUS)
    {
        if(!isCommStart)
        {
            isCommStart = true;
            ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt = 0;
            lostCnt = 0;
            GetClockTime(&_PsuCommCheck_time);
        }

        if((GetTimeoutValue(_PsuCommCheck_time) / uSEC_VAL) >= PSU_COMM_CHECK_TIME)
        {
            ShmChargerInfo->Control.CommInfo.PsuComm.CommCnt = ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt;
            ShmChargerInfo->Control.CommInfo.PsuComm.RxCnt = 0;

            if(ShmChargerInfo->Control.CommInfo.PsuComm.CommCnt > COMM_LOST_CRITICAL)
            {
                if(ShmChargerInfo->Control.PsuCtrl.bits.CommunicationLost)
                {
                    LOG_INFO("Psu Communication Recovery");
                }
                ShmChargerInfo->Control.PsuCtrl.bits.CommunicationLost = NO;
                lostCnt = 0;
            }
            else
            {
                lostCnt++;
                if(lostCnt >= LOST_CNT_CRITICAL)
                {
                    if(!ShmChargerInfo->Control.PsuCtrl.bits.CommunicationLost)
                    {
                        LOG_INFO("Psu Communication Lost");
                    }
                    ShmChargerInfo->Control.PsuCtrl.bits.CommunicationLost = YES;
                }
            }
            GetClockTime(&_PsuCommCheck_time);
        }
    }
    else
    {
        isCommStart = false;
        ShmChargerInfo->Control.PsuCtrl.bits.CommunicationLost = NO;
        lostCnt = 0;
        /*
        if(ShmChargerInfo->Control.PsuCtrl.bits.Paused)
        {
            system("ifconfig can1 down");
            sleep(1);
            system("ifconfig can1 up");
            sleep(1);
            LOG_INFO("Psu Can Port Down & Up");
        }
        ShmChargerInfo->Control.PsuCtrl.bits.Paused = NO;
        */
    }
}

int main(void)
{
    byte _TotalModuleCount = 0;
    byte _PrePsuWorkStep = 0;
    byte isInitialComp = NO;

	if(InitShareMemory() == FAIL)
	{
		#ifdef SystemLogMessage
		LOG_ERROR("InitShareMemory NG");
		#endif
		if(ShmStatusCodeData != NULL)
		{
			ShmStatusCodeData->AlarmCode.AlarmEvents.bits.FailToCreateShareMemory = 1;
		}
		sleep(5);
		return 0;
	}

	LOG_INFO("InitShareMemory OK");

	signal(SIGCHLD, SIG_IGN);

	// register callback function
	RefreshStatus(&GetStatusCallback);
	RefreshModuleCount(&GetModuleCountCallback);
	RefreshAvailableCap(&GetAvailableCapCallback);
	RefreshFwVersion(&GetFwCallback);
	RefreshInputVol(&GetInputVoltageCallback);
	RefreshGetOutput(&GetPresentOutputCallback);
	RefreshMisInfo(&GetMisCallback);
	RefreshIavailable(&GetIavailableCallback);
	RefreshGetOutputF(&GetPresentOutputFCallback);

	// GetPresentOutputCallback & GetStatusCallback
	AutoMode_RefreshOutputAndTemp(&GetOutputAndTempCallback);
	// GetStatusCallback
	AutoMode_RefreshModuleStatus(&GetModuleStatusCallback);
	// GetInputVoltageCallback
	AutoMode_RefreshModuleInput(&GetModuleInputCallback);

	sleep(2);
	_gunCount = GENERAL_GUN_QUANTITY;
	_maxGroupCount = GENERAL_GUN_QUANTITY;
	_PrePsuWorkStep = _INIT_PSU_STATUS;
	ShmPsuData->Work_Step = INITIAL_START;
	isInitialComp = NO;
	isCommStart = false;

	LOG_INFO("PSU Work Status = %d", ShmPsuData->Work_Step);

	// initial object
	Initialization();
	InitialPsuData();
	libInitialize = InitialCommunication();

	//main loop
	while (libInitialize)
	{
	    PsuCommLostCheck();

		// 斷電狀態
        if (ShmChargerInfo->Control.RelayCtrl.bits.AcContactor == NO ||
            ShmChargerInfo->Control.RelayCtrl.bits.AcContactorOffByPsu == YES ||
            ShmChargerInfo->Control.RelayCtrl.bits.AcContactorOffByEmergency == YES ||
            ShmChargerInfo->Control.PsuCtrl.bits.NeedSelfTest == YES ||
            ShmChargerInfo->Control.PsuCtrl.bits.Paused == YES)
		{
			//一但 AC Off PSU 斷電全部的 PSU Group ID 會全部清 0
			if (!isInitialComp)
			{
			    _PrePsuWorkStep = _INIT_PSU_STATUS;
				ShmPsuData->Work_Step = INITIAL_START;
				psuCmdSeq = _PSU_CMD_STATUS;
				_TotalModuleCount = 0;
				PsuGroupingInitial();

				sleep(2);
				InitialPsuData();
				isInitialComp = YES;
			}
			if(ShmChargerInfo->Control.PsuCtrl.bits.NeedSelfTest)
			{
			    ShmChargerInfo->Control.PsuCtrl.bits.NeedSelfTest = NO;
			    ShmChargerInfo->Control.PsuCtrl.bits.SelfTestOK = NO;
			    memset(ShmChargerInfo->Control.PsuInitQuantity, 0x00, sizeof(ShmChargerInfo->Control.PsuInitQuantity));
			    LOG_INFO("Psu Need Execute Self Test!");
			}
			sleep(1);
			continue;
		}
		else
		{
            if(isInitialComp)
            {
                system("ifconfig can1 down");
                sleep(1);
                system("ifconfig can1 up");
                LOG_INFO("Psu Can Port Down & Up");
            }
            isInitialComp = NO;
		}

        // only for test & debug purpose
        if(ShmChargerInfo->Control.TestCtrl.bits.ChargingSimulation)
        {
            ShmPsuData->Work_Step = _WORK_CHARGING;

            ShmPsuData->PsuGroup[0].GroupPresentPsuQuantity = 3;
            ShmPsuData->PsuGroup[1].GroupPresentPsuQuantity = 3;
            ShmPsuData->PsuGroup[2].GroupPresentPsuQuantity = 3;
            ShmPsuData->PsuGroup[3].GroupPresentPsuQuantity = 3;

            ShmPsuData->PsuGroup[0].GroupAvailableCurrent = ShmPsuData->PsuGroup[0].GroupPresentPsuQuantity *  1000;
            ShmPsuData->PsuGroup[1].GroupAvailableCurrent = ShmPsuData->PsuGroup[1].GroupPresentPsuQuantity *  1000;
            ShmPsuData->PsuGroup[2].GroupAvailableCurrent = ShmPsuData->PsuGroup[2].GroupPresentPsuQuantity *  1000;
            ShmPsuData->PsuGroup[3].GroupAvailableCurrent = ShmPsuData->PsuGroup[3].GroupPresentPsuQuantity *  1000;

            ShmPsuData->PsuGroup[0].GroupAvailablePower = ShmPsuData->PsuGroup[0].GroupPresentPsuQuantity * 300;
            ShmPsuData->PsuGroup[1].GroupAvailablePower = ShmPsuData->PsuGroup[1].GroupPresentPsuQuantity * 300;
            ShmPsuData->PsuGroup[2].GroupAvailablePower = ShmPsuData->PsuGroup[2].GroupPresentPsuQuantity * 300;
            ShmPsuData->PsuGroup[3].GroupAvailablePower = ShmPsuData->PsuGroup[3].GroupPresentPsuQuantity * 300;

            PsuGroupSimulation();
        }

		// 自檢失敗
		if (ShmPsuData->Work_Step == _NO_WORKING)
		{
			LOG_INFO("== PSU == self test fail.");
			sleep(5);
		}

		if((GetTimeoutValue(_PsuReceiveRecoveryCheck_time) / uSEC_VAL) >= PSU_TASK_CHECK_TIME)
		{
		    PsuReceiveRecoveryCheck();
		    GetClockTime(&_PsuReceiveRecoveryCheck_time);
		}

		switch(ShmPsuData->Work_Step)
		{
			case INITIAL_START:
			{
			    if(_PrePsuWorkStep != ShmPsuData->Work_Step)
			    {
			        _PrePsuWorkStep = ShmPsuData->Work_Step;
			        LOG_INFO("== PSU == INITIAL_START");
			    }

				sleep(5);
				SwitchPower(SYSTEM_CMD, PSU_POWER_OFF);
				SetWalkInConfig(SYSTEM_CMD, NO, 0);
				for (byte index = 0; index < ShmPsuData->GroupCount; index++)
				{
					isStartOutputSwitch[index] = false;
				}
				ShmPsuData->Work_Step = GET_PSU_COUNT;
			}
				break;
            case GET_PSU_COUNT:
            {
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == GET_PSU_COUNT");
                    GetClockTime(&_PsuWorkStep_time);
                    GetClockTime(&_cmdSubPriority_time);
                }

				int time = GetTimeoutValue(_PsuWorkStep_time) / uSEC_VAL;
				int interval = GetTimeoutValue(_cmdSubPriority_time) / uSEC_VAL;

				if(interval > GET_PSU_COUNT_INTERVAL)
				{
				    _TotalModuleCount = 0;

				    for (byte index = 0; index < ShmPsuData->GroupCount; index++)
				    {
				        // 總和各群模組數量
				        _TotalModuleCount += ShmPsuData->PsuGroup[index].GroupPresentPsuQuantity;
				    }

					LOG_INFO("== PSU == indexCount = %d, moduleCount = %d, sysCount = %d",
                        ShmPsuData->GroupCount, _TotalModuleCount, ShmPsuData->SystemPresentPsuQuantity);

					// 判斷系統數量與各群數量一致
					if(_TotalModuleCount == ShmPsuData->SystemPresentPsuQuantity && _TotalModuleCount > 0 &&
					        time > GET_PSU_COUNT_TIME)
					{
						LOG_INFO("Psu Count = %d", _TotalModuleCount);
                        ShmPsuData->Work_Step = Get_PSU_LOCATION;
					}
					else
					{
	                    // 發送取得目前全部模組數量
	                    GetModuleCount(SYSTEM_CMD);

	                    for (byte index = 0; index < ShmPsuData->GroupCount; index++)
	                    {
	                        // 取各群模組數量
	                        GetModuleCount(index);
	                    }
					}
					GetClockTime(&_cmdSubPriority_time);
				}
			}
				break;
            case Get_PSU_LOCATION:
            {
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == Get_PSU_LOCATION");

                    // clean psu location info
                    memset(ShmPsuPosition, 0x00, sizeof(PsuPositionInfoData));

                    GetClockTime(&_cmdSubPriority_time);
                }

                int interval = GetTimeoutValue(_cmdSubPriority_time) / mSEC_VAL;

                if(interval > GET_PSU_LOCATION_INTERVAL)
                {
                    for(byte index = 0; index < ShmPsuData->GroupCount; index++)
                    {
                        if(ShmPsuData->PsuGroup[index].GroupPresentPsuQuantity > 0)
                        {
                            // 取得狀態 : 支援模塊不須按照順序功能
                            GetStatus(index);
                        }
                    }
                }

                if(ShmPsuPosition->PsuLocationInit)
                {
#if 1
                    for(int i = 0; i < _maxGroupCount; i++)
                    {
                        if(ShmPsuPosition->GroupLocationInfo[i].GroupPsuQuantity > 0)
                        {
                            ShowGroupMember(i);
                        }
                    }
#endif

                    //ShmPsuData->Work_Step = Get_PSU_VERSION;
                    ShmPsuData->Work_Step = PSU_COUNT_CONFIRM;
                }
                if(ShmPsuPosition->ReInitPsuLocation)
                {
                    LOG_INFO("Retry Psu Location Initialization");
                    ShmPsuData->Work_Step = GET_PSU_COUNT;
                }
            }
				break;
            case PSU_COUNT_CONFIRM:
            {
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == PSU_COUNT_CONFIRM");

                    GetClockTime(&_PsuWorkStep_time);
                    GetClockTime(&_cmdSubPriority_time);
                }

                int time = GetTimeoutValue(_PsuWorkStep_time) / uSEC_VAL;
                int interval = GetTimeoutValue(_cmdSubPriority_time) / mSEC_VAL;

                if(interval > PSU_COUNT_CONFIRM_INTERVAL)
                {
                    _TotalModuleCount = 0;

                    for (byte index = 0; index < _maxGroupCount; index++)
                    {
                        // 總和各群模組數量
                        _TotalModuleCount += ShmPsuData->PsuGroup[index].GroupPresentPsuQuantity;
                    }

                    if(time > PSU_COUNT_CONFIRM_TIME)
                    {
                        if(_TotalModuleCount == ShmPsuData->SystemPresentPsuQuantity && _TotalModuleCount > 0)
                        {
                            ShmPsuData->Work_Step = Get_PSU_VERSION;
                        }
                        else
                        {
                            LOG_INFO("Total PSU = %d, System PSU Quantity = %d", _TotalModuleCount, ShmPsuData->SystemPresentPsuQuantity);
                            LOG_INFO("PSU Quantity Confirm Fail");

                            ShmPsuData->Work_Step = GET_PSU_COUNT;
                        }
                        break;
                    }

                    // 發送取得目前全部模組數量
                    GetModuleCount(SYSTEM_CMD);

                    for (byte index = 0; index < ShmPsuData->GroupCount; index++)
                    {
                        // 取各群模組數量
                        GetModuleCount(index);
                    }
                    GetClockTime(&_cmdSubPriority_time);
                }
            }
                break;
            case Get_PSU_VERSION:
            {
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == Get_PSU_VERSION");
                    GetClockTime(&_cmdSubPriority_time);

                    // clean version info
                    memset(ShmPsuData->PsuVersion, 0x00, sizeof(ShmPsuData->PsuVersion));
                }

                int interval = GetTimeoutValue(_cmdSubPriority_time) / mSEC_VAL;

                if (interval > GET_PSU_VERSION_INTERVAL)
                {
                    bool isGetVersion = true;
                    for(byte psu = 0; psu < ShmPsuData->SystemPresentPsuQuantity; psu++)
                    {
                        if (strcmp((char *)ShmPsuData->PsuVersion[psu].FwPrimaryVersion, "") == EQUAL)
                        {
                            isGetVersion = false;
                            break;
                        }
                    }

                    if(isGetVersion)
                    {
#if 1
                        for(int i = 0; i < _maxGroupCount; i++)
                        {
                            if(ShmPsuPosition->GroupLocationInfo[i].GroupPsuQuantity > 0)
                            {
                                ShowPsuVersion(i);
                            }
                        }
#endif
                        ShmPsuData->Work_Step = GET_SYS_CAP;
                    }
                    else
                    {
                        for(byte group = 0; group < _maxGroupCount; group++)
                        {
                            if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
                            {
                                // 取版號
                                GetModuleVer(group);
                            }
                        }
                    }
                    GetClockTime(&_cmdSubPriority_time);
                }
            }
                break;
			case GET_SYS_CAP:
			{
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == GET_SYS_CAP");
                    GetClockTime(&_PsuWorkStep_time);
                    GetClockTime(&_cmdSubPriority_time);
                }

                int time = GetTimeoutValue(_PsuWorkStep_time) / uSEC_VAL;
				int interval = GetTimeoutValue(_cmdSubPriority_time) / mSEC_VAL;

				if (interval > GET_PSU_CAP_INTERVAL)
				{
				    if(time > GET_PSU_CAP_TIME && ShmPsuData->SystemAvailablePower > 0 &&
                        ShmPsuData->SystemAvailableCurrent > 0)
				    {
#if 1
                        for(int i = 0; i < _maxGroupCount; i++)
                        {
                            if(ShmPsuPosition->GroupLocationInfo[i].GroupPsuQuantity > 0)
                            {
                                ShowGroupAvailableCurrentPower(i);
                            }
                        }
#endif
                        // 判斷系統輸出額定功率與電流
                        LOG_INFO("SystemAvailableCurrent = %d, SystemAvailablePower = %d",
                            ShmPsuData->SystemAvailableCurrent, ShmPsuData->SystemAvailablePower);

                        ShmPsuData->Work_Step = BOOTING_COMPLETE;
				    }
				    else
				    {
                        for(byte index = 0; index < ShmPsuData->GroupCount; index++)
                        {
                            if(ShmPsuData->PsuGroup[index].GroupPresentPsuQuantity > 0)
                            {
                                // 取系統總輸出能力
                                GetModuleCap(index);
                            }
                        }
				    }
				    GetClockTime(&_cmdSubPriority_time);
				}
			}
				break;
			case BOOTING_COMPLETE:
			{
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == BOOTING_COMPLETE");
                }

				if(ShmChargerInfo->Control.PsuCtrl.bits.SelfTestOK)
				{
					ShmPsuData->Work_Step = _WORK_CHARGING;
				}
				else
				{
				    LOG_INFO("== PSU == Self Test OK!");
                    for(int i = 0; i < MAX_GROUP_QUANTITY; i++)
                    {
                        ShmChargerInfo->Control.PsuInitQuantity[i] = ShmPsuData->PsuGroup[i].GroupPresentPsuQuantity;
                    }
				}
				ShmChargerInfo->Control.PsuCtrl.bits.SelfTestOK = true;
				sleep(1);
			}
				break;
			case _WORK_CHARGING:
			{
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == _WORK_CHARGING");
                    GetClockTime(&_PsuWorkStep_time);
                    GetClockTime(&_cmdSubPriority_time);

                    PsuGroupingInitial();
                }

				int time = GetTimeoutValue(_cmdSubPriority_time) / 1000;

				// 低 Priority 的指令
				if (time > 1000)
				{
					//PreCheckSmartChargingStep();
					startModuleFlag = true;

				    for(byte group = 0; group < GENERAL_GUN_QUANTITY; group++)
				    {
				        if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
				        {
				            GetStatus(group);
				        }
				    }
                    for(byte group = 0; group < GENERAL_GUN_QUANTITY; group++)
                    {
                        if(ShmPsuData->PsuGroup[group].GroupPresentPsuQuantity > 0)
                        {
                            GetModuleInput(group);
                        }
                    }
                    GetClockTime(&_cmdSubPriority_time);
				}

				PsuGroupRoutineQuery();

				PsuGroupControlProcess();
				break;
			}
			case _TEST_MODE:
			{
				// 在測試模式中,保持與模塊的通訊
				int time = GetTimeoutValue(_cmdSubPriority_time) / 1000;

				if (time > 1500)
				{
					for (byte index = 0; index < ShmPsuData->GroupCount; index++)
					{
						// 取系統總輸出能力
						GetModuleCap(index); Await();

						// 取各群輸出電壓電流 (float)
						GetModuleOutputF(index); Await();
					}

					GetClockTime(&_cmdSubPriority_time);
				}

				byte _switch = 0x00;
				if ((chargingInfo[0]->EvBatterytargetVoltage * 10) > 0 && (chargingInfo[0]->EvBatterytargetCurrent * 10) > 0)
					_switch = 0x01;

				for (byte _groupCount_1 = 0; _groupCount_1 < conn_1_count; _groupCount_1++)
				{
					SetDirModulePresentOutput(connector_1[_groupCount_1],
						(chargingInfo[0]->EvBatterytargetVoltage * 10),
						(chargingInfo[0]->EvBatterytargetCurrent * 10),
						_switch, _switch); Await();
				}

				for (byte _groupCount_2 = 0; _groupCount_2 < conn_2_count; _groupCount_2++)
				{
					SetDirModulePresentOutput(connector_2[_groupCount_2],
						(chargingInfo[0]->EvBatterytargetVoltage * 10),
						(chargingInfo[0]->EvBatterytargetCurrent * 10),
						_switch, _switch); Await();
				}
			}
				break;
			case _INIT_PSU_STATUS:
                if(_PrePsuWorkStep != ShmPsuData->Work_Step)
                {
                    _PrePsuWorkStep = ShmPsuData->Work_Step;
                    LOG_INFO("== PSU == _INIT_PSU_STATUS");
                    GetClockTime(&_cmdSubPriority_time);
                }
			    break;
			default:
			    break;
		}
		usleep(20000);
	}
	return FAIL;
}