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 moment from
'moment';
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
,
50 const moduleName
= 'ChargingStationUtils';
52 export class ChargingStationUtils
{
53 private constructor() {
54 // This is intentional
57 public static getChargingStationId(
59 stationTemplate
: ChargingStationTemplate
61 // In case of multiple instances: add instance index to charging station id
62 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
63 const idSuffix
= stationTemplate
?.nameSuffix
?? '';
64 const idStr
= `000000000${index.toString()}`;
65 return stationTemplate
?.fixedName
66 ? stationTemplate
.baseName
67 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
72 public static countReservableConnectors(connectors
: Map
<number, ConnectorStatus
>) {
73 let reservableConnectors
= 0;
74 for (const [connectorId
, connectorStatus
] of connectors
) {
75 if (connectorId
=== 0) {
78 if (connectorStatus
.status === ConnectorStatusEnum
.Available
) {
79 ++reservableConnectors
;
82 return reservableConnectors
;
85 public static getHashId(index
: number, stationTemplate
: ChargingStationTemplate
): string {
86 const chargingStationInfo
= {
87 chargePointModel
: stationTemplate
.chargePointModel
,
88 chargePointVendor
: stationTemplate
.chargePointVendor
,
89 ...(!isUndefined(stationTemplate
.chargeBoxSerialNumberPrefix
) && {
90 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
92 ...(!isUndefined(stationTemplate
.chargePointSerialNumberPrefix
) && {
93 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
95 ...(!isUndefined(stationTemplate
.meterSerialNumberPrefix
) && {
96 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
98 ...(!isUndefined(stationTemplate
.meterType
) && {
99 meterType
: stationTemplate
.meterType
,
102 return createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
104 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
112 public static checkChargingStation(chargingStation
: ChargingStation
, logPrefix
: string): boolean {
113 if (chargingStation
.started
=== false && chargingStation
.starting
=== false) {
114 logger
.warn(`${logPrefix} charging station is stopped, cannot proceed`);
120 public static getPhaseRotationValue(
122 numberOfPhases
: number
123 ): string | undefined {
125 if (connectorId
=== 0 && numberOfPhases
=== 0) {
126 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
127 } else if (connectorId
> 0 && numberOfPhases
=== 0) {
128 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
130 } else if (connectorId
> 0 && numberOfPhases
=== 1) {
131 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
132 } else if (connectorId
> 0 && numberOfPhases
=== 3) {
133 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
137 public static getMaxNumberOfEvses(evses
: Record
<string, EvseTemplate
>): number {
141 return Object.keys(evses
).length
;
144 public static getMaxNumberOfConnectors(connectors
: Record
<string, ConnectorStatus
>): number {
148 return Object.keys(connectors
).length
;
151 public static getBootConnectorStatus(
152 chargingStation
: ChargingStation
,
154 connectorStatus
: ConnectorStatus
155 ): ConnectorStatusEnum
{
156 let connectorBootStatus
: ConnectorStatusEnum
;
158 !connectorStatus
?.status &&
159 (chargingStation
.isChargingStationAvailable() === false ||
160 chargingStation
.isConnectorAvailable(connectorId
) === false)
162 connectorBootStatus
= ConnectorStatusEnum
.Unavailable
;
163 } else if (!connectorStatus
?.status && connectorStatus
?.bootStatus
) {
164 // Set boot status in template at startup
165 connectorBootStatus
= connectorStatus
?.bootStatus
;
166 } else if (connectorStatus
?.status) {
167 // Set previous status at startup
168 connectorBootStatus
= connectorStatus
?.status;
170 // Set default status
171 connectorBootStatus
= ConnectorStatusEnum
.Available
;
173 return connectorBootStatus
;
176 public static checkTemplate(
177 stationTemplate
: ChargingStationTemplate
,
181 if (isNullOrUndefined(stationTemplate
)) {
182 const errorMsg
= `Failed to read charging station template file ${templateFile}`;
183 logger
.error(`${logPrefix} ${errorMsg}`);
184 throw new BaseError(errorMsg
);
186 if (isEmptyObject(stationTemplate
)) {
187 const errorMsg
= `Empty charging station information from template file ${templateFile}`;
188 logger
.error(`${logPrefix} ${errorMsg}`);
189 throw new BaseError(errorMsg
);
191 if (isEmptyObject(stationTemplate
.AutomaticTransactionGenerator
)) {
192 stationTemplate
.AutomaticTransactionGenerator
= Constants
.DEFAULT_ATG_CONFIGURATION
;
194 `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
195 Constants
.DEFAULT_ATG_CONFIGURATION
199 isNullOrUndefined(stationTemplate
.idTagsFile
) ||
200 isEmptyString(stationTemplate
.idTagsFile
)
203 `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`
208 public static checkConnectorsConfiguration(
209 stationTemplate
: ChargingStationTemplate
,
213 configuredMaxConnectors
: number;
214 templateMaxConnectors
: number;
215 templateMaxAvailableConnectors
: number;
217 const configuredMaxConnectors
=
218 ChargingStationUtils
.getConfiguredNumberOfConnectors(stationTemplate
);
219 ChargingStationUtils
.checkConfiguredMaxConnectors(
220 configuredMaxConnectors
,
224 const templateMaxConnectors
= ChargingStationUtils
.getMaxNumberOfConnectors(
225 stationTemplate
.Connectors
227 ChargingStationUtils
.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 public static checkStationInfoConnectorStatus(
245 connectorStatus
: ConnectorStatus
,
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 public static buildConnectorsMap(
258 connectors
: Record
<string, ConnectorStatus
>,
261 ): Map
<number, ConnectorStatus
> {
262 const connectorsMap
= new Map
<number, ConnectorStatus
>();
263 if (ChargingStationUtils
.getMaxNumberOfConnectors(connectors
) > 0) {
264 for (const connector
in connectors
) {
265 const connectorStatus
= connectors
[connector
];
266 const connectorId
= convertToInt(connector
);
267 ChargingStationUtils
.checkStationInfoConnectorStatus(
273 connectorsMap
.set(connectorId
, cloneObject
<ConnectorStatus
>(connectorStatus
));
277 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
280 return connectorsMap
;
283 public static initializeConnectorsMapStatus(
284 connectors
: Map
<number, ConnectorStatus
>,
287 for (const connectorId
of connectors
.keys()) {
288 if (connectorId
> 0 && connectors
.get(connectorId
)?.transactionStarted
=== true) {
290 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
291 connectors.get(connectorId)?.transactionId
295 if (connectorId
=== 0) {
296 connectors
.get(connectorId
).availability
= AvailabilityType
.Operative
;
297 if (isUndefined(connectors
.get(connectorId
)?.chargingProfiles
)) {
298 connectors
.get(connectorId
).chargingProfiles
= [];
302 isNullOrUndefined(connectors
.get(connectorId
)?.transactionStarted
)
304 ChargingStationUtils
.initializeConnectorStatus(connectors
.get(connectorId
));
309 public static resetConnectorStatus(connectorStatus
: ConnectorStatus
): void {
310 connectorStatus
.idTagLocalAuthorized
= false;
311 connectorStatus
.idTagAuthorized
= false;
312 connectorStatus
.transactionRemoteStarted
= false;
313 connectorStatus
.transactionStarted
= false;
314 delete connectorStatus
?.localAuthorizeIdTag
;
315 delete connectorStatus
?.authorizeIdTag
;
316 delete connectorStatus
?.transactionId
;
317 delete connectorStatus
?.transactionIdTag
;
318 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
319 delete connectorStatus
?.transactionBeginMeterValue
;
322 public static createBootNotificationRequest(
323 stationInfo
: ChargingStationInfo
,
324 bootReason
: BootReasonEnumType
= BootReasonEnumType
.PowerUp
325 ): BootNotificationRequest
{
326 const ocppVersion
= stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
;
327 switch (ocppVersion
) {
328 case OCPPVersion
.VERSION_16
:
330 chargePointModel
: stationInfo
.chargePointModel
,
331 chargePointVendor
: stationInfo
.chargePointVendor
,
332 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
333 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
335 ...(!isUndefined(stationInfo
.chargePointSerialNumber
) && {
336 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
338 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
339 firmwareVersion
: stationInfo
.firmwareVersion
,
341 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
342 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
343 ...(!isUndefined(stationInfo
.meterSerialNumber
) && {
344 meterSerialNumber
: stationInfo
.meterSerialNumber
,
346 ...(!isUndefined(stationInfo
.meterType
) && {
347 meterType
: stationInfo
.meterType
,
349 } as OCPP16BootNotificationRequest
;
350 case OCPPVersion
.VERSION_20
:
351 case OCPPVersion
.VERSION_201
:
355 model
: stationInfo
.chargePointModel
,
356 vendorName
: stationInfo
.chargePointVendor
,
357 ...(!isUndefined(stationInfo
.firmwareVersion
) && {
358 firmwareVersion
: stationInfo
.firmwareVersion
,
360 ...(!isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
361 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
363 ...((!isUndefined(stationInfo
.iccid
) || !isUndefined(stationInfo
.imsi
)) && {
365 ...(!isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
366 ...(!isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
370 } as OCPP20BootNotificationRequest
;
374 public static warnTemplateKeysDeprecation(
375 stationTemplate
: ChargingStationTemplate
,
379 const templateKeys
: { key
: string; deprecatedKey
: string }[] = [
380 { key
: 'supervisionUrls', deprecatedKey
: 'supervisionUrl' },
381 { key
: 'idTagsFile', deprecatedKey
: 'authorizationFile' },
383 for (const templateKey
of templateKeys
) {
384 ChargingStationUtils
.warnDeprecatedTemplateKey(
386 templateKey
.deprecatedKey
,
389 `Use '${templateKey.key}' instead`
391 ChargingStationUtils
.convertDeprecatedTemplateKey(
393 templateKey
.deprecatedKey
,
399 public static stationTemplateToStationInfo(
400 stationTemplate
: ChargingStationTemplate
401 ): ChargingStationInfo
{
402 stationTemplate
= cloneObject
<ChargingStationTemplate
>(stationTemplate
);
403 delete stationTemplate
.power
;
404 delete stationTemplate
.powerUnit
;
405 delete stationTemplate
?.Connectors
;
406 delete stationTemplate
?.Evses
;
407 delete stationTemplate
.Configuration
;
408 delete stationTemplate
.AutomaticTransactionGenerator
;
409 delete stationTemplate
.chargeBoxSerialNumberPrefix
;
410 delete stationTemplate
.chargePointSerialNumberPrefix
;
411 delete stationTemplate
.meterSerialNumberPrefix
;
412 return stationTemplate
as unknown
as ChargingStationInfo
;
415 public static createSerialNumber(
416 stationTemplate
: ChargingStationTemplate
,
417 stationInfo
: ChargingStationInfo
,
419 randomSerialNumberUpperCase
?: boolean;
420 randomSerialNumber
?: boolean;
422 randomSerialNumberUpperCase
: true,
423 randomSerialNumber
: true,
426 params
= { ...{ randomSerialNumberUpperCase
: true, randomSerialNumber
: true }, ...params
};
427 const serialNumberSuffix
= params
?.randomSerialNumber
428 ? ChargingStationUtils
.getRandomSerialNumberSuffix({
429 upperCase
: params
.randomSerialNumberUpperCase
,
432 isNotEmptyString(stationTemplate
?.chargePointSerialNumberPrefix
) &&
433 (stationInfo
.chargePointSerialNumber
= `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`);
434 isNotEmptyString(stationTemplate
?.chargeBoxSerialNumberPrefix
) &&
435 (stationInfo
.chargeBoxSerialNumber
= `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`);
436 isNotEmptyString(stationTemplate
?.meterSerialNumberPrefix
) &&
437 (stationInfo
.meterSerialNumber
= `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`);
440 public static propagateSerialNumber(
441 stationTemplate
: ChargingStationTemplate
,
442 stationInfoSrc
: ChargingStationInfo
,
443 stationInfoDst
: ChargingStationInfo
445 if (!stationInfoSrc
|| !stationTemplate
) {
447 'Missing charging station template or existing configuration to propagate serial number'
450 stationTemplate
?.chargePointSerialNumberPrefix
&& stationInfoSrc
?.chargePointSerialNumber
451 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
452 : stationInfoDst
?.chargePointSerialNumber
&& delete stationInfoDst
.chargePointSerialNumber
;
453 stationTemplate
?.chargeBoxSerialNumberPrefix
&& stationInfoSrc
?.chargeBoxSerialNumber
454 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
455 : stationInfoDst
?.chargeBoxSerialNumber
&& delete stationInfoDst
.chargeBoxSerialNumber
;
456 stationTemplate
?.meterSerialNumberPrefix
&& stationInfoSrc
?.meterSerialNumber
457 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
458 : stationInfoDst
?.meterSerialNumber
&& delete stationInfoDst
.meterSerialNumber
;
461 public static getAmperageLimitationUnitDivider(stationInfo
: ChargingStationInfo
): number {
463 switch (stationInfo
.amperageLimitationUnit
) {
464 case AmpereUnits
.DECI_AMPERE
:
467 case AmpereUnits
.CENTI_AMPERE
:
470 case AmpereUnits
.MILLI_AMPERE
:
477 public static getChargingStationConnectorChargingProfilesPowerLimit(
478 chargingStation
: ChargingStation
,
480 ): number | undefined {
481 let limit
: number, matchingChargingProfile
: ChargingProfile
;
482 // Get charging profiles for connector and sort by stack level
483 const chargingProfiles
=
484 cloneObject
<ChargingProfile
[]>(
485 chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
486 )?.sort((a
, b
) => b
.stackLevel
- a
.stackLevel
) ?? [];
487 // Get profiles on connector 0
488 if (chargingStation
.getConnectorStatus(0)?.chargingProfiles
) {
489 chargingProfiles
.push(
490 ...cloneObject
<ChargingProfile
[]>(
491 chargingStation
.getConnectorStatus(0).chargingProfiles
492 ).sort((a
, b
) => b
.stackLevel
- a
.stackLevel
)
495 if (isNotEmptyArray(chargingProfiles
)) {
496 const result
= ChargingStationUtils
.getLimitFromChargingProfiles(
498 chargingStation
.logPrefix()
500 if (!isNullOrUndefined(result
)) {
501 limit
= result
?.limit
;
502 matchingChargingProfile
= result
?.matchingChargingProfile
;
503 switch (chargingStation
.getCurrentOutType()) {
506 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
507 ChargingRateUnitType
.WATT
509 : ACElectricUtils
.powerTotal(
510 chargingStation
.getNumberOfPhases(),
511 chargingStation
.getVoltageOut(),
517 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
518 ChargingRateUnitType
.WATT
520 : DCElectricUtils
.power(chargingStation
.getVoltageOut(), limit
);
522 const connectorMaximumPower
=
523 chargingStation
.getMaximumPower() / chargingStation
.powerDivider
;
524 if (limit
> connectorMaximumPower
) {
526 `${chargingStation.logPrefix()} Charging profile id ${
527 matchingChargingProfile.chargingProfileId
528 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
531 limit
= connectorMaximumPower
;
538 public static getDefaultVoltageOut(
539 currentType
: CurrentType
,
543 const errorMsg
= `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
544 let defaultVoltageOut
: number;
545 switch (currentType
) {
547 defaultVoltageOut
= Voltage
.VOLTAGE_230
;
550 defaultVoltageOut
= Voltage
.VOLTAGE_400
;
553 logger
.error(`${logPrefix} ${errorMsg}`);
554 throw new BaseError(errorMsg
);
556 return defaultVoltageOut
;
559 public static getIdTagsFile(stationInfo
: ChargingStationInfo
): string | undefined {
561 stationInfo
.idTagsFile
&&
562 join(dirname(fileURLToPath(import.meta
.url
)), 'assets', basename(stationInfo
.idTagsFile
))
566 public static waitForChargingStationEvents
= async (
567 emitter
: EventEmitter
,
568 event
: ChargingStationWorkerMessageEvents
,
570 ): Promise
<number> => {
571 return new Promise((resolve
) => {
573 if (eventsToWait
=== 0) {
576 emitter
.on(event
, () => {
578 if (events
=== eventsToWait
) {
585 private static getConfiguredNumberOfConnectors(stationTemplate
: ChargingStationTemplate
): number {
586 let configuredMaxConnectors
: number;
587 if (isNotEmptyArray(stationTemplate
.numberOfConnectors
) === true) {
588 const numberOfConnectors
= stationTemplate
.numberOfConnectors
as number[];
589 configuredMaxConnectors
=
590 numberOfConnectors
[Math.floor(secureRandom() * numberOfConnectors
.length
)];
591 } else if (isUndefined(stationTemplate
.numberOfConnectors
) === false) {
592 configuredMaxConnectors
= stationTemplate
.numberOfConnectors
as number;
593 } else if (stationTemplate
.Connectors
&& !stationTemplate
.Evses
) {
594 configuredMaxConnectors
= stationTemplate
?.Connectors
[0]
595 ? ChargingStationUtils
.getMaxNumberOfConnectors(stationTemplate
.Connectors
) - 1
596 : ChargingStationUtils
.getMaxNumberOfConnectors(stationTemplate
.Connectors
);
597 } else if (stationTemplate
.Evses
&& !stationTemplate
.Connectors
) {
598 configuredMaxConnectors
= 0;
599 for (const evse
in stationTemplate
.Evses
) {
603 configuredMaxConnectors
+= ChargingStationUtils
.getMaxNumberOfConnectors(
604 stationTemplate
.Evses
[evse
].Connectors
608 return configuredMaxConnectors
;
611 private static checkConfiguredMaxConnectors(
612 configuredMaxConnectors
: number,
616 if (configuredMaxConnectors
<= 0) {
618 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
623 private static checkTemplateMaxConnectors(
624 templateMaxConnectors
: number,
628 if (templateMaxConnectors
=== 0) {
630 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
632 } else if (templateMaxConnectors
< 0) {
634 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
639 private static initializeConnectorStatus(connectorStatus
: ConnectorStatus
): void {
640 connectorStatus
.availability
= AvailabilityType
.Operative
;
641 connectorStatus
.idTagLocalAuthorized
= false;
642 connectorStatus
.idTagAuthorized
= false;
643 connectorStatus
.transactionRemoteStarted
= false;
644 connectorStatus
.transactionStarted
= false;
645 connectorStatus
.energyActiveImportRegisterValue
= 0;
646 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
647 if (isUndefined(connectorStatus
.chargingProfiles
)) {
648 connectorStatus
.chargingProfiles
= [];
652 private static warnDeprecatedTemplateKey(
653 template
: ChargingStationTemplate
,
656 templateFile
: string,
659 if (!isUndefined(template
[key
])) {
660 const logMsg
= `Deprecated template key '${key}' usage in file '${templateFile}'${
661 isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}
` : ''
663 logger
.warn(`${logPrefix} ${logMsg}`);
664 console
.warn(chalk
.yellow(`${logMsg}`));
668 private static convertDeprecatedTemplateKey(
669 template
: ChargingStationTemplate
,
670 deprecatedKey
: string,
673 if (!isUndefined(template
[deprecatedKey
])) {
674 template
[key
] = template
[deprecatedKey
] as unknown
;
675 delete template
[deprecatedKey
];
680 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
682 * @param chargingProfiles -
686 private static getLimitFromChargingProfiles(
687 chargingProfiles
: ChargingProfile
[],
691 matchingChargingProfile
: ChargingProfile
;
693 const debugLogMsg
= `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
694 const currentMoment
= moment();
695 const currentDate
= new Date();
696 for (const chargingProfile
of chargingProfiles
) {
698 const chargingSchedule
= chargingProfile
.chargingSchedule
;
699 if (!chargingSchedule
?.startSchedule
) {
701 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
704 // Check type (recurring) and if it is already active
705 // Adjust the daily recurring schedule to today
707 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
708 chargingProfile
.recurrencyKind
=== RecurrencyKindType
.DAILY
&&
709 currentMoment
.isAfter(chargingSchedule
.startSchedule
)
711 if (!(chargingSchedule
?.startSchedule
instanceof Date)) {
713 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
715 chargingSchedule
.startSchedule
= new Date(chargingSchedule
.startSchedule
);
717 chargingSchedule
.startSchedule
.setFullYear(
718 currentDate
.getFullYear(),
719 currentDate
.getMonth(),
720 currentDate
.getDate()
722 // Check if the start of the schedule is yesterday
723 if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
724 chargingSchedule
.startSchedule
.setDate(currentDate
.getDate() - 1);
726 } else if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
729 // Check if the charging profile is active
731 moment(chargingSchedule
.startSchedule
)
732 .add(chargingSchedule
.duration
, 's')
733 .isAfter(currentMoment
)
735 let lastButOneSchedule
: ChargingSchedulePeriod
;
736 // Search the right schedule period
737 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
738 // Handling of only one period
740 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
741 schedulePeriod
.startPeriod
=== 0
744 limit
: schedulePeriod
.limit
,
745 matchingChargingProfile
: chargingProfile
,
747 logger
.debug(debugLogMsg
, result
);
750 // Find the right schedule period
752 moment(chargingSchedule
.startSchedule
)
753 .add(schedulePeriod
.startPeriod
, 's')
754 .isAfter(currentMoment
)
756 // Found the schedule: last but one is the correct one
758 limit
: lastButOneSchedule
.limit
,
759 matchingChargingProfile
: chargingProfile
,
761 logger
.debug(debugLogMsg
, result
);
765 lastButOneSchedule
= schedulePeriod
;
766 // Handle the last schedule period
768 schedulePeriod
.startPeriod
===
769 chargingSchedule
.chargingSchedulePeriod
[
770 chargingSchedule
.chargingSchedulePeriod
.length
- 1
774 limit
: lastButOneSchedule
.limit
,
775 matchingChargingProfile
: chargingProfile
,
777 logger
.debug(debugLogMsg
, result
);
786 private static getRandomSerialNumberSuffix(params
?: {
787 randomBytesLength
?: number;
790 const randomSerialNumberSuffix
= randomBytes(params
?.randomBytesLength
?? 16).toString('hex');
791 if (params
?.upperCase
) {
792 return randomSerialNumberSuffix
.toUpperCase();
794 return randomSerialNumberSuffix
;