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