1 import crypto from
'node:crypto';
2 import path from
'node:path';
3 import { fileURLToPath
} from
'node:url';
5 import chalk from
'chalk';
6 import moment from
'moment';
8 import type { ChargingStation
} from
'./internal';
9 import { BaseError
} from
'../exception';
13 type BootNotificationRequest
,
16 ChargingProfileKindType
,
18 type ChargingSchedulePeriod
,
19 type ChargingStationInfo
,
20 type ChargingStationTemplate
,
25 type OCPP16BootNotificationRequest
,
26 type OCPP20BootNotificationRequest
,
39 import { WorkerProcessType
} from
'../worker';
41 const moduleName
= 'ChargingStationUtils';
43 export class ChargingStationUtils
{
44 private constructor() {
45 // This is intentional
48 public static getChargingStationId(
50 stationTemplate
: ChargingStationTemplate
52 // In case of multiple instances: add instance index to charging station id
53 const instanceIndex
= process
.env
.CF_INSTANCE_INDEX
?? 0;
54 const idSuffix
= stationTemplate
?.nameSuffix
?? '';
55 const idStr
= `000000000${index.toString()}`;
56 return stationTemplate
?.fixedName
57 ? stationTemplate
.baseName
58 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
63 public static getHashId(index
: number, stationTemplate
: ChargingStationTemplate
): string {
64 const chargingStationInfo
= {
65 chargePointModel
: stationTemplate
.chargePointModel
,
66 chargePointVendor
: stationTemplate
.chargePointVendor
,
67 ...(!Utils
.isUndefined(stationTemplate
.chargeBoxSerialNumberPrefix
) && {
68 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
70 ...(!Utils
.isUndefined(stationTemplate
.chargePointSerialNumberPrefix
) && {
71 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
73 ...(!Utils
.isUndefined(stationTemplate
.meterSerialNumberPrefix
) && {
74 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
76 ...(!Utils
.isUndefined(stationTemplate
.meterType
) && {
77 meterType
: stationTemplate
.meterType
,
81 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
83 `${JSON.stringify(chargingStationInfo)}${ChargingStationUtils.getChargingStationId(
91 public static checkChargingStation(chargingStation
: ChargingStation
, logPrefix
: string): boolean {
92 if (chargingStation
.started
=== false && chargingStation
.starting
=== false) {
93 logger
.warn(`${logPrefix} charging station is stopped, cannot proceed`);
99 public static getMaxNumberOfEvses(evses
: Record
<string, EvseTemplate
>): number {
103 return Object.keys(evses
).length
;
106 public static getMaxNumberOfConnectors(connectors
: Record
<string, ConnectorStatus
>): number {
110 return Object.keys(connectors
).length
;
113 public static checkTemplateMaxConnectors(
114 templateMaxConnectors
: number,
115 templateFile
: string,
118 if (templateMaxConnectors
=== 0) {
120 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
122 } else if (templateMaxConnectors
< 0) {
124 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
129 public static getBootConnectorStatus(
130 chargingStation
: ChargingStation
,
132 connectorStatus
: ConnectorStatus
133 ): ConnectorStatusEnum
{
134 let connectorBootStatus
: ConnectorStatusEnum
;
136 !connectorStatus
?.status &&
137 (chargingStation
.isChargingStationAvailable() === false ||
138 chargingStation
.isConnectorAvailable(connectorId
) === false)
140 connectorBootStatus
= ConnectorStatusEnum
.Unavailable
;
141 } else if (!connectorStatus
?.status && connectorStatus
?.bootStatus
) {
142 // Set boot status in template at startup
143 connectorBootStatus
= connectorStatus
?.bootStatus
;
144 } else if (connectorStatus
?.status) {
145 // Set previous status at startup
146 connectorBootStatus
= connectorStatus
?.status;
148 // Set default status
149 connectorBootStatus
= ConnectorStatusEnum
.Available
;
151 return connectorBootStatus
;
154 public static getConfiguredNumberOfConnectors(stationInfo
: ChargingStationInfo
): number {
155 let configuredMaxConnectors
: number;
156 if (Utils
.isNotEmptyArray(stationInfo
.numberOfConnectors
) === true) {
157 const numberOfConnectors
= stationInfo
.numberOfConnectors
as number[];
158 configuredMaxConnectors
=
159 numberOfConnectors
[Math.floor(Utils
.secureRandom() * numberOfConnectors
.length
)];
160 } else if (Utils
.isUndefined(stationInfo
.numberOfConnectors
) === false) {
161 configuredMaxConnectors
= stationInfo
.numberOfConnectors
as number;
162 } else if (stationInfo
.Connectors
&& !stationInfo
.Evses
) {
163 configuredMaxConnectors
= stationInfo
?.Connectors
[0]
164 ? ChargingStationUtils
.getMaxNumberOfConnectors(stationInfo
.Connectors
) - 1
165 : ChargingStationUtils
.getMaxNumberOfConnectors(stationInfo
.Connectors
);
166 } else if (stationInfo
.Evses
&& !stationInfo
.Connectors
) {
167 configuredMaxConnectors
= 0;
168 for (const evse
in stationInfo
.Evses
) {
172 configuredMaxConnectors
+= ChargingStationUtils
.getMaxNumberOfConnectors(
173 stationInfo
.Evses
[evse
].Connectors
177 return configuredMaxConnectors
;
180 public static checkConfiguredMaxConnectors(
181 configuredMaxConnectors
: number,
182 templateFile
: string,
185 if (configuredMaxConnectors
<= 0) {
187 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
192 public static checkStationInfoConnectorStatus(
194 connectorStatus
: ConnectorStatus
,
198 if (!Utils
.isNullOrUndefined(connectorStatus
?.status)) {
200 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
202 delete connectorStatus
.status;
206 public static buildConnectorsMap(
207 connectors
: Record
<string, ConnectorStatus
>,
210 ): Map
<number, ConnectorStatus
> {
211 const connectorsMap
= new Map
<number, ConnectorStatus
>();
212 if (ChargingStationUtils
.getMaxNumberOfConnectors(connectors
) > 0) {
213 for (const connector
in connectors
) {
214 const connectorStatus
= connectors
[connector
];
215 const connectorId
= Utils
.convertToInt(connector
);
216 ChargingStationUtils
.checkStationInfoConnectorStatus(
222 connectorsMap
.set(connectorId
, Utils
.cloneObject
<ConnectorStatus
>(connectorStatus
));
226 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
229 return connectorsMap
;
232 public static initializeConnectorsMapStatus(
233 connectors
: Map
<number, ConnectorStatus
>,
236 for (const connectorId
of connectors
.keys()) {
237 if (connectorId
> 0 && connectors
.get(connectorId
)?.transactionStarted
=== true) {
239 `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
240 connectors.get(connectorId)?.transactionId
244 if (connectorId
=== 0) {
245 connectors
.get(connectorId
).availability
= AvailabilityType
.Operative
;
246 if (Utils
.isUndefined(connectors
.get(connectorId
)?.chargingProfiles
)) {
247 connectors
.get(connectorId
).chargingProfiles
= [];
251 Utils
.isNullOrUndefined(connectors
.get(connectorId
)?.transactionStarted
)
253 ChargingStationUtils
.initializeConnectorStatus(connectors
.get(connectorId
));
258 public static resetConnectorStatus(connectorStatus
: ConnectorStatus
): void {
259 connectorStatus
.idTagLocalAuthorized
= false;
260 connectorStatus
.idTagAuthorized
= false;
261 connectorStatus
.transactionRemoteStarted
= false;
262 connectorStatus
.transactionStarted
= false;
263 delete connectorStatus
?.localAuthorizeIdTag
;
264 delete connectorStatus
?.authorizeIdTag
;
265 delete connectorStatus
?.transactionId
;
266 delete connectorStatus
?.transactionIdTag
;
267 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
268 delete connectorStatus
?.transactionBeginMeterValue
;
271 public static createBootNotificationRequest(
272 stationInfo
: ChargingStationInfo
,
273 bootReason
: BootReasonEnumType
= BootReasonEnumType
.PowerUp
274 ): BootNotificationRequest
{
275 const ocppVersion
= stationInfo
.ocppVersion
?? OCPPVersion
.VERSION_16
;
276 switch (ocppVersion
) {
277 case OCPPVersion
.VERSION_16
:
279 chargePointModel
: stationInfo
.chargePointModel
,
280 chargePointVendor
: stationInfo
.chargePointVendor
,
281 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
282 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
284 ...(!Utils
.isUndefined(stationInfo
.chargePointSerialNumber
) && {
285 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
287 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
288 firmwareVersion
: stationInfo
.firmwareVersion
,
290 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
291 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
292 ...(!Utils
.isUndefined(stationInfo
.meterSerialNumber
) && {
293 meterSerialNumber
: stationInfo
.meterSerialNumber
,
295 ...(!Utils
.isUndefined(stationInfo
.meterType
) && {
296 meterType
: stationInfo
.meterType
,
298 } as OCPP16BootNotificationRequest
;
299 case OCPPVersion
.VERSION_20
:
300 case OCPPVersion
.VERSION_201
:
304 model
: stationInfo
.chargePointModel
,
305 vendorName
: stationInfo
.chargePointVendor
,
306 ...(!Utils
.isUndefined(stationInfo
.firmwareVersion
) && {
307 firmwareVersion
: stationInfo
.firmwareVersion
,
309 ...(!Utils
.isUndefined(stationInfo
.chargeBoxSerialNumber
) && {
310 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
312 ...((!Utils
.isUndefined(stationInfo
.iccid
) || !Utils
.isUndefined(stationInfo
.imsi
)) && {
314 ...(!Utils
.isUndefined(stationInfo
.iccid
) && { iccid
: stationInfo
.iccid
}),
315 ...(!Utils
.isUndefined(stationInfo
.imsi
) && { imsi
: stationInfo
.imsi
}),
319 } as OCPP20BootNotificationRequest
;
323 public static workerPoolInUse(): boolean {
324 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.staticPool
].includes(
325 Configuration
.getWorker().processType
329 public static workerDynamicPoolInUse(): boolean {
330 return Configuration
.getWorker().processType
=== WorkerProcessType
.dynamicPool
;
333 public static warnTemplateKeysDeprecation(
334 templateFile
: string,
335 stationTemplate
: ChargingStationTemplate
,
338 const templateKeys
: { key
: string; deprecatedKey
: string }[] = [
339 { key
: 'supervisionUrls', deprecatedKey
: 'supervisionUrl' },
340 { key
: 'idTagsFile', deprecatedKey
: 'authorizationFile' },
342 for (const templateKey
of templateKeys
) {
343 ChargingStationUtils
.warnDeprecatedTemplateKey(
345 templateKey
.deprecatedKey
,
348 `Use '${templateKey.key}' instead`
350 ChargingStationUtils
.convertDeprecatedTemplateKey(
352 templateKey
.deprecatedKey
,
358 public static stationTemplateToStationInfo(
359 stationTemplate
: ChargingStationTemplate
360 ): ChargingStationInfo
{
361 stationTemplate
= Utils
.cloneObject(stationTemplate
);
362 delete stationTemplate
.power
;
363 delete stationTemplate
.powerUnit
;
364 delete stationTemplate
.Configuration
;
365 delete stationTemplate
.AutomaticTransactionGenerator
;
366 delete stationTemplate
.chargeBoxSerialNumberPrefix
;
367 delete stationTemplate
.chargePointSerialNumberPrefix
;
368 delete stationTemplate
.meterSerialNumberPrefix
;
369 return stationTemplate
as unknown
as ChargingStationInfo
;
372 public static createStationInfoHash(stationInfo
: ChargingStationInfo
): void {
373 delete stationInfo
.infoHash
;
374 stationInfo
.infoHash
= crypto
375 .createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
376 .update(JSON
.stringify(stationInfo
))
380 public static createSerialNumber(
381 stationTemplate
: ChargingStationTemplate
,
382 stationInfo
: ChargingStationInfo
,
384 randomSerialNumberUpperCase
?: boolean;
385 randomSerialNumber
?: boolean;
387 randomSerialNumberUpperCase
: true,
388 randomSerialNumber
: true,
391 params
= params
?? {};
392 params
.randomSerialNumberUpperCase
= params
?.randomSerialNumberUpperCase
?? true;
393 params
.randomSerialNumber
= params
?.randomSerialNumber
?? true;
394 const serialNumberSuffix
= params
?.randomSerialNumber
395 ? ChargingStationUtils
.getRandomSerialNumberSuffix({
396 upperCase
: params
.randomSerialNumberUpperCase
,
399 stationInfo
.chargePointSerialNumber
= Utils
.isNotEmptyString(
400 stationTemplate
?.chargePointSerialNumberPrefix
402 ? `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`
404 stationInfo
.chargeBoxSerialNumber
= Utils
.isNotEmptyString(
405 stationTemplate
?.chargeBoxSerialNumberPrefix
407 ? `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`
409 stationInfo
.meterSerialNumber
= Utils
.isNotEmptyString(stationTemplate
?.meterSerialNumberPrefix
)
410 ? `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`
414 public static propagateSerialNumber(
415 stationTemplate
: ChargingStationTemplate
,
416 stationInfoSrc
: ChargingStationInfo
,
417 stationInfoDst
: ChargingStationInfo
419 if (!stationInfoSrc
|| !stationTemplate
) {
421 'Missing charging station template or existing configuration to propagate serial number'
424 stationTemplate
?.chargePointSerialNumberPrefix
&& stationInfoSrc
?.chargePointSerialNumber
425 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
426 : stationInfoDst
?.chargePointSerialNumber
&& delete stationInfoDst
.chargePointSerialNumber
;
427 stationTemplate
?.chargeBoxSerialNumberPrefix
&& stationInfoSrc
?.chargeBoxSerialNumber
428 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
429 : stationInfoDst
?.chargeBoxSerialNumber
&& delete stationInfoDst
.chargeBoxSerialNumber
;
430 stationTemplate
?.meterSerialNumberPrefix
&& stationInfoSrc
?.meterSerialNumber
431 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
432 : stationInfoDst
?.meterSerialNumber
&& delete stationInfoDst
.meterSerialNumber
;
435 public static getAmperageLimitationUnitDivider(stationInfo
: ChargingStationInfo
): number {
437 switch (stationInfo
.amperageLimitationUnit
) {
438 case AmpereUnits
.DECI_AMPERE
:
441 case AmpereUnits
.CENTI_AMPERE
:
444 case AmpereUnits
.MILLI_AMPERE
:
451 public static getChargingStationConnectorChargingProfilesPowerLimit(
452 chargingStation
: ChargingStation
,
454 ): number | undefined {
455 let limit
: number, matchingChargingProfile
: ChargingProfile
;
456 // Get charging profiles for connector and sort by stack level
457 const chargingProfiles
=
458 Utils
.cloneObject(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)?.sort(
459 (a
, b
) => b
.stackLevel
- a
.stackLevel
461 // Get profiles on connector 0
462 if (chargingStation
.getConnectorStatus(0)?.chargingProfiles
) {
463 chargingProfiles
.push(
464 ...Utils
.cloneObject(chargingStation
.getConnectorStatus(0).chargingProfiles
).sort(
465 (a
, b
) => b
.stackLevel
- a
.stackLevel
469 if (Utils
.isNotEmptyArray(chargingProfiles
)) {
470 const result
= ChargingStationUtils
.getLimitFromChargingProfiles(
472 chargingStation
.logPrefix()
474 if (!Utils
.isNullOrUndefined(result
)) {
475 limit
= result
?.limit
;
476 matchingChargingProfile
= result
?.matchingChargingProfile
;
477 switch (chargingStation
.getCurrentOutType()) {
480 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
481 ChargingRateUnitType
.WATT
483 : ACElectricUtils
.powerTotal(
484 chargingStation
.getNumberOfPhases(),
485 chargingStation
.getVoltageOut(),
491 matchingChargingProfile
.chargingSchedule
.chargingRateUnit
===
492 ChargingRateUnitType
.WATT
494 : DCElectricUtils
.power(chargingStation
.getVoltageOut(), limit
);
496 const connectorMaximumPower
=
497 chargingStation
.getMaximumPower() / chargingStation
.powerDivider
;
498 if (limit
> connectorMaximumPower
) {
500 `${chargingStation.logPrefix()} Charging profile id ${
501 matchingChargingProfile.chargingProfileId
502 } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
505 limit
= connectorMaximumPower
;
512 public static getDefaultVoltageOut(
513 currentType
: CurrentType
,
514 templateFile
: string,
517 const errMsg
= `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
518 let defaultVoltageOut
: number;
519 switch (currentType
) {
521 defaultVoltageOut
= Voltage
.VOLTAGE_230
;
524 defaultVoltageOut
= Voltage
.VOLTAGE_400
;
527 logger
.error(`${logPrefix} ${errMsg}`);
528 throw new BaseError(errMsg
);
530 return defaultVoltageOut
;
533 public static getIdTagsFile(stationInfo
: ChargingStationInfo
): string | undefined {
535 stationInfo
.idTagsFile
&&
537 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
539 path
.basename(stationInfo
.idTagsFile
)
544 private static initializeConnectorStatus(connectorStatus
: ConnectorStatus
): void {
545 connectorStatus
.availability
= AvailabilityType
.Operative
;
546 connectorStatus
.idTagLocalAuthorized
= false;
547 connectorStatus
.idTagAuthorized
= false;
548 connectorStatus
.transactionRemoteStarted
= false;
549 connectorStatus
.transactionStarted
= false;
550 connectorStatus
.energyActiveImportRegisterValue
= 0;
551 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0;
552 if (Utils
.isUndefined(connectorStatus
.chargingProfiles
)) {
553 connectorStatus
.chargingProfiles
= [];
557 private static warnDeprecatedTemplateKey(
558 template
: ChargingStationTemplate
,
560 templateFile
: string,
564 if (!Utils
.isUndefined(template
[key
])) {
565 const logMsg
= `Deprecated template key '${key}' usage in file '${templateFile}'${
566 Utils.isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}
` : ''
568 logger
.warn(`${logPrefix} ${logMsg}`);
569 console
.warn(chalk
.yellow(`${logMsg}`));
573 private static convertDeprecatedTemplateKey(
574 template
: ChargingStationTemplate
,
575 deprecatedKey
: string,
578 if (!Utils
.isUndefined(template
[deprecatedKey
])) {
579 template
[key
] = template
[deprecatedKey
] as unknown
;
580 delete template
[deprecatedKey
];
585 * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
587 * @param chargingProfiles -
591 private static getLimitFromChargingProfiles(
592 chargingProfiles
: ChargingProfile
[],
596 matchingChargingProfile
: ChargingProfile
;
598 const debugLogMsg
= `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
599 const currentMoment
= moment();
600 const currentDate
= new Date();
601 for (const chargingProfile
of chargingProfiles
) {
603 const chargingSchedule
= chargingProfile
.chargingSchedule
;
604 if (!chargingSchedule
?.startSchedule
) {
606 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
609 // Check type (recurring) and if it is already active
610 // Adjust the daily recurring schedule to today
612 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
613 chargingProfile
.recurrencyKind
=== RecurrencyKindType
.DAILY
&&
614 currentMoment
.isAfter(chargingSchedule
.startSchedule
)
616 if (!(chargingSchedule
?.startSchedule
instanceof Date)) {
618 `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
620 chargingSchedule
.startSchedule
= new Date(chargingSchedule
.startSchedule
);
622 chargingSchedule
.startSchedule
.setFullYear(
623 currentDate
.getFullYear(),
624 currentDate
.getMonth(),
625 currentDate
.getDate()
627 // Check if the start of the schedule is yesterday
628 if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
629 chargingSchedule
.startSchedule
.setDate(currentDate
.getDate() - 1);
631 } else if (moment(chargingSchedule
.startSchedule
).isAfter(currentMoment
)) {
634 // Check if the charging profile is active
636 moment(chargingSchedule
.startSchedule
)
637 .add(chargingSchedule
.duration
, 's')
638 .isAfter(currentMoment
)
640 let lastButOneSchedule
: ChargingSchedulePeriod
;
641 // Search the right schedule period
642 for (const schedulePeriod
of chargingSchedule
.chargingSchedulePeriod
) {
643 // Handling of only one period
645 chargingSchedule
.chargingSchedulePeriod
.length
=== 1 &&
646 schedulePeriod
.startPeriod
=== 0
649 limit
: schedulePeriod
.limit
,
650 matchingChargingProfile
: chargingProfile
,
652 logger
.debug(debugLogMsg
, result
);
655 // Find the right schedule period
657 moment(chargingSchedule
.startSchedule
)
658 .add(schedulePeriod
.startPeriod
, 's')
659 .isAfter(currentMoment
)
661 // Found the schedule: last but one is the correct one
663 limit
: lastButOneSchedule
.limit
,
664 matchingChargingProfile
: chargingProfile
,
666 logger
.debug(debugLogMsg
, result
);
670 lastButOneSchedule
= schedulePeriod
;
671 // Handle the last schedule period
673 schedulePeriod
.startPeriod
===
674 chargingSchedule
.chargingSchedulePeriod
[
675 chargingSchedule
.chargingSchedulePeriod
.length
- 1
679 limit
: lastButOneSchedule
.limit
,
680 matchingChargingProfile
: chargingProfile
,
682 logger
.debug(debugLogMsg
, result
);
691 private static getRandomSerialNumberSuffix(params
?: {
692 randomBytesLength
?: number;
695 const randomSerialNumberSuffix
= crypto
696 .randomBytes(params
?.randomBytesLength
?? 16)
698 if (params
?.upperCase
) {
699 return randomSerialNumberSuffix
.toUpperCase();
701 return randomSerialNumberSuffix
;