X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStationUtils.ts;h=628b7eb9c03510511c6d48736726186daf028534;hb=ec4a242aa5f1a9d4201d0ec9988f9dd931978589;hp=8d595b22acae2be8e7d0a4fe0f19edd243f884ad;hpb=252a7d22e33f6c4a9bfcc83cb4fb941f76d15dab;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index 8d595b22..628b7eb9 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -9,14 +9,12 @@ import { addSeconds, addWeeks, differenceInDays, + differenceInSeconds, differenceInWeeks, - endOfDay, - endOfWeek, isAfter, isBefore, isWithinInterval, - startOfDay, - startOfWeek, + toDate, } from 'date-fns'; import type { ChargingStation } from './ChargingStation'; @@ -51,12 +49,14 @@ import { cloneObject, convertToDate, convertToInt, + isArraySorted, isEmptyObject, isEmptyString, isNotEmptyArray, isNotEmptyString, isNullOrUndefined, isUndefined, + isValidDate, logger, secureRandom, } from '../utils'; @@ -475,7 +475,7 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( chargingStation.getConnectorStatus(connectorId)!.chargingProfiles!, )?.sort((a, b) => b.stackLevel - a.stackLevel) ?? []; // Get charging profiles on connector 0 and sort by stack level - if (chargingStation.getConnectorStatus(0)?.chargingProfiles) { + if (isNotEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)) { chargingProfiles.push( ...cloneObject( chargingStation.getConnectorStatus(0)!.chargingProfiles!, @@ -515,7 +515,7 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( chargingStation.getMaximumPower() / chargingStation.powerDivider; if (limit! > connectorMaximumPower) { logger.error( - `${chargingStation.logPrefix()} Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`, + `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`, result, ); limit = connectorMaximumPower; @@ -672,8 +672,10 @@ interface ChargingProfilesLimit { } /** - * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority) + * Charging profiles shall already be sorted by connector id and stack level (highest stack level has priority) * + * @param chargingStation - + * @param connectorId - * @param chargingProfiles - * @param logPrefix - * @returns ChargingProfilesLimit @@ -686,14 +688,12 @@ 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); for (const chargingProfile of chargingProfiles) { if ( - chargingProfile.validFrom && - chargingProfile.validTo && - !isWithinInterval(currentDate, { - start: chargingProfile.validFrom, - end: chargingProfile.validTo, - }) + (isValidDate(chargingProfile.validFrom) && + isBefore(currentDate, chargingProfile.validFrom!)) || + (isValidDate(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo!)) ) { logger.debug( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${ @@ -703,19 +703,18 @@ const getLimitFromChargingProfiles = ( continue; } const chargingSchedule = chargingProfile.chargingSchedule; - if (!chargingSchedule?.startSchedule) { + if (connectorStatus?.transactionStarted && !chargingSchedule?.startSchedule) { logger.debug( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}. Trying to set it to the connector transaction start date`, + `${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 = - chargingStation.getConnectorStatus(connectorId)?.transactionStart; + chargingSchedule.startSchedule = connectorStatus?.transactionStart; } if (!(chargingSchedule?.startSchedule instanceof Date)) { logger.warn( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`, + `${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)!; + chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!; } if ( chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING && @@ -726,64 +725,59 @@ const getLimitFromChargingProfiles = ( ); continue; } - // Adjust recurring start schedule if (chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING) { - switch (chargingProfile.recurrencyKind) { - case RecurrencyKindType.DAILY: - if (isBefore(chargingSchedule.startSchedule, startOfDay(currentDate))) { - addDays( - chargingSchedule.startSchedule, - differenceInDays(chargingSchedule.startSchedule, endOfDay(currentDate)), - ); - if ( - isBefore(chargingSchedule.startSchedule, startOfDay(currentDate)) || - isAfter(chargingSchedule.startSchedule, endOfDay(currentDate)) - ) { - logger.error( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring ${ - chargingProfile.recurrencyKind - } charging profile id ${ - chargingProfile.chargingProfileId - } startSchedule ${chargingSchedule.startSchedule.toISOString()} is not properly translated to the current day`, - ); - } - } - break; - case RecurrencyKindType.WEEKLY: - if (isBefore(chargingSchedule.startSchedule, startOfWeek(currentDate))) { - addWeeks( - chargingSchedule.startSchedule, - differenceInWeeks(chargingSchedule.startSchedule, endOfWeek(currentDate)), - ); - if ( - isBefore(chargingSchedule.startSchedule, startOfWeek(currentDate)) || - isAfter(chargingSchedule.startSchedule, endOfWeek(currentDate)) - ) { - logger.error( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring ${ - chargingProfile.recurrencyKind - } charging profile id ${ - chargingProfile.chargingProfileId - } startSchedule ${chargingSchedule.startSchedule.toISOString()} is not properly translated to the current week`, - ); - } - } - break; - } - } else if (chargingProfile.chargingProfileKind === ChargingProfileKindType.RELATIVE) { - chargingSchedule.startSchedule = - chargingStation.getConnectorStatus(connectorId)?.transactionStart; + prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix); + } else if ( + chargingProfile.chargingProfileKind === ChargingProfileKindType.RELATIVE && + connectorStatus?.transactionStarted + ) { + chargingSchedule.startSchedule = connectorStatus?.transactionStart; + } + if (isNullOrUndefined(chargingSchedule?.startSchedule)) { + logger.error( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has (still) no startSchedule defined`, + ); + continue; + } + if (isNullOrUndefined(chargingSchedule?.duration)) { + logger.error( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`, + ); + continue; } // Check if the charging profile is active if ( - isAfter(addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), currentDate) + isValidDate(chargingSchedule?.startSchedule) && + isWithinInterval(currentDate, { + start: chargingSchedule.startSchedule!, + end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), + }) ) { if (isNotEmptyArray(chargingSchedule.chargingSchedulePeriod)) { - // Handling of only one period + const chargingSchedulePeriodCompareFn = ( + a: ChargingSchedulePeriod, + b: ChargingSchedulePeriod, + ) => a.startPeriod - b.startPeriod; if ( - chargingSchedule.chargingSchedulePeriod.length === 1 && - chargingSchedule.chargingSchedulePeriod[0].startPeriod === 0 + 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 + 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`, + ); + continue; + } + // Handle only one schedule period + if (chargingSchedule.chargingSchedulePeriod.length === 1) { const result: ChargingProfilesLimit = { limit: chargingSchedule.chargingSchedulePeriod[0].limit, matchingChargingProfile: chargingProfile, @@ -793,7 +787,7 @@ const getLimitFromChargingProfiles = ( } let lastButOneSchedule: ChargingSchedulePeriod | undefined; // Search for the right schedule period - for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) { + for (const [index, schedulePeriod] of chargingSchedule.chargingSchedulePeriod.entries()) { // Find the right schedule period if ( isAfter( @@ -801,7 +795,7 @@ const getLimitFromChargingProfiles = ( currentDate, ) ) { - // Found the schedule: last but one is the correct one + // Found the schedule period: last but one is the correct one const result: ChargingProfilesLimit = { limit: lastButOneSchedule!.limit, matchingChargingProfile: chargingProfile, @@ -811,12 +805,18 @@ const getLimitFromChargingProfiles = ( } // Keep it lastButOneSchedule = schedulePeriod; - // Handle the last schedule period + // Handle the last schedule period within the charging profile duration if ( - schedulePeriod.startPeriod === - chargingSchedule.chargingSchedulePeriod[ - chargingSchedule.chargingSchedulePeriod.length - 1 - ].startPeriod + index === chargingSchedule.chargingSchedulePeriod.length - 1 || + (index < chargingSchedule.chargingSchedulePeriod.length - 1 && + chargingSchedule.duration! > + differenceInSeconds( + addSeconds( + chargingSchedule.startSchedule!, + chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod, + ), + chargingSchedule.startSchedule!, + )) ) { const result: ChargingProfilesLimit = { limit: lastButOneSchedule.limit, @@ -831,6 +831,117 @@ const getLimitFromChargingProfiles = ( } }; +/** + * Adjust recurring charging profile startSchedule to the current recurrency time interval if needed + * + * @param chargingProfile - + * @param currentDate - + * @param logPrefix - + */ +const prepareRecurringChargingProfile = ( + chargingProfile: ChargingProfile, + currentDate: Date, + logPrefix: string, +): boolean => { + const chargingSchedule = chargingProfile.chargingSchedule; + let recurringIntervalTranslated = false; + let recurringInterval: Interval; + switch (chargingProfile.recurrencyKind) { + case RecurrencyKindType.DAILY: + recurringInterval = { + start: chargingSchedule.startSchedule!, + end: addDays(chargingSchedule.startSchedule!, 1), + }; + checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix); + if ( + !isWithinInterval(currentDate, recurringInterval) && + isBefore(recurringInterval.end, currentDate) + ) { + chargingSchedule.startSchedule = addDays( + recurringInterval.start, + differenceInDays(currentDate, recurringInterval.start), + ); + recurringInterval = { + start: chargingSchedule.startSchedule, + end: addDays(chargingSchedule.startSchedule, 1), + }; + recurringIntervalTranslated = true; + } + break; + case RecurrencyKindType.WEEKLY: + recurringInterval = { + start: chargingSchedule.startSchedule!, + end: addWeeks(chargingSchedule.startSchedule!, 1), + }; + checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix); + if ( + !isWithinInterval(currentDate, recurringInterval) && + isBefore(recurringInterval.end, currentDate) + ) { + chargingSchedule.startSchedule = addWeeks( + recurringInterval.start, + differenceInWeeks(currentDate, recurringInterval.start), + ); + recurringInterval = { + start: chargingSchedule.startSchedule, + end: addWeeks(chargingSchedule.startSchedule, 1), + }; + recurringIntervalTranslated = true; + } + break; + default: + logger.error( + `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} recurrency kind ${chargingProfile.recurrencyKind} is not supported`, + ); + } + if (recurringIntervalTranslated && !isWithinInterval(currentDate, recurringInterval!)) { + logger.error( + `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${ + chargingProfile.recurrencyKind + } charging profile id ${chargingProfile.chargingProfileId} recurrency time interval [${toDate( + recurringInterval!.start, + ).toISOString()}, ${toDate( + recurringInterval!.end, + ).toISOString()}] has not been properly translated to current date ${currentDate.toISOString()} `, + ); + } + return recurringIntervalTranslated; +}; + +const checkRecurringChargingProfileDuration = ( + chargingProfile: ChargingProfile, + interval: Interval, + logPrefix: string, +): void => { + if (isNullOrUndefined(chargingProfile.chargingSchedule.duration)) { + logger.warn( + `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${ + chargingProfile.chargingProfileKind + } charging profile id ${ + chargingProfile.chargingProfileId + } duration is not defined, set it to the recurrency time interval duration ${differenceInSeconds( + interval.end, + interval.start, + )}`, + ); + chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start); + } else if ( + chargingProfile.chargingSchedule.duration! > differenceInSeconds(interval.end, interval.start) + ) { + logger.warn( + `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${ + chargingProfile.chargingProfileKind + } charging profile id ${chargingProfile.chargingProfileId} duration ${ + chargingProfile.chargingSchedule.duration + } is greater than the recurrency time interval duration ${differenceInSeconds( + interval.end, + interval.start, + )}`, + ); + chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start); + } +}; + const getRandomSerialNumberSuffix = (params?: { randomBytesLength?: number; upperCase?: boolean;