refactor: factor out template file check
[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 getBootConnectorStatus(
132 chargingStation: ChargingStation,
133 connectorId: number,
134 connectorStatus: ConnectorStatus
135 ): ConnectorStatusEnum {
136 let connectorBootStatus: ConnectorStatusEnum;
137 if (
138 !connectorStatus?.status &&
139 (chargingStation.isChargingStationAvailable() === false ||
140 chargingStation.isConnectorAvailable(connectorId) === false)
141 ) {
142 connectorBootStatus = ConnectorStatusEnum.Unavailable;
143 } else if (!connectorStatus?.status && connectorStatus?.bootStatus) {
144 // Set boot status in template at startup
145 connectorBootStatus = connectorStatus?.bootStatus;
146 } else if (connectorStatus?.status) {
147 // Set previous status at startup
148 connectorBootStatus = connectorStatus?.status;
149 } else {
150 // Set default status
151 connectorBootStatus = ConnectorStatusEnum.Available;
152 }
153 return connectorBootStatus;
154 }
155
156 public static checkTemplateFile(
157 stationTemplate: ChargingStationTemplate,
158 logPrefix: string,
159 templateFile: string
160 ) {
161 if (Utils.isNullOrUndefined(stationTemplate)) {
162 const errorMsg = `Failed to read charging station template file ${templateFile}`;
163 logger.error(`${logPrefix} ${errorMsg}`);
164 throw new BaseError(errorMsg);
165 }
166 if (Utils.isEmptyObject(stationTemplate)) {
167 const errorMsg = `Empty charging station information from template file ${templateFile}`;
168 logger.error(`${logPrefix} ${errorMsg}`);
169 throw new BaseError(errorMsg);
170 }
171 }
172
173 public static checkConnectorsConfiguration(
174 stationTemplate: ChargingStationTemplate,
175 logPrefix: string,
176 templateFile: string
177 ): {
178 configuredMaxConnectors: number;
179 templateMaxConnectors: number;
180 templateMaxAvailableConnectors: number;
181 } {
182 const configuredMaxConnectors =
183 ChargingStationUtils.getConfiguredNumberOfConnectors(stationTemplate);
184 ChargingStationUtils.checkConfiguredMaxConnectors(
185 configuredMaxConnectors,
186 logPrefix,
187 templateFile
188 );
189 const templateMaxConnectors = ChargingStationUtils.getMaxNumberOfConnectors(
190 stationTemplate.Connectors
191 );
192 ChargingStationUtils.checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile);
193 const templateMaxAvailableConnectors = stationTemplate?.Connectors[0]
194 ? templateMaxConnectors - 1
195 : templateMaxConnectors;
196 if (
197 configuredMaxConnectors > templateMaxAvailableConnectors &&
198 !stationTemplate?.randomConnectors
199 ) {
200 logger.warn(
201 `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`
202 );
203 stationTemplate.randomConnectors = true;
204 }
205 return { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors };
206 }
207
208 public static checkStationInfoConnectorStatus(
209 connectorId: number,
210 connectorStatus: ConnectorStatus,
211 logPrefix: string,
212 templateFile: string
213 ): void {
214 if (!Utils.isNullOrUndefined(connectorStatus?.status)) {
215 logger.warn(
216 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
217 );
218 delete connectorStatus.status;
219 }
220 }
221
222 public static buildConnectorsMap(
223 connectors: Record<string, ConnectorStatus>,
224 logPrefix: string,
225 templateFile: string
226 ): Map<number, ConnectorStatus> {
227 const connectorsMap = new Map<number, ConnectorStatus>();
228 if (ChargingStationUtils.getMaxNumberOfConnectors(connectors) > 0) {
229 for (const connector in connectors) {
230 const connectorStatus = connectors[connector];
231 const connectorId = Utils.convertToInt(connector);
232 ChargingStationUtils.checkStationInfoConnectorStatus(
233 connectorId,
234 connectorStatus,
235 logPrefix,
236 templateFile
237 );
238 connectorsMap.set(connectorId, Utils.cloneObject<ConnectorStatus>(connectorStatus));
239 }
240 } else {
241 logger.warn(
242 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
243 );
244 }
245 return connectorsMap;
246 }
247
248 public static initializeConnectorsMapStatus(
249 connectors: Map<number, ConnectorStatus>,
250 logPrefix: string
251 ): void {
252 for (const connectorId of connectors.keys()) {
253 if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
254 logger.warn(
255 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
256 connectors.get(connectorId)?.transactionId
257 }`
258 );
259 }
260 if (connectorId === 0) {
261 connectors.get(connectorId).availability = AvailabilityType.Operative;
262 if (Utils.isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
263 connectors.get(connectorId).chargingProfiles = [];
264 }
265 } else if (
266 connectorId > 0 &&
267 Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
268 ) {
269 ChargingStationUtils.initializeConnectorStatus(connectors.get(connectorId));
270 }
271 }
272 }
273
274 public static resetConnectorStatus(connectorStatus: ConnectorStatus): void {
275 connectorStatus.idTagLocalAuthorized = false;
276 connectorStatus.idTagAuthorized = false;
277 connectorStatus.transactionRemoteStarted = false;
278 connectorStatus.transactionStarted = false;
279 delete connectorStatus?.localAuthorizeIdTag;
280 delete connectorStatus?.authorizeIdTag;
281 delete connectorStatus?.transactionId;
282 delete connectorStatus?.transactionIdTag;
283 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
284 delete connectorStatus?.transactionBeginMeterValue;
285 }
286
287 public static createBootNotificationRequest(
288 stationInfo: ChargingStationInfo,
289 bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
290 ): BootNotificationRequest {
291 const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
292 switch (ocppVersion) {
293 case OCPPVersion.VERSION_16:
294 return {
295 chargePointModel: stationInfo.chargePointModel,
296 chargePointVendor: stationInfo.chargePointVendor,
297 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
298 chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
299 }),
300 ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && {
301 chargePointSerialNumber: stationInfo.chargePointSerialNumber,
302 }),
303 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
304 firmwareVersion: stationInfo.firmwareVersion,
305 }),
306 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
307 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
308 ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && {
309 meterSerialNumber: stationInfo.meterSerialNumber,
310 }),
311 ...(!Utils.isUndefined(stationInfo.meterType) && {
312 meterType: stationInfo.meterType,
313 }),
314 } as OCPP16BootNotificationRequest;
315 case OCPPVersion.VERSION_20:
316 case OCPPVersion.VERSION_201:
317 return {
318 reason: bootReason,
319 chargingStation: {
320 model: stationInfo.chargePointModel,
321 vendorName: stationInfo.chargePointVendor,
322 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
323 firmwareVersion: stationInfo.firmwareVersion,
324 }),
325 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
326 serialNumber: stationInfo.chargeBoxSerialNumber,
327 }),
328 ...((!Utils.isUndefined(stationInfo.iccid) || !Utils.isUndefined(stationInfo.imsi)) && {
329 modem: {
330 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
331 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
332 },
333 }),
334 },
335 } as OCPP20BootNotificationRequest;
336 }
337 }
338
339 public static workerPoolInUse(): boolean {
340 return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
341 Configuration.getWorker().processType
342 );
343 }
344
345 public static workerDynamicPoolInUse(): boolean {
346 return Configuration.getWorker().processType === WorkerProcessType.dynamicPool;
347 }
348
349 public static warnTemplateKeysDeprecation(
350 stationTemplate: ChargingStationTemplate,
351 logPrefix: string,
352 templateFile: string
353 ) {
354 const templateKeys: { key: string; deprecatedKey: string }[] = [
355 { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
356 { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
357 ];
358 for (const templateKey of templateKeys) {
359 ChargingStationUtils.warnDeprecatedTemplateKey(
360 stationTemplate,
361 templateKey.deprecatedKey,
362 logPrefix,
363 templateFile,
364 `Use '${templateKey.key}' instead`
365 );
366 ChargingStationUtils.convertDeprecatedTemplateKey(
367 stationTemplate,
368 templateKey.deprecatedKey,
369 templateKey.key
370 );
371 }
372 }
373
374 public static stationTemplateToStationInfo(
375 stationTemplate: ChargingStationTemplate
376 ): ChargingStationInfo {
377 stationTemplate = Utils.cloneObject(stationTemplate);
378 delete stationTemplate.power;
379 delete stationTemplate.powerUnit;
380 delete stationTemplate?.Connectors;
381 delete stationTemplate?.Evses;
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 logPrefix: string,
516 templateFile: 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 getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number {
546 let configuredMaxConnectors: number;
547 if (Utils.isNotEmptyArray(stationTemplate.numberOfConnectors) === true) {
548 const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
549 configuredMaxConnectors =
550 numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)];
551 } else if (Utils.isUndefined(stationTemplate.numberOfConnectors) === false) {
552 configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
553 } else if (stationTemplate.Connectors && !stationTemplate.Evses) {
554 configuredMaxConnectors = stationTemplate?.Connectors[0]
555 ? ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors) - 1
556 : ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors);
557 } else if (stationTemplate.Evses && !stationTemplate.Connectors) {
558 configuredMaxConnectors = 0;
559 for (const evse in stationTemplate.Evses) {
560 if (evse === '0') {
561 continue;
562 }
563 configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors(
564 stationTemplate.Evses[evse].Connectors
565 );
566 }
567 }
568 return configuredMaxConnectors;
569 }
570
571 private static checkConfiguredMaxConnectors(
572 configuredMaxConnectors: number,
573 logPrefix: string,
574 templateFile: string
575 ): void {
576 if (configuredMaxConnectors <= 0) {
577 logger.warn(
578 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
579 );
580 }
581 }
582
583 private static checkTemplateMaxConnectors(
584 templateMaxConnectors: number,
585 logPrefix: string,
586 templateFile: string
587 ): void {
588 if (templateMaxConnectors === 0) {
589 logger.warn(
590 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
591 );
592 } else if (templateMaxConnectors < 0) {
593 logger.error(
594 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
595 );
596 }
597 }
598
599 private static initializeConnectorStatus(connectorStatus: ConnectorStatus): void {
600 connectorStatus.availability = AvailabilityType.Operative;
601 connectorStatus.idTagLocalAuthorized = false;
602 connectorStatus.idTagAuthorized = false;
603 connectorStatus.transactionRemoteStarted = false;
604 connectorStatus.transactionStarted = false;
605 connectorStatus.energyActiveImportRegisterValue = 0;
606 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
607 if (Utils.isUndefined(connectorStatus.chargingProfiles)) {
608 connectorStatus.chargingProfiles = [];
609 }
610 }
611
612 private static warnDeprecatedTemplateKey(
613 template: ChargingStationTemplate,
614 key: string,
615 logPrefix: string,
616 templateFile: string,
617 logMsgToAppend = ''
618 ): void {
619 if (!Utils.isUndefined(template[key])) {
620 const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
621 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
622 }`;
623 logger.warn(`${logPrefix} ${logMsg}`);
624 console.warn(chalk.yellow(`${logMsg}`));
625 }
626 }
627
628 private static convertDeprecatedTemplateKey(
629 template: ChargingStationTemplate,
630 deprecatedKey: string,
631 key: string
632 ): void {
633 if (!Utils.isUndefined(template[deprecatedKey])) {
634 template[key] = template[deprecatedKey] as unknown;
635 delete template[deprecatedKey];
636 }
637 }
638
639 /**
640 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
641 *
642 * @param chargingProfiles -
643 * @param logPrefix -
644 * @returns
645 */
646 private static getLimitFromChargingProfiles(
647 chargingProfiles: ChargingProfile[],
648 logPrefix: string
649 ): {
650 limit: number;
651 matchingChargingProfile: ChargingProfile;
652 } | null {
653 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
654 const currentMoment = moment();
655 const currentDate = new Date();
656 for (const chargingProfile of chargingProfiles) {
657 // Set helpers
658 const chargingSchedule = chargingProfile.chargingSchedule;
659 if (!chargingSchedule?.startSchedule) {
660 logger.warn(
661 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
662 );
663 }
664 // Check type (recurring) and if it is already active
665 // Adjust the daily recurring schedule to today
666 if (
667 chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
668 chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
669 currentMoment.isAfter(chargingSchedule.startSchedule)
670 ) {
671 if (!(chargingSchedule?.startSchedule instanceof Date)) {
672 logger.warn(
673 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
674 );
675 chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
676 }
677 chargingSchedule.startSchedule.setFullYear(
678 currentDate.getFullYear(),
679 currentDate.getMonth(),
680 currentDate.getDate()
681 );
682 // Check if the start of the schedule is yesterday
683 if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
684 chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
685 }
686 } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
687 return null;
688 }
689 // Check if the charging profile is active
690 if (
691 moment(chargingSchedule.startSchedule)
692 .add(chargingSchedule.duration, 's')
693 .isAfter(currentMoment)
694 ) {
695 let lastButOneSchedule: ChargingSchedulePeriod;
696 // Search the right schedule period
697 for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
698 // Handling of only one period
699 if (
700 chargingSchedule.chargingSchedulePeriod.length === 1 &&
701 schedulePeriod.startPeriod === 0
702 ) {
703 const result = {
704 limit: schedulePeriod.limit,
705 matchingChargingProfile: chargingProfile,
706 };
707 logger.debug(debugLogMsg, result);
708 return result;
709 }
710 // Find the right schedule period
711 if (
712 moment(chargingSchedule.startSchedule)
713 .add(schedulePeriod.startPeriod, 's')
714 .isAfter(currentMoment)
715 ) {
716 // Found the schedule: last but one is the correct one
717 const result = {
718 limit: lastButOneSchedule.limit,
719 matchingChargingProfile: chargingProfile,
720 };
721 logger.debug(debugLogMsg, result);
722 return result;
723 }
724 // Keep it
725 lastButOneSchedule = schedulePeriod;
726 // Handle the last schedule period
727 if (
728 schedulePeriod.startPeriod ===
729 chargingSchedule.chargingSchedulePeriod[
730 chargingSchedule.chargingSchedulePeriod.length - 1
731 ].startPeriod
732 ) {
733 const result = {
734 limit: lastButOneSchedule.limit,
735 matchingChargingProfile: chargingProfile,
736 };
737 logger.debug(debugLogMsg, result);
738 return result;
739 }
740 }
741 }
742 }
743 return null;
744 }
745
746 private static getRandomSerialNumberSuffix(params?: {
747 randomBytesLength?: number;
748 upperCase?: boolean;
749 }): string {
750 const randomSerialNumberSuffix = crypto
751 .randomBytes(params?.randomBytesLength ?? 16)
752 .toString('hex');
753 if (params?.upperCase) {
754 return randomSerialNumberSuffix.toUpperCase();
755 }
756 return randomSerialNumberSuffix;
757 }
758 }