X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fcharging-station%2FHelpers.ts;h=b722926205f0d5b949ae450a92990abfe86e971b;hb=da332e702310d2a717d759040727e4e2a3f3fe87;hp=05773f0159f3f1378dd78af4cf2a46ac3395b373;hpb=ad490d5f65e55103448a5c933cf9ea1ae4f512a5;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/Helpers.ts b/src/charging-station/Helpers.ts index 05773f01..b7229262 100644 --- a/src/charging-station/Helpers.ts +++ b/src/charging-station/Helpers.ts @@ -16,6 +16,7 @@ import { isDate, isPast, isWithinInterval, + maxTime, toDate, } from 'date-fns'; @@ -514,17 +515,36 @@ export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInf return unitDivider; }; +/** + * Gets the connector cloned charging profiles applying a power limitation + * and sorted by connector id ascending then stack level descending + * + * @param chargingStation - + * @param connectorId - + * @returns connector charging profiles array + */ +export const getConnectorChargingProfiles = ( + chargingStation: ChargingStation, + connectorId: number, +) => { + return cloneObject( + (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? []) + .sort((a, b) => b.stackLevel - a.stackLevel) + .concat( + (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []).sort( + (a, b) => b.stackLevel - a.stackLevel, + ), + ), + ); +}; + export const getChargingStationConnectorChargingProfilesPowerLimit = ( chargingStation: ChargingStation, connectorId: number, ): number | undefined => { - let limit: number | undefined, matchingChargingProfile: ChargingProfile | undefined; - // Get charging profiles for connector id and sort by stack level - const chargingProfiles = cloneObject( - (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []).concat( - chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [], - ), - ).sort((a, b) => b.stackLevel - a.stackLevel); + let limit: number | undefined, chargingProfile: ChargingProfile | undefined; + // Get charging profiles sorted by connector id then stack level + const chargingProfiles = getConnectorChargingProfiles(chargingStation, connectorId); if (isNotEmptyArray(chargingProfiles)) { const result = getLimitFromChargingProfiles( chargingStation, @@ -534,12 +554,11 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( ); if (!isNullOrUndefined(result)) { limit = result?.limit; - matchingChargingProfile = result?.matchingChargingProfile; + chargingProfile = result?.chargingProfile; switch (chargingStation.getCurrentOutType()) { case CurrentType.AC: limit = - matchingChargingProfile?.chargingSchedule?.chargingRateUnit === - ChargingRateUnitType.WATT + chargingProfile?.chargingSchedule?.chargingRateUnit === ChargingRateUnitType.WATT ? limit : ACElectricUtils.powerTotal( chargingStation.getNumberOfPhases(), @@ -549,8 +568,7 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( break; case CurrentType.DC: limit = - matchingChargingProfile?.chargingSchedule?.chargingRateUnit === - ChargingRateUnitType.WATT + chargingProfile?.chargingSchedule?.chargingRateUnit === ChargingRateUnitType.WATT ? limit : DCElectricUtils.power(chargingStation.getVoltageOut(), limit!); } @@ -558,7 +576,7 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( chargingStation.getMaximumPower() / chargingStation.powerDivider; if (limit! > connectorMaximumPower) { logger.error( - `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`, + `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${chargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`, result, ); limit = connectorMaximumPower; @@ -713,11 +731,11 @@ const convertDeprecatedTemplateKey = ( interface ChargingProfilesLimit { limit: number; - matchingChargingProfile: ChargingProfile; + chargingProfile: ChargingProfile; } /** - * Charging profiles shall already be sorted by connector id and stack level (highest stack level has priority) + * Charging profiles shall already be sorted by connector id ascending then stack level descending * * @param chargingStation - * @param connectorId - @@ -733,39 +751,34 @@ const getLimitFromChargingProfiles = ( ): ChargingProfilesLimit | undefined => { const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`; const currentDate = new Date(); - const connectorStatus = chargingStation.getConnectorStatus(connectorId); - if (!isArraySorted(chargingProfiles, (a, b) => b.stackLevel - a.stackLevel)) { - logger.warn( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profiles are not sorted by stack level. Trying to sort them`, - ); - chargingProfiles.sort((a, b) => b.stackLevel - a.stackLevel); - } + const connectorStatus = chargingStation.getConnectorStatus(connectorId)!; for (const chargingProfile of chargingProfiles) { const chargingSchedule = chargingProfile.chargingSchedule; - if (connectorStatus?.transactionStarted && isNullOrUndefined(chargingSchedule?.startSchedule)) { + if (isNullOrUndefined(chargingSchedule?.startSchedule) && connectorStatus?.transactionStarted) { logger.debug( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined. Trying to set it to the connector current transaction start date`, ); // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction chargingSchedule.startSchedule = connectorStatus?.transactionStart; } + if ( + !isNullOrUndefined(chargingSchedule?.startSchedule) && + isNullOrUndefined(chargingSchedule?.duration) + ) { + logger.debug( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined and will be set to the maximum time allowed`, + ); + // OCPP specifies that if duration is not defined, it should be infinite + chargingSchedule.duration = differenceInSeconds(maxTime, chargingSchedule.startSchedule!); + } if (!isDate(chargingSchedule?.startSchedule)) { logger.warn( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date object. Trying to convert it to a Date object`, ); chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!; } - switch (chargingProfile.chargingProfileKind) { - case ChargingProfileKindType.RECURRING: - if (!canProceedRecurringChargingProfile(chargingProfile, logPrefix)) { - continue; - } - prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix); - break; - case ChargingProfileKindType.RELATIVE: - connectorStatus?.transactionStarted && - (chargingSchedule.startSchedule = connectorStatus?.transactionStart); - break; + if (!prepareChargingProfileKind(connectorStatus, chargingProfile, currentDate, logPrefix)) { + continue; } if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) { continue; @@ -784,17 +797,17 @@ const getLimitFromChargingProfiles = ( b: ChargingSchedulePeriod, ) => a.startPeriod - b.startPeriod; if ( - isArraySorted( + !isArraySorted( chargingSchedule.chargingSchedulePeriod, chargingSchedulePeriodCompareFn, - ) === false + ) ) { logger.warn( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`, ); chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn); } - // Check if the first schedule period start period is equal to 0 + // Check if the first schedule period startPeriod property is equal to 0 if (chargingSchedule.chargingSchedulePeriod[0].startPeriod !== 0) { logger.error( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0`, @@ -805,7 +818,7 @@ const getLimitFromChargingProfiles = ( if (chargingSchedule.chargingSchedulePeriod.length === 1) { const result: ChargingProfilesLimit = { limit: chargingSchedule.chargingSchedulePeriod[0].limit, - matchingChargingProfile: chargingProfile, + chargingProfile, }; logger.debug(debugLogMsg, result); return result; @@ -826,7 +839,7 @@ const getLimitFromChargingProfiles = ( // Found the schedule period: previous is the correct one const result: ChargingProfilesLimit = { limit: previousChargingSchedulePeriod!.limit, - matchingChargingProfile: chargingProfile, + chargingProfile, }; logger.debug(debugLogMsg, result); return result; @@ -837,18 +850,17 @@ const getLimitFromChargingProfiles = ( if ( index === chargingSchedule.chargingSchedulePeriod.length - 1 || (index < chargingSchedule.chargingSchedulePeriod.length - 1 && - chargingSchedule.duration! > - differenceInSeconds( - addSeconds( - chargingSchedule.startSchedule!, - chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod, - ), + differenceInSeconds( + addSeconds( chargingSchedule.startSchedule!, - )) + chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod, + ), + chargingSchedule.startSchedule!, + ) > chargingSchedule.duration!) ) { const result: ChargingProfilesLimit = { limit: previousChargingSchedulePeriod.limit, - matchingChargingProfile: chargingProfile, + chargingProfile, }; logger.debug(debugLogMsg, result); return result; @@ -859,6 +871,33 @@ const getLimitFromChargingProfiles = ( } }; +export const prepareChargingProfileKind = ( + connectorStatus: ConnectorStatus, + chargingProfile: ChargingProfile, + currentDate: Date, + logPrefix: string, +): boolean => { + switch (chargingProfile.chargingProfileKind) { + case ChargingProfileKindType.RECURRING: + if (!canProceedRecurringChargingProfile(chargingProfile, logPrefix)) { + return false; + } + prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix); + break; + case ChargingProfileKindType.RELATIVE: + if (!isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule)) { + logger.warn( + `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfile.chargingProfileId} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`, + ); + delete chargingProfile.chargingSchedule.startSchedule; + } + connectorStatus?.transactionStarted && + (chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart); + break; + } + return true; +}; + export const canProceedChargingProfile = ( chargingProfile: ChargingProfile, currentDate: Date, @@ -882,16 +921,10 @@ export const canProceedChargingProfile = ( ); return false; } - if (isNullOrUndefined(chargingSchedule?.duration)) { - logger.error( - `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`, - ); - return false; - } return true; }; -export const canProceedRecurringChargingProfile = ( +const canProceedRecurringChargingProfile = ( chargingProfile: ChargingProfile, logPrefix: string, ): boolean => { @@ -914,7 +947,7 @@ export const canProceedRecurringChargingProfile = ( * @param currentDate - * @param logPrefix - */ -export const prepareRecurringChargingProfile = ( +const prepareRecurringChargingProfile = ( chargingProfile: ChargingProfile, currentDate: Date, logPrefix: string, @@ -967,7 +1000,7 @@ export const prepareRecurringChargingProfile = ( break; default: logger.error( - `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} recurrency kind ${chargingProfile.recurrencyKind} is not supported`, + `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId} is not supported`, ); } if (recurringIntervalTranslated && !isWithinInterval(currentDate, recurringInterval!)) {