feat: save connectors/evses map in charging station configuration file
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
CommitLineData
43ee4373 1import crypto from 'node:crypto';
130783a7
JB
2import path from 'node:path';
3import { fileURLToPath } from 'node:url';
8114d10e 4
e302df1d 5import chalk from 'chalk';
8114d10e
JB
6import moment from 'moment';
7
2896e06d 8import type { ChargingStation } from './internal';
268a74bb 9import { BaseError } from '../exception';
981ebfbe 10import {
492cf6ab 11 AmpereUnits,
04b1261c 12 AvailabilityType,
268a74bb
JB
13 type BootNotificationRequest,
14 BootReasonEnumType,
15068be9 15 type ChargingProfile,
268a74bb 16 ChargingProfileKindType,
15068be9
JB
17 ChargingRateUnitType,
18 type ChargingSchedulePeriod,
268a74bb
JB
19 type ChargingStationInfo,
20 type ChargingStationTemplate,
a78ef5ed 21 type ConnectorStatus,
268a74bb 22 CurrentType,
ae25f265 23 type EvseTemplate,
268a74bb
JB
24 type OCPP16BootNotificationRequest,
25 type OCPP20BootNotificationRequest,
26 OCPPVersion,
27 RecurrencyKindType,
28 Voltage,
29} from '../types';
60a74391
JB
30import {
31 ACElectricUtils,
32 Configuration,
33 Constants,
34 DCElectricUtils,
35 Utils,
36 logger,
37} from '../utils';
268a74bb 38import { WorkerProcessType } from '../worker';
17ac262c 39
91a4f151
JB
40const moduleName = 'ChargingStationUtils';
41
17ac262c 42export class ChargingStationUtils {
d5bd1c00
JB
43 private constructor() {
44 // This is intentional
45 }
46
17ac262c
JB
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;
1b271a54 53 const idSuffix = stationTemplate?.nameSuffix ?? '';
44eb6026 54 const idStr = `000000000${index.toString()}`;
ccb1d6e9 55 return stationTemplate?.fixedName
17ac262c 56 ? stationTemplate.baseName
44eb6026
JB
57 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
58 idStr.length - 4
59 )}${idSuffix}`;
17ac262c
JB
60 }
61
fa7bccf4 62 public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
99e92237 63 const chargingStationInfo = {
fa7bccf4
JB
64 chargePointModel: stationTemplate.chargePointModel,
65 chargePointVendor: stationTemplate.chargePointVendor,
66 ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
67 chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
17ac262c 68 }),
fa7bccf4
JB
69 ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
70 chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
17ac262c 71 }),
fa7bccf4
JB
72 ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && {
73 meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
17ac262c 74 }),
fa7bccf4
JB
75 ...(!Utils.isUndefined(stationTemplate.meterType) && {
76 meterType: stationTemplate.meterType,
17ac262c
JB
77 }),
78 };
79 return crypto
80 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
fa7bccf4 81 .update(
14ecae6a
JB
82 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
83 index,
84 stationTemplate
85 )}`
fa7bccf4 86 )
17ac262c
JB
87 .digest('hex');
88 }
89
1bf29f5b
JB
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
ae25f265
JB
98 public static getMaxNumberOfEvses(evses: Record<string, EvseTemplate>): number {
99 if (!evses) {
100 return -1;
101 }
102 return Object.keys(evses).length;
103 }
104
a78ef5ed
JB
105 public static getMaxNumberOfConnectors(connectors: Record<string, ConnectorStatus>): number {
106 if (!connectors) {
fa7bccf4
JB
107 return -1;
108 }
a78ef5ed 109 return Object.keys(connectors).length;
fa7bccf4
JB
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
ae25f265 128 public static getConfiguredNumberOfConnectors(stationInfo: ChargingStationInfo): number {
fa7bccf4 129 let configuredMaxConnectors: number;
ae25f265
JB
130 if (Utils.isNotEmptyArray(stationInfo.numberOfConnectors) === true) {
131 const numberOfConnectors = stationInfo.numberOfConnectors as number[];
c72f6634
JB
132 configuredMaxConnectors =
133 numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)];
ae25f265
JB
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) {
a78ef5ed 141 configuredMaxConnectors = 0;
ae25f265 142 for (const evse in stationInfo.Evses) {
a78ef5ed
JB
143 if (evse === '0') {
144 continue;
145 }
146 configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors(
ae25f265 147 stationInfo.Evses[evse].Connectors
a78ef5ed
JB
148 );
149 }
fa7bccf4
JB
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
04b1261c
JB
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>();
ae25f265
JB
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));
04b1261c 197 }
ae25f265
JB
198 } else {
199 logger.warn(
200 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
201 );
04b1261c
JB
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 (
52952bf8
JB
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 (
04b1261c
JB
227 connectorId > 0 &&
228 Utils.isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
229 ) {
230 ChargingStationUtils.initializeConnectorStatus(connectors.get(connectorId));
231 }
232 }
233 }
234
04b1261c
JB
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
17ac262c 248 public static createBootNotificationRequest(
15068be9
JB
249 stationInfo: ChargingStationInfo,
250 bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
17ac262c 251 ): BootNotificationRequest {
d270cc87
JB
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 {
15068be9 279 reason: bootReason,
d270cc87
JB
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 }),
98fc1389
JB
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 }),
d270cc87
JB
295 },
296 } as OCPP20BootNotificationRequest;
297 }
17ac262c
JB
298 }
299
300 public static workerPoolInUse(): boolean {
721646e9 301 return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
cf2a5d9b 302 Configuration.getWorker().processType
17ac262c
JB
303 );
304 }
305
306 public static workerDynamicPoolInUse(): boolean {
721646e9 307 return Configuration.getWorker().processType === WorkerProcessType.dynamicPool;
17ac262c
JB
308 }
309
ae5020a3 310 public static warnTemplateKeysDeprecation(
17ac262c 311 templateFile: string,
ae5020a3
JB
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 );
17ac262c
JB
332 }
333 }
334
fa7bccf4
JB
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;
fec4d204 345 delete stationTemplate.meterSerialNumberPrefix;
51c83d6f 346 return stationTemplate as unknown as ChargingStationInfo;
fa7bccf4
JB
347 }
348
349 public static createStationInfoHash(stationInfo: ChargingStationInfo): void {
ccb1d6e9 350 delete stationInfo.infoHash;
7c72977b 351 stationInfo.infoHash = crypto
ccb1d6e9
JB
352 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
353 .update(JSON.stringify(stationInfo))
354 .digest('hex');
17ac262c
JB
355 }
356
357 public static createSerialNumber(
fa7bccf4 358 stationTemplate: ChargingStationTemplate,
4d20f040 359 stationInfo: ChargingStationInfo,
fa7bccf4
JB
360 params: {
361 randomSerialNumberUpperCase?: boolean;
362 randomSerialNumber?: boolean;
363 } = {
17ac262c
JB
364 randomSerialNumberUpperCase: true,
365 randomSerialNumber: true,
366 }
367 ): void {
abe9e9dd 368 params = params ?? {};
17ac262c
JB
369 params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
370 params.randomSerialNumber = params?.randomSerialNumber ?? true;
fa7bccf4
JB
371 const serialNumberSuffix = params?.randomSerialNumber
372 ? ChargingStationUtils.getRandomSerialNumberSuffix({
373 upperCase: params.randomSerialNumberUpperCase,
374 })
375 : '';
9a15316c
JB
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;
fec4d204
JB
389 }
390
391 public static propagateSerialNumber(
392 stationTemplate: ChargingStationTemplate,
393 stationInfoSrc: ChargingStationInfo,
4d20f040 394 stationInfoDst: ChargingStationInfo
fec4d204
JB
395 ) {
396 if (!stationInfoSrc || !stationTemplate) {
baf93dda
JB
397 throw new BaseError(
398 'Missing charging station template or existing configuration to propagate serial number'
399 );
fec4d204
JB
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;
17ac262c
JB
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
15068be9
JB
428 public static getChargingStationConnectorChargingProfilesPowerLimit(
429 chargingStation: ChargingStation,
430 connectorId: number
431 ): number | undefined {
432 let limit: number, matchingChargingProfile: ChargingProfile;
15068be9 433 // Get charging profiles for connector and sort by stack level
66a62eac
JB
434 const chargingProfiles =
435 Utils.cloneObject(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)?.sort(
436 (a, b) => b.stackLevel - a.stackLevel
437 ) ?? [];
15068be9 438 // Get profiles on connector 0
1895299d 439 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
15068be9 440 chargingProfiles.push(
66a62eac
JB
441 ...Utils.cloneObject(chargingStation.getConnectorStatus(0).chargingProfiles).sort(
442 (a, b) => b.stackLevel - a.stackLevel
443 )
15068be9
JB
444 );
445 }
53ac516c 446 if (Utils.isNotEmptyArray(chargingProfiles)) {
15068be9
JB
447 const result = ChargingStationUtils.getLimitFromChargingProfiles(
448 chargingProfiles,
449 chargingStation.logPrefix()
450 );
451 if (!Utils.isNullOrUndefined(result)) {
1895299d
JB
452 limit = result?.limit;
453 matchingChargingProfile = result?.matchingChargingProfile;
15068be9
JB
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
e302df1d 510 public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined {
15068be9 511 return (
e302df1d 512 stationInfo.idTagsFile &&
15068be9
JB
513 path.join(
514 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
515 'assets',
e302df1d 516 path.basename(stationInfo.idTagsFile)
15068be9
JB
517 )
518 );
519 }
520
52952bf8
JB
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
ae5020a3
JB
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
17ac262c
JB
561 /**
562 * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
563 *
0e4fa348
JB
564 * @param chargingProfiles -
565 * @param logPrefix -
566 * @returns
17ac262c 567 */
15068be9 568 private static getLimitFromChargingProfiles(
17ac262c
JB
569 chargingProfiles: ChargingProfile[],
570 logPrefix: string
571 ): {
572 limit: number;
573 matchingChargingProfile: ChargingProfile;
574 } | null {
91a4f151 575 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
41189456
JB
576 const currentMoment = moment();
577 const currentDate = new Date();
17ac262c
JB
578 for (const chargingProfile of chargingProfiles) {
579 // Set helpers
17ac262c 580 const chargingSchedule = chargingProfile.chargingSchedule;
41189456
JB
581 if (!chargingSchedule?.startSchedule) {
582 logger.warn(
583 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
584 );
585 }
17ac262c
JB
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 ) {
41189456
JB
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 }
17ac262c
JB
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 };
91a4f151 629 logger.debug(debugLogMsg, result);
17ac262c
JB
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 };
91a4f151 643 logger.debug(debugLogMsg, result);
17ac262c
JB
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 };
91a4f151 659 logger.debug(debugLogMsg, result);
17ac262c
JB
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}