X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fcharging-station%2FHelpers.ts;h=15bd2c1f48cfb86438636f3f245c1d36912cca7c;hb=f1e3871b2a063e5ee0fddbf597df76d02f81e332;hp=7f32b4871d3e0c717af79e46d093958419b32e92;hpb=08b58f0020986c0ad1b55e562aaf325a47b9d58c;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/Helpers.ts b/src/charging-station/Helpers.ts index 7f32b487..15bd2c1f 100644 --- a/src/charging-station/Helpers.ts +++ b/src/charging-station/Helpers.ts @@ -1,10 +1,12 @@ import { createHash, randomBytes } from 'node:crypto'; import type { EventEmitter } from 'node:events'; import { basename, dirname, join } from 'node:path'; +import { env } from 'node:process'; import { fileURLToPath } from 'node:url'; import chalk from 'chalk'; import { + type Interval, addDays, addSeconds, addWeeks, @@ -14,7 +16,9 @@ import { isAfter, isBefore, isDate, + isPast, isWithinInterval, + maxTime, toDate, } from 'date-fns'; @@ -30,6 +34,7 @@ import { ChargingProfileKindType, ChargingRateUnitType, type ChargingSchedulePeriod, + type ChargingStationConfiguration, type ChargingStationInfo, type ChargingStationTemplate, ChargingStationWorkerMessageEvents, @@ -42,6 +47,8 @@ import { type OCPP20BootNotificationRequest, OCPPVersion, RecurrencyKindType, + type Reservation, + ReservationTerminationReason, StandardParametersKey, SupportedFeatureProfiles, Voltage, @@ -69,10 +76,13 @@ const moduleName = 'Helpers'; export const getChargingStationId = ( index: number, - stationTemplate: ChargingStationTemplate, + stationTemplate: ChargingStationTemplate | undefined, ): string => { + if (stationTemplate === undefined) { + return "Unknown 'chargingStationId'"; + } // In case of multiple instances: add instance index to charging station id - const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0; + const instanceIndex = env.CF_INSTANCE_INDEX ?? 0; const idSuffix = stationTemplate?.nameSuffix ?? ''; const idStr = `000000000${index.toString()}`; return stationTemplate?.fixedName @@ -82,17 +92,49 @@ export const getChargingStationId = ( )}${idSuffix}`; }; -export const countReservableConnectors = (connectors: Map) => { - let reservableConnectors = 0; +export const hasReservationExpired = (reservation: Reservation): boolean => { + return isPast(reservation.expiryDate); +}; + +export const removeExpiredReservations = async ( + chargingStation: ChargingStation, +): Promise => { + if (chargingStation.hasEvses) { + for (const evseStatus of chargingStation.evses.values()) { + for (const connectorStatus of evseStatus.connectors.values()) { + if (connectorStatus.reservation && hasReservationExpired(connectorStatus.reservation)) { + await chargingStation.removeReservation( + connectorStatus.reservation, + ReservationTerminationReason.EXPIRED, + ); + } + } + } + } else { + for (const connectorStatus of chargingStation.connectors.values()) { + if (connectorStatus.reservation && hasReservationExpired(connectorStatus.reservation)) { + await chargingStation.removeReservation( + connectorStatus.reservation, + ReservationTerminationReason.EXPIRED, + ); + } + } + } +}; + +export const getNumberOfReservableConnectors = ( + connectors: Map, +): number => { + let numberOfReservableConnectors = 0; for (const [connectorId, connectorStatus] of connectors) { if (connectorId === 0) { continue; } if (connectorStatus.status === ConnectorStatusEnum.Available) { - ++reservableConnectors; + ++numberOfReservableConnectors; } } - return reservableConnectors; + return numberOfReservableConnectors; }; export const getHashId = (index: number, stationTemplate: ChargingStationTemplate): string => { @@ -138,9 +180,9 @@ export const getPhaseRotationValue = ( } else if (connectorId > 0 && numberOfPhases === 0) { return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`; // AC - } else if (connectorId > 0 && numberOfPhases === 1) { + } else if (connectorId >= 0 && numberOfPhases === 1) { return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`; - } else if (connectorId > 0 && numberOfPhases === 3) { + } else if (connectorId >= 0 && numberOfPhases === 3) { return `${connectorId}.${ConnectorPhaseRotation.RST}`; } }; @@ -213,6 +255,23 @@ export const checkTemplate = ( } }; +export const checkConfiguration = ( + stationConfiguration: ChargingStationConfiguration | undefined, + logPrefix: string, + configurationFile: string, +): void => { + if (isNullOrUndefined(stationConfiguration)) { + const errorMsg = `Failed to read charging station configuration file ${configurationFile}`; + logger.error(`${logPrefix} ${errorMsg}`); + throw new BaseError(errorMsg); + } + if (isEmptyObject(stationConfiguration!)) { + const errorMsg = `Empty charging station configuration from file ${configurationFile}`; + logger.error(`${logPrefix} ${errorMsg}`); + throw new BaseError(errorMsg); + } +}; + export const checkConnectorsConfiguration = ( stationTemplate: ChargingStationTemplate, logPrefix: string, @@ -222,11 +281,11 @@ export const checkConnectorsConfiguration = ( templateMaxConnectors: number; templateMaxAvailableConnectors: number; } => { - const configuredMaxConnectors = getConfiguredNumberOfConnectors(stationTemplate); + const configuredMaxConnectors = getConfiguredMaxNumberOfConnectors(stationTemplate); checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile); const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors!); checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile); - const templateMaxAvailableConnectors = stationTemplate.Connectors![0] + const templateMaxAvailableConnectors = stationTemplate.Connectors?.[0] ? templateMaxConnectors - 1 : templateMaxConnectors; if ( @@ -303,6 +362,12 @@ export const initializeConnectorsMapStatus = ( }; export const resetConnectorStatus = (connectorStatus: ConnectorStatus): void => { + connectorStatus.chargingProfiles = + connectorStatus.transactionId && isNotEmptyArray(connectorStatus.chargingProfiles) + ? connectorStatus.chargingProfiles?.filter( + (chargingProfile) => chargingProfile.transactionId !== connectorStatus.transactionId, + ) + : []; connectorStatus.idTagLocalAuthorized = false; connectorStatus.idTagAuthorized = false; connectorStatus.transactionRemoteStarted = false; @@ -320,7 +385,7 @@ export const createBootNotificationRequest = ( stationInfo: ChargingStationInfo, bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp, ): BootNotificationRequest => { - const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16; + const ocppVersion = stationInfo.ocppVersion!; switch (ocppVersion) { case OCPPVersion.VERSION_16: return { @@ -377,6 +442,7 @@ export const warnTemplateKeysDeprecation = ( { deprecatedKey: 'supervisionUrl', key: 'supervisionUrls' }, { deprecatedKey: 'authorizationFile', key: 'idTagsFile' }, { deprecatedKey: 'payloadSchemaValidation', key: 'ocppStrictCompliance' }, + { deprecatedKey: 'mustAuthorizeAtRemoteStart', key: 'remoteAuthorization' }, ]; for (const templateKey of templateKeys) { warnDeprecatedTemplateKey( @@ -409,12 +475,9 @@ export const stationTemplateToStationInfo = ( export const createSerialNumber = ( stationTemplate: ChargingStationTemplate, stationInfo: ChargingStationInfo, - params: { + params?: { randomSerialNumberUpperCase?: boolean; randomSerialNumber?: boolean; - } = { - randomSerialNumberUpperCase: true, - randomSerialNumber: true, }, ): void => { params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params }; @@ -478,24 +541,36 @@ export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInf return unitDivider; }; +/** + * Gets the connector cloned charging profiles applying a power limitation + * and sorted by connector id descending then stack level descending + * + * @param chargingStation - + * @param connectorId - + * @returns connector charging profiles array + */ +export const getConnectorChargingProfiles = ( + chargingStation: ChargingStation, + connectorId: number, +) => { + return cloneObject( + (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []) + .sort((a, b) => b.stackLevel - a.stackLevel) + .concat( + (chargingStation.getConnectorStatus(0)?.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!, - )?.sort((a, b) => b.stackLevel - a.stackLevel) ?? []; - // Get charging profiles on connector 0 and sort by stack level - if (isNotEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)) { - chargingProfiles.push( - ...cloneObject( - 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, @@ -505,31 +580,29 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( ); if (!isNullOrUndefined(result)) { limit = result?.limit; - matchingChargingProfile = result?.matchingChargingProfile; - switch (chargingStation.getCurrentOutType()) { + chargingProfile = result?.chargingProfile; + switch (chargingStation.stationInfo?.currentOutType) { case CurrentType.AC: limit = - matchingChargingProfile?.chargingSchedule?.chargingRateUnit === - ChargingRateUnitType.WATT + chargingProfile?.chargingSchedule?.chargingRateUnit === ChargingRateUnitType.WATT ? limit : ACElectricUtils.powerTotal( chargingStation.getNumberOfPhases(), - chargingStation.getVoltageOut(), + chargingStation.stationInfo.voltageOut!, limit!, ); break; case CurrentType.DC: limit = - matchingChargingProfile?.chargingSchedule?.chargingRateUnit === - ChargingRateUnitType.WATT + chargingProfile?.chargingSchedule?.chargingRateUnit === ChargingRateUnitType.WATT ? limit - : DCElectricUtils.power(chargingStation.getVoltageOut(), limit!); + : DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit!); } const connectorMaximumPower = - chargingStation.getMaximumPower() / chargingStation.powerDivider; + chargingStation.stationInfo.maximumPower! / 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; @@ -586,16 +659,16 @@ export const waitChargingStationEvents = async ( }); }; -const getConfiguredNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => { - let configuredMaxConnectors = 0; +const getConfiguredMaxNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => { + let configuredMaxNumberOfConnectors = 0; if (isNotEmptyArray(stationTemplate.numberOfConnectors) === true) { const numberOfConnectors = stationTemplate.numberOfConnectors as number[]; - configuredMaxConnectors = + configuredMaxNumberOfConnectors = numberOfConnectors[Math.floor(secureRandom() * numberOfConnectors.length)]; } else if (isUndefined(stationTemplate.numberOfConnectors) === false) { - configuredMaxConnectors = stationTemplate.numberOfConnectors as number; + configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors as number; } else if (stationTemplate.Connectors && !stationTemplate.Evses) { - configuredMaxConnectors = stationTemplate.Connectors[0] + configuredMaxNumberOfConnectors = stationTemplate.Connectors?.[0] ? getMaxNumberOfConnectors(stationTemplate.Connectors) - 1 : getMaxNumberOfConnectors(stationTemplate.Connectors); } else if (stationTemplate.Evses && !stationTemplate.Connectors) { @@ -603,10 +676,12 @@ const getConfiguredNumberOfConnectors = (stationTemplate: ChargingStationTemplat if (evse === '0') { continue; } - configuredMaxConnectors += getMaxNumberOfConnectors(stationTemplate.Evses[evse].Connectors); + configuredMaxNumberOfConnectors += getMaxNumberOfConnectors( + stationTemplate.Evses[evse].Connectors, + ); } } - return configuredMaxConnectors; + return configuredMaxNumberOfConnectors; }; const checkConfiguredMaxConnectors = ( @@ -657,12 +732,12 @@ const warnDeprecatedTemplateKey = ( templateFile: string, logMsgToAppend = '', ): void => { - if (!isUndefined(template[key as keyof ChargingStationTemplate])) { + if (!isUndefined(template?.[key as keyof ChargingStationTemplate])) { const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${ isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : '' }`; logger.warn(`${logPrefix} ${logMsg}`); - console.warn(chalk.yellow(`${logMsg}`)); + console.warn(`${chalk.green(logPrefix)} ${chalk.yellow(logMsg)}`); } }; @@ -671,7 +746,7 @@ const convertDeprecatedTemplateKey = ( deprecatedKey: string, key?: string, ): void => { - if (!isUndefined(template[deprecatedKey as keyof ChargingStationTemplate])) { + if (!isUndefined(template?.[deprecatedKey as keyof ChargingStationTemplate])) { if (!isUndefined(key)) { (template as unknown as Record)[key!] = template[deprecatedKey as keyof ChargingStationTemplate]; @@ -682,11 +757,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 descending then stack level descending * * @param chargingStation - * @param connectorId - @@ -702,40 +777,43 @@ 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); + 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 (!isDate(chargingSchedule?.startSchedule)) { + if ( + !isNullOrUndefined(chargingSchedule?.startSchedule) && + !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`, + `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance`, ); 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 ( + !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 (!prepareChargingProfileKind(connectorStatus, chargingProfile, currentDate, logPrefix)) { + continue; } if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) { continue; } // Check if the charging profile is active if ( - isValidTime(chargingSchedule?.startSchedule) && isWithinInterval(currentDate, { start: chargingSchedule.startSchedule!, end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), @@ -747,17 +825,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`, @@ -768,7 +846,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; @@ -789,7 +867,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; @@ -800,18 +878,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; @@ -822,7 +899,36 @@ const getLimitFromChargingProfiles = ( } }; -const canProceedChargingProfile = ( +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; + } + if (connectorStatus?.transactionStarted) { + chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart; + } + // FIXME: Handle relative charging profile duration + break; + } + return true; +}; + +export const canProceedChargingProfile = ( chargingProfile: ChargingProfile, currentDate: Date, logPrefix: string, @@ -838,16 +944,30 @@ const canProceedChargingProfile = ( ); return false; } - const chargingSchedule = chargingProfile.chargingSchedule; - if (isNullOrUndefined(chargingSchedule?.startSchedule)) { + if ( + isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) || + isNullOrUndefined(chargingProfile.chargingSchedule.duration) + ) { logger.error( - `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has (still) no startSchedule defined`, + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule or duration defined`, ); return false; } - if (isNullOrUndefined(chargingSchedule?.duration)) { + if ( + !isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) && + !isValidTime(chargingProfile.chargingSchedule.startSchedule) + ) { logger.error( - `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`, + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has an invalid startSchedule date defined`, + ); + return false; + } + if ( + !isNullOrUndefined(chargingProfile.chargingSchedule.duration) && + !Number.isSafeInteger(chargingProfile.chargingSchedule.duration) + ) { + logger.error( + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has non integer duration defined`, ); return false; } @@ -867,6 +987,15 @@ const canProceedRecurringChargingProfile = ( ); return false; } + if ( + chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING && + isNullOrUndefined(chargingProfile.chargingSchedule.startSchedule) + ) { + logger.error( + `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined`, + ); + return false; + } return true; }; @@ -930,7 +1059,7 @@ 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!)) {