feat: make evse and connector configurations in template mutually
[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,
268a74bb
JB
12 type BootNotificationRequest,
13 BootReasonEnumType,
15068be9 14 type ChargingProfile,
268a74bb 15 ChargingProfileKindType,
15068be9
JB
16 ChargingRateUnitType,
17 type ChargingSchedulePeriod,
268a74bb
JB
18 type ChargingStationInfo,
19 type ChargingStationTemplate,
a78ef5ed 20 type ConnectorStatus,
268a74bb
JB
21 CurrentType,
22 type OCPP16BootNotificationRequest,
23 type OCPP20BootNotificationRequest,
24 OCPPVersion,
25 RecurrencyKindType,
26 Voltage,
27} from '../types';
60a74391
JB
28import {
29 ACElectricUtils,
30 Configuration,
31 Constants,
32 DCElectricUtils,
33 Utils,
34 logger,
35} from '../utils';
268a74bb 36import { WorkerProcessType } from '../worker';
17ac262c 37
91a4f151
JB
38const moduleName = 'ChargingStationUtils';
39
17ac262c 40export class ChargingStationUtils {
d5bd1c00
JB
41 private constructor() {
42 // This is intentional
43 }
44
17ac262c
JB
45 public static getChargingStationId(
46 index: number,
47 stationTemplate: ChargingStationTemplate
48 ): string {
49 // In case of multiple instances: add instance index to charging station id
50 const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0;
1b271a54 51 const idSuffix = stationTemplate?.nameSuffix ?? '';
44eb6026 52 const idStr = `000000000${index.toString()}`;
ccb1d6e9 53 return stationTemplate?.fixedName
17ac262c 54 ? stationTemplate.baseName
44eb6026
JB
55 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
56 idStr.length - 4
57 )}${idSuffix}`;
17ac262c
JB
58 }
59
fa7bccf4 60 public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
99e92237 61 const chargingStationInfo = {
fa7bccf4
JB
62 chargePointModel: stationTemplate.chargePointModel,
63 chargePointVendor: stationTemplate.chargePointVendor,
64 ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
65 chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
17ac262c 66 }),
fa7bccf4
JB
67 ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
68 chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
17ac262c 69 }),
fa7bccf4
JB
70 ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && {
71 meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
17ac262c 72 }),
fa7bccf4
JB
73 ...(!Utils.isUndefined(stationTemplate.meterType) && {
74 meterType: stationTemplate.meterType,
17ac262c
JB
75 }),
76 };
77 return crypto
78 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
fa7bccf4 79 .update(
14ecae6a
JB
80 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
81 index,
82 stationTemplate
83 )}`
fa7bccf4 84 )
17ac262c
JB
85 .digest('hex');
86 }
87
1bf29f5b
JB
88 public static checkChargingStation(chargingStation: ChargingStation, logPrefix: string): boolean {
89 if (chargingStation.started === false && chargingStation.starting === false) {
90 logger.warn(`${logPrefix} charging station is stopped, cannot proceed`);
91 return false;
92 }
93 return true;
94 }
95
a78ef5ed
JB
96 public static getMaxNumberOfConnectors(connectors: Record<string, ConnectorStatus>): number {
97 if (!connectors) {
fa7bccf4
JB
98 return -1;
99 }
a78ef5ed 100 return Object.keys(connectors).length;
fa7bccf4
JB
101 }
102
103 public static checkTemplateMaxConnectors(
104 templateMaxConnectors: number,
105 templateFile: string,
106 logPrefix: string
107 ): void {
108 if (templateMaxConnectors === 0) {
109 logger.warn(
110 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
111 );
112 } else if (templateMaxConnectors < 0) {
113 logger.error(
114 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
115 );
116 }
117 }
118
c72f6634 119 public static getConfiguredNumberOfConnectors(stationTemplate: ChargingStationTemplate): number {
fa7bccf4 120 let configuredMaxConnectors: number;
53ac516c 121 if (Utils.isNotEmptyArray(stationTemplate.numberOfConnectors) === true) {
fa7bccf4 122 const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
c72f6634
JB
123 configuredMaxConnectors =
124 numberOfConnectors[Math.floor(Utils.secureRandom() * numberOfConnectors.length)];
125 } else if (Utils.isUndefined(stationTemplate.numberOfConnectors) === false) {
fa7bccf4 126 configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
a78ef5ed 127 } else if (stationTemplate.Connectors && !stationTemplate.Evses) {
fa7bccf4 128 configuredMaxConnectors = stationTemplate?.Connectors[0]
a78ef5ed
JB
129 ? ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors) - 1
130 : ChargingStationUtils.getMaxNumberOfConnectors(stationTemplate.Connectors);
131 } else if (stationTemplate.Evses && !stationTemplate.Connectors) {
132 configuredMaxConnectors = 0;
133 for (const evse in stationTemplate.Evses) {
134 if (evse === '0') {
135 continue;
136 }
137 configuredMaxConnectors += ChargingStationUtils.getMaxNumberOfConnectors(
138 stationTemplate.Evses[evse].Connectors
139 );
140 }
fa7bccf4
JB
141 }
142 return configuredMaxConnectors;
143 }
144
145 public static checkConfiguredMaxConnectors(
146 configuredMaxConnectors: number,
147 templateFile: string,
148 logPrefix: string
149 ): void {
150 if (configuredMaxConnectors <= 0) {
151 logger.warn(
152 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
153 );
154 }
155 }
156
17ac262c 157 public static createBootNotificationRequest(
15068be9
JB
158 stationInfo: ChargingStationInfo,
159 bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
17ac262c 160 ): BootNotificationRequest {
d270cc87
JB
161 const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
162 switch (ocppVersion) {
163 case OCPPVersion.VERSION_16:
164 return {
165 chargePointModel: stationInfo.chargePointModel,
166 chargePointVendor: stationInfo.chargePointVendor,
167 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
168 chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
169 }),
170 ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && {
171 chargePointSerialNumber: stationInfo.chargePointSerialNumber,
172 }),
173 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
174 firmwareVersion: stationInfo.firmwareVersion,
175 }),
176 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
177 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
178 ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && {
179 meterSerialNumber: stationInfo.meterSerialNumber,
180 }),
181 ...(!Utils.isUndefined(stationInfo.meterType) && {
182 meterType: stationInfo.meterType,
183 }),
184 } as OCPP16BootNotificationRequest;
185 case OCPPVersion.VERSION_20:
186 case OCPPVersion.VERSION_201:
187 return {
15068be9 188 reason: bootReason,
d270cc87
JB
189 chargingStation: {
190 model: stationInfo.chargePointModel,
191 vendorName: stationInfo.chargePointVendor,
192 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
193 firmwareVersion: stationInfo.firmwareVersion,
194 }),
195 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
196 serialNumber: stationInfo.chargeBoxSerialNumber,
197 }),
98fc1389
JB
198 ...((!Utils.isUndefined(stationInfo.iccid) || !Utils.isUndefined(stationInfo.imsi)) && {
199 modem: {
200 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
201 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
202 },
203 }),
d270cc87
JB
204 },
205 } as OCPP20BootNotificationRequest;
206 }
17ac262c
JB
207 }
208
209 public static workerPoolInUse(): boolean {
721646e9 210 return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
cf2a5d9b 211 Configuration.getWorker().processType
17ac262c
JB
212 );
213 }
214
215 public static workerDynamicPoolInUse(): boolean {
721646e9 216 return Configuration.getWorker().processType === WorkerProcessType.dynamicPool;
17ac262c
JB
217 }
218
ae5020a3 219 public static warnTemplateKeysDeprecation(
17ac262c 220 templateFile: string,
ae5020a3
JB
221 stationTemplate: ChargingStationTemplate,
222 logPrefix: string
223 ) {
224 const templateKeys: { key: string; deprecatedKey: string }[] = [
225 { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
226 { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
227 ];
228 for (const templateKey of templateKeys) {
229 ChargingStationUtils.warnDeprecatedTemplateKey(
230 stationTemplate,
231 templateKey.deprecatedKey,
232 templateFile,
233 logPrefix,
234 `Use '${templateKey.key}' instead`
235 );
236 ChargingStationUtils.convertDeprecatedTemplateKey(
237 stationTemplate,
238 templateKey.deprecatedKey,
239 templateKey.key
240 );
17ac262c
JB
241 }
242 }
243
fa7bccf4
JB
244 public static stationTemplateToStationInfo(
245 stationTemplate: ChargingStationTemplate
246 ): ChargingStationInfo {
247 stationTemplate = Utils.cloneObject(stationTemplate);
248 delete stationTemplate.power;
249 delete stationTemplate.powerUnit;
250 delete stationTemplate.Configuration;
251 delete stationTemplate.AutomaticTransactionGenerator;
252 delete stationTemplate.chargeBoxSerialNumberPrefix;
253 delete stationTemplate.chargePointSerialNumberPrefix;
fec4d204 254 delete stationTemplate.meterSerialNumberPrefix;
51c83d6f 255 return stationTemplate as unknown as ChargingStationInfo;
fa7bccf4
JB
256 }
257
258 public static createStationInfoHash(stationInfo: ChargingStationInfo): void {
ccb1d6e9 259 delete stationInfo.infoHash;
7c72977b 260 stationInfo.infoHash = crypto
ccb1d6e9
JB
261 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
262 .update(JSON.stringify(stationInfo))
263 .digest('hex');
17ac262c
JB
264 }
265
266 public static createSerialNumber(
fa7bccf4 267 stationTemplate: ChargingStationTemplate,
4d20f040 268 stationInfo: ChargingStationInfo,
fa7bccf4
JB
269 params: {
270 randomSerialNumberUpperCase?: boolean;
271 randomSerialNumber?: boolean;
272 } = {
17ac262c
JB
273 randomSerialNumberUpperCase: true,
274 randomSerialNumber: true,
275 }
276 ): void {
abe9e9dd 277 params = params ?? {};
17ac262c
JB
278 params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
279 params.randomSerialNumber = params?.randomSerialNumber ?? true;
fa7bccf4
JB
280 const serialNumberSuffix = params?.randomSerialNumber
281 ? ChargingStationUtils.getRandomSerialNumberSuffix({
282 upperCase: params.randomSerialNumberUpperCase,
283 })
284 : '';
9a15316c
JB
285 stationInfo.chargePointSerialNumber = Utils.isNotEmptyString(
286 stationTemplate?.chargePointSerialNumberPrefix
287 )
288 ? `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`
289 : undefined;
290 stationInfo.chargeBoxSerialNumber = Utils.isNotEmptyString(
291 stationTemplate?.chargeBoxSerialNumberPrefix
292 )
293 ? `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`
294 : undefined;
295 stationInfo.meterSerialNumber = Utils.isNotEmptyString(stationTemplate?.meterSerialNumberPrefix)
296 ? `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`
297 : undefined;
fec4d204
JB
298 }
299
300 public static propagateSerialNumber(
301 stationTemplate: ChargingStationTemplate,
302 stationInfoSrc: ChargingStationInfo,
4d20f040 303 stationInfoDst: ChargingStationInfo
fec4d204
JB
304 ) {
305 if (!stationInfoSrc || !stationTemplate) {
baf93dda
JB
306 throw new BaseError(
307 'Missing charging station template or existing configuration to propagate serial number'
308 );
fec4d204
JB
309 }
310 stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber
311 ? (stationInfoDst.chargePointSerialNumber = stationInfoSrc.chargePointSerialNumber)
312 : stationInfoDst?.chargePointSerialNumber && delete stationInfoDst.chargePointSerialNumber;
313 stationTemplate?.chargeBoxSerialNumberPrefix && stationInfoSrc?.chargeBoxSerialNumber
314 ? (stationInfoDst.chargeBoxSerialNumber = stationInfoSrc.chargeBoxSerialNumber)
315 : stationInfoDst?.chargeBoxSerialNumber && delete stationInfoDst.chargeBoxSerialNumber;
316 stationTemplate?.meterSerialNumberPrefix && stationInfoSrc?.meterSerialNumber
317 ? (stationInfoDst.meterSerialNumber = stationInfoSrc.meterSerialNumber)
318 : stationInfoDst?.meterSerialNumber && delete stationInfoDst.meterSerialNumber;
17ac262c
JB
319 }
320
321 public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number {
322 let unitDivider = 1;
323 switch (stationInfo.amperageLimitationUnit) {
324 case AmpereUnits.DECI_AMPERE:
325 unitDivider = 10;
326 break;
327 case AmpereUnits.CENTI_AMPERE:
328 unitDivider = 100;
329 break;
330 case AmpereUnits.MILLI_AMPERE:
331 unitDivider = 1000;
332 break;
333 }
334 return unitDivider;
335 }
336
15068be9
JB
337 public static getChargingStationConnectorChargingProfilesPowerLimit(
338 chargingStation: ChargingStation,
339 connectorId: number
340 ): number | undefined {
341 let limit: number, matchingChargingProfile: ChargingProfile;
15068be9 342 // Get charging profiles for connector and sort by stack level
66a62eac
JB
343 const chargingProfiles =
344 Utils.cloneObject(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)?.sort(
345 (a, b) => b.stackLevel - a.stackLevel
346 ) ?? [];
15068be9 347 // Get profiles on connector 0
1895299d 348 if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
15068be9 349 chargingProfiles.push(
66a62eac
JB
350 ...Utils.cloneObject(chargingStation.getConnectorStatus(0).chargingProfiles).sort(
351 (a, b) => b.stackLevel - a.stackLevel
352 )
15068be9
JB
353 );
354 }
53ac516c 355 if (Utils.isNotEmptyArray(chargingProfiles)) {
15068be9
JB
356 const result = ChargingStationUtils.getLimitFromChargingProfiles(
357 chargingProfiles,
358 chargingStation.logPrefix()
359 );
360 if (!Utils.isNullOrUndefined(result)) {
1895299d
JB
361 limit = result?.limit;
362 matchingChargingProfile = result?.matchingChargingProfile;
15068be9
JB
363 switch (chargingStation.getCurrentOutType()) {
364 case CurrentType.AC:
365 limit =
366 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
367 ChargingRateUnitType.WATT
368 ? limit
369 : ACElectricUtils.powerTotal(
370 chargingStation.getNumberOfPhases(),
371 chargingStation.getVoltageOut(),
372 limit
373 );
374 break;
375 case CurrentType.DC:
376 limit =
377 matchingChargingProfile.chargingSchedule.chargingRateUnit ===
378 ChargingRateUnitType.WATT
379 ? limit
380 : DCElectricUtils.power(chargingStation.getVoltageOut(), limit);
381 }
382 const connectorMaximumPower =
383 chargingStation.getMaximumPower() / chargingStation.powerDivider;
384 if (limit > connectorMaximumPower) {
385 logger.error(
386 `${chargingStation.logPrefix()} Charging profile id ${
387 matchingChargingProfile.chargingProfileId
388 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
389 result
390 );
391 limit = connectorMaximumPower;
392 }
393 }
394 }
395 return limit;
396 }
397
398 public static getDefaultVoltageOut(
399 currentType: CurrentType,
400 templateFile: string,
401 logPrefix: string
402 ): Voltage {
403 const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
404 let defaultVoltageOut: number;
405 switch (currentType) {
406 case CurrentType.AC:
407 defaultVoltageOut = Voltage.VOLTAGE_230;
408 break;
409 case CurrentType.DC:
410 defaultVoltageOut = Voltage.VOLTAGE_400;
411 break;
412 default:
413 logger.error(`${logPrefix} ${errMsg}`);
414 throw new BaseError(errMsg);
415 }
416 return defaultVoltageOut;
417 }
418
e302df1d 419 public static getIdTagsFile(stationInfo: ChargingStationInfo): string | undefined {
15068be9 420 return (
e302df1d 421 stationInfo.idTagsFile &&
15068be9
JB
422 path.join(
423 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
424 'assets',
e302df1d 425 path.basename(stationInfo.idTagsFile)
15068be9
JB
426 )
427 );
428 }
429
ae5020a3
JB
430 private static warnDeprecatedTemplateKey(
431 template: ChargingStationTemplate,
432 key: string,
433 templateFile: string,
434 logPrefix: string,
435 logMsgToAppend = ''
436 ): void {
437 if (!Utils.isUndefined(template[key])) {
438 const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
439 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
440 }`;
441 logger.warn(`${logPrefix} ${logMsg}`);
442 console.warn(chalk.yellow(`${logMsg}`));
443 }
444 }
445
446 private static convertDeprecatedTemplateKey(
447 template: ChargingStationTemplate,
448 deprecatedKey: string,
449 key: string
450 ): void {
451 if (!Utils.isUndefined(template[deprecatedKey])) {
452 template[key] = template[deprecatedKey] as unknown;
453 delete template[deprecatedKey];
454 }
455 }
456
17ac262c
JB
457 /**
458 * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
459 *
0e4fa348
JB
460 * @param chargingProfiles -
461 * @param logPrefix -
462 * @returns
17ac262c 463 */
15068be9 464 private static getLimitFromChargingProfiles(
17ac262c
JB
465 chargingProfiles: ChargingProfile[],
466 logPrefix: string
467 ): {
468 limit: number;
469 matchingChargingProfile: ChargingProfile;
470 } | null {
91a4f151 471 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
41189456
JB
472 const currentMoment = moment();
473 const currentDate = new Date();
17ac262c
JB
474 for (const chargingProfile of chargingProfiles) {
475 // Set helpers
17ac262c 476 const chargingSchedule = chargingProfile.chargingSchedule;
41189456
JB
477 if (!chargingSchedule?.startSchedule) {
478 logger.warn(
479 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
480 );
481 }
17ac262c
JB
482 // Check type (recurring) and if it is already active
483 // Adjust the daily recurring schedule to today
484 if (
485 chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
486 chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
487 currentMoment.isAfter(chargingSchedule.startSchedule)
488 ) {
41189456
JB
489 if (!(chargingSchedule?.startSchedule instanceof Date)) {
490 logger.warn(
491 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
492 );
493 chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
494 }
17ac262c
JB
495 chargingSchedule.startSchedule.setFullYear(
496 currentDate.getFullYear(),
497 currentDate.getMonth(),
498 currentDate.getDate()
499 );
500 // Check if the start of the schedule is yesterday
501 if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
502 chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
503 }
504 } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
505 return null;
506 }
507 // Check if the charging profile is active
508 if (
509 moment(chargingSchedule.startSchedule)
510 .add(chargingSchedule.duration, 's')
511 .isAfter(currentMoment)
512 ) {
513 let lastButOneSchedule: ChargingSchedulePeriod;
514 // Search the right schedule period
515 for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
516 // Handling of only one period
517 if (
518 chargingSchedule.chargingSchedulePeriod.length === 1 &&
519 schedulePeriod.startPeriod === 0
520 ) {
521 const result = {
522 limit: schedulePeriod.limit,
523 matchingChargingProfile: chargingProfile,
524 };
91a4f151 525 logger.debug(debugLogMsg, result);
17ac262c
JB
526 return result;
527 }
528 // Find the right schedule period
529 if (
530 moment(chargingSchedule.startSchedule)
531 .add(schedulePeriod.startPeriod, 's')
532 .isAfter(currentMoment)
533 ) {
534 // Found the schedule: last but one is the correct one
535 const result = {
536 limit: lastButOneSchedule.limit,
537 matchingChargingProfile: chargingProfile,
538 };
91a4f151 539 logger.debug(debugLogMsg, result);
17ac262c
JB
540 return result;
541 }
542 // Keep it
543 lastButOneSchedule = schedulePeriod;
544 // Handle the last schedule period
545 if (
546 schedulePeriod.startPeriod ===
547 chargingSchedule.chargingSchedulePeriod[
548 chargingSchedule.chargingSchedulePeriod.length - 1
549 ].startPeriod
550 ) {
551 const result = {
552 limit: lastButOneSchedule.limit,
553 matchingChargingProfile: chargingProfile,
554 };
91a4f151 555 logger.debug(debugLogMsg, result);
17ac262c
JB
556 return result;
557 }
558 }
559 }
560 }
561 return null;
562 }
563
564 private static getRandomSerialNumberSuffix(params?: {
565 randomBytesLength?: number;
566 upperCase?: boolean;
567 }): string {
568 const randomSerialNumberSuffix = crypto
569 .randomBytes(params?.randomBytesLength ?? 16)
570 .toString('hex');
571 if (params?.upperCase) {
572 return randomSerialNumberSuffix.toUpperCase();
573 }
574 return randomSerialNumberSuffix;
575 }
576}