feat: save connectors/evses map in charging station configuration file
[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 ${connectorId} at initialization has a transaction started: ${
214 connectors.get(connectorId)?.transactionId
215 }`
216 );
217 }
218 if (
219 connectorId === 0 &&
220 Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
221 ) {
222 connectors.get(connectorId).availability = AvailabilityType.Operative;
223 if (Utils.isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
224 connectors.get(connectorId).chargingProfiles = [];
225 }
226 } else if (
227 connectorId > 0 &&
228 Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
229 ) {
230 ChargingStationUtils.initializeConnectorStatus(connectors.get(connectorId));
231 }
232 }
233 }
234
235 public static resetConnectorStatus(connectorStatus: ConnectorStatus): void {
236 connectorStatus.idTagLocalAuthorized = false;
237 connectorStatus.idTagAuthorized = false;
238 connectorStatus.transactionRemoteStarted = false;
239 connectorStatus.transactionStarted = false;
240 delete connectorStatus?.localAuthorizeIdTag;
241 delete connectorStatus?.authorizeIdTag;
242 delete connectorStatus?.transactionId;
243 delete connectorStatus?.transactionIdTag;
244 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
245 delete connectorStatus?.transactionBeginMeterValue;
246 }
247
248 public static createBootNotificationRequest(
249 stationInfo: ChargingStationInfo,
250 bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
251 ): BootNotificationRequest {
252 const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
253 switch (ocppVersion) {
254 case OCPPVersion.VERSION_16:
255 return {
256 chargePointModel: stationInfo.chargePointModel,
257 chargePointVendor: stationInfo.chargePointVendor,
258 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
259 chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
260 }),
261 ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && {
262 chargePointSerialNumber: stationInfo.chargePointSerialNumber,
263 }),
264 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
265 firmwareVersion: stationInfo.firmwareVersion,
266 }),
267 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
268 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
269 ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && {
270 meterSerialNumber: stationInfo.meterSerialNumber,
271 }),
272 ...(!Utils.isUndefined(stationInfo.meterType) && {
273 meterType: stationInfo.meterType,
274 }),
275 } as OCPP16BootNotificationRequest;
276 case OCPPVersion.VERSION_20:
277 case OCPPVersion.VERSION_201:
278 return {
279 reason: bootReason,
280 chargingStation: {
281 model: stationInfo.chargePointModel,
282 vendorName: stationInfo.chargePointVendor,
283 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
284 firmwareVersion: stationInfo.firmwareVersion,
285 }),
286 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
287 serialNumber: stationInfo.chargeBoxSerialNumber,
288 }),
289 ...((!Utils.isUndefined(stationInfo.iccid) || !Utils.isUndefined(stationInfo.imsi)) && {
290 modem: {
291 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
292 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
293 },
294 }),
295 },
296 } as OCPP20BootNotificationRequest;
297 }
298 }
299
300 public static workerPoolInUse(): boolean {
301 return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
302 Configuration.getWorker().processType
303 );
304 }
305
306 public static workerDynamicPoolInUse(): boolean {
307 return Configuration.getWorker().processType === WorkerProcessType.dynamicPool;
308 }
309
310 public static warnTemplateKeysDeprecation(
311 templateFile: string,
312 stationTemplate: ChargingStationTemplate,
313 logPrefix: string
314 ) {
315 const templateKeys: { key: string; deprecatedKey: string }[] = [
316 { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
317 { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
318 ];
319 for (const templateKey of templateKeys) {
320 ChargingStationUtils.warnDeprecatedTemplateKey(
321 stationTemplate,
322 templateKey.deprecatedKey,
323 templateFile,
324 logPrefix,
325 `Use '${templateKey.key}' instead`
326 );
327 ChargingStationUtils.convertDeprecatedTemplateKey(
328 stationTemplate,
329 templateKey.deprecatedKey,
330 templateKey.key
331 );
332 }
333 }
334
335 public static stationTemplateToStationInfo(
336 stationTemplate: ChargingStationTemplate
337 ): ChargingStationInfo {
338 stationTemplate = Utils.cloneObject(stationTemplate);
339 delete stationTemplate.power;
340 delete stationTemplate.powerUnit;
341 delete stationTemplate.Configuration;
342 delete stationTemplate.AutomaticTransactionGenerator;
343 delete stationTemplate.chargeBoxSerialNumberPrefix;
344 delete stationTemplate.chargePointSerialNumberPrefix;
345 delete stationTemplate.meterSerialNumberPrefix;
346 return stationTemplate as unknown as ChargingStationInfo;
347 }
348
349 public static createStationInfoHash(stationInfo: ChargingStationInfo): void {
350 delete stationInfo.infoHash;
351 stationInfo.infoHash = crypto
352 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
353 .update(JSON.stringify(stationInfo))
354 .digest('hex');
355 }
356
357 public static createSerialNumber(
358 stationTemplate: ChargingStationTemplate,
359 stationInfo: ChargingStationInfo,
360 params: {
361 randomSerialNumberUpperCase?: boolean;
362 randomSerialNumber?: boolean;
363 } = {
364 randomSerialNumberUpperCase: true,
365 randomSerialNumber: true,
366 }
367 ): void {
368 params = params ?? {};
369 params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
370 params.randomSerialNumber = params?.randomSerialNumber ?? true;
371 const serialNumberSuffix = params?.randomSerialNumber
372 ? ChargingStationUtils.getRandomSerialNumberSuffix({
373 upperCase: params.randomSerialNumberUpperCase,
374 })
375 : '';
376 stationInfo.chargePointSerialNumber = Utils.isNotEmptyString(
377 stationTemplate?.chargePointSerialNumberPrefix
378 )
379 ? `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`
380 : undefined;
381 stationInfo.chargeBoxSerialNumber = Utils.isNotEmptyString(
382 stationTemplate?.chargeBoxSerialNumberPrefix
383 )
384 ? `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`
385 : undefined;
386 stationInfo.meterSerialNumber = Utils.isNotEmptyString(stationTemplate?.meterSerialNumberPrefix)
387 ? `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`
388 : undefined;
389 }
390
391 public static propagateSerialNumber(
392 stationTemplate: ChargingStationTemplate,
393 stationInfoSrc: ChargingStationInfo,
394 stationInfoDst: ChargingStationInfo
395 ) {
396 if (!stationInfoSrc || !stationTemplate) {
397 throw new BaseError(
398 'Missing charging station template or existing configuration to propagate serial number'
399 );
400 }
401 stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber
402 ? (stationInfoDst.chargePointSerialNumber = stationInfoSrc.chargePointSerialNumber)
403 : stationInfoDst?.chargePointSerialNumber && delete stationInfoDst.chargePointSerialNumber;
404 stationTemplate?.chargeBoxSerialNumberPrefix && stationInfoSrc?.chargeBoxSerialNumber
405 ? (stationInfoDst.chargeBoxSerialNumber = stationInfoSrc.chargeBoxSerialNumber)
406 : stationInfoDst?.chargeBoxSerialNumber && delete stationInfoDst.chargeBoxSerialNumber;
407 stationTemplate?.meterSerialNumberPrefix && stationInfoSrc?.meterSerialNumber
408 ? (stationInfoDst.meterSerialNumber = stationInfoSrc.meterSerialNumber)
409 : stationInfoDst?.meterSerialNumber && delete stationInfoDst.meterSerialNumber;
410 }
411
412 public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number {
413 let unitDivider = 1;
414 switch (stationInfo.amperageLimitationUnit) {
415 case AmpereUnits.DECI_AMPERE:
416 unitDivider = 10;
417 break;
418 case AmpereUnits.CENTI_AMPERE:
419 unitDivider = 100;
420 break;
421 case AmpereUnits.MILLI_AMPERE:
422 unitDivider = 1000;
423 break;
424 }
425 return unitDivider;
426 }
427
428 public static getChargingStationConnectorChargingProfilesPowerLimit(
429 chargingStation: ChargingStation,
430 connectorId: number
431 ): number | undefined {
432 let limit: number, matchingChargingProfile: ChargingProfile;
433 // Get charging profiles for connector and sort by stack level
434 const chargingProfiles =
435 Utils.cloneObject(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)?.sort(
436 (a, b) => b.stackLevel - a.stackLevel
437 ) ?? [];
438 // Get profiles on connector 0
439 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
440 chargingProfiles.push(
441 ...Utils.cloneObject(chargingStation.getConnectorStatus(0).chargingProfiles).sort(
442 (a, b) => b.stackLevel - a.stackLevel
443 )
444 );
445 }
446 if (Utils.isNotEmptyArray(chargingProfiles)) {
447 const result = ChargingStationUtils.getLimitFromChargingProfiles(
448 chargingProfiles,
449 chargingStation.logPrefix()
450 );
451 if (!Utils.isNullOrUndefined(result)) {
452 limit = result?.limit;
453 matchingChargingProfile = result?.matchingChargingProfile;
454 switch (chargingStation.getCurrentOutType()) {
455 case CurrentType.AC:
456 limit =
457 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
458 ChargingRateUnitType.WATT
459 ? limit
460 : ACElectricUtils.powerTotal(
461 chargingStation.getNumberOfPhases(),
462 chargingStation.getVoltageOut(),
463 limit
464 );
465 break;
466 case CurrentType.DC:
467 limit =
468 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
469 ChargingRateUnitType.WATT
470 ? limit
471 : DCElectricUtils.power(chargingStation.getVoltageOut(), limit);
472 }
473 const connectorMaximumPower =
474 chargingStation.getMaximumPower() / chargingStation.powerDivider;
475 if (limit > connectorMaximumPower) {
476 logger.error(
477 `${chargingStation.logPrefix()} Charging profile id ${
478 matchingChargingProfile.chargingProfileId
479 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
480 result
481 );
482 limit = connectorMaximumPower;
483 }
484 }
485 }
486 return limit;
487 }
488
489 public static getDefaultVoltageOut(
490 currentType: CurrentType,
491 templateFile: string,
492 logPrefix: string
493 ): Voltage {
494 const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
495 let defaultVoltageOut: number;
496 switch (currentType) {
497 case CurrentType.AC:
498 defaultVoltageOut = Voltage.VOLTAGE_230;
499 break;
500 case CurrentType.DC:
501 defaultVoltageOut = Voltage.VOLTAGE_400;
502 break;
503 default:
504 logger.error(`${logPrefix} ${errMsg}`);
505 throw new BaseError(errMsg);
506 }
507 return defaultVoltageOut;
508 }
509
510 public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined {
511 return (
512 stationInfo.idTagsFile &&
513 path.join(
514 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
515 'assets',
516 path.basename(stationInfo.idTagsFile)
517 )
518 );
519 }
520
521 private static initializeConnectorStatus(connectorStatus: ConnectorStatus): void {
522 connectorStatus.availability = AvailabilityType.Operative;
523 connectorStatus.idTagLocalAuthorized = false;
524 connectorStatus.idTagAuthorized = false;
525 connectorStatus.transactionRemoteStarted = false;
526 connectorStatus.transactionStarted = false;
527 connectorStatus.energyActiveImportRegisterValue = 0;
528 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
529 if (Utils.isUndefined(connectorStatus.chargingProfiles)) {
530 connectorStatus.chargingProfiles = [];
531 }
532 }
533
534 private static warnDeprecatedTemplateKey(
535 template: ChargingStationTemplate,
536 key: string,
537 templateFile: string,
538 logPrefix: string,
539 logMsgToAppend = ''
540 ): void {
541 if (!Utils.isUndefined(template[key])) {
542 const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
543 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
544 }`;
545 logger.warn(`${logPrefix} ${logMsg}`);
546 console.warn(chalk.yellow(`${logMsg}`));
547 }
548 }
549
550 private static convertDeprecatedTemplateKey(
551 template: ChargingStationTemplate,
552 deprecatedKey: string,
553 key: string
554 ): void {
555 if (!Utils.isUndefined(template[deprecatedKey])) {
556 template[key] = template[deprecatedKey] as unknown;
557 delete template[deprecatedKey];
558 }
559 }
560
561 /**
562 * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
563 *
564 * @param chargingProfiles -
565 * @param logPrefix -
566 * @returns
567 */
568 private static getLimitFromChargingProfiles(
569 chargingProfiles: ChargingProfile[],
570 logPrefix: string
571 ): {
572 limit: number;
573 matchingChargingProfile: ChargingProfile;
574 } | null {
575 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
576 const currentMoment = moment();
577 const currentDate = new Date();
578 for (const chargingProfile of chargingProfiles) {
579 // Set helpers
580 const chargingSchedule = chargingProfile.chargingSchedule;
581 if (!chargingSchedule?.startSchedule) {
582 logger.warn(
583 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
584 );
585 }
586 // Check type (recurring) and if it is already active
587 // Adjust the daily recurring schedule to today
588 if (
589 chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
590 chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
591 currentMoment.isAfter(chargingSchedule.startSchedule)
592 ) {
593 if (!(chargingSchedule?.startSchedule instanceof Date)) {
594 logger.warn(
595 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
596 );
597 chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
598 }
599 chargingSchedule.startSchedule.setFullYear(
600 currentDate.getFullYear(),
601 currentDate.getMonth(),
602 currentDate.getDate()
603 );
604 // Check if the start of the schedule is yesterday
605 if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
606 chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
607 }
608 } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
609 return null;
610 }
611 // Check if the charging profile is active
612 if (
613 moment(chargingSchedule.startSchedule)
614 .add(chargingSchedule.duration, 's')
615 .isAfter(currentMoment)
616 ) {
617 let lastButOneSchedule: ChargingSchedulePeriod;
618 // Search the right schedule period
619 for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
620 // Handling of only one period
621 if (
622 chargingSchedule.chargingSchedulePeriod.length === 1 &&
623 schedulePeriod.startPeriod === 0
624 ) {
625 const result = {
626 limit: schedulePeriod.limit,
627 matchingChargingProfile: chargingProfile,
628 };
629 logger.debug(debugLogMsg, result);
630 return result;
631 }
632 // Find the right schedule period
633 if (
634 moment(chargingSchedule.startSchedule)
635 .add(schedulePeriod.startPeriod, 's')
636 .isAfter(currentMoment)
637 ) {
638 // Found the schedule: last but one is the correct one
639 const result = {
640 limit: lastButOneSchedule.limit,
641 matchingChargingProfile: chargingProfile,
642 };
643 logger.debug(debugLogMsg, result);
644 return result;
645 }
646 // Keep it
647 lastButOneSchedule = schedulePeriod;
648 // Handle the last schedule period
649 if (
650 schedulePeriod.startPeriod ===
651 chargingSchedule.chargingSchedulePeriod[
652 chargingSchedule.chargingSchedulePeriod.length - 1
653 ].startPeriod
654 ) {
655 const result = {
656 limit: lastButOneSchedule.limit,
657 matchingChargingProfile: chargingProfile,
658 };
659 logger.debug(debugLogMsg, result);
660 return result;
661 }
662 }
663 }
664 }
665 return null;
666 }
667
668 private static getRandomSerialNumberSuffix(params?: {
669 randomBytesLength?: number;
670 upperCase?: boolean;
671 }): string {
672 const randomSerialNumberSuffix = crypto
673 .randomBytes(params?.randomBytesLength ?? 16)
674 .toString('hex');
675 if (params?.upperCase) {
676 return randomSerialNumberSuffix.toUpperCase();
677 }
678 return randomSerialNumberSuffix;
679 }
680 }