1 import crypto from
'node:crypto';
2 import type EventEmitter from
'node:events';
3 import path 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
,
34 import { ACElectricUtils
, Constants
, DCElectricUtils
, Utils
, logger
} from
'../utils';
36 const moduleName
= 'ChargingStationUtils';
38 export class ChargingStationUtils
{
39 private constructor() {
40 // This is intentional
43 public static getChargingStationId(
45 stationTemplate
: ChargingStationTemplate
47 // In case of multiple instances: add instance index to charging station id
48 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
49 const idSuffix
= stationTemplate
?.nameSuffix
?? '';
50 const idStr
= `000000000${index.toString()}`;
51 return stationTemplate
?.fixedName
52 ? stationTemplate
.baseName
53 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
58 public static countReservableConnectors(connectors
: Map
<number, ConnectorStatus
>) {
59 let reservableConnectors
= 0;
60 for (const [connectorId
, connectorStatus
] of connectors
) {
61 if (connectorId
=== 0) {
64 if (connectorStatus
.status === ConnectorStatusEnum
.Available
) {
65 ++reservableConnectors
;
68 return reservableConnectors
;
71 public static getHashId(index
: number, stationTemplate
: ChargingStationTemplate
): string {
72 const chargingStationInfo
= {
73 chargePointModel
: stationTemplate
.chargePointModel
,
74 chargePointVendor
: stationTemplate
.chargePointVendor
,
75 ...(!Utils
.isUndefined(stationTemplate
.chargeBoxSerialNumberPrefix
) && {
76 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
78 ...(!Utils
.isUndefined(stationTemplate
.chargePointSerialNumberPrefix
) && {
79 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
81 ...(!Utils
.isUndefined(stationTemplate
.meterSerialNumberPrefix
) && {
82 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
84 ...(!Utils
.isUndefined(stationTemplate
.meterType
) && {
85 meterType
: stationTemplate
.meterType
,
89 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
91 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
99 public static checkChargingStation(chargingStation
: ChargingStation
, logPrefix
: string): boolean {
100 if (chargingStation
.started
=== false && chargingStation
.starting
=== false) {
101 logger
.warn(`${logPrefix} charging station is stopped, cannot proceed`);
107 public static getPhaseRotationValue(
109 numberOfPhases
: number
110 ): string | undefined {
112 if (connectorId
=== 0 && numberOfPhases
=== 0) {
113 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
114 } else if (connectorId
> 0 && numberOfPhases
=== 0) {
115 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
117 } else if (connectorId
> 0 && numberOfPhases
=== 1) {
118 return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`;
119 } else if (connectorId
> 0 && numberOfPhases
=== 3) {
120 return `${connectorId}.${ConnectorPhaseRotation.RST}`;
124 public static getMaxNumberOfEvses(evses
: Record
<string, EvseTemplate
>): number {
128 return Object.keys(evses
).length
;
131 public static getMaxNumberOfConnectors(connectors
: Record
<string, ConnectorStatus
>): number {
135 return Object.keys(connectors
).length
;
138 public static getBootConnectorStatus(
139 chargingStation
: ChargingStation
,
141 connectorStatus
: ConnectorStatus
142 ): ConnectorStatusEnum
{
143 let connectorBootStatus
: ConnectorStatusEnum
;
145 !connectorStatus
?.status &&
146 (chargingStation
.isChargingStationAvailable() === false ||
147 chargingStation
.isConnectorAvailable(connectorId
) === false)
149 connectorBootStatus
= ConnectorStatusEnum
.Unavailable
;
150 } else if (!connectorStatus
?.status && connectorStatus
?.bootStatus
) {
151 // Set boot status in template at startup
152 connectorBootStatus
= connectorStatus
?.bootStatus
;
153 } else if (connectorStatus
?.status) {
154 // Set previous status at startup
155 connectorBootStatus
= connectorStatus
?.status;
157 // Set default status
158 connectorBootStatus
= ConnectorStatusEnum
.Available
;
160 return connectorBootStatus
;
163 public static checkTemplate(
164 stationTemplate
: ChargingStationTemplate
,
168 if (Utils
.isNullOrUndefined(stationTemplate
)) {
169 const errorMsg
= `Failed to read charging station template file ${templateFile}`;
170 logger
.error(`${logPrefix} ${errorMsg}`);
171 throw new BaseError(errorMsg
);
173 if (Utils
.isEmptyObject(stationTemplate
)) {
174 const errorMsg
= `Empty charging station information from template file ${templateFile}`;
175 logger
.error(`${logPrefix} ${errorMsg}`);
176 throw new BaseError(errorMsg
);
178 if (Utils
.isEmptyObject(stationTemplate
.AutomaticTransactionGenerator
)) {
179 stationTemplate
.AutomaticTransactionGenerator
= Constants
.DEFAULT_ATG_CONFIGURATION
;
181 `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
182 Constants
.DEFAULT_ATG_CONFIGURATION
186 Utils
.isNullOrUndefined(stationTemplate
.idTagsFile
) ||
187 Utils
.isEmptyString(stationTemplate
.idTagsFile
)
190 `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`
195 public static checkConnectorsConfiguration(
196 stationTemplate
: ChargingStationTemplate
,
200 configuredMaxConnectors
: number;
201 templateMaxConnectors
: number;
202 templateMaxAvailableConnectors
: number;
204 const configuredMaxConnectors
=
205 ChargingStationUtils
.getConfiguredNumberOfConnectors(stationTemplate
);
206 ChargingStationUtils
.checkConfiguredMaxConnectors(
207 configuredMaxConnectors
,
211 const templateMaxConnectors
= ChargingStationUtils
.getMaxNumberOfConnectors(
212 stationTemplate
.Connectors
214 ChargingStationUtils
.checkTemplateMaxConnectors(templateMaxConnectors
, logPrefix
, templateFile
);
215 const templateMaxAvailableConnectors
= stationTemplate
?.Connectors
[0]
216 ? templateMaxConnectors
- 1
217 : templateMaxConnectors
;
219 configuredMaxConnectors
> templateMaxAvailableConnectors
&&
220 !stationTemplate
?.randomConnectors
223 `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`
225 stationTemplate
.randomConnectors
= true;
227 return { configuredMaxConnectors
, templateMaxConnectors
, templateMaxAvailableConnectors
};
230 public static checkStationInfoConnectorStatus(
232 connectorStatus
: ConnectorStatus
,
236 if (!Utils
.isNullOrUndefined(connectorStatus
?.status)) {
238 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
240 delete connectorStatus
.status;
244 public static buildConnectorsMap(
245 connectors
: Record
<string, ConnectorStatus
>,
248 ): Map
<number, ConnectorStatus
> {
249 const connectorsMap
= new Map
<number, ConnectorStatus
>();
250 if (ChargingStationUtils
.getMaxNumberOfConnectors(connectors
) > 0) {
251 for (const connector
in connectors
) {
252 const connectorStatus
= connectors
[connector
];
253 const connectorId
= Utils
.convertToInt(connector
);
254 ChargingStationUtils
.checkStationInfoConnectorStatus(
260 connectorsMap
.set(connectorId
, Utils
.cloneObject
<ConnectorStatus
>(connectorStatus
));
264 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
267 return connectorsMap
;
270 public static 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 ${
278 connectors.get(connectorId)?.transactionId
282 if (connectorId
=== 0) {
283 connectors
.get(connectorId
).availability
= AvailabilityType
.Operative
;
284 if (Utils
.isUndefined(connectors
.get(connectorId
)?.chargingProfiles
)) {
285 connectors
.get(connectorId
).chargingProfiles
= [];
289 Utils
.isNullOrUndefined(connectors
.get(connectorId
)?.transactionStarted
)
291 ChargingStationUtils
.initializeConnectorStatus(connectors
.get(connectorId
));
296 public static 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 public static 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 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
320 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
322 ...(!Utils
.isUndefined(stationInfo
.chargePointSerialNumber
) && {
323 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
325 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
326 firmwareVersion
: stationInfo
.firmwareVersion
,
328 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
329 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
330 ...(!Utils
.isUndefined(stationInfo
.meterSerialNumber
) && {
331 meterSerialNumber
: stationInfo
.meterSerialNumber
,
333 ...(!Utils
.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 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
345 firmwareVersion
: stationInfo
.firmwareVersion
,
347 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
348 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
350 ...((!Utils
.isUndefined(stationInfo
.iccid
) || !Utils
.isUndefined(stationInfo
.imsi
)) && {
352 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
353 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
357 } as OCPP20BootNotificationRequest
;
361 public static warnTemplateKeysDeprecation(
362 stationTemplate
: ChargingStationTemplate
,
366 const templateKeys
: { key
: string; deprecatedKey
: string }[] = [
367 { key
: 'supervisionUrls', deprecatedKey
: 'supervisionUrl' },
368 { key
: 'idTagsFile', deprecatedKey
: 'authorizationFile' },
370 for (const templateKey
of templateKeys
) {
371 ChargingStationUtils
.warnDeprecatedTemplateKey(
373 templateKey
.deprecatedKey
,
376 `Use '${templateKey.key}' instead`
378 ChargingStationUtils
.convertDeprecatedTemplateKey(
380 templateKey
.deprecatedKey
,
386 public static stationTemplateToStationInfo(
387 stationTemplate
: ChargingStationTemplate
388 ): ChargingStationInfo
{
389 stationTemplate
= Utils
.cloneObject
<ChargingStationTemplate
>(stationTemplate
);
390 delete stationTemplate
.power
;
391 delete stationTemplate
.powerUnit
;
392 delete stationTemplate
?.Connectors
;
393 delete stationTemplate
?.Evses
;
394 delete stationTemplate
.Configuration
;
395 delete stationTemplate
.AutomaticTransactionGenerator
;
396 delete stationTemplate
.chargeBoxSerialNumberPrefix
;
397 delete stationTemplate
.chargePointSerialNumberPrefix
;
398 delete stationTemplate
.meterSerialNumberPrefix
;
399 return stationTemplate
as unknown
as ChargingStationInfo
;
402 public static createSerialNumber(
403 stationTemplate
: ChargingStationTemplate
,
404 stationInfo
: ChargingStationInfo
,
406 randomSerialNumberUpperCase
?: boolean;
407 randomSerialNumber
?: boolean;
409 randomSerialNumberUpperCase
: true,
410 randomSerialNumber
: true,
413 params
= { ...{ randomSerialNumberUpperCase
: true, randomSerialNumber
: true }, ...params
};
414 const serialNumberSuffix
= params
?.randomSerialNumber
415 ? ChargingStationUtils
.getRandomSerialNumberSuffix({
416 upperCase
: params
.randomSerialNumberUpperCase
,
419 Utils
.isNotEmptyString(stationTemplate
?.chargePointSerialNumberPrefix
) &&
420 (stationInfo
.chargePointSerialNumber
= `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`);
421 Utils
.isNotEmptyString(stationTemplate
?.chargeBoxSerialNumberPrefix
) &&
422 (stationInfo
.chargeBoxSerialNumber
= `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`);
423 Utils
.isNotEmptyString(stationTemplate
?.meterSerialNumberPrefix
) &&
424 (stationInfo
.meterSerialNumber
= `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`);
427 public static propagateSerialNumber(
428 stationTemplate
: ChargingStationTemplate
,
429 stationInfoSrc
: ChargingStationInfo
,
430 stationInfoDst
: ChargingStationInfo
432 if (!stationInfoSrc
|| !stationTemplate
) {
434 'Missing charging station template or existing configuration to propagate serial number'
437 stationTemplate
?.chargePointSerialNumberPrefix
&& stationInfoSrc
?.chargePointSerialNumber
438 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
439 : stationInfoDst
?.chargePointSerialNumber
&& delete stationInfoDst
.chargePointSerialNumber
;
440 stationTemplate
?.chargeBoxSerialNumberPrefix
&& stationInfoSrc
?.chargeBoxSerialNumber
441 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
442 : stationInfoDst
?.chargeBoxSerialNumber
&& delete stationInfoDst
.chargeBoxSerialNumber
;
443 stationTemplate
?.meterSerialNumberPrefix
&& stationInfoSrc
?.meterSerialNumber
444 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
445 : stationInfoDst
?.meterSerialNumber
&& delete stationInfoDst
.meterSerialNumber
;
448 public static getAmperageLimitationUnitDivider(stationInfo
: ChargingStationInfo
): number {
450 switch (stationInfo
.amperageLimitationUnit
) {
451 case AmpereUnits
.DECI_AMPERE
:
454 case AmpereUnits
.CENTI_AMPERE
:
457 case AmpereUnits
.MILLI_AMPERE
:
464 public static getChargingStationConnectorChargingProfilesPowerLimit(
465 chargingStation
: ChargingStation
,
467 ): number | undefined {
468 let limit
: number, matchingChargingProfile
: ChargingProfile
;
469 // Get charging profiles for connector and sort by stack level
470 const chargingProfiles
=
471 Utils
.cloneObject
<ChargingProfile
[]>(
472 chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
473 )?.sort((a
, b
) => b
.stackLevel
- a
.stackLevel
) ?? [];
474 // Get profiles on connector 0
475 if (chargingStation
.getConnectorStatus(0)?.chargingProfiles
) {
476 chargingProfiles
.push(
477 ...Utils
.cloneObject
<ChargingProfile
[]>(
478 chargingStation
.getConnectorStatus(0).chargingProfiles
479 ).sort((a
, b
) => b
.stackLevel
- a
.stackLevel
)
482 if (Utils
.isNotEmptyArray(chargingProfiles
)) {
483 const result
= ChargingStationUtils
.getLimitFromChargingProfiles(
485 chargingStation
.logPrefix()
487 if (!Utils
.isNullOrUndefined(result
)) {
488 limit
= result
?.limit
;
489 matchingChargingProfile
= result
?.matchingChargingProfile
;
490 switch (chargingStation
.getCurrentOutType()) {
493 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
494 ChargingRateUnitType
.WATT
496 : ACElectricUtils
.powerTotal(
497 chargingStation
.getNumberOfPhases(),
498 chargingStation
.getVoltageOut(),
504 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
505 ChargingRateUnitType
.WATT
507 : DCElectricUtils
.power(chargingStation
.getVoltageOut(), limit
);
509 const connectorMaximumPower
=
510 chargingStation
.getMaximumPower() / chargingStation
.powerDivider
;
511 if (limit
> connectorMaximumPower
) {
513 `${chargingStation.logPrefix()} Charging profile id ${
514 matchingChargingProfile.chargingProfileId
515 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
518 limit
= connectorMaximumPower
;
525 public static getDefaultVoltageOut(
526 currentType
: CurrentType
,
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 public static getIdTagsFile(stationInfo
: ChargingStationInfo
): string | undefined {
548 stationInfo
.idTagsFile
&&
550 path
.dirname(fileURLToPath(import.meta
.url
)),
552 path
.basename(stationInfo
.idTagsFile
)
557 public static waitForChargingStationEvents
= async (
558 emitter
: EventEmitter
,
559 event
: ChargingStationWorkerMessageEvents
,
561 ): Promise
<number> => {
562 return new Promise((resolve
) => {
564 if (eventsToWait
=== 0) {
567 emitter
.on(event
, () => {
569 if (events
=== eventsToWait
) {
576 private static getConfiguredNumberOfConnectors(stationTemplate
: ChargingStationTemplate
): number {
577 let configuredMaxConnectors
: number;
578 if (Utils
.isNotEmptyArray(stationTemplate
.numberOfConnectors
) === true) {
579 const numberOfConnectors
= stationTemplate
.numberOfConnectors
as number[];
580 configuredMaxConnectors
=
581 numberOfConnectors
[Math.floor(Utils
.secureRandom() * numberOfConnectors
.length
)];
582 } else if (Utils
.isUndefined(stationTemplate
.numberOfConnectors
) === false) {
583 configuredMaxConnectors
= stationTemplate
.numberOfConnectors
as number;
584 } else if (stationTemplate
.Connectors
&& !stationTemplate
.Evses
) {
585 configuredMaxConnectors
= stationTemplate
?.Connectors
[0]
586 ? ChargingStationUtils
.getMaxNumberOfConnectors(stationTemplate
.Connectors
) - 1
587 : ChargingStationUtils
.getMaxNumberOfConnectors(stationTemplate
.Connectors
);
588 } else if (stationTemplate
.Evses
&& !stationTemplate
.Connectors
) {
589 configuredMaxConnectors
= 0;
590 for (const evse
in stationTemplate
.Evses
) {
594 configuredMaxConnectors
+= ChargingStationUtils
.getMaxNumberOfConnectors(
595 stationTemplate
.Evses
[evse
].Connectors
599 return configuredMaxConnectors
;
602 private static checkConfiguredMaxConnectors(
603 configuredMaxConnectors
: number,
607 if (configuredMaxConnectors
<= 0) {
609 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
614 private static checkTemplateMaxConnectors(
615 templateMaxConnectors
: number,
619 if (templateMaxConnectors
=== 0) {
621 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
623 } else if (templateMaxConnectors
< 0) {
625 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
630 private static initializeConnectorStatus(connectorStatus
: ConnectorStatus
): void {
631 connectorStatus
.availability
= AvailabilityType
.Operative
;
632 connectorStatus
.idTagLocalAuthorized
= false;
633 connectorStatus
.idTagAuthorized
= false;
634 connectorStatus
.transactionRemoteStarted
= false;
635 connectorStatus
.transactionStarted
= false;
636 connectorStatus
.energyActiveImportRegisterValue
= 0;
637 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
638 if (Utils
.isUndefined(connectorStatus
.chargingProfiles
)) {
639 connectorStatus
.chargingProfiles
= [];
643 private static warnDeprecatedTemplateKey(
644 template
: ChargingStationTemplate
,
647 templateFile
: string,
650 if (!Utils
.isUndefined(template
[key
])) {
651 const logMsg
= `Deprecated template key '${key}' usage in file '${templateFile}'${
652 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}
` : ''
654 logger
.warn(`${logPrefix} ${logMsg}`);
655 console
.warn(chalk
.yellow(`${logMsg}`));
659 private static convertDeprecatedTemplateKey(
660 template
: ChargingStationTemplate
,
661 deprecatedKey
: string,
664 if (!Utils
.isUndefined(template
[deprecatedKey
])) {
665 template
[key
] = template
[deprecatedKey
] as unknown
;
666 delete template
[deprecatedKey
];
671 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
673 * @param chargingProfiles -
677 private static getLimitFromChargingProfiles(
678 chargingProfiles
: ChargingProfile
[],
682 matchingChargingProfile
: ChargingProfile
;
684 const debugLogMsg
= `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
685 const currentMoment
= moment();
686 const currentDate
= new Date();
687 for (const chargingProfile
of chargingProfiles
) {
689 const chargingSchedule
= chargingProfile
.chargingSchedule
;
690 if (!chargingSchedule
?.startSchedule
) {
692 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
695 // Check type (recurring) and if it is already active
696 // Adjust the daily recurring schedule to today
698 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
699 chargingProfile
.recurrencyKind
=== RecurrencyKindType
.DAILY
&&
700 currentMoment
.isAfter(chargingSchedule
.startSchedule
)
702 if (!(chargingSchedule
?.startSchedule
instanceof Date)) {
704 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
706 chargingSchedule
.startSchedule
= new Date(chargingSchedule
.startSchedule
);
708 chargingSchedule
.startSchedule
.setFullYear(
709 currentDate
.getFullYear(),
710 currentDate
.getMonth(),
711 currentDate
.getDate()
713 // Check if the start of the schedule is yesterday
714 if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
715 chargingSchedule
.startSchedule
.setDate(currentDate
.getDate() - 1);
717 } else if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
720 // Check if the charging profile is active
722 moment(chargingSchedule
.startSchedule
)
723 .add(chargingSchedule
.duration
, 's')
724 .isAfter(currentMoment
)
726 let lastButOneSchedule
: ChargingSchedulePeriod
;
727 // Search the right schedule period
728 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
729 // Handling of only one period
731 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
732 schedulePeriod
.startPeriod
=== 0
735 limit
: schedulePeriod
.limit
,
736 matchingChargingProfile
: chargingProfile
,
738 logger
.debug(debugLogMsg
, result
);
741 // Find the right schedule period
743 moment(chargingSchedule
.startSchedule
)
744 .add(schedulePeriod
.startPeriod
, 's')
745 .isAfter(currentMoment
)
747 // Found the schedule: last but one is the correct one
749 limit
: lastButOneSchedule
.limit
,
750 matchingChargingProfile
: chargingProfile
,
752 logger
.debug(debugLogMsg
, result
);
756 lastButOneSchedule
= schedulePeriod
;
757 // Handle the last schedule period
759 schedulePeriod
.startPeriod
===
760 chargingSchedule
.chargingSchedulePeriod
[
761 chargingSchedule
.chargingSchedulePeriod
.length
- 1
765 limit
: lastButOneSchedule
.limit
,
766 matchingChargingProfile
: chargingProfile
,
768 logger
.debug(debugLogMsg
, result
);
777 private static getRandomSerialNumberSuffix(params
?: {
778 randomBytesLength
?: number;
781 const randomSerialNumberSuffix
= crypto
782 .randomBytes(params
?.randomBytesLength
?? 16)
784 if (params
?.upperCase
) {
785 return randomSerialNumberSuffix
.toUpperCase();
787 return randomSerialNumberSuffix
;