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';
17 import type { ChargingStation
} from
'./ChargingStation';
18 import { BaseError
} from
'../exception';
22 type BootNotificationRequest
,
25 ChargingProfileKindType
,
27 type ChargingSchedulePeriod
,
28 type ChargingStationInfo
,
29 type ChargingStationTemplate
,
30 ChargingStationWorkerMessageEvents
,
31 ConnectorPhaseRotation
,
36 type OCPP16BootNotificationRequest
,
37 type OCPP20BootNotificationRequest
,
59 const moduleName
= 'ChargingStationUtils';
61 export const getChargingStationId
= (
63 stationTemplate
: ChargingStationTemplate
,
65 // In case of multiple instances: add instance index to charging station id
66 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
67 const idSuffix
= stationTemplate
?.nameSuffix
?? '';
68 const idStr
= `000000000${index.toString()}`;
69 return stationTemplate
?.fixedName
70 ? stationTemplate
.baseName
71 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
76 export const countReservableConnectors
= (connectors
: Map
<number, ConnectorStatus
>) => {
77 let reservableConnectors
= 0;
78 for (const [connectorId
, connectorStatus
] of connectors
) {
79 if (connectorId
=== 0) {
82 if (connectorStatus
.status === ConnectorStatusEnum
.Available
) {
83 ++reservableConnectors
;
86 return reservableConnectors
;
89 export const getHashId
= (index
: number, stationTemplate
: ChargingStationTemplate
): string => {
90 const chargingStationInfo
= {
91 chargePointModel
: stationTemplate
.chargePointModel
,
92 chargePointVendor
: stationTemplate
.chargePointVendor
,
93 ...(!isUndefined(stationTemplate
.chargeBoxSerialNumberPrefix
) && {
94 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
96 ...(!isUndefined(stationTemplate
.chargePointSerialNumberPrefix
) && {
97 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
99 ...(!isUndefined(stationTemplate
.meterSerialNumberPrefix
) && {
100 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
102 ...(!isUndefined(stationTemplate
.meterType
) && {
103 meterType
: stationTemplate
.meterType
,
106 return createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
107 .update(`${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`)
111 export const checkChargingStation
= (
112 chargingStation
: ChargingStation
,
115 if (chargingStation
.started
=== false && chargingStation
.starting
=== false) {
116 logger
.warn(`${logPrefix} charging station is stopped, cannot proceed`);
122 export const getPhaseRotationValue
= (
124 numberOfPhases
: number,
125 ): string | undefined => {
127 if (connectorId
=== 0 && numberOfPhases
=== 0) {
128 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
129 } else if (connectorId
> 0 && numberOfPhases
=== 0) {
130 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
132 } else if (connectorId
> 0 && numberOfPhases
=== 1) {
133 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
134 } else if (connectorId
> 0 && numberOfPhases
=== 3) {
135 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
139 export const getMaxNumberOfEvses
= (evses
: Record
<string, EvseTemplate
>): number => {
143 return Object.keys(evses
).length
;
146 const getMaxNumberOfConnectors
= (connectors
: Record
<string, ConnectorStatus
>): number => {
150 return Object.keys(connectors
).length
;
153 export const getBootConnectorStatus
= (
154 chargingStation
: ChargingStation
,
156 connectorStatus
: ConnectorStatus
,
157 ): ConnectorStatusEnum
=> {
158 let connectorBootStatus
: ConnectorStatusEnum
;
160 !connectorStatus
?.status &&
161 (chargingStation
.isChargingStationAvailable() === false ||
162 chargingStation
.isConnectorAvailable(connectorId
) === false)
164 connectorBootStatus
= ConnectorStatusEnum
.Unavailable
;
165 } else if (!connectorStatus
?.status && connectorStatus
?.bootStatus
) {
166 // Set boot status in template at startup
167 connectorBootStatus
= connectorStatus
?.bootStatus
;
168 } else if (connectorStatus
?.status) {
169 // Set previous status at startup
170 connectorBootStatus
= connectorStatus
?.status;
172 // Set default status
173 connectorBootStatus
= ConnectorStatusEnum
.Available
;
175 return connectorBootStatus
;
178 export const checkTemplate
= (
179 stationTemplate
: ChargingStationTemplate
,
181 templateFile
: string,
183 if (isNullOrUndefined(stationTemplate
)) {
184 const errorMsg
= `Failed to read charging station template file ${templateFile}`;
185 logger
.error(`${logPrefix} ${errorMsg}`);
186 throw new BaseError(errorMsg
);
188 if (isEmptyObject(stationTemplate
)) {
189 const errorMsg
= `Empty charging station information from template file ${templateFile}`;
190 logger
.error(`${logPrefix} ${errorMsg}`);
191 throw new BaseError(errorMsg
);
193 if (isEmptyObject(stationTemplate
.AutomaticTransactionGenerator
!)) {
194 stationTemplate
.AutomaticTransactionGenerator
= Constants
.DEFAULT_ATG_CONFIGURATION
;
196 `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
197 Constants
.DEFAULT_ATG_CONFIGURATION
,
200 if (isNullOrUndefined(stationTemplate
.idTagsFile
) || isEmptyString(stationTemplate
.idTagsFile
)) {
202 `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`,
207 export const checkConnectorsConfiguration
= (
208 stationTemplate
: ChargingStationTemplate
,
210 templateFile
: string,
212 configuredMaxConnectors
: number;
213 templateMaxConnectors
: number;
214 templateMaxAvailableConnectors
: number;
216 const configuredMaxConnectors
= getConfiguredNumberOfConnectors(stationTemplate
);
217 checkConfiguredMaxConnectors(configuredMaxConnectors
, logPrefix
, templateFile
);
218 const templateMaxConnectors
= getMaxNumberOfConnectors(stationTemplate
.Connectors
!);
219 checkTemplateMaxConnectors(templateMaxConnectors
, logPrefix
, templateFile
);
220 const templateMaxAvailableConnectors
= stationTemplate
.Connectors
![0]
221 ? templateMaxConnectors
- 1
222 : templateMaxConnectors
;
224 configuredMaxConnectors
> templateMaxAvailableConnectors
&&
225 !stationTemplate
?.randomConnectors
228 `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`,
230 stationTemplate
.randomConnectors
= true;
232 return { configuredMaxConnectors
, templateMaxConnectors
, templateMaxAvailableConnectors
};
235 export const checkStationInfoConnectorStatus
= (
237 connectorStatus
: ConnectorStatus
,
239 templateFile
: string,
241 if (!isNullOrUndefined(connectorStatus
?.status)) {
243 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`,
245 delete connectorStatus
.status;
249 export const buildConnectorsMap
= (
250 connectors
: Record
<string, ConnectorStatus
>,
252 templateFile
: string,
253 ): Map
<number, ConnectorStatus
> => {
254 const connectorsMap
= new Map
<number, ConnectorStatus
>();
255 if (getMaxNumberOfConnectors(connectors
) > 0) {
256 for (const connector
in connectors
) {
257 const connectorStatus
= connectors
[connector
];
258 const connectorId
= convertToInt(connector
);
259 checkStationInfoConnectorStatus(connectorId
, connectorStatus
, logPrefix
, templateFile
);
260 connectorsMap
.set(connectorId
, cloneObject
<ConnectorStatus
>(connectorStatus
));
264 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`,
267 return connectorsMap
;
270 export const initializeConnectorsMapStatus
= (
271 connectors
: Map
<number, ConnectorStatus
>,
274 for (const connectorId
of connectors
.keys()) {
275 if (connectorId
> 0 && connectors
.get(connectorId
)?.transactionStarted
=== true) {
277 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${connectors.get(
282 if (connectorId
=== 0) {
283 connectors
.get(connectorId
)!.availability
= AvailabilityType
.Operative
;
284 if (isUndefined(connectors
.get(connectorId
)?.chargingProfiles
)) {
285 connectors
.get(connectorId
)!.chargingProfiles
= [];
289 isNullOrUndefined(connectors
.get(connectorId
)?.transactionStarted
)
291 initializeConnectorStatus(connectors
.get(connectorId
)!);
296 export const resetConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
297 connectorStatus
.idTagLocalAuthorized
= false;
298 connectorStatus
.idTagAuthorized
= false;
299 connectorStatus
.transactionRemoteStarted
= false;
300 connectorStatus
.transactionStarted
= false;
301 delete connectorStatus
?.localAuthorizeIdTag
;
302 delete connectorStatus
?.authorizeIdTag
;
303 delete connectorStatus
?.transactionId
;
304 delete connectorStatus
?.transactionIdTag
;
305 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
306 delete connectorStatus
?.transactionBeginMeterValue
;
309 export const createBootNotificationRequest
= (
310 stationInfo
: ChargingStationInfo
,
311 bootReason
: BootReasonEnumType
= BootReasonEnumType
.PowerUp
,
312 ): BootNotificationRequest
=> {
313 const ocppVersion
= stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
;
314 switch (ocppVersion
) {
315 case OCPPVersion
.VERSION_16
:
317 chargePointModel
: stationInfo
.chargePointModel
,
318 chargePointVendor
: stationInfo
.chargePointVendor
,
319 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
320 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
322 ...(!isUndefined(stationInfo
.chargePointSerialNumber
) && {
323 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
325 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
326 firmwareVersion
: stationInfo
.firmwareVersion
,
328 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
329 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
330 ...(!isUndefined(stationInfo
.meterSerialNumber
) && {
331 meterSerialNumber
: stationInfo
.meterSerialNumber
,
333 ...(!isUndefined(stationInfo
.meterType
) && {
334 meterType
: stationInfo
.meterType
,
336 } as OCPP16BootNotificationRequest
;
337 case OCPPVersion
.VERSION_20
:
338 case OCPPVersion
.VERSION_201
:
342 model
: stationInfo
.chargePointModel
,
343 vendorName
: stationInfo
.chargePointVendor
,
344 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
345 firmwareVersion
: stationInfo
.firmwareVersion
,
347 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
348 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
350 ...((!isUndefined(stationInfo
.iccid
) || !isUndefined(stationInfo
.imsi
)) && {
352 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
353 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
357 } as OCPP20BootNotificationRequest
;
361 export const warnTemplateKeysDeprecation
= (
362 stationTemplate
: ChargingStationTemplate
,
364 templateFile
: string,
366 const templateKeys
: { deprecatedKey
: string; key
?: string }[] = [
367 { deprecatedKey
: 'supervisionUrl', key
: 'supervisionUrls' },
368 { deprecatedKey
: 'authorizationFile', key
: 'idTagsFile' },
369 { deprecatedKey
: 'payloadSchemaValidation', key
: 'ocppStrictCompliance' },
371 for (const templateKey
of templateKeys
) {
372 warnDeprecatedTemplateKey(
374 templateKey
.deprecatedKey
,
377 !isUndefined(templateKey
.key
) ? `Use '${templateKey.key}' instead` : undefined,
379 convertDeprecatedTemplateKey(stationTemplate
, templateKey
.deprecatedKey
, templateKey
.key
);
383 export const stationTemplateToStationInfo
= (
384 stationTemplate
: ChargingStationTemplate
,
385 ): ChargingStationInfo
=> {
386 stationTemplate
= cloneObject
<ChargingStationTemplate
>(stationTemplate
);
387 delete stationTemplate
.power
;
388 delete stationTemplate
.powerUnit
;
389 delete stationTemplate
.Connectors
;
390 delete stationTemplate
.Evses
;
391 delete stationTemplate
.Configuration
;
392 delete stationTemplate
.AutomaticTransactionGenerator
;
393 delete stationTemplate
.chargeBoxSerialNumberPrefix
;
394 delete stationTemplate
.chargePointSerialNumberPrefix
;
395 delete stationTemplate
.meterSerialNumberPrefix
;
396 return stationTemplate
as unknown
as ChargingStationInfo
;
399 export const createSerialNumber
= (
400 stationTemplate
: ChargingStationTemplate
,
401 stationInfo
: ChargingStationInfo
,
403 randomSerialNumberUpperCase
?: boolean;
404 randomSerialNumber
?: boolean;
406 randomSerialNumberUpperCase
: true,
407 randomSerialNumber
: true,
410 params
= { ...{ randomSerialNumberUpperCase
: true, randomSerialNumber
: true }, ...params
};
411 const serialNumberSuffix
= params
?.randomSerialNumber
412 ? getRandomSerialNumberSuffix({
413 upperCase
: params
.randomSerialNumberUpperCase
,
416 isNotEmptyString(stationTemplate
?.chargePointSerialNumberPrefix
) &&
417 (stationInfo
.chargePointSerialNumber
= `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`);
418 isNotEmptyString(stationTemplate
?.chargeBoxSerialNumberPrefix
) &&
419 (stationInfo
.chargeBoxSerialNumber
= `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`);
420 isNotEmptyString(stationTemplate
?.meterSerialNumberPrefix
) &&
421 (stationInfo
.meterSerialNumber
= `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`);
424 export const propagateSerialNumber
= (
425 stationTemplate
: ChargingStationTemplate
,
426 stationInfoSrc
: ChargingStationInfo
,
427 stationInfoDst
: ChargingStationInfo
,
429 if (!stationInfoSrc
|| !stationTemplate
) {
431 'Missing charging station template or existing configuration to propagate serial number',
434 stationTemplate
?.chargePointSerialNumberPrefix
&& stationInfoSrc
?.chargePointSerialNumber
435 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
436 : stationInfoDst
?.chargePointSerialNumber
&& delete stationInfoDst
.chargePointSerialNumber
;
437 stationTemplate
?.chargeBoxSerialNumberPrefix
&& stationInfoSrc
?.chargeBoxSerialNumber
438 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
439 : stationInfoDst
?.chargeBoxSerialNumber
&& delete stationInfoDst
.chargeBoxSerialNumber
;
440 stationTemplate
?.meterSerialNumberPrefix
&& stationInfoSrc
?.meterSerialNumber
441 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
442 : stationInfoDst
?.meterSerialNumber
&& delete stationInfoDst
.meterSerialNumber
;
445 export const getAmperageLimitationUnitDivider
= (stationInfo
: ChargingStationInfo
): number => {
447 switch (stationInfo
.amperageLimitationUnit
) {
448 case AmpereUnits
.DECI_AMPERE
:
451 case AmpereUnits
.CENTI_AMPERE
:
454 case AmpereUnits
.MILLI_AMPERE
:
461 export const getChargingStationConnectorChargingProfilesPowerLimit
= (
462 chargingStation
: ChargingStation
,
464 ): number | undefined => {
465 let limit
: number | undefined, matchingChargingProfile
: ChargingProfile
| undefined;
466 // Get charging profiles for connector and sort by stack level
467 const chargingProfiles
=
468 cloneObject
<ChargingProfile
[]>(
469 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
!,
470 )?.sort((a
, b
) => b
.stackLevel
- a
.stackLevel
) ?? [];
471 // Get profiles on connector 0
472 if (chargingStation
.getConnectorStatus(0)?.chargingProfiles
) {
473 chargingProfiles
.push(
474 ...cloneObject
<ChargingProfile
[]>(
475 chargingStation
.getConnectorStatus(0)!.chargingProfiles
!,
476 ).sort((a
, b
) => b
.stackLevel
- a
.stackLevel
),
479 if (isNotEmptyArray(chargingProfiles
)) {
480 const result
= getLimitFromChargingProfiles(chargingProfiles
, chargingStation
.logPrefix());
481 if (!isNullOrUndefined(result
)) {
482 limit
= result
?.limit
;
483 matchingChargingProfile
= result
?.matchingChargingProfile
;
484 switch (chargingStation
.getCurrentOutType()) {
487 matchingChargingProfile
?.chargingSchedule
?.chargingRateUnit
===
488 ChargingRateUnitType
.WATT
490 : ACElectricUtils
.powerTotal(
491 chargingStation
.getNumberOfPhases(),
492 chargingStation
.getVoltageOut(),
498 matchingChargingProfile
?.chargingSchedule
?.chargingRateUnit
===
499 ChargingRateUnitType
.WATT
501 : DCElectricUtils
.power(chargingStation
.getVoltageOut(), limit
!);
503 const connectorMaximumPower
=
504 chargingStation
.getMaximumPower() / chargingStation
.powerDivider
;
505 if (limit
! > connectorMaximumPower
) {
507 `${chargingStation.logPrefix()} Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
510 limit
= connectorMaximumPower
;
517 export const getDefaultVoltageOut
= (
518 currentType
: CurrentType
,
520 templateFile
: string,
522 const errorMsg
= `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
523 let defaultVoltageOut
: number;
524 switch (currentType
) {
526 defaultVoltageOut
= Voltage
.VOLTAGE_230
;
529 defaultVoltageOut
= Voltage
.VOLTAGE_400
;
532 logger
.error(`${logPrefix} ${errorMsg}`);
533 throw new BaseError(errorMsg
);
535 return defaultVoltageOut
;
538 export const getIdTagsFile
= (stationInfo
: ChargingStationInfo
): string | undefined => {
540 stationInfo
.idTagsFile
&&
541 join(dirname(fileURLToPath(import.meta
.url
)), 'assets', basename(stationInfo
.idTagsFile
))
545 export const waitChargingStationEvents
= async (
546 emitter
: EventEmitter
,
547 event
: ChargingStationWorkerMessageEvents
,
548 eventsToWait
: number,
549 ): Promise
<number> => {
550 return new Promise
<number>((resolve
) => {
552 if (eventsToWait
=== 0) {
555 emitter
.on(event
, () => {
557 if (events
=== eventsToWait
) {
564 const getConfiguredNumberOfConnectors
= (stationTemplate
: ChargingStationTemplate
): number => {
565 let configuredMaxConnectors
= 0;
566 if (isNotEmptyArray(stationTemplate
.numberOfConnectors
) === true) {
567 const numberOfConnectors
= stationTemplate
.numberOfConnectors
as number[];
568 configuredMaxConnectors
=
569 numberOfConnectors
[Math.floor(secureRandom() * numberOfConnectors
.length
)];
570 } else if (isUndefined(stationTemplate
.numberOfConnectors
) === false) {
571 configuredMaxConnectors
= stationTemplate
.numberOfConnectors
as number;
572 } else if (stationTemplate
.Connectors
&& !stationTemplate
.Evses
) {
573 configuredMaxConnectors
= stationTemplate
.Connectors
[0]
574 ? getMaxNumberOfConnectors(stationTemplate
.Connectors
) - 1
575 : getMaxNumberOfConnectors(stationTemplate
.Connectors
);
576 } else if (stationTemplate
.Evses
&& !stationTemplate
.Connectors
) {
577 for (const evse
in stationTemplate
.Evses
) {
581 configuredMaxConnectors
+= getMaxNumberOfConnectors(stationTemplate
.Evses
[evse
].Connectors
);
584 return configuredMaxConnectors
;
587 const checkConfiguredMaxConnectors
= (
588 configuredMaxConnectors
: number,
590 templateFile
: string,
592 if (configuredMaxConnectors
<= 0) {
594 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`,
599 const checkTemplateMaxConnectors
= (
600 templateMaxConnectors
: number,
602 templateFile
: string,
604 if (templateMaxConnectors
=== 0) {
606 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`,
608 } else if (templateMaxConnectors
< 0) {
610 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`,
615 const initializeConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
616 connectorStatus
.availability
= AvailabilityType
.Operative
;
617 connectorStatus
.idTagLocalAuthorized
= false;
618 connectorStatus
.idTagAuthorized
= false;
619 connectorStatus
.transactionRemoteStarted
= false;
620 connectorStatus
.transactionStarted
= false;
621 connectorStatus
.energyActiveImportRegisterValue
= 0;
622 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
623 if (isUndefined(connectorStatus
.chargingProfiles
)) {
624 connectorStatus
.chargingProfiles
= [];
628 const warnDeprecatedTemplateKey
= (
629 template
: ChargingStationTemplate
,
632 templateFile
: string,
635 if (!isUndefined(template
[key
as keyof ChargingStationTemplate
])) {
636 const logMsg
= `Deprecated template key '${key}' usage in file '${templateFile}'${
637 isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}
` : ''
639 logger
.warn(`${logPrefix} ${logMsg}`);
640 console
.warn(chalk
.yellow(`${logMsg}`));
644 const convertDeprecatedTemplateKey
= (
645 template
: ChargingStationTemplate
,
646 deprecatedKey
: string,
649 if (!isUndefined(template
[deprecatedKey
as keyof ChargingStationTemplate
])) {
650 if (!isUndefined(key
)) {
651 (template
as unknown
as Record
<string, unknown
>)[key
!] =
652 template
[deprecatedKey
as keyof ChargingStationTemplate
];
654 delete template
[deprecatedKey
as keyof ChargingStationTemplate
];
659 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
661 * @param chargingProfiles -
665 const getLimitFromChargingProfiles
= (
666 chargingProfiles
: ChargingProfile
[],
671 matchingChargingProfile
: ChargingProfile
;
674 const debugLogMsg
= `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
675 const currentDate
= new Date();
676 for (const chargingProfile
of chargingProfiles
) {
678 const chargingSchedule
= chargingProfile
.chargingSchedule
;
679 if (!chargingSchedule
?.startSchedule
) {
681 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`,
684 if (!(chargingSchedule
?.startSchedule
instanceof Date)) {
686 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`,
688 chargingSchedule
.startSchedule
= convertToDate(chargingSchedule
.startSchedule
)!;
690 // Adjust recurring start schedule
691 if (chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
) {
692 switch (chargingProfile
.recurrencyKind
) {
693 case RecurrencyKindType
.DAILY
:
694 if (isYesterday(chargingSchedule
.startSchedule
)) {
695 addDays(chargingSchedule
.startSchedule
, 1);
698 case RecurrencyKindType
.WEEKLY
:
699 if (isBefore(chargingSchedule
.startSchedule
, startOfWeek(currentDate
))) {
700 addWeeks(chargingSchedule
.startSchedule
, 1);
705 // Check if the charging profile is active
707 isAfter(addSeconds(chargingSchedule
.startSchedule
, chargingSchedule
.duration
!), currentDate
)
709 let lastButOneSchedule
: ChargingSchedulePeriod
| undefined;
710 // Search the right schedule period
711 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
712 // Handling of only one period
714 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
715 schedulePeriod
.startPeriod
=== 0
718 limit
: schedulePeriod
.limit
,
719 matchingChargingProfile
: chargingProfile
,
721 logger
.debug(debugLogMsg
, result
);
724 // Find the right schedule period
727 addSeconds(chargingSchedule
.startSchedule
, schedulePeriod
.startPeriod
),
731 // Found the schedule: last but one is the correct one
733 limit
: lastButOneSchedule
!.limit
,
734 matchingChargingProfile
: chargingProfile
,
736 logger
.debug(debugLogMsg
, result
);
740 lastButOneSchedule
= schedulePeriod
;
741 // Handle the last schedule period
743 schedulePeriod
.startPeriod
===
744 chargingSchedule
.chargingSchedulePeriod
[
745 chargingSchedule
.chargingSchedulePeriod
.length
- 1
749 limit
: lastButOneSchedule
.limit
,
750 matchingChargingProfile
: chargingProfile
,
752 logger
.debug(debugLogMsg
, result
);
760 const getRandomSerialNumberSuffix
= (params
?: {
761 randomBytesLength
?: number;
764 const randomSerialNumberSuffix
= randomBytes(params
?.randomBytesLength
?? 16).toString('hex');
765 if (params
?.upperCase
) {
766 return randomSerialNumberSuffix
.toUpperCase();
768 return randomSerialNumberSuffix
;