1 import { createHash
, randomBytes
} from
'node:crypto';
2 import type { EventEmitter
} from
'node:events';
3 import { basename
, dirname
, join
} from
'node:path';
4 import { fileURLToPath
} from
'node:url';
6 import chalk from
'chalk';
7 import { addSeconds
, isAfter
, isTomorrow
, isYesterday
} from
'date-fns';
9 import type { ChargingStation
} from
'./ChargingStation';
10 import { BaseError
} from
'../exception';
14 type BootNotificationRequest
,
17 ChargingProfileKindType
,
19 type ChargingSchedulePeriod
,
20 type ChargingStationInfo
,
21 type ChargingStationTemplate
,
22 ChargingStationWorkerMessageEvents
,
23 ConnectorPhaseRotation
,
28 type OCPP16BootNotificationRequest
,
29 type OCPP20BootNotificationRequest
,
51 const moduleName
= 'ChargingStationUtils';
53 export const getChargingStationId
= (
55 stationTemplate
: ChargingStationTemplate
,
57 // In case of multiple instances: add instance index to charging station id
58 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
59 const idSuffix
= stationTemplate
?.nameSuffix
?? '';
60 const idStr
= `000000000${index.toString()}`;
61 return stationTemplate
?.fixedName
62 ? stationTemplate
.baseName
63 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
68 export const countReservableConnectors
= (connectors
: Map
<number, ConnectorStatus
>) => {
69 let reservableConnectors
= 0;
70 for (const [connectorId
, connectorStatus
] of connectors
) {
71 if (connectorId
=== 0) {
74 if (connectorStatus
.status === ConnectorStatusEnum
.Available
) {
75 ++reservableConnectors
;
78 return reservableConnectors
;
81 export const getHashId
= (index
: number, stationTemplate
: ChargingStationTemplate
): string => {
82 const chargingStationInfo
= {
83 chargePointModel
: stationTemplate
.chargePointModel
,
84 chargePointVendor
: stationTemplate
.chargePointVendor
,
85 ...(!isUndefined(stationTemplate
.chargeBoxSerialNumberPrefix
) && {
86 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
88 ...(!isUndefined(stationTemplate
.chargePointSerialNumberPrefix
) && {
89 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
91 ...(!isUndefined(stationTemplate
.meterSerialNumberPrefix
) && {
92 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
94 ...(!isUndefined(stationTemplate
.meterType
) && {
95 meterType
: stationTemplate
.meterType
,
98 return createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
99 .update(`${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`)
103 export const checkChargingStation
= (
104 chargingStation
: ChargingStation
,
107 if (chargingStation
.started
=== false && chargingStation
.starting
=== false) {
108 logger
.warn(`${logPrefix} charging station is stopped, cannot proceed`);
114 export const getPhaseRotationValue
= (
116 numberOfPhases
: number,
117 ): string | undefined => {
119 if (connectorId
=== 0 && numberOfPhases
=== 0) {
120 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
121 } else if (connectorId
> 0 && numberOfPhases
=== 0) {
122 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
124 } else if (connectorId
> 0 && numberOfPhases
=== 1) {
125 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
126 } else if (connectorId
> 0 && numberOfPhases
=== 3) {
127 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
131 export const getMaxNumberOfEvses
= (evses
: Record
<string, EvseTemplate
>): number => {
135 return Object.keys(evses
).length
;
138 const getMaxNumberOfConnectors
= (connectors
: Record
<string, ConnectorStatus
>): number => {
142 return Object.keys(connectors
).length
;
145 export const getBootConnectorStatus
= (
146 chargingStation
: ChargingStation
,
148 connectorStatus
: ConnectorStatus
,
149 ): ConnectorStatusEnum
=> {
150 let connectorBootStatus
: ConnectorStatusEnum
;
152 !connectorStatus
?.status &&
153 (chargingStation
.isChargingStationAvailable() === false ||
154 chargingStation
.isConnectorAvailable(connectorId
) === false)
156 connectorBootStatus
= ConnectorStatusEnum
.Unavailable
;
157 } else if (!connectorStatus
?.status && connectorStatus
?.bootStatus
) {
158 // Set boot status in template at startup
159 connectorBootStatus
= connectorStatus
?.bootStatus
;
160 } else if (connectorStatus
?.status) {
161 // Set previous status at startup
162 connectorBootStatus
= connectorStatus
?.status;
164 // Set default status
165 connectorBootStatus
= ConnectorStatusEnum
.Available
;
167 return connectorBootStatus
;
170 export const checkTemplate
= (
171 stationTemplate
: ChargingStationTemplate
,
173 templateFile
: string,
175 if (isNullOrUndefined(stationTemplate
)) {
176 const errorMsg
= `Failed to read charging station template file ${templateFile}`;
177 logger
.error(`${logPrefix} ${errorMsg}`);
178 throw new BaseError(errorMsg
);
180 if (isEmptyObject(stationTemplate
)) {
181 const errorMsg
= `Empty charging station information from template file ${templateFile}`;
182 logger
.error(`${logPrefix} ${errorMsg}`);
183 throw new BaseError(errorMsg
);
185 if (isEmptyObject(stationTemplate
.AutomaticTransactionGenerator
!)) {
186 stationTemplate
.AutomaticTransactionGenerator
= Constants
.DEFAULT_ATG_CONFIGURATION
;
188 `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
189 Constants
.DEFAULT_ATG_CONFIGURATION
,
192 if (isNullOrUndefined(stationTemplate
.idTagsFile
) || isEmptyString(stationTemplate
.idTagsFile
)) {
194 `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`,
199 export const checkConnectorsConfiguration
= (
200 stationTemplate
: ChargingStationTemplate
,
202 templateFile
: string,
204 configuredMaxConnectors
: number;
205 templateMaxConnectors
: number;
206 templateMaxAvailableConnectors
: number;
208 const configuredMaxConnectors
= getConfiguredNumberOfConnectors(stationTemplate
);
209 checkConfiguredMaxConnectors(configuredMaxConnectors
, logPrefix
, templateFile
);
210 const templateMaxConnectors
= getMaxNumberOfConnectors(stationTemplate
.Connectors
!);
211 checkTemplateMaxConnectors(templateMaxConnectors
, logPrefix
, templateFile
);
212 const templateMaxAvailableConnectors
= stationTemplate
.Connectors
![0]
213 ? templateMaxConnectors
- 1
214 : templateMaxConnectors
;
216 configuredMaxConnectors
> templateMaxAvailableConnectors
&&
217 !stationTemplate
?.randomConnectors
220 `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`,
222 stationTemplate
.randomConnectors
= true;
224 return { configuredMaxConnectors
, templateMaxConnectors
, templateMaxAvailableConnectors
};
227 export const checkStationInfoConnectorStatus
= (
229 connectorStatus
: ConnectorStatus
,
231 templateFile
: string,
233 if (!isNullOrUndefined(connectorStatus
?.status)) {
235 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`,
237 delete connectorStatus
.status;
241 export const buildConnectorsMap
= (
242 connectors
: Record
<string, ConnectorStatus
>,
244 templateFile
: string,
245 ): Map
<number, ConnectorStatus
> => {
246 const connectorsMap
= new Map
<number, ConnectorStatus
>();
247 if (getMaxNumberOfConnectors(connectors
) > 0) {
248 for (const connector
in connectors
) {
249 const connectorStatus
= connectors
[connector
];
250 const connectorId
= convertToInt(connector
);
251 checkStationInfoConnectorStatus(connectorId
, connectorStatus
, logPrefix
, templateFile
);
252 connectorsMap
.set(connectorId
, cloneObject
<ConnectorStatus
>(connectorStatus
));
256 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`,
259 return connectorsMap
;
262 export const initializeConnectorsMapStatus
= (
263 connectors
: Map
<number, ConnectorStatus
>,
266 for (const connectorId
of connectors
.keys()) {
267 if (connectorId
> 0 && connectors
.get(connectorId
)?.transactionStarted
=== true) {
269 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${connectors.get(
274 if (connectorId
=== 0) {
275 connectors
.get(connectorId
)!.availability
= AvailabilityType
.Operative
;
276 if (isUndefined(connectors
.get(connectorId
)?.chargingProfiles
)) {
277 connectors
.get(connectorId
)!.chargingProfiles
= [];
281 isNullOrUndefined(connectors
.get(connectorId
)?.transactionStarted
)
283 initializeConnectorStatus(connectors
.get(connectorId
)!);
288 export const resetConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
289 connectorStatus
.idTagLocalAuthorized
= false;
290 connectorStatus
.idTagAuthorized
= false;
291 connectorStatus
.transactionRemoteStarted
= false;
292 connectorStatus
.transactionStarted
= false;
293 delete connectorStatus
?.localAuthorizeIdTag
;
294 delete connectorStatus
?.authorizeIdTag
;
295 delete connectorStatus
?.transactionId
;
296 delete connectorStatus
?.transactionIdTag
;
297 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
298 delete connectorStatus
?.transactionBeginMeterValue
;
301 export const createBootNotificationRequest
= (
302 stationInfo
: ChargingStationInfo
,
303 bootReason
: BootReasonEnumType
= BootReasonEnumType
.PowerUp
,
304 ): BootNotificationRequest
=> {
305 const ocppVersion
= stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
;
306 switch (ocppVersion
) {
307 case OCPPVersion
.VERSION_16
:
309 chargePointModel
: stationInfo
.chargePointModel
,
310 chargePointVendor
: stationInfo
.chargePointVendor
,
311 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
312 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
314 ...(!isUndefined(stationInfo
.chargePointSerialNumber
) && {
315 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
317 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
318 firmwareVersion
: stationInfo
.firmwareVersion
,
320 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
321 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
322 ...(!isUndefined(stationInfo
.meterSerialNumber
) && {
323 meterSerialNumber
: stationInfo
.meterSerialNumber
,
325 ...(!isUndefined(stationInfo
.meterType
) && {
326 meterType
: stationInfo
.meterType
,
328 } as OCPP16BootNotificationRequest
;
329 case OCPPVersion
.VERSION_20
:
330 case OCPPVersion
.VERSION_201
:
334 model
: stationInfo
.chargePointModel
,
335 vendorName
: stationInfo
.chargePointVendor
,
336 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
337 firmwareVersion
: stationInfo
.firmwareVersion
,
339 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
340 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
342 ...((!isUndefined(stationInfo
.iccid
) || !isUndefined(stationInfo
.imsi
)) && {
344 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
345 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
349 } as OCPP20BootNotificationRequest
;
353 export const warnTemplateKeysDeprecation
= (
354 stationTemplate
: ChargingStationTemplate
,
356 templateFile
: string,
358 const templateKeys
: { deprecatedKey
: string; key
?: string }[] = [
359 { deprecatedKey
: 'supervisionUrl', key
: 'supervisionUrls' },
360 { deprecatedKey
: 'authorizationFile', key
: 'idTagsFile' },
361 { deprecatedKey
: 'payloadSchemaValidation', key
: 'ocppStrictCompliance' },
363 for (const templateKey
of templateKeys
) {
364 warnDeprecatedTemplateKey(
366 templateKey
.deprecatedKey
,
369 !isUndefined(templateKey
.key
) ? `Use '${templateKey.key}' instead` : undefined,
371 convertDeprecatedTemplateKey(stationTemplate
, templateKey
.deprecatedKey
, templateKey
.key
);
375 export const stationTemplateToStationInfo
= (
376 stationTemplate
: ChargingStationTemplate
,
377 ): ChargingStationInfo
=> {
378 stationTemplate
= cloneObject
<ChargingStationTemplate
>(stationTemplate
);
379 delete stationTemplate
.power
;
380 delete stationTemplate
.powerUnit
;
381 delete stationTemplate
.Connectors
;
382 delete stationTemplate
.Evses
;
383 delete stationTemplate
.Configuration
;
384 delete stationTemplate
.AutomaticTransactionGenerator
;
385 delete stationTemplate
.chargeBoxSerialNumberPrefix
;
386 delete stationTemplate
.chargePointSerialNumberPrefix
;
387 delete stationTemplate
.meterSerialNumberPrefix
;
388 return stationTemplate
as unknown
as ChargingStationInfo
;
391 export const createSerialNumber
= (
392 stationTemplate
: ChargingStationTemplate
,
393 stationInfo
: ChargingStationInfo
,
395 randomSerialNumberUpperCase
?: boolean;
396 randomSerialNumber
?: boolean;
398 randomSerialNumberUpperCase
: true,
399 randomSerialNumber
: true,
402 params
= { ...{ randomSerialNumberUpperCase
: true, randomSerialNumber
: true }, ...params
};
403 const serialNumberSuffix
= params
?.randomSerialNumber
404 ? getRandomSerialNumberSuffix({
405 upperCase
: params
.randomSerialNumberUpperCase
,
408 isNotEmptyString(stationTemplate
?.chargePointSerialNumberPrefix
) &&
409 (stationInfo
.chargePointSerialNumber
= `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`);
410 isNotEmptyString(stationTemplate
?.chargeBoxSerialNumberPrefix
) &&
411 (stationInfo
.chargeBoxSerialNumber
= `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`);
412 isNotEmptyString(stationTemplate
?.meterSerialNumberPrefix
) &&
413 (stationInfo
.meterSerialNumber
= `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`);
416 export const propagateSerialNumber
= (
417 stationTemplate
: ChargingStationTemplate
,
418 stationInfoSrc
: ChargingStationInfo
,
419 stationInfoDst
: ChargingStationInfo
,
421 if (!stationInfoSrc
|| !stationTemplate
) {
423 'Missing charging station template or existing configuration to propagate serial number',
426 stationTemplate
?.chargePointSerialNumberPrefix
&& stationInfoSrc
?.chargePointSerialNumber
427 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
428 : stationInfoDst
?.chargePointSerialNumber
&& delete stationInfoDst
.chargePointSerialNumber
;
429 stationTemplate
?.chargeBoxSerialNumberPrefix
&& stationInfoSrc
?.chargeBoxSerialNumber
430 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
431 : stationInfoDst
?.chargeBoxSerialNumber
&& delete stationInfoDst
.chargeBoxSerialNumber
;
432 stationTemplate
?.meterSerialNumberPrefix
&& stationInfoSrc
?.meterSerialNumber
433 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
434 : stationInfoDst
?.meterSerialNumber
&& delete stationInfoDst
.meterSerialNumber
;
437 export const getAmperageLimitationUnitDivider
= (stationInfo
: ChargingStationInfo
): number => {
439 switch (stationInfo
.amperageLimitationUnit
) {
440 case AmpereUnits
.DECI_AMPERE
:
443 case AmpereUnits
.CENTI_AMPERE
:
446 case AmpereUnits
.MILLI_AMPERE
:
453 export const getChargingStationConnectorChargingProfilesPowerLimit
= (
454 chargingStation
: ChargingStation
,
456 ): number | undefined => {
457 let limit
: number | undefined, matchingChargingProfile
: ChargingProfile
| undefined;
458 // Get charging profiles for connector and sort by stack level
459 const chargingProfiles
=
460 cloneObject
<ChargingProfile
[]>(
461 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
!,
462 )?.sort((a
, b
) => b
.stackLevel
- a
.stackLevel
) ?? [];
463 // Get profiles on connector 0
464 if (chargingStation
.getConnectorStatus(0)?.chargingProfiles
) {
465 chargingProfiles
.push(
466 ...cloneObject
<ChargingProfile
[]>(
467 chargingStation
.getConnectorStatus(0)!.chargingProfiles
!,
468 ).sort((a
, b
) => b
.stackLevel
- a
.stackLevel
),
471 if (isNotEmptyArray(chargingProfiles
)) {
472 const result
= getLimitFromChargingProfiles(chargingProfiles
, chargingStation
.logPrefix());
473 if (!isNullOrUndefined(result
)) {
474 limit
= result
?.limit
;
475 matchingChargingProfile
= result
?.matchingChargingProfile
;
476 switch (chargingStation
.getCurrentOutType()) {
479 matchingChargingProfile
?.chargingSchedule
?.chargingRateUnit
===
480 ChargingRateUnitType
.WATT
482 : ACElectricUtils
.powerTotal(
483 chargingStation
.getNumberOfPhases(),
484 chargingStation
.getVoltageOut(),
490 matchingChargingProfile
?.chargingSchedule
?.chargingRateUnit
===
491 ChargingRateUnitType
.WATT
493 : DCElectricUtils
.power(chargingStation
.getVoltageOut(), limit
!);
495 const connectorMaximumPower
=
496 chargingStation
.getMaximumPower() / chargingStation
.powerDivider
;
497 if (limit
! > connectorMaximumPower
) {
499 `${chargingStation.logPrefix()} Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
502 limit
= connectorMaximumPower
;
509 export const getDefaultVoltageOut
= (
510 currentType
: CurrentType
,
512 templateFile
: string,
514 const errorMsg
= `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
515 let defaultVoltageOut
: number;
516 switch (currentType
) {
518 defaultVoltageOut
= Voltage
.VOLTAGE_230
;
521 defaultVoltageOut
= Voltage
.VOLTAGE_400
;
524 logger
.error(`${logPrefix} ${errorMsg}`);
525 throw new BaseError(errorMsg
);
527 return defaultVoltageOut
;
530 export const getIdTagsFile
= (stationInfo
: ChargingStationInfo
): string | undefined => {
532 stationInfo
.idTagsFile
&&
533 join(dirname(fileURLToPath(import.meta
.url
)), 'assets', basename(stationInfo
.idTagsFile
))
537 export const waitChargingStationEvents
= async (
538 emitter
: EventEmitter
,
539 event
: ChargingStationWorkerMessageEvents
,
540 eventsToWait
: number,
541 ): Promise
<number> => {
542 return new Promise
<number>((resolve
) => {
544 if (eventsToWait
=== 0) {
547 emitter
.on(event
, () => {
549 if (events
=== eventsToWait
) {
556 const getConfiguredNumberOfConnectors
= (stationTemplate
: ChargingStationTemplate
): number => {
557 let configuredMaxConnectors
= 0;
558 if (isNotEmptyArray(stationTemplate
.numberOfConnectors
) === true) {
559 const numberOfConnectors
= stationTemplate
.numberOfConnectors
as number[];
560 configuredMaxConnectors
=
561 numberOfConnectors
[Math.floor(secureRandom() * numberOfConnectors
.length
)];
562 } else if (isUndefined(stationTemplate
.numberOfConnectors
) === false) {
563 configuredMaxConnectors
= stationTemplate
.numberOfConnectors
as number;
564 } else if (stationTemplate
.Connectors
&& !stationTemplate
.Evses
) {
565 configuredMaxConnectors
= stationTemplate
.Connectors
[0]
566 ? getMaxNumberOfConnectors(stationTemplate
.Connectors
) - 1
567 : getMaxNumberOfConnectors(stationTemplate
.Connectors
);
568 } else if (stationTemplate
.Evses
&& !stationTemplate
.Connectors
) {
569 for (const evse
in stationTemplate
.Evses
) {
573 configuredMaxConnectors
+= getMaxNumberOfConnectors(stationTemplate
.Evses
[evse
].Connectors
);
576 return configuredMaxConnectors
;
579 const checkConfiguredMaxConnectors
= (
580 configuredMaxConnectors
: number,
582 templateFile
: string,
584 if (configuredMaxConnectors
<= 0) {
586 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`,
591 const checkTemplateMaxConnectors
= (
592 templateMaxConnectors
: number,
594 templateFile
: string,
596 if (templateMaxConnectors
=== 0) {
598 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`,
600 } else if (templateMaxConnectors
< 0) {
602 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`,
607 const initializeConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
608 connectorStatus
.availability
= AvailabilityType
.Operative
;
609 connectorStatus
.idTagLocalAuthorized
= false;
610 connectorStatus
.idTagAuthorized
= false;
611 connectorStatus
.transactionRemoteStarted
= false;
612 connectorStatus
.transactionStarted
= false;
613 connectorStatus
.energyActiveImportRegisterValue
= 0;
614 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
615 if (isUndefined(connectorStatus
.chargingProfiles
)) {
616 connectorStatus
.chargingProfiles
= [];
620 const warnDeprecatedTemplateKey
= (
621 template
: ChargingStationTemplate
,
624 templateFile
: string,
627 if (!isUndefined(template
[key
as keyof ChargingStationTemplate
])) {
628 const logMsg
= `Deprecated template key '${key}' usage in file '${templateFile}'${
629 isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}
` : ''
631 logger
.warn(`${logPrefix} ${logMsg}`);
632 console
.warn(chalk
.yellow(`${logMsg}`));
636 const convertDeprecatedTemplateKey
= (
637 template
: ChargingStationTemplate
,
638 deprecatedKey
: string,
641 if (!isUndefined(template
[deprecatedKey
as keyof ChargingStationTemplate
])) {
642 if (!isUndefined(key
)) {
643 (template
as unknown
as Record
<string, unknown
>)[key
!] =
644 template
[deprecatedKey
as keyof ChargingStationTemplate
];
646 delete template
[deprecatedKey
as keyof ChargingStationTemplate
];
651 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
653 * @param chargingProfiles -
657 const getLimitFromChargingProfiles
= (
658 chargingProfiles
: ChargingProfile
[],
663 matchingChargingProfile
: ChargingProfile
;
666 const debugLogMsg
= `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
667 const currentDate
= new Date();
668 for (const chargingProfile
of chargingProfiles
) {
670 const chargingSchedule
= chargingProfile
.chargingSchedule
;
671 if (!chargingSchedule
?.startSchedule
) {
673 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`,
676 if (!(chargingSchedule
?.startSchedule
instanceof Date)) {
678 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`,
680 chargingSchedule
.startSchedule
= convertToDate(chargingSchedule
.startSchedule
)!;
682 // Adjust the daily recurring schedule to today
684 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
685 chargingProfile
.recurrencyKind
=== RecurrencyKindType
.DAILY
687 if (isYesterday(chargingSchedule
.startSchedule
)) {
688 chargingSchedule
.startSchedule
.setFullYear(
689 currentDate
.getFullYear(),
690 currentDate
.getMonth(),
691 currentDate
.getDate(),
693 } else if (isTomorrow(chargingSchedule
.startSchedule
)) {
694 chargingSchedule
.startSchedule
.setDate(currentDate
.getDate() - 1);
697 // Check if the charging profile is active
699 isAfter(addSeconds(chargingSchedule
.startSchedule
, chargingSchedule
.duration
!), currentDate
)
701 let lastButOneSchedule
: ChargingSchedulePeriod
| undefined;
702 // Search the right schedule period
703 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
704 // Handling of only one period
706 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
707 schedulePeriod
.startPeriod
=== 0
710 limit
: schedulePeriod
.limit
,
711 matchingChargingProfile
: chargingProfile
,
713 logger
.debug(debugLogMsg
, result
);
716 // Find the right schedule period
719 addSeconds(chargingSchedule
.startSchedule
, schedulePeriod
.startPeriod
),
723 // Found the schedule: last but one is the correct one
725 limit
: lastButOneSchedule
!.limit
,
726 matchingChargingProfile
: chargingProfile
,
728 logger
.debug(debugLogMsg
, result
);
732 lastButOneSchedule
= schedulePeriod
;
733 // Handle the last schedule period
735 schedulePeriod
.startPeriod
===
736 chargingSchedule
.chargingSchedulePeriod
[
737 chargingSchedule
.chargingSchedulePeriod
.length
- 1
741 limit
: lastButOneSchedule
.limit
,
742 matchingChargingProfile
: chargingProfile
,
744 logger
.debug(debugLogMsg
, result
);
752 const getRandomSerialNumberSuffix
= (params
?: {
753 randomBytesLength
?: number;
756 const randomSerialNumberSuffix
= randomBytes(params
?.randomBytesLength
?? 16).toString('hex');
757 if (params
?.upperCase
) {
758 return randomSerialNumberSuffix
.toUpperCase();
760 return randomSerialNumberSuffix
;