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