refactor: remove unneeded connector 0 handling at connectors init
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
1 import crypto from 'node:crypto';
2 import path from 'node:path';
3 import { fileURLToPath } from 'node:url';
4
5 import chalk from 'chalk';
6 import moment from 'moment';
7
8 import type { ChargingStation } from './internal';
9 import { BaseError } from '../exception';
10 import {
11 AmpereUnits,
12 AvailabilityType,
13 type BootNotificationRequest,
14 BootReasonEnumType,
15 type ChargingProfile,
16 ChargingProfileKindType,
17 ChargingRateUnitType,
18 type ChargingSchedulePeriod,
19 type ChargingStationInfo,
20 type ChargingStationTemplate,
21 type ConnectorStatus,
22 CurrentType,
23 type EvseTemplate,
24 type OCPP16BootNotificationRequest,
25 type OCPP20BootNotificationRequest,
26 OCPPVersion,
27 RecurrencyKindType,
28 Voltage,
29 } from '../types';
30 import {
31 ACElectricUtils,
32 Configuration,
33 Constants,
34 DCElectricUtils,
35 Utils,
36 logger,
37 } from '../utils';
38 import { WorkerProcessType } from '../worker';
39
40 const moduleName = 'ChargingStationUtils';
41
42 export class ChargingStationUtils {
43 private constructor() {
44 // This is intentional
45 }
46
47 public static getChargingStationId(
48 index: number,
49 stationTemplate: ChargingStationTemplate
50 ): string {
51 // In case of multiple instances: add instance index to charging station id
52 const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0;
53 const idSuffix = stationTemplate?.nameSuffix ?? '';
54 const idStr = `000000000${index.toString()}`;
55 return stationTemplate?.fixedName
56 ? stationTemplate.baseName
57 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
58 idStr.length - 4
59 )}${idSuffix}`;
60 }
61
62 public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
63 const chargingStationInfo = {
64 chargePointModel: stationTemplate.chargePointModel,
65 chargePointVendor: stationTemplate.chargePointVendor,
66 ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
67 chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
68 }),
69 ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
70 chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
71 }),
72 ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && {
73 meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
74 }),
75 ...(!Utils.isUndefined(stationTemplate.meterType) && {
76 meterType: stationTemplate.meterType,
77 }),
78 };
79 return crypto
80 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
81 .update(
82 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
83 index,
84 stationTemplate
85 )}`
86 )
87 .digest('hex');
88 }
89
90 public static checkChargingStation(chargingStation: ChargingStation, logPrefix: string): boolean {
91 if (chargingStation.started === false && chargingStation.starting === false) {
92 logger.warn(`${logPrefix} charging station is stopped, cannot proceed`);
93 return false;
94 }
95 return true;
96 }
97
98 public static getMaxNumberOfEvses(evses: Record<string, EvseTemplate>): number {
99 if (!evses) {
100 return -1;
101 }
102 return Object.keys(evses).length;
103 }
104
105 public static getMaxNumberOfConnectors(connectors: Record<string, ConnectorStatus>): number {
106 if (!connectors) {
107 return -1;
108 }
109 return Object.keys(connectors).length;
110 }
111
112 public static checkTemplateMaxConnectors(
113 templateMaxConnectors: number,
114 templateFile: string,
115 logPrefix: string
116 ): void {
117 if (templateMaxConnectors === 0) {
118 logger.warn(
119 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
120 );
121 } else if (templateMaxConnectors < 0) {
122 logger.error(
123 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
124 );
125 }
126 }
127
128 public static getConfiguredNumberOfConnectors(stationInfo: ChargingStationInfo): number {
129 let configuredMaxConnectors: number;
130 if (Utils.isNotEmptyArray(stationInfo.numberOfConnectors) === true) {
131 const numberOfConnectors = stationInfo.numberOfConnectors as number[];
132 configuredMaxConnectors =
133 numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)];
134 } else if (Utils.isUndefined(stationInfo.numberOfConnectors) === false) {
135 configuredMaxConnectors = stationInfo.numberOfConnectors as number;
136 } else if (stationInfo.Connectors && !stationInfo.Evses) {
137 configuredMaxConnectors = stationInfo?.Connectors[0]
138 ? ChargingStationUtils.getMaxNumberOfConnectors(stationInfo.Connectors) - 1
139 : ChargingStationUtils.getMaxNumberOfConnectors(stationInfo.Connectors);
140 } else if (stationInfo.Evses && !stationInfo.Connectors) {
141 configuredMaxConnectors = 0;
142 for (const evse in stationInfo.Evses) {
143 if (evse === '0') {
144 continue;
145 }
146 configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors(
147 stationInfo.Evses[evse].Connectors
148 );
149 }
150 }
151 return configuredMaxConnectors;
152 }
153
154 public static checkConfiguredMaxConnectors(
155 configuredMaxConnectors: number,
156 templateFile: string,
157 logPrefix: string
158 ): void {
159 if (configuredMaxConnectors <= 0) {
160 logger.warn(
161 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
162 );
163 }
164 }
165
166 public static checkStationInfoConnectorStatus(
167 connectorId: number,
168 connectorStatus: ConnectorStatus,
169 logPrefix: string,
170 templateFile: string
171 ): void {
172 if (!Utils.isNullOrUndefined(connectorStatus?.status)) {
173 logger.warn(
174 `${logPrefix} Charging station information from template ${templateFile} with connector ${connectorId} status configuration defined, undefine it`
175 );
176 delete connectorStatus.status;
177 }
178 }
179
180 public static buildConnectorsMap(
181 connectors: Record<string, ConnectorStatus>,
182 logPrefix: string,
183 templateFile: string
184 ): Map<number, ConnectorStatus> {
185 const connectorsMap = new Map<number, ConnectorStatus>();
186 if (ChargingStationUtils.getMaxNumberOfConnectors(connectors) > 0) {
187 for (const connector in connectors) {
188 const connectorStatus = connectors[connector];
189 const connectorId = Utils.convertToInt(connector);
190 ChargingStationUtils.checkStationInfoConnectorStatus(
191 connectorId,
192 connectorStatus,
193 logPrefix,
194 templateFile
195 );
196 connectorsMap.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
197 }
198 } else {
199 logger.warn(
200 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
201 );
202 }
203 return connectorsMap;
204 }
205
206 public static initializeConnectorsMapStatus(
207 connectors: Map<number, ConnectorStatus>,
208 logPrefix: string
209 ): void {
210 for (const connectorId of connectors.keys()) {
211 if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
212 logger.warn(
213 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started: ${
214 connectors.get(connectorId)?.transactionId
215 }`
216 );
217 }
218 if (connectorId === 0) {
219 connectors.get(connectorId).availability = AvailabilityType.Operative;
220 if (Utils.isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
221 connectors.get(connectorId).chargingProfiles = [];
222 }
223 } else if (
224 connectorId > 0 &&
225 Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
226 ) {
227 ChargingStationUtils.initializeConnectorStatus(connectors.get(connectorId));
228 }
229 }
230 }
231
232 public static resetConnectorStatus(connectorStatus: ConnectorStatus): void {
233 connectorStatus.idTagLocalAuthorized = false;
234 connectorStatus.idTagAuthorized = false;
235 connectorStatus.transactionRemoteStarted = false;
236 connectorStatus.transactionStarted = false;
237 delete connectorStatus?.localAuthorizeIdTag;
238 delete connectorStatus?.authorizeIdTag;
239 delete connectorStatus?.transactionId;
240 delete connectorStatus?.transactionIdTag;
241 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
242 delete connectorStatus?.transactionBeginMeterValue;
243 }
244
245 public static createBootNotificationRequest(
246 stationInfo: ChargingStationInfo,
247 bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
248 ): BootNotificationRequest {
249 const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
250 switch (ocppVersion) {
251 case OCPPVersion.VERSION_16:
252 return {
253 chargePointModel: stationInfo.chargePointModel,
254 chargePointVendor: stationInfo.chargePointVendor,
255 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
256 chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
257 }),
258 ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && {
259 chargePointSerialNumber: stationInfo.chargePointSerialNumber,
260 }),
261 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
262 firmwareVersion: stationInfo.firmwareVersion,
263 }),
264 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
265 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
266 ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && {
267 meterSerialNumber: stationInfo.meterSerialNumber,
268 }),
269 ...(!Utils.isUndefined(stationInfo.meterType) && {
270 meterType: stationInfo.meterType,
271 }),
272 } as OCPP16BootNotificationRequest;
273 case OCPPVersion.VERSION_20:
274 case OCPPVersion.VERSION_201:
275 return {
276 reason: bootReason,
277 chargingStation: {
278 model: stationInfo.chargePointModel,
279 vendorName: stationInfo.chargePointVendor,
280 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
281 firmwareVersion: stationInfo.firmwareVersion,
282 }),
283 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
284 serialNumber: stationInfo.chargeBoxSerialNumber,
285 }),
286 ...((!Utils.isUndefined(stationInfo.iccid) || !Utils.isUndefined(stationInfo.imsi)) && {
287 modem: {
288 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
289 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
290 },
291 }),
292 },
293 } as OCPP20BootNotificationRequest;
294 }
295 }
296
297 public static workerPoolInUse(): boolean {
298 return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
299 Configuration.getWorker().processType
300 );
301 }
302
303 public static workerDynamicPoolInUse(): boolean {
304 return Configuration.getWorker().processType === WorkerProcessType.dynamicPool;
305 }
306
307 public static warnTemplateKeysDeprecation(
308 templateFile: string,
309 stationTemplate: ChargingStationTemplate,
310 logPrefix: string
311 ) {
312 const templateKeys: { key: string; deprecatedKey: string }[] = [
313 { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
314 { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
315 ];
316 for (const templateKey of templateKeys) {
317 ChargingStationUtils.warnDeprecatedTemplateKey(
318 stationTemplate,
319 templateKey.deprecatedKey,
320 templateFile,
321 logPrefix,
322 `Use '${templateKey.key}' instead`
323 );
324 ChargingStationUtils.convertDeprecatedTemplateKey(
325 stationTemplate,
326 templateKey.deprecatedKey,
327 templateKey.key
328 );
329 }
330 }
331
332 public static stationTemplateToStationInfo(
333 stationTemplate: ChargingStationTemplate
334 ): ChargingStationInfo {
335 stationTemplate = Utils.cloneObject(stationTemplate);
336 delete stationTemplate.power;
337 delete stationTemplate.powerUnit;
338 delete stationTemplate.Configuration;
339 delete stationTemplate.AutomaticTransactionGenerator;
340 delete stationTemplate.chargeBoxSerialNumberPrefix;
341 delete stationTemplate.chargePointSerialNumberPrefix;
342 delete stationTemplate.meterSerialNumberPrefix;
343 return stationTemplate as unknown as ChargingStationInfo;
344 }
345
346 public static createStationInfoHash(stationInfo: ChargingStationInfo): void {
347 delete stationInfo.infoHash;
348 stationInfo.infoHash = crypto
349 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
350 .update(JSON.stringify(stationInfo))
351 .digest('hex');
352 }
353
354 public static createSerialNumber(
355 stationTemplate: ChargingStationTemplate,
356 stationInfo: ChargingStationInfo,
357 params: {
358 randomSerialNumberUpperCase?: boolean;
359 randomSerialNumber?: boolean;
360 } = {
361 randomSerialNumberUpperCase: true,
362 randomSerialNumber: true,
363 }
364 ): void {
365 params = params ?? {};
366 params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
367 params.randomSerialNumber = params?.randomSerialNumber ?? true;
368 const serialNumberSuffix = params?.randomSerialNumber
369 ? ChargingStationUtils.getRandomSerialNumberSuffix({
370 upperCase: params.randomSerialNumberUpperCase,
371 })
372 : '';
373 stationInfo.chargePointSerialNumber = Utils.isNotEmptyString(
374 stationTemplate?.chargePointSerialNumberPrefix
375 )
376 ? `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`
377 : undefined;
378 stationInfo.chargeBoxSerialNumber = Utils.isNotEmptyString(
379 stationTemplate?.chargeBoxSerialNumberPrefix
380 )
381 ? `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`
382 : undefined;
383 stationInfo.meterSerialNumber = Utils.isNotEmptyString(stationTemplate?.meterSerialNumberPrefix)
384 ? `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`
385 : undefined;
386 }
387
388 public static propagateSerialNumber(
389 stationTemplate: ChargingStationTemplate,
390 stationInfoSrc: ChargingStationInfo,
391 stationInfoDst: ChargingStationInfo
392 ) {
393 if (!stationInfoSrc || !stationTemplate) {
394 throw new BaseError(
395 'Missing charging station template or existing configuration to propagate serial number'
396 );
397 }
398 stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber
399 ? (stationInfoDst.chargePointSerialNumber = stationInfoSrc.chargePointSerialNumber)
400 : stationInfoDst?.chargePointSerialNumber && delete stationInfoDst.chargePointSerialNumber;
401 stationTemplate?.chargeBoxSerialNumberPrefix && stationInfoSrc?.chargeBoxSerialNumber
402 ? (stationInfoDst.chargeBoxSerialNumber = stationInfoSrc.chargeBoxSerialNumber)
403 : stationInfoDst?.chargeBoxSerialNumber && delete stationInfoDst.chargeBoxSerialNumber;
404 stationTemplate?.meterSerialNumberPrefix && stationInfoSrc?.meterSerialNumber
405 ? (stationInfoDst.meterSerialNumber = stationInfoSrc.meterSerialNumber)
406 : stationInfoDst?.meterSerialNumber && delete stationInfoDst.meterSerialNumber;
407 }
408
409 public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number {
410 let unitDivider = 1;
411 switch (stationInfo.amperageLimitationUnit) {
412 case AmpereUnits.DECI_AMPERE:
413 unitDivider = 10;
414 break;
415 case AmpereUnits.CENTI_AMPERE:
416 unitDivider = 100;
417 break;
418 case AmpereUnits.MILLI_AMPERE:
419 unitDivider = 1000;
420 break;
421 }
422 return unitDivider;
423 }
424
425 public static getChargingStationConnectorChargingProfilesPowerLimit(
426 chargingStation: ChargingStation,
427 connectorId: number
428 ): number | undefined {
429 let limit: number, matchingChargingProfile: ChargingProfile;
430 // Get charging profiles for connector and sort by stack level
431 const chargingProfiles =
432 Utils.cloneObject(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)?.sort(
433 (a, b) => b.stackLevel - a.stackLevel
434 ) ?? [];
435 // Get profiles on connector 0
436 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
437 chargingProfiles.push(
438 ...Utils.cloneObject(chargingStation.getConnectorStatus(0).chargingProfiles).sort(
439 (a, b) => b.stackLevel - a.stackLevel
440 )
441 );
442 }
443 if (Utils.isNotEmptyArray(chargingProfiles)) {
444 const result = ChargingStationUtils.getLimitFromChargingProfiles(
445 chargingProfiles,
446 chargingStation.logPrefix()
447 );
448 if (!Utils.isNullOrUndefined(result)) {
449 limit = result?.limit;
450 matchingChargingProfile = result?.matchingChargingProfile;
451 switch (chargingStation.getCurrentOutType()) {
452 case CurrentType.AC:
453 limit =
454 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
455 ChargingRateUnitType.WATT
456 ? limit
457 : ACElectricUtils.powerTotal(
458 chargingStation.getNumberOfPhases(),
459 chargingStation.getVoltageOut(),
460 limit
461 );
462 break;
463 case CurrentType.DC:
464 limit =
465 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
466 ChargingRateUnitType.WATT
467 ? limit
468 : DCElectricUtils.power(chargingStation.getVoltageOut(), limit);
469 }
470 const connectorMaximumPower =
471 chargingStation.getMaximumPower() / chargingStation.powerDivider;
472 if (limit > connectorMaximumPower) {
473 logger.error(
474 `${chargingStation.logPrefix()} Charging profile id ${
475 matchingChargingProfile.chargingProfileId
476 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
477 result
478 );
479 limit = connectorMaximumPower;
480 }
481 }
482 }
483 return limit;
484 }
485
486 public static getDefaultVoltageOut(
487 currentType: CurrentType,
488 templateFile: string,
489 logPrefix: string
490 ): Voltage {
491 const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
492 let defaultVoltageOut: number;
493 switch (currentType) {
494 case CurrentType.AC:
495 defaultVoltageOut = Voltage.VOLTAGE_230;
496 break;
497 case CurrentType.DC:
498 defaultVoltageOut = Voltage.VOLTAGE_400;
499 break;
500 default:
501 logger.error(`${logPrefix} ${errMsg}`);
502 throw new BaseError(errMsg);
503 }
504 return defaultVoltageOut;
505 }
506
507 public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined {
508 return (
509 stationInfo.idTagsFile &&
510 path.join(
511 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
512 'assets',
513 path.basename(stationInfo.idTagsFile)
514 )
515 );
516 }
517
518 private static initializeConnectorStatus(connectorStatus: ConnectorStatus): void {
519 connectorStatus.availability = AvailabilityType.Operative;
520 connectorStatus.idTagLocalAuthorized = false;
521 connectorStatus.idTagAuthorized = false;
522 connectorStatus.transactionRemoteStarted = false;
523 connectorStatus.transactionStarted = false;
524 connectorStatus.energyActiveImportRegisterValue = 0;
525 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
526 if (Utils.isUndefined(connectorStatus.chargingProfiles)) {
527 connectorStatus.chargingProfiles = [];
528 }
529 }
530
531 private static warnDeprecatedTemplateKey(
532 template: ChargingStationTemplate,
533 key: string,
534 templateFile: string,
535 logPrefix: string,
536 logMsgToAppend = ''
537 ): void {
538 if (!Utils.isUndefined(template[key])) {
539 const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
540 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
541 }`;
542 logger.warn(`${logPrefix} ${logMsg}`);
543 console.warn(chalk.yellow(`${logMsg}`));
544 }
545 }
546
547 private static convertDeprecatedTemplateKey(
548 template: ChargingStationTemplate,
549 deprecatedKey: string,
550 key: string
551 ): void {
552 if (!Utils.isUndefined(template[deprecatedKey])) {
553 template[key] = template[deprecatedKey] as unknown;
554 delete template[deprecatedKey];
555 }
556 }
557
558 /**
559 * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
560 *
561 * @param chargingProfiles -
562 * @param logPrefix -
563 * @returns
564 */
565 private static getLimitFromChargingProfiles(
566 chargingProfiles: ChargingProfile[],
567 logPrefix: string
568 ): {
569 limit: number;
570 matchingChargingProfile: ChargingProfile;
571 } | null {
572 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
573 const currentMoment = moment();
574 const currentDate = new Date();
575 for (const chargingProfile of chargingProfiles) {
576 // Set helpers
577 const chargingSchedule = chargingProfile.chargingSchedule;
578 if (!chargingSchedule?.startSchedule) {
579 logger.warn(
580 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
581 );
582 }
583 // Check type (recurring) and if it is already active
584 // Adjust the daily recurring schedule to today
585 if (
586 chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
587 chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
588 currentMoment.isAfter(chargingSchedule.startSchedule)
589 ) {
590 if (!(chargingSchedule?.startSchedule instanceof Date)) {
591 logger.warn(
592 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
593 );
594 chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
595 }
596 chargingSchedule.startSchedule.setFullYear(
597 currentDate.getFullYear(),
598 currentDate.getMonth(),
599 currentDate.getDate()
600 );
601 // Check if the start of the schedule is yesterday
602 if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
603 chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
604 }
605 } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
606 return null;
607 }
608 // Check if the charging profile is active
609 if (
610 moment(chargingSchedule.startSchedule)
611 .add(chargingSchedule.duration, 's')
612 .isAfter(currentMoment)
613 ) {
614 let lastButOneSchedule: ChargingSchedulePeriod;
615 // Search the right schedule period
616 for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
617 // Handling of only one period
618 if (
619 chargingSchedule.chargingSchedulePeriod.length === 1 &&
620 schedulePeriod.startPeriod === 0
621 ) {
622 const result = {
623 limit: schedulePeriod.limit,
624 matchingChargingProfile: chargingProfile,
625 };
626 logger.debug(debugLogMsg, result);
627 return result;
628 }
629 // Find the right schedule period
630 if (
631 moment(chargingSchedule.startSchedule)
632 .add(schedulePeriod.startPeriod, 's')
633 .isAfter(currentMoment)
634 ) {
635 // Found the schedule: last but one is the correct one
636 const result = {
637 limit: lastButOneSchedule.limit,
638 matchingChargingProfile: chargingProfile,
639 };
640 logger.debug(debugLogMsg, result);
641 return result;
642 }
643 // Keep it
644 lastButOneSchedule = schedulePeriod;
645 // Handle the last schedule period
646 if (
647 schedulePeriod.startPeriod ===
648 chargingSchedule.chargingSchedulePeriod[
649 chargingSchedule.chargingSchedulePeriod.length - 1
650 ].startPeriod
651 ) {
652 const result = {
653 limit: lastButOneSchedule.limit,
654 matchingChargingProfile: chargingProfile,
655 };
656 logger.debug(debugLogMsg, result);
657 return result;
658 }
659 }
660 }
661 }
662 return null;
663 }
664
665 private static getRandomSerialNumberSuffix(params?: {
666 randomBytesLength?: number;
667 upperCase?: boolean;
668 }): string {
669 const randomSerialNumberSuffix = crypto
670 .randomBytes(params?.randomBytesLength ?? 16)
671 .toString('hex');
672 if (params?.upperCase) {
673 return randomSerialNumberSuffix.toUpperCase();
674 }
675 return randomSerialNumberSuffix;
676 }
677 }