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