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