X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStationUtils.ts;h=0d349d22518def767afb3d535ba0fb5abbd96525;hb=d476bc1be87bb63cfd8c494ce1ed41204992806f;hp=1d1119d32bcea57b7fb19b9c54c1ea210f49f836;hpb=8d75a4031f01609a0e788a62c2835ebc0a627330;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index 1d1119d3..0d349d22 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -4,7 +4,18 @@ import { basename, dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import chalk from 'chalk'; -import { addSeconds, isAfter, isBefore } from 'date-fns'; +import { + addDays, + addSeconds, + addWeeks, + differenceInDays, + differenceInSeconds, + differenceInWeeks, + isAfter, + isBefore, + isWithinInterval, + toDate, +} from 'date-fns'; import type { ChargingStation } from './ChargingStation'; import { BaseError } from '../exception'; @@ -44,6 +55,7 @@ import { isNotEmptyString, isNullOrUndefined, isUndefined, + isValidDate, logger, secureRandom, } from '../utils'; @@ -290,9 +302,10 @@ export const resetConnectorStatus = (connectorStatus: ConnectorStatus): void => connectorStatus.idTagAuthorized = false; connectorStatus.transactionRemoteStarted = false; connectorStatus.transactionStarted = false; + delete connectorStatus?.transactionStart; + delete connectorStatus?.transactionId; delete connectorStatus?.localAuthorizeIdTag; delete connectorStatus?.authorizeIdTag; - delete connectorStatus?.transactionId; delete connectorStatus?.transactionIdTag; connectorStatus.transactionEnergyActiveImportRegisterValue = 0; delete connectorStatus?.transactionBeginMeterValue; @@ -455,12 +468,12 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( connectorId: number, ): number | undefined => { let limit: number | undefined, matchingChargingProfile: ChargingProfile | undefined; - // Get charging profiles for connector and sort by stack level + // Get charging profiles for connector id and sort by stack level const chargingProfiles = cloneObject( chargingStation.getConnectorStatus(connectorId)!.chargingProfiles!, )?.sort((a, b) => b.stackLevel - a.stackLevel) ?? []; - // Get profiles on connector 0 + // Get charging profiles on connector 0 and sort by stack level if (chargingStation.getConnectorStatus(0)?.chargingProfiles) { chargingProfiles.push( ...cloneObject( @@ -469,7 +482,12 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( ); } if (isNotEmptyArray(chargingProfiles)) { - const result = getLimitFromChargingProfiles(chargingProfiles, chargingStation.logPrefix()); + const result = getLimitFromChargingProfiles( + chargingStation, + connectorId, + chargingProfiles, + chargingStation.logPrefix(), + ); if (!isNullOrUndefined(result)) { limit = result?.limit; matchingChargingProfile = result?.matchingChargingProfile; @@ -647,29 +665,50 @@ const convertDeprecatedTemplateKey = ( } }; +interface ChargingProfilesLimit { + limit: number; + matchingChargingProfile: ChargingProfile; +} + /** * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority) * * @param chargingProfiles - * @param logPrefix - - * @returns + * @returns ChargingProfilesLimit */ const getLimitFromChargingProfiles = ( + chargingStation: ChargingStation, + connectorId: number, chargingProfiles: ChargingProfile[], logPrefix: string, -): { - limit: number; - matchingChargingProfile: ChargingProfile; -} | null => { +): 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) { - // Set helpers + if ( + isValidDate(chargingProfile.validFrom) && + isValidDate(chargingProfile.validTo) && + !isWithinInterval(currentDate, { + start: chargingProfile.validFrom!, + end: chargingProfile.validTo!, + }) + ) { + logger.debug( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${ + chargingProfile.chargingProfileId + } is not valid for the current date ${currentDate.toISOString()}`, + ); + continue; + } const chargingSchedule = chargingProfile.chargingSchedule; - if (!chargingSchedule?.startSchedule) { - logger.warn( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`, + 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`, ); + // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction + chargingSchedule.startSchedule = connectorStatus?.transactionStart; } if (!(chargingSchedule?.startSchedule instanceof Date)) { logger.warn( @@ -677,80 +716,169 @@ const getLimitFromChargingProfiles = ( ); chargingSchedule.startSchedule = convertToDate(chargingSchedule.startSchedule)!; } - // Check type (recurring) and if it is already active - // Adjust the daily recurring schedule if ( chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING && - chargingProfile.recurrencyKind === RecurrencyKindType.DAILY + isNullOrUndefined(chargingProfile.recurrencyKind) ) { - if (isBefore(chargingSchedule.startSchedule, currentDate)) { - chargingSchedule.startSchedule.setFullYear( - currentDate.getFullYear(), - currentDate.getMonth(), - currentDate.getDate(), - ); - // Check if the start of the schedule must be set to yesterday - } else if (isAfter(chargingSchedule.startSchedule, currentDate)) { - chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1); - } + logger.error( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`, + ); + continue; + } + if (chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING) { + prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix); + } else if ( + chargingProfile.chargingProfileKind === ChargingProfileKindType.RELATIVE && + connectorStatus?.transactionStarted + ) { + chargingSchedule.startSchedule = connectorStatus?.transactionStart; } - // if (isAfter(chargingSchedule.startSchedule, currentDate)) { - // return null; - // } // Check if the charging profile is active if ( - isAfter(addSeconds(chargingSchedule.startSchedule, chargingSchedule.duration!), currentDate) + isValidDate(chargingSchedule.startSchedule) && + isAfter(addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), currentDate) ) { - let lastButOneSchedule: ChargingSchedulePeriod | undefined; - // Search the right schedule period - for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) { - // Handling of only one period + if (isNotEmptyArray(chargingSchedule.chargingSchedulePeriod)) { + // Handling of only one schedule period if ( chargingSchedule.chargingSchedulePeriod.length === 1 && - schedulePeriod.startPeriod === 0 - ) { - const result = { - limit: schedulePeriod.limit, - matchingChargingProfile: chargingProfile, - }; - logger.debug(debugLogMsg, result); - return result; - } - // Find the right schedule period - if ( - isAfter( - addSeconds(chargingSchedule.startSchedule, schedulePeriod.startPeriod), - currentDate, - ) + chargingSchedule.chargingSchedulePeriod[0].startPeriod === 0 ) { - // Found the schedule: last but one is the correct one - const result = { - limit: lastButOneSchedule!.limit, + const result: ChargingProfilesLimit = { + limit: chargingSchedule.chargingSchedulePeriod[0].limit, matchingChargingProfile: chargingProfile, }; logger.debug(debugLogMsg, result); return result; } - // Keep it - lastButOneSchedule = schedulePeriod; - // Handle the last schedule period - if ( - schedulePeriod.startPeriod === - chargingSchedule.chargingSchedulePeriod[ - chargingSchedule.chargingSchedulePeriod.length - 1 - ].startPeriod - ) { - const result = { - limit: lastButOneSchedule.limit, - matchingChargingProfile: chargingProfile, - }; - logger.debug(debugLogMsg, result); - return result; + let lastButOneSchedule: ChargingSchedulePeriod | undefined; + // Search for the right schedule period + for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) { + // Find the right schedule period + if ( + isAfter( + addSeconds(chargingSchedule.startSchedule!, schedulePeriod.startPeriod), + currentDate, + ) + ) { + // Found the schedule period: last but one is the correct one + const result: ChargingProfilesLimit = { + limit: lastButOneSchedule!.limit, + matchingChargingProfile: chargingProfile, + }; + logger.debug(debugLogMsg, result); + return result; + } + // Keep it + lastButOneSchedule = schedulePeriod; + // Handle the last schedule period + if ( + schedulePeriod.startPeriod === + chargingSchedule.chargingSchedulePeriod[ + chargingSchedule.chargingSchedulePeriod.length - 1 + ].startPeriod + ) { + const result: ChargingProfilesLimit = { + limit: lastButOneSchedule.limit, + matchingChargingProfile: chargingProfile, + }; + logger.debug(debugLogMsg, result); + return result; + } } } } } - return null; +}; + +/** + * 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, +) => { + const chargingSchedule = chargingProfile.chargingSchedule; + 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(chargingSchedule.startSchedule!, currentDate) + ) { + chargingSchedule.startSchedule = addDays( + chargingSchedule.startSchedule!, + differenceInDays(chargingSchedule.startSchedule!, recurringInterval.end), + ); + recurringInterval = { + start: chargingSchedule.startSchedule, + end: addDays(chargingSchedule.startSchedule, 1), + }; + } + break; + case RecurrencyKindType.WEEKLY: + recurringInterval = { + start: chargingSchedule.startSchedule!, + end: addWeeks(chargingSchedule.startSchedule!, 1), + }; + checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix); + if ( + !isWithinInterval(currentDate, recurringInterval) && + isBefore(chargingSchedule.startSchedule!, currentDate) + ) { + chargingSchedule.startSchedule = addWeeks( + chargingSchedule.startSchedule!, + differenceInWeeks(chargingSchedule.startSchedule!, recurringInterval.end), + ); + recurringInterval = { + start: chargingSchedule.startSchedule, + end: addWeeks(chargingSchedule.startSchedule, 1), + }; + } + break; + } + if (!isWithinInterval(currentDate, recurringInterval!)) { + logger.error( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring ${ + chargingProfile.recurrencyKind + } charging profile id ${ + chargingProfile.chargingProfileId + } startSchedule ${chargingSchedule.startSchedule!.toISOString()} is not properly translated to current recurrency time interval [${toDate( + recurringInterval!.start, + ).toISOString()}, ${toDate(recurringInterval!.end).toISOString()}]`, + ); + } +}; + +const checkRecurringChargingProfileDuration = ( + chargingProfile: ChargingProfile, + interval: Interval, + logPrefix: string, +) => { + if ( + chargingProfile.chargingSchedule.duration! > differenceInSeconds(interval.end, interval.start) + ) { + logger.warn( + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring ${ + chargingProfile.chargingProfileKind + } charging profile id ${chargingProfile.chargingProfileId} duration ${ + chargingProfile.chargingSchedule.duration + } is greater than the recurrency time interval ${differenceInSeconds( + interval.end, + interval.start, + )} duration`, + ); + } }; const getRandomSerialNumberSuffix = (params?: {