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