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';
25 import type { ChargingStation
} from
'./ChargingStation';
26 import { BaseError
} from
'../exception';
30 type BootNotificationRequest
,
33 ChargingProfileKindType
,
35 type ChargingSchedulePeriod
,
36 type ChargingStationInfo
,
37 type ChargingStationTemplate
,
38 ChargingStationWorkerMessageEvents
,
39 ConnectorPhaseRotation
,
44 type OCPP16BootNotificationRequest
,
45 type OCPP20BootNotificationRequest
,
67 const moduleName
= 'ChargingStationUtils';
69 export const getChargingStationId
= (
71 stationTemplate
: ChargingStationTemplate
,
73 // In case of multiple instances: add instance index to charging station id
74 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
75 const idSuffix
= stationTemplate
?.nameSuffix
?? '';
76 const idStr
= `000000000${index.toString()}`;
77 return stationTemplate
?.fixedName
78 ? stationTemplate
.baseName
79 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
84 export const countReservableConnectors
= (connectors
: Map
<number, ConnectorStatus
>) => {
85 let reservableConnectors
= 0;
86 for (const [connectorId
, connectorStatus
] of connectors
) {
87 if (connectorId
=== 0) {
90 if (connectorStatus
.status === ConnectorStatusEnum
.Available
) {
91 ++reservableConnectors
;
94 return reservableConnectors
;
97 export const getHashId
= (index
: number, stationTemplate
: ChargingStationTemplate
): string => {
98 const chargingStationInfo
= {
99 chargePointModel
: stationTemplate
.chargePointModel
,
100 chargePointVendor
: stationTemplate
.chargePointVendor
,
101 ...(!isUndefined(stationTemplate
.chargeBoxSerialNumberPrefix
) && {
102 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
104 ...(!isUndefined(stationTemplate
.chargePointSerialNumberPrefix
) && {
105 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
107 ...(!isUndefined(stationTemplate
.meterSerialNumberPrefix
) && {
108 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
110 ...(!isUndefined(stationTemplate
.meterType
) && {
111 meterType
: stationTemplate
.meterType
,
114 return createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
115 .update(`${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`)
119 export const checkChargingStation
= (
120 chargingStation
: ChargingStation
,
123 if (chargingStation
.started
=== false && chargingStation
.starting
=== false) {
124 logger
.warn(`${logPrefix} charging station is stopped, cannot proceed`);
130 export const getPhaseRotationValue
= (
132 numberOfPhases
: number,
133 ): string | undefined => {
135 if (connectorId
=== 0 && numberOfPhases
=== 0) {
136 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
137 } else if (connectorId
> 0 && numberOfPhases
=== 0) {
138 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
140 } else if (connectorId
> 0 && numberOfPhases
=== 1) {
141 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
142 } else if (connectorId
> 0 && numberOfPhases
=== 3) {
143 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
147 export const getMaxNumberOfEvses
= (evses
: Record
<string, EvseTemplate
>): number => {
151 return Object.keys(evses
).length
;
154 const getMaxNumberOfConnectors
= (connectors
: Record
<string, ConnectorStatus
>): number => {
158 return Object.keys(connectors
).length
;
161 export const getBootConnectorStatus
= (
162 chargingStation
: ChargingStation
,
164 connectorStatus
: ConnectorStatus
,
165 ): ConnectorStatusEnum
=> {
166 let connectorBootStatus
: ConnectorStatusEnum
;
168 !connectorStatus
?.status &&
169 (chargingStation
.isChargingStationAvailable() === false ||
170 chargingStation
.isConnectorAvailable(connectorId
) === false)
172 connectorBootStatus
= ConnectorStatusEnum
.Unavailable
;
173 } else if (!connectorStatus
?.status && connectorStatus
?.bootStatus
) {
174 // Set boot status in template at startup
175 connectorBootStatus
= connectorStatus
?.bootStatus
;
176 } else if (connectorStatus
?.status) {
177 // Set previous status at startup
178 connectorBootStatus
= connectorStatus
?.status;
180 // Set default status
181 connectorBootStatus
= ConnectorStatusEnum
.Available
;
183 return connectorBootStatus
;
186 export const checkTemplate
= (
187 stationTemplate
: ChargingStationTemplate
,
189 templateFile
: string,
191 if (isNullOrUndefined(stationTemplate
)) {
192 const errorMsg
= `Failed to read charging station template file ${templateFile}`;
193 logger
.error(`${logPrefix} ${errorMsg}`);
194 throw new BaseError(errorMsg
);
196 if (isEmptyObject(stationTemplate
)) {
197 const errorMsg
= `Empty charging station information from template file ${templateFile}`;
198 logger
.error(`${logPrefix} ${errorMsg}`);
199 throw new BaseError(errorMsg
);
201 if (isEmptyObject(stationTemplate
.AutomaticTransactionGenerator
!)) {
202 stationTemplate
.AutomaticTransactionGenerator
= Constants
.DEFAULT_ATG_CONFIGURATION
;
204 `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
205 Constants
.DEFAULT_ATG_CONFIGURATION
,
208 if (isNullOrUndefined(stationTemplate
.idTagsFile
) || isEmptyString(stationTemplate
.idTagsFile
)) {
210 `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`,
215 export const checkConnectorsConfiguration
= (
216 stationTemplate
: ChargingStationTemplate
,
218 templateFile
: string,
220 configuredMaxConnectors
: number;
221 templateMaxConnectors
: number;
222 templateMaxAvailableConnectors
: number;
224 const configuredMaxConnectors
= getConfiguredNumberOfConnectors(stationTemplate
);
225 checkConfiguredMaxConnectors(configuredMaxConnectors
, logPrefix
, templateFile
);
226 const templateMaxConnectors
= getMaxNumberOfConnectors(stationTemplate
.Connectors
!);
227 checkTemplateMaxConnectors(templateMaxConnectors
, logPrefix
, templateFile
);
228 const templateMaxAvailableConnectors
= stationTemplate
.Connectors
![0]
229 ? templateMaxConnectors
- 1
230 : templateMaxConnectors
;
232 configuredMaxConnectors
> templateMaxAvailableConnectors
&&
233 !stationTemplate
?.randomConnectors
236 `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`,
238 stationTemplate
.randomConnectors
= true;
240 return { configuredMaxConnectors
, templateMaxConnectors
, templateMaxAvailableConnectors
};
243 export const checkStationInfoConnectorStatus
= (
245 connectorStatus
: ConnectorStatus
,
247 templateFile
: string,
249 if (!isNullOrUndefined(connectorStatus
?.status)) {
251 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`,
253 delete connectorStatus
.status;
257 export const buildConnectorsMap
= (
258 connectors
: Record
<string, ConnectorStatus
>,
260 templateFile
: string,
261 ): Map
<number, ConnectorStatus
> => {
262 const connectorsMap
= new Map
<number, ConnectorStatus
>();
263 if (getMaxNumberOfConnectors(connectors
) > 0) {
264 for (const connector
in connectors
) {
265 const connectorStatus
= connectors
[connector
];
266 const connectorId
= convertToInt(connector
);
267 checkStationInfoConnectorStatus(connectorId
, connectorStatus
, logPrefix
, templateFile
);
268 connectorsMap
.set(connectorId
, cloneObject
<ConnectorStatus
>(connectorStatus
));
272 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`,
275 return connectorsMap
;
278 export const initializeConnectorsMapStatus
= (
279 connectors
: Map
<number, ConnectorStatus
>,
282 for (const connectorId
of connectors
.keys()) {
283 if (connectorId
> 0 && connectors
.get(connectorId
)?.transactionStarted
=== true) {
285 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${connectors.get(
290 if (connectorId
=== 0) {
291 connectors
.get(connectorId
)!.availability
= AvailabilityType
.Operative
;
292 if (isUndefined(connectors
.get(connectorId
)?.chargingProfiles
)) {
293 connectors
.get(connectorId
)!.chargingProfiles
= [];
297 isNullOrUndefined(connectors
.get(connectorId
)?.transactionStarted
)
299 initializeConnectorStatus(connectors
.get(connectorId
)!);
304 export const resetConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
305 connectorStatus
.idTagLocalAuthorized
= false;
306 connectorStatus
.idTagAuthorized
= false;
307 connectorStatus
.transactionRemoteStarted
= false;
308 connectorStatus
.transactionStarted
= false;
309 delete connectorStatus
?.localAuthorizeIdTag
;
310 delete connectorStatus
?.authorizeIdTag
;
311 delete connectorStatus
?.transactionId
;
312 delete connectorStatus
?.transactionIdTag
;
313 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
314 delete connectorStatus
?.transactionBeginMeterValue
;
317 export const createBootNotificationRequest
= (
318 stationInfo
: ChargingStationInfo
,
319 bootReason
: BootReasonEnumType
= BootReasonEnumType
.PowerUp
,
320 ): BootNotificationRequest
=> {
321 const ocppVersion
= stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
;
322 switch (ocppVersion
) {
323 case OCPPVersion
.VERSION_16
:
325 chargePointModel
: stationInfo
.chargePointModel
,
326 chargePointVendor
: stationInfo
.chargePointVendor
,
327 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
328 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
330 ...(!isUndefined(stationInfo
.chargePointSerialNumber
) && {
331 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
333 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
334 firmwareVersion
: stationInfo
.firmwareVersion
,
336 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
337 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
338 ...(!isUndefined(stationInfo
.meterSerialNumber
) && {
339 meterSerialNumber
: stationInfo
.meterSerialNumber
,
341 ...(!isUndefined(stationInfo
.meterType
) && {
342 meterType
: stationInfo
.meterType
,
344 } as OCPP16BootNotificationRequest
;
345 case OCPPVersion
.VERSION_20
:
346 case OCPPVersion
.VERSION_201
:
350 model
: stationInfo
.chargePointModel
,
351 vendorName
: stationInfo
.chargePointVendor
,
352 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
353 firmwareVersion
: stationInfo
.firmwareVersion
,
355 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
356 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
358 ...((!isUndefined(stationInfo
.iccid
) || !isUndefined(stationInfo
.imsi
)) && {
360 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
361 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
365 } as OCPP20BootNotificationRequest
;
369 export const warnTemplateKeysDeprecation
= (
370 stationTemplate
: ChargingStationTemplate
,
372 templateFile
: string,
374 const templateKeys
: { deprecatedKey
: string; key
?: string }[] = [
375 { deprecatedKey
: 'supervisionUrl', key
: 'supervisionUrls' },
376 { deprecatedKey
: 'authorizationFile', key
: 'idTagsFile' },
377 { deprecatedKey
: 'payloadSchemaValidation', key
: 'ocppStrictCompliance' },
379 for (const templateKey
of templateKeys
) {
380 warnDeprecatedTemplateKey(
382 templateKey
.deprecatedKey
,
385 !isUndefined(templateKey
.key
) ? `Use '${templateKey.key}' instead` : undefined,
387 convertDeprecatedTemplateKey(stationTemplate
, templateKey
.deprecatedKey
, templateKey
.key
);
391 export const stationTemplateToStationInfo
= (
392 stationTemplate
: ChargingStationTemplate
,
393 ): ChargingStationInfo
=> {
394 stationTemplate
= cloneObject
<ChargingStationTemplate
>(stationTemplate
);
395 delete stationTemplate
.power
;
396 delete stationTemplate
.powerUnit
;
397 delete stationTemplate
.Connectors
;
398 delete stationTemplate
.Evses
;
399 delete stationTemplate
.Configuration
;
400 delete stationTemplate
.AutomaticTransactionGenerator
;
401 delete stationTemplate
.chargeBoxSerialNumberPrefix
;
402 delete stationTemplate
.chargePointSerialNumberPrefix
;
403 delete stationTemplate
.meterSerialNumberPrefix
;
404 return stationTemplate
as unknown
as ChargingStationInfo
;
407 export const createSerialNumber
= (
408 stationTemplate
: ChargingStationTemplate
,
409 stationInfo
: ChargingStationInfo
,
411 randomSerialNumberUpperCase
?: boolean;
412 randomSerialNumber
?: boolean;
414 randomSerialNumberUpperCase
: true,
415 randomSerialNumber
: true,
418 params
= { ...{ randomSerialNumberUpperCase
: true, randomSerialNumber
: true }, ...params
};
419 const serialNumberSuffix
= params
?.randomSerialNumber
420 ? getRandomSerialNumberSuffix({
421 upperCase
: params
.randomSerialNumberUpperCase
,
424 isNotEmptyString(stationTemplate
?.chargePointSerialNumberPrefix
) &&
425 (stationInfo
.chargePointSerialNumber
= `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`);
426 isNotEmptyString(stationTemplate
?.chargeBoxSerialNumberPrefix
) &&
427 (stationInfo
.chargeBoxSerialNumber
= `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`);
428 isNotEmptyString(stationTemplate
?.meterSerialNumberPrefix
) &&
429 (stationInfo
.meterSerialNumber
= `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`);
432 export const propagateSerialNumber
= (
433 stationTemplate
: ChargingStationTemplate
,
434 stationInfoSrc
: ChargingStationInfo
,
435 stationInfoDst
: ChargingStationInfo
,
437 if (!stationInfoSrc
|| !stationTemplate
) {
439 'Missing charging station template or existing configuration to propagate serial number',
442 stationTemplate
?.chargePointSerialNumberPrefix
&& stationInfoSrc
?.chargePointSerialNumber
443 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
444 : stationInfoDst
?.chargePointSerialNumber
&& delete stationInfoDst
.chargePointSerialNumber
;
445 stationTemplate
?.chargeBoxSerialNumberPrefix
&& stationInfoSrc
?.chargeBoxSerialNumber
446 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
447 : stationInfoDst
?.chargeBoxSerialNumber
&& delete stationInfoDst
.chargeBoxSerialNumber
;
448 stationTemplate
?.meterSerialNumberPrefix
&& stationInfoSrc
?.meterSerialNumber
449 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
450 : stationInfoDst
?.meterSerialNumber
&& delete stationInfoDst
.meterSerialNumber
;
453 export const getAmperageLimitationUnitDivider
= (stationInfo
: ChargingStationInfo
): number => {
455 switch (stationInfo
.amperageLimitationUnit
) {
456 case AmpereUnits
.DECI_AMPERE
:
459 case AmpereUnits
.CENTI_AMPERE
:
462 case AmpereUnits
.MILLI_AMPERE
:
469 export const getChargingStationConnectorChargingProfilesPowerLimit
= (
470 chargingStation
: ChargingStation
,
472 ): number | undefined => {
473 let limit
: number | undefined, matchingChargingProfile
: ChargingProfile
| undefined;
474 // Get charging profiles for connector and sort by stack level
475 const chargingProfiles
=
476 cloneObject
<ChargingProfile
[]>(
477 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
!,
478 )?.sort((a
, b
) => b
.stackLevel
- a
.stackLevel
) ?? [];
479 // Get profiles on connector 0
480 if (chargingStation
.getConnectorStatus(0)?.chargingProfiles
) {
481 chargingProfiles
.push(
482 ...cloneObject
<ChargingProfile
[]>(
483 chargingStation
.getConnectorStatus(0)!.chargingProfiles
!,
484 ).sort((a
, b
) => b
.stackLevel
- a
.stackLevel
),
487 if (isNotEmptyArray(chargingProfiles
)) {
488 const result
= getLimitFromChargingProfiles(chargingProfiles
, chargingStation
.logPrefix());
489 if (!isNullOrUndefined(result
)) {
490 limit
= result
?.limit
;
491 matchingChargingProfile
= result
?.matchingChargingProfile
;
492 switch (chargingStation
.getCurrentOutType()) {
495 matchingChargingProfile
?.chargingSchedule
?.chargingRateUnit
===
496 ChargingRateUnitType
.WATT
498 : ACElectricUtils
.powerTotal(
499 chargingStation
.getNumberOfPhases(),
500 chargingStation
.getVoltageOut(),
506 matchingChargingProfile
?.chargingSchedule
?.chargingRateUnit
===
507 ChargingRateUnitType
.WATT
509 : DCElectricUtils
.power(chargingStation
.getVoltageOut(), limit
!);
511 const connectorMaximumPower
=
512 chargingStation
.getMaximumPower() / chargingStation
.powerDivider
;
513 if (limit
! > connectorMaximumPower
) {
515 `${chargingStation.logPrefix()} Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
518 limit
= connectorMaximumPower
;
525 export const getDefaultVoltageOut
= (
526 currentType
: CurrentType
,
528 templateFile
: string,
530 const errorMsg
= `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
531 let defaultVoltageOut
: number;
532 switch (currentType
) {
534 defaultVoltageOut
= Voltage
.VOLTAGE_230
;
537 defaultVoltageOut
= Voltage
.VOLTAGE_400
;
540 logger
.error(`${logPrefix} ${errorMsg}`);
541 throw new BaseError(errorMsg
);
543 return defaultVoltageOut
;
546 export const getIdTagsFile
= (stationInfo
: ChargingStationInfo
): string | undefined => {
548 stationInfo
.idTagsFile
&&
549 join(dirname(fileURLToPath(import.meta
.url
)), 'assets', basename(stationInfo
.idTagsFile
))
553 export const waitChargingStationEvents
= async (
554 emitter
: EventEmitter
,
555 event
: ChargingStationWorkerMessageEvents
,
556 eventsToWait
: number,
557 ): Promise
<number> => {
558 return new Promise
<number>((resolve
) => {
560 if (eventsToWait
=== 0) {
563 emitter
.on(event
, () => {
565 if (events
=== eventsToWait
) {
572 const getConfiguredNumberOfConnectors
= (stationTemplate
: ChargingStationTemplate
): number => {
573 let configuredMaxConnectors
= 0;
574 if (isNotEmptyArray(stationTemplate
.numberOfConnectors
) === true) {
575 const numberOfConnectors
= stationTemplate
.numberOfConnectors
as number[];
576 configuredMaxConnectors
=
577 numberOfConnectors
[Math.floor(secureRandom() * numberOfConnectors
.length
)];
578 } else if (isUndefined(stationTemplate
.numberOfConnectors
) === false) {
579 configuredMaxConnectors
= stationTemplate
.numberOfConnectors
as number;
580 } else if (stationTemplate
.Connectors
&& !stationTemplate
.Evses
) {
581 configuredMaxConnectors
= stationTemplate
.Connectors
[0]
582 ? getMaxNumberOfConnectors(stationTemplate
.Connectors
) - 1
583 : getMaxNumberOfConnectors(stationTemplate
.Connectors
);
584 } else if (stationTemplate
.Evses
&& !stationTemplate
.Connectors
) {
585 for (const evse
in stationTemplate
.Evses
) {
589 configuredMaxConnectors
+= getMaxNumberOfConnectors(stationTemplate
.Evses
[evse
].Connectors
);
592 return configuredMaxConnectors
;
595 const checkConfiguredMaxConnectors
= (
596 configuredMaxConnectors
: number,
598 templateFile
: string,
600 if (configuredMaxConnectors
<= 0) {
602 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`,
607 const checkTemplateMaxConnectors
= (
608 templateMaxConnectors
: number,
610 templateFile
: string,
612 if (templateMaxConnectors
=== 0) {
614 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`,
616 } else if (templateMaxConnectors
< 0) {
618 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`,
623 const initializeConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
624 connectorStatus
.availability
= AvailabilityType
.Operative
;
625 connectorStatus
.idTagLocalAuthorized
= false;
626 connectorStatus
.idTagAuthorized
= false;
627 connectorStatus
.transactionRemoteStarted
= false;
628 connectorStatus
.transactionStarted
= false;
629 connectorStatus
.energyActiveImportRegisterValue
= 0;
630 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
631 if (isUndefined(connectorStatus
.chargingProfiles
)) {
632 connectorStatus
.chargingProfiles
= [];
636 const warnDeprecatedTemplateKey
= (
637 template
: ChargingStationTemplate
,
640 templateFile
: string,
643 if (!isUndefined(template
[key
as keyof ChargingStationTemplate
])) {
644 const logMsg
= `Deprecated template key '${key}' usage in file '${templateFile}'${
645 isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}
` : ''
647 logger
.warn(`${logPrefix} ${logMsg}`);
648 console
.warn(chalk
.yellow(`${logMsg}`));
652 const convertDeprecatedTemplateKey
= (
653 template
: ChargingStationTemplate
,
654 deprecatedKey
: string,
657 if (!isUndefined(template
[deprecatedKey
as keyof ChargingStationTemplate
])) {
658 if (!isUndefined(key
)) {
659 (template
as unknown
as Record
<string, unknown
>)[key
!] =
660 template
[deprecatedKey
as keyof ChargingStationTemplate
];
662 delete template
[deprecatedKey
as keyof ChargingStationTemplate
];
667 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
669 * @param chargingProfiles -
673 const getLimitFromChargingProfiles
= (
674 chargingProfiles
: ChargingProfile
[],
679 matchingChargingProfile
: ChargingProfile
;
682 const debugLogMsg
= `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
683 const currentDate
= new Date();
684 for (const chargingProfile
of chargingProfiles
) {
686 const chargingSchedule
= chargingProfile
.chargingSchedule
;
687 if (!chargingSchedule
?.startSchedule
) {
689 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`,
692 if (!(chargingSchedule
?.startSchedule
instanceof Date)) {
694 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`,
696 chargingSchedule
.startSchedule
= convertToDate(chargingSchedule
.startSchedule
)!;
698 // Adjust recurring schedule
699 if (chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
) {
700 switch (chargingProfile
.recurrencyKind
) {
701 case RecurrencyKindType
.DAILY
:
702 if (isYesterday(chargingSchedule
.startSchedule
)) {
703 addDays(chargingSchedule
.startSchedule
, 1);
704 } else if (isTomorrow(chargingSchedule
.startSchedule
)) {
705 subDays(chargingSchedule
.startSchedule
, 1);
708 case RecurrencyKindType
.WEEKLY
:
709 if (isBefore(chargingSchedule
.startSchedule
, startOfWeek(currentDate
))) {
710 addWeeks(chargingSchedule
.startSchedule
, 1);
711 } else if (isAfter(chargingSchedule
.startSchedule
, endOfWeek(currentDate
))) {
712 subWeeks(chargingSchedule
.startSchedule
, 1);
715 case RecurrencyKindType
.MONTHLY
:
716 if (isBefore(chargingSchedule
.startSchedule
, startOfMonth(currentDate
))) {
717 addMonths(chargingSchedule
.startSchedule
, 1);
718 } else if (isAfter(chargingSchedule
.startSchedule
, endOfMonth(currentDate
))) {
719 subMonths(chargingSchedule
.startSchedule
, 1);
724 // Check if the charging profile is active
726 isAfter(addSeconds(chargingSchedule
.startSchedule
, chargingSchedule
.duration
!), currentDate
)
728 let lastButOneSchedule
: ChargingSchedulePeriod
| undefined;
729 // Search the right schedule period
730 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
731 // Handling of only one period
733 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
734 schedulePeriod
.startPeriod
=== 0
737 limit
: schedulePeriod
.limit
,
738 matchingChargingProfile
: chargingProfile
,
740 logger
.debug(debugLogMsg
, result
);
743 // Find the right schedule period
746 addSeconds(chargingSchedule
.startSchedule
, schedulePeriod
.startPeriod
),
750 // Found the schedule: last but one is the correct one
752 limit
: lastButOneSchedule
!.limit
,
753 matchingChargingProfile
: chargingProfile
,
755 logger
.debug(debugLogMsg
, result
);
759 lastButOneSchedule
= schedulePeriod
;
760 // Handle the last schedule period
762 schedulePeriod
.startPeriod
===
763 chargingSchedule
.chargingSchedulePeriod
[
764 chargingSchedule
.chargingSchedulePeriod
.length
- 1
768 limit
: lastButOneSchedule
.limit
,
769 matchingChargingProfile
: chargingProfile
,
771 logger
.debug(debugLogMsg
, result
);
779 const getRandomSerialNumberSuffix
= (params
?: {
780 randomBytesLength
?: number;
783 const randomSerialNumberSuffix
= randomBytes(params
?.randomBytesLength
?? 16).toString('hex');
784 if (params
?.upperCase
) {
785 return randomSerialNumberSuffix
.toUpperCase();
787 return randomSerialNumberSuffix
;