1 import type { EventEmitter
} from
'node:events'
3 import chalk from
'chalk'
19 import { maxTime
} from
'date-fns/constants'
20 import { createHash
, randomBytes
} from
'node:crypto'
21 import { basename
, dirname
, isAbsolute
, join
, parse
, relative
, resolve
} from
'node:path'
22 import { env
} from
'node:process'
23 import { fileURLToPath
} from
'node:url'
24 import { isEmpty
} from
'rambda'
26 import type { ChargingStation
} from
'./ChargingStation.js'
28 import { BaseError
} from
'../exception/index.js'
32 type BootNotificationRequest
,
35 ChargingProfileKindType
,
36 ChargingProfilePurposeType
,
38 type ChargingSchedulePeriod
,
39 type ChargingStationConfiguration
,
40 type ChargingStationInfo
,
41 type ChargingStationOptions
,
42 type ChargingStationTemplate
,
43 type ChargingStationWorkerMessageEvents
,
44 ConnectorPhaseRotation
,
49 type OCPP16BootNotificationRequest
,
50 type OCPP20BootNotificationRequest
,
54 ReservationTerminationReason
,
55 StandardParametersKey
,
56 type SupportedFeatureProfiles
,
58 } from
'../types/index.js'
72 } from
'../utils/index.js'
73 import { getConfigurationKey
} from
'./ConfigurationKeyUtils.js'
75 const moduleName
= 'Helpers'
77 export const buildTemplateName
= (templateFile
: string): string => {
78 if (isAbsolute(templateFile
)) {
79 templateFile
= relative(
80 resolve(join(dirname(fileURLToPath(import.meta
.url
)), 'assets', 'station-templates')),
84 const templateFileParsedPath
= parse(templateFile
)
85 return join(templateFileParsedPath
.dir
, templateFileParsedPath
.name
)
88 export const getChargingStationId
= (
90 stationTemplate
: ChargingStationTemplate
| undefined
92 if (stationTemplate
== null) {
93 return "Unknown 'chargingStationId'"
95 // In case of multiple instances: add instance index to charging station id
96 const instanceIndex
= env
.CF_INSTANCE_INDEX
?? 0
97 const idSuffix
= stationTemplate
.nameSuffix
?? ''
98 const idStr
= `000000000${index.toString()}`
99 return stationTemplate
.fixedName
=== true
100 ? stationTemplate
.baseName
101 : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
106 export const hasReservationExpired
= (reservation
: Reservation
): boolean => {
107 return isPast(reservation
.expiryDate
)
110 export const removeExpiredReservations
= async (
111 chargingStation
: ChargingStation
112 ): Promise
<void> => {
113 if (chargingStation
.hasEvses
) {
114 for (const evseStatus
of chargingStation
.evses
.values()) {
115 for (const connectorStatus
of evseStatus
.connectors
.values()) {
117 connectorStatus
.reservation
!= null &&
118 hasReservationExpired(connectorStatus
.reservation
)
120 await chargingStation
.removeReservation(
121 connectorStatus
.reservation
,
122 ReservationTerminationReason
.EXPIRED
128 for (const connectorStatus
of chargingStation
.connectors
.values()) {
130 connectorStatus
.reservation
!= null &&
131 hasReservationExpired(connectorStatus
.reservation
)
133 await chargingStation
.removeReservation(
134 connectorStatus
.reservation
,
135 ReservationTerminationReason
.EXPIRED
142 export const getNumberOfReservableConnectors
= (
143 connectors
: Map
<number, ConnectorStatus
>
145 let numberOfReservableConnectors
= 0
146 for (const [connectorId
, connectorStatus
] of connectors
) {
147 if (connectorId
=== 0) {
150 if (connectorStatus
.status === ConnectorStatusEnum
.Available
) {
151 ++numberOfReservableConnectors
154 return numberOfReservableConnectors
157 export const getHashId
= (index
: number, stationTemplate
: ChargingStationTemplate
): string => {
158 const chargingStationInfo
= {
159 chargePointModel
: stationTemplate
.chargePointModel
,
160 chargePointVendor
: stationTemplate
.chargePointVendor
,
161 ...(stationTemplate
.chargeBoxSerialNumberPrefix
!= null && {
162 chargeBoxSerialNumber
: stationTemplate
.chargeBoxSerialNumberPrefix
,
164 ...(stationTemplate
.chargePointSerialNumberPrefix
!= null && {
165 chargePointSerialNumber
: stationTemplate
.chargePointSerialNumberPrefix
,
167 ...(stationTemplate
.meterSerialNumberPrefix
!= null && {
168 meterSerialNumber
: stationTemplate
.meterSerialNumberPrefix
,
170 ...(stationTemplate
.meterType
!= null && {
171 meterType
: stationTemplate
.meterType
,
174 return createHash(Constants
.DEFAULT_HASH_ALGORITHM
)
175 .update(`${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`)
179 export const validateStationInfo
= (chargingStation
: ChargingStation
): void => {
180 if (chargingStation
.stationInfo
== null || isEmpty(chargingStation
.stationInfo
)) {
181 throw new BaseError('Missing charging station information')
184 chargingStation
.stationInfo
.chargingStationId
== null ||
185 isEmpty(chargingStation
.stationInfo
.chargingStationId
.trim())
187 throw new BaseError('Missing chargingStationId in stationInfo properties')
189 const chargingStationId
= chargingStation
.stationInfo
.chargingStationId
191 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
192 chargingStation
.stationInfo
.hashId
== null ||
193 isEmpty(chargingStation
.stationInfo
.hashId
.trim())
195 throw new BaseError(`${chargingStationId}: Missing hashId in stationInfo properties`)
197 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
198 if (chargingStation
.stationInfo
.templateIndex
== null) {
199 throw new BaseError(`${chargingStationId}: Missing templateIndex in stationInfo properties`)
201 if (chargingStation
.stationInfo
.templateIndex
<= 0) {
203 `${chargingStationId}: Invalid templateIndex value in stationInfo properties`
207 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
208 chargingStation
.stationInfo
.templateName
== null ||
209 isEmpty(chargingStation
.stationInfo
.templateName
.trim())
211 throw new BaseError(`${chargingStationId}: Missing templateName in stationInfo properties`)
213 if (chargingStation
.stationInfo
.maximumPower
== null) {
214 throw new BaseError(`${chargingStationId}: Missing maximumPower in stationInfo properties`)
216 if (chargingStation
.stationInfo
.maximumPower
<= 0) {
217 throw new RangeError(
218 `${chargingStationId}: Invalid maximumPower value in stationInfo properties`
221 if (chargingStation
.stationInfo
.maximumAmperage
== null) {
222 throw new BaseError(`${chargingStationId}: Missing maximumAmperage in stationInfo properties`)
224 if (chargingStation
.stationInfo
.maximumAmperage
<= 0) {
225 throw new RangeError(
226 `${chargingStationId}: Invalid maximumAmperage value in stationInfo properties`
229 switch (chargingStation
.stationInfo
.ocppVersion
) {
230 case OCPPVersion
.VERSION_20
:
231 case OCPPVersion
.VERSION_201
:
232 if (chargingStation
.evses
.size
=== 0) {
234 `${chargingStationId}: OCPP 2.0 or superior requires at least one EVSE defined in the charging station template/configuration`
240 export const checkChargingStationState
= (
241 chargingStation
: ChargingStation
,
244 if (!chargingStation
.started
&& !chargingStation
.starting
) {
245 logger
.warn(`${logPrefix} charging station is stopped, cannot proceed`)
251 export const getPhaseRotationValue
= (
253 numberOfPhases
: number
254 ): string | undefined => {
256 if (connectorId
=== 0 && numberOfPhases
=== 0) {
257 return `${connectorId.toString()}.${ConnectorPhaseRotation.RST}`
258 } else if (connectorId
> 0 && numberOfPhases
=== 0) {
259 return `${connectorId.toString()}.${ConnectorPhaseRotation.NotApplicable}`
261 } else if (connectorId
>= 0 && numberOfPhases
=== 1) {
262 return `${connectorId.toString()}.${ConnectorPhaseRotation.NotApplicable}`
263 } else if (connectorId
>= 0 && numberOfPhases
=== 3) {
264 return `${connectorId.toString()}.${ConnectorPhaseRotation.RST}`
268 export const getMaxNumberOfEvses
= (evses
: Record
<string, EvseTemplate
> | undefined): number => {
272 return Object.keys(evses
).length
275 const getMaxNumberOfConnectors
= (
276 connectors
: Record
<string, ConnectorStatus
> | undefined
278 if (connectors
== null) {
281 return Object.keys(connectors
).length
284 export const getBootConnectorStatus
= (
285 chargingStation
: ChargingStation
,
287 connectorStatus
: ConnectorStatus
288 ): ConnectorStatusEnum
=> {
289 let connectorBootStatus
: ConnectorStatusEnum
291 connectorStatus
.status == null &&
292 (!chargingStation
.isChargingStationAvailable() ||
293 !chargingStation
.isConnectorAvailable(connectorId
))
295 connectorBootStatus
= ConnectorStatusEnum
.Unavailable
296 } else if (connectorStatus
.status == null && connectorStatus
.bootStatus
!= null) {
297 // Set boot status in template at startup
298 connectorBootStatus
= connectorStatus
.bootStatus
299 } else if (connectorStatus
.status != null) {
300 // Set previous status at startup
301 connectorBootStatus
= connectorStatus
.status
303 // Set default status
304 connectorBootStatus
= ConnectorStatusEnum
.Available
306 return connectorBootStatus
309 export const checkTemplate
= (
310 stationTemplate
: ChargingStationTemplate
| undefined,
314 if (stationTemplate
== null) {
315 const errorMsg
= `Failed to read charging station template file ${templateFile}`
316 logger
.error(`${logPrefix} ${errorMsg}`)
317 throw new BaseError(errorMsg
)
319 if (isEmpty(stationTemplate
)) {
320 const errorMsg
= `Empty charging station information from template file ${templateFile}`
321 logger
.error(`${logPrefix} ${errorMsg}`)
322 throw new BaseError(errorMsg
)
324 if (stationTemplate
.idTagsFile
== null || isEmpty(stationTemplate
.idTagsFile
)) {
326 `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`
331 export const checkConfiguration
= (
332 stationConfiguration
: ChargingStationConfiguration
| undefined,
334 configurationFile
: string
336 if (stationConfiguration
== null) {
337 const errorMsg
= `Failed to read charging station configuration file ${configurationFile}`
338 logger
.error(`${logPrefix} ${errorMsg}`)
339 throw new BaseError(errorMsg
)
341 if (isEmpty(stationConfiguration
)) {
342 const errorMsg
= `Empty charging station configuration from file ${configurationFile}`
343 logger
.error(`${logPrefix} ${errorMsg}`)
344 throw new BaseError(errorMsg
)
348 export const checkConnectorsConfiguration
= (
349 stationTemplate
: ChargingStationTemplate
,
353 configuredMaxConnectors
: number
354 templateMaxAvailableConnectors
: number
355 templateMaxConnectors
: number
357 const configuredMaxConnectors
= getConfiguredMaxNumberOfConnectors(stationTemplate
)
358 checkConfiguredMaxConnectors(configuredMaxConnectors
, logPrefix
, templateFile
)
359 const templateMaxConnectors
= getMaxNumberOfConnectors(stationTemplate
.Connectors
)
360 checkTemplateMaxConnectors(templateMaxConnectors
, logPrefix
, templateFile
)
361 const templateMaxAvailableConnectors
=
362 stationTemplate
.Connectors
?.[0] != null ? templateMaxConnectors
- 1 : templateMaxConnectors
364 configuredMaxConnectors
> templateMaxAvailableConnectors
&&
365 stationTemplate
.randomConnectors
!== true
368 `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`
370 stationTemplate
.randomConnectors
= true
373 configuredMaxConnectors
,
374 templateMaxAvailableConnectors
,
375 templateMaxConnectors
,
379 export const checkStationInfoConnectorStatus
= (
381 connectorStatus
: ConnectorStatus
,
385 if (connectorStatus
.status != null) {
387 `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId.toString()} status configuration defined, undefine it`
389 delete connectorStatus
.status
393 export const setChargingStationOptions
= (
394 stationInfo
: ChargingStationInfo
,
395 options
?: ChargingStationOptions
396 ): ChargingStationInfo
=> {
397 if (options
?.supervisionUrls
!= null) {
398 stationInfo
.supervisionUrls
= options
.supervisionUrls
400 if (options
?.persistentConfiguration
!= null) {
401 stationInfo
.stationInfoPersistentConfiguration
= options
.persistentConfiguration
402 stationInfo
.ocppPersistentConfiguration
= options
.persistentConfiguration
403 stationInfo
.automaticTransactionGeneratorPersistentConfiguration
=
404 options
.persistentConfiguration
406 if (options
?.autoStart
!= null) {
407 stationInfo
.autoStart
= options
.autoStart
409 if (options
?.autoRegister
!= null) {
410 stationInfo
.autoRegister
= options
.autoRegister
412 if (options
?.enableStatistics
!= null) {
413 stationInfo
.enableStatistics
= options
.enableStatistics
415 if (options
?.ocppStrictCompliance
!= null) {
416 stationInfo
.ocppStrictCompliance
= options
.ocppStrictCompliance
418 if (options
?.stopTransactionsOnStopped
!= null) {
419 stationInfo
.stopTransactionsOnStopped
= options
.stopTransactionsOnStopped
424 export const buildConnectorsMap
= (
425 connectors
: Record
<string, ConnectorStatus
>,
428 ): Map
<number, ConnectorStatus
> => {
429 const connectorsMap
= new Map
<number, ConnectorStatus
>()
430 if (getMaxNumberOfConnectors(connectors
) > 0) {
431 for (const connector
in connectors
) {
432 const connectorStatus
= connectors
[connector
]
433 const connectorId
= convertToInt(connector
)
434 checkStationInfoConnectorStatus(connectorId
, connectorStatus
, logPrefix
, templateFile
)
435 connectorsMap
.set(connectorId
, clone
<ConnectorStatus
>(connectorStatus
))
439 `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
445 export const initializeConnectorsMapStatus
= (
446 connectors
: Map
<number, ConnectorStatus
>,
449 for (const connectorId
of connectors
.keys()) {
450 if (connectorId
> 0 && connectors
.get(connectorId
)?.transactionStarted
=== true) {
452 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
453 `${logPrefix} Connector id ${connectorId.toString()} at initialization has a transaction started with id ${connectors
455 ?.transactionId?.toString()}`
458 if (connectorId
=== 0) {
459 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
460 connectors
.get(connectorId
)!.availability
= AvailabilityType
.Operative
461 if (connectors
.get(connectorId
)?.chargingProfiles
== null) {
462 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
463 connectors
.get(connectorId
)!.chargingProfiles
= []
465 } else if (connectorId
> 0 && connectors
.get(connectorId
)?.transactionStarted
== null) {
466 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
467 initializeConnectorStatus(connectors
.get(connectorId
)!)
472 export const resetAuthorizeConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
473 connectorStatus
.idTagLocalAuthorized
= false
474 connectorStatus
.idTagAuthorized
= false
475 delete connectorStatus
.localAuthorizeIdTag
476 delete connectorStatus
.authorizeIdTag
479 export const resetConnectorStatus
= (connectorStatus
: ConnectorStatus
| undefined): void => {
480 if (connectorStatus
== null) {
483 if (isNotEmptyArray(connectorStatus
.chargingProfiles
)) {
484 connectorStatus
.chargingProfiles
= connectorStatus
.chargingProfiles
.filter(
486 (chargingProfile
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
&&
487 chargingProfile
.transactionId
!= null &&
488 connectorStatus
.transactionId
!= null &&
489 chargingProfile
.transactionId
!== connectorStatus
.transactionId
) ||
490 chargingProfile
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
493 resetAuthorizeConnectorStatus(connectorStatus
)
494 connectorStatus
.transactionRemoteStarted
= false
495 connectorStatus
.transactionStarted
= false
496 delete connectorStatus
.transactionStart
497 delete connectorStatus
.transactionId
498 delete connectorStatus
.transactionIdTag
499 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0
500 delete connectorStatus
.transactionBeginMeterValue
503 export const prepareConnectorStatus
= (connectorStatus
: ConnectorStatus
): ConnectorStatus
=> {
504 if (connectorStatus
.reservation
!= null) {
505 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
506 connectorStatus
.reservation
.expiryDate
= convertToDate(connectorStatus
.reservation
.expiryDate
)!
508 if (isNotEmptyArray(connectorStatus
.chargingProfiles
)) {
509 connectorStatus
.chargingProfiles
= connectorStatus
.chargingProfiles
512 chargingProfile
.chargingProfilePurpose
!== ChargingProfilePurposeType
.TX_PROFILE
514 .map(chargingProfile
=> {
515 chargingProfile
.chargingSchedule
.startSchedule
= convertToDate(
516 chargingProfile
.chargingSchedule
.startSchedule
518 chargingProfile
.validFrom
= convertToDate(chargingProfile
.validFrom
)
519 chargingProfile
.validTo
= convertToDate(chargingProfile
.validTo
)
520 return chargingProfile
523 return connectorStatus
526 export const createBootNotificationRequest
= (
527 stationInfo
: ChargingStationInfo
,
528 bootReason
: BootReasonEnumType
= BootReasonEnumType
.PowerUp
529 ): BootNotificationRequest
| undefined => {
530 const ocppVersion
= stationInfo
.ocppVersion
531 switch (ocppVersion
) {
532 case OCPPVersion
.VERSION_16
:
534 chargePointModel
: stationInfo
.chargePointModel
,
535 chargePointVendor
: stationInfo
.chargePointVendor
,
536 ...(stationInfo
.chargeBoxSerialNumber
!= null && {
537 chargeBoxSerialNumber
: stationInfo
.chargeBoxSerialNumber
,
539 ...(stationInfo
.chargePointSerialNumber
!= null && {
540 chargePointSerialNumber
: stationInfo
.chargePointSerialNumber
,
542 ...(stationInfo
.firmwareVersion
!= null && {
543 firmwareVersion
: stationInfo
.firmwareVersion
,
545 ...(stationInfo
.iccid
!= null && { iccid
: stationInfo
.iccid
}),
546 ...(stationInfo
.imsi
!= null && { imsi
: stationInfo
.imsi
}),
547 ...(stationInfo
.meterSerialNumber
!= null && {
548 meterSerialNumber
: stationInfo
.meterSerialNumber
,
550 ...(stationInfo
.meterType
!= null && {
551 meterType
: stationInfo
.meterType
,
553 } satisfies OCPP16BootNotificationRequest
554 case OCPPVersion
.VERSION_20
:
555 case OCPPVersion
.VERSION_201
:
558 model
: stationInfo
.chargePointModel
,
559 vendorName
: stationInfo
.chargePointVendor
,
560 ...(stationInfo
.firmwareVersion
!= null && {
561 firmwareVersion
: stationInfo
.firmwareVersion
,
563 ...(stationInfo
.chargeBoxSerialNumber
!= null && {
564 serialNumber
: stationInfo
.chargeBoxSerialNumber
,
566 ...((stationInfo
.iccid
!= null || stationInfo
.imsi
!= null) && {
568 ...(stationInfo
.iccid
!= null && { iccid
: stationInfo
.iccid
}),
569 ...(stationInfo
.imsi
!= null && { imsi
: stationInfo
.imsi
}),
574 } satisfies OCPP20BootNotificationRequest
578 export const warnTemplateKeysDeprecation
= (
579 stationTemplate
: ChargingStationTemplate
,
583 const templateKeys
: { deprecatedKey
: string; key
?: string }[] = [
584 { deprecatedKey
: 'supervisionUrl', key
: 'supervisionUrls' },
585 { deprecatedKey
: 'authorizationFile', key
: 'idTagsFile' },
586 { deprecatedKey
: 'payloadSchemaValidation', key
: 'ocppStrictCompliance' },
587 { deprecatedKey
: 'mustAuthorizeAtRemoteStart', key
: 'remoteAuthorization' },
589 for (const templateKey
of templateKeys
) {
590 warnDeprecatedTemplateKey(
592 templateKey
.deprecatedKey
,
595 templateKey
.key
!= null ? `Use '${templateKey.key}' instead` : undefined
597 convertDeprecatedTemplateKey(stationTemplate
, templateKey
.deprecatedKey
, templateKey
.key
)
601 export const stationTemplateToStationInfo
= (
602 stationTemplate
: ChargingStationTemplate
603 ): ChargingStationInfo
=> {
604 stationTemplate
= clone
<ChargingStationTemplate
>(stationTemplate
)
605 delete stationTemplate
.power
606 delete stationTemplate
.powerUnit
607 delete stationTemplate
.Connectors
608 delete stationTemplate
.Evses
609 delete stationTemplate
.Configuration
610 delete stationTemplate
.AutomaticTransactionGenerator
611 delete stationTemplate
.numberOfConnectors
612 delete stationTemplate
.chargeBoxSerialNumberPrefix
613 delete stationTemplate
.chargePointSerialNumberPrefix
614 delete stationTemplate
.meterSerialNumberPrefix
615 return stationTemplate
as ChargingStationInfo
618 export const createSerialNumber
= (
619 stationTemplate
: ChargingStationTemplate
,
620 stationInfo
: ChargingStationInfo
,
622 randomSerialNumber
?: boolean
623 randomSerialNumberUpperCase
?: boolean
627 ...{ randomSerialNumber
: true, randomSerialNumberUpperCase
: true },
630 const serialNumberSuffix
= params
.randomSerialNumber
631 ? getRandomSerialNumberSuffix({
632 upperCase
: params
.randomSerialNumberUpperCase
,
635 isNotEmptyString(stationTemplate
.chargePointSerialNumberPrefix
) &&
636 (stationInfo
.chargePointSerialNumber
= `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`)
637 isNotEmptyString(stationTemplate
.chargeBoxSerialNumberPrefix
) &&
638 (stationInfo
.chargeBoxSerialNumber
= `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`)
639 isNotEmptyString(stationTemplate
.meterSerialNumberPrefix
) &&
640 (stationInfo
.meterSerialNumber
= `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`)
643 export const propagateSerialNumber
= (
644 stationTemplate
: ChargingStationTemplate
| undefined,
645 stationInfoSrc
: ChargingStationInfo
| undefined,
646 stationInfoDst
: ChargingStationInfo
648 if (stationInfoSrc
== null || stationTemplate
== null) {
650 'Missing charging station template or existing configuration to propagate serial number'
653 stationTemplate
.chargePointSerialNumberPrefix
!= null &&
654 stationInfoSrc
.chargePointSerialNumber
!= null
655 ? (stationInfoDst
.chargePointSerialNumber
= stationInfoSrc
.chargePointSerialNumber
)
656 : stationInfoDst
.chargePointSerialNumber
!= null &&
657 delete stationInfoDst
.chargePointSerialNumber
658 stationTemplate
.chargeBoxSerialNumberPrefix
!= null &&
659 stationInfoSrc
.chargeBoxSerialNumber
!= null
660 ? (stationInfoDst
.chargeBoxSerialNumber
= stationInfoSrc
.chargeBoxSerialNumber
)
661 : stationInfoDst
.chargeBoxSerialNumber
!= null && delete stationInfoDst
.chargeBoxSerialNumber
662 stationTemplate
.meterSerialNumberPrefix
!= null && stationInfoSrc
.meterSerialNumber
!= null
663 ? (stationInfoDst
.meterSerialNumber
= stationInfoSrc
.meterSerialNumber
)
664 : stationInfoDst
.meterSerialNumber
!= null && delete stationInfoDst
.meterSerialNumber
667 export const hasFeatureProfile
= (
668 chargingStation
: ChargingStation
,
669 featureProfile
: SupportedFeatureProfiles
670 ): boolean | undefined => {
671 return getConfigurationKey(
673 StandardParametersKey
.SupportedFeatureProfiles
674 )?.value
?.includes(featureProfile
)
677 export const getAmperageLimitationUnitDivider
= (stationInfo
: ChargingStationInfo
): number => {
679 switch (stationInfo
.amperageLimitationUnit
) {
680 case AmpereUnits
.DECI_AMPERE
:
683 case AmpereUnits
.CENTI_AMPERE
:
686 case AmpereUnits
.MILLI_AMPERE
:
693 const getChargingStationChargingProfiles
= (
694 chargingStation
: ChargingStation
695 ): ChargingProfile
[] => {
696 return (chargingStation
.getConnectorStatus(0)?.chargingProfiles
?? [])
699 chargingProfile
.chargingProfilePurpose
===
700 ChargingProfilePurposeType
.CHARGE_POINT_MAX_PROFILE
702 .sort((a
, b
) => b
.stackLevel
- a
.stackLevel
)
705 export const getChargingStationChargingProfilesLimit
= (
706 chargingStation
: ChargingStation
707 ): number | undefined => {
708 const chargingProfiles
= getChargingStationChargingProfiles(chargingStation
)
709 if (isNotEmptyArray(chargingProfiles
)) {
710 const chargingProfilesLimit
= getChargingProfilesLimit(chargingStation
, 0, chargingProfiles
)
711 if (chargingProfilesLimit
!= null) {
712 const limit
= buildChargingProfilesLimit(chargingStation
, chargingProfilesLimit
)
713 const chargingStationMaximumPower
=
714 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
715 chargingStation
.stationInfo
!.maximumPower
!
716 if (limit
> chargingStationMaximumPower
) {
718 `${chargingStation.logPrefix()} ${moduleName}.getChargingStationChargingProfilesLimit: Charging profile id ${chargingProfilesLimit.chargingProfile.chargingProfileId.toString()} limit ${limit.toString()} is greater than charging station maximum ${chargingStationMaximumPower.toString()}: %j`,
719 chargingProfilesLimit
721 return chargingStationMaximumPower
729 * Gets the connector charging profiles relevant for power limitation shallow cloned
730 * and sorted by priorities
731 * @param chargingStation - Charging station
732 * @param connectorId - Connector id
733 * @returns connector charging profiles array
735 export const getConnectorChargingProfiles
= (
736 chargingStation
: ChargingStation
,
738 ): ChargingProfile
[] => {
739 return (chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?? [])
743 a
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
&&
744 b
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_DEFAULT_PROFILE
748 a
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_DEFAULT_PROFILE
&&
749 b
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_PROFILE
753 return b
.stackLevel
- a
.stackLevel
756 (chargingStation
.getConnectorStatus(0)?.chargingProfiles
?? [])
759 chargingProfile
.chargingProfilePurpose
=== ChargingProfilePurposeType
.TX_DEFAULT_PROFILE
761 .sort((a
, b
) => b
.stackLevel
- a
.stackLevel
)
765 export const getConnectorChargingProfilesLimit
= (
766 chargingStation
: ChargingStation
,
768 ): number | undefined => {
769 const chargingProfiles
= getConnectorChargingProfiles(chargingStation
, connectorId
)
770 if (isNotEmptyArray(chargingProfiles
)) {
771 const chargingProfilesLimit
= getChargingProfilesLimit(
776 if (chargingProfilesLimit
!= null) {
777 const limit
= buildChargingProfilesLimit(chargingStation
, chargingProfilesLimit
)
778 const connectorMaximumPower
=
779 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
780 chargingStation
.stationInfo
!.maximumPower
! / chargingStation
.powerDivider
!
781 if (limit
> connectorMaximumPower
) {
783 `${chargingStation.logPrefix()} ${moduleName}.getConnectorChargingProfilesLimit: Charging profile id ${chargingProfilesLimit.chargingProfile.chargingProfileId.toString()} limit ${limit.toString()} is greater than connector ${connectorId.toString()} maximum ${connectorMaximumPower.toString()}: %j`,
784 chargingProfilesLimit
786 return connectorMaximumPower
793 const buildChargingProfilesLimit
= (
794 chargingStation
: ChargingStation
,
795 chargingProfilesLimit
: ChargingProfilesLimit
797 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
798 const errorMsg
= `Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in charging station information, cannot build charging profiles limit`
799 const { chargingProfile
, limit
} = chargingProfilesLimit
800 switch (chargingStation
.stationInfo
?.currentOutType
) {
802 return chargingProfile
.chargingSchedule
.chargingRateUnit
=== ChargingRateUnitType
.WATT
804 : ACElectricUtils
.powerTotal(
805 chargingStation
.getNumberOfPhases(),
806 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
807 chargingStation
.stationInfo
.voltageOut
!,
811 return chargingProfile
.chargingSchedule
.chargingRateUnit
=== ChargingRateUnitType
.WATT
813 : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
814 DCElectricUtils
.power(chargingStation
.stationInfo
.voltageOut
!, limit
)
817 `${chargingStation.logPrefix()} ${moduleName}.buildChargingProfilesLimit: ${errorMsg}`
819 throw new BaseError(errorMsg
)
823 export const getDefaultVoltageOut
= (
824 currentType
: CurrentType
,
828 const errorMsg
= `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`
829 let defaultVoltageOut
: number
830 switch (currentType
) {
832 defaultVoltageOut
= Voltage
.VOLTAGE_230
835 defaultVoltageOut
= Voltage
.VOLTAGE_400
838 logger
.error(`${logPrefix} ${errorMsg}`)
839 throw new BaseError(errorMsg
)
841 return defaultVoltageOut
844 export const getIdTagsFile
= (stationInfo
: ChargingStationInfo
): string | undefined => {
845 return stationInfo
.idTagsFile
!= null
846 ? join(dirname(fileURLToPath(import.meta
.url
)), 'assets', basename(stationInfo
.idTagsFile
))
850 export const waitChargingStationEvents
= async (
851 emitter
: EventEmitter
,
852 event
: ChargingStationWorkerMessageEvents
,
854 ): Promise
<number> => {
855 return await new Promise
<number>(resolve
=> {
857 if (eventsToWait
=== 0) {
861 emitter
.on(event
, () => {
863 if (events
=== eventsToWait
) {
870 const getConfiguredMaxNumberOfConnectors
= (stationTemplate
: ChargingStationTemplate
): number => {
871 let configuredMaxNumberOfConnectors
= 0
872 if (isNotEmptyArray
<number>(stationTemplate
.numberOfConnectors
)) {
873 const numberOfConnectors
= stationTemplate
.numberOfConnectors
874 configuredMaxNumberOfConnectors
=
875 numberOfConnectors
[Math.floor(secureRandom() * numberOfConnectors
.length
)]
876 } else if (typeof stationTemplate
.numberOfConnectors
=== 'number') {
877 configuredMaxNumberOfConnectors
= stationTemplate
.numberOfConnectors
878 } else if (stationTemplate
.Connectors
!= null && stationTemplate
.Evses
== null) {
879 configuredMaxNumberOfConnectors
=
880 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
881 stationTemplate
.Connectors
[0] != null
882 ? getMaxNumberOfConnectors(stationTemplate
.Connectors
) - 1
883 : getMaxNumberOfConnectors(stationTemplate
.Connectors
)
884 } else if (stationTemplate
.Evses
!= null && stationTemplate
.Connectors
== null) {
885 for (const evse
in stationTemplate
.Evses
) {
889 configuredMaxNumberOfConnectors
+= getMaxNumberOfConnectors(
890 stationTemplate
.Evses
[evse
].Connectors
894 return configuredMaxNumberOfConnectors
897 const checkConfiguredMaxConnectors
= (
898 configuredMaxConnectors
: number,
902 if (configuredMaxConnectors
<= 0) {
904 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors.toString()} connectors`
909 const checkTemplateMaxConnectors
= (
910 templateMaxConnectors
: number,
914 if (templateMaxConnectors
=== 0) {
916 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
918 } else if (templateMaxConnectors
< 0) {
920 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
925 const initializeConnectorStatus
= (connectorStatus
: ConnectorStatus
): void => {
926 connectorStatus
.availability
= AvailabilityType
.Operative
927 connectorStatus
.idTagLocalAuthorized
= false
928 connectorStatus
.idTagAuthorized
= false
929 connectorStatus
.transactionRemoteStarted
= false
930 connectorStatus
.transactionStarted
= false
931 connectorStatus
.energyActiveImportRegisterValue
= 0
932 connectorStatus
.transactionEnergyActiveImportRegisterValue
= 0
933 if (connectorStatus
.chargingProfiles
== null) {
934 connectorStatus
.chargingProfiles
= []
938 const warnDeprecatedTemplateKey
= (
939 template
: ChargingStationTemplate
,
942 templateFile
: string,
945 if (template
[key
as keyof ChargingStationTemplate
] != null) {
946 const logMsg
= `Deprecated template key '${key}' usage in file '${templateFile}'${
947 isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}
` : ''
949 logger
.warn(`${logPrefix} ${logMsg}`)
950 console
.warn(`${chalk.green(logPrefix)} ${chalk.yellow(logMsg)}`)
954 const convertDeprecatedTemplateKey
= (
955 template
: ChargingStationTemplate
,
956 deprecatedKey
: string,
959 if (template
[deprecatedKey
as keyof ChargingStationTemplate
] != null) {
961 ;(template
as unknown
as Record
<string, unknown
>)[key
] =
962 template
[deprecatedKey
as keyof ChargingStationTemplate
]
964 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
965 delete template
[deprecatedKey
as keyof ChargingStationTemplate
]
969 interface ChargingProfilesLimit
{
970 chargingProfile
: ChargingProfile
975 * Get the charging profiles limit for a connector
976 * Charging profiles shall already be sorted by priorities
977 * @param chargingStation -
978 * @param connectorId -
979 * @param chargingProfiles -
980 * @returns ChargingProfilesLimit
982 const getChargingProfilesLimit
= (
983 chargingStation
: ChargingStation
,
985 chargingProfiles
: ChargingProfile
[]
986 ): ChargingProfilesLimit
| undefined => {
987 const debugLogMsg
= `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profiles limit found: %j`
988 const currentDate
= new Date()
989 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)
990 let previousActiveChargingProfile
: ChargingProfile
| undefined
991 for (const chargingProfile
of chargingProfiles
) {
992 const chargingSchedule
= chargingProfile
.chargingSchedule
993 if (chargingSchedule
.startSchedule
== null) {
995 `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule defined. Trying to set it to the connector current transaction start date`
997 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
998 chargingSchedule
.startSchedule
= connectorStatus
?.transactionStart
1000 if (!isDate(chargingSchedule
.startSchedule
)) {
1002 `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} startSchedule property is not a Date instance. Trying to convert it to a Date instance`
1004 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1005 chargingSchedule
.startSchedule
= convertToDate(chargingSchedule
.startSchedule
)!
1007 if (chargingSchedule
.duration
== null) {
1009 `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no duration defined and will be set to the maximum time allowed`
1011 // OCPP specifies that if duration is not defined, it should be infinite
1012 chargingSchedule
.duration
= differenceInSeconds(maxTime
, chargingSchedule
.startSchedule
)
1015 !prepareChargingProfileKind(
1019 chargingStation
.logPrefix()
1024 if (!canProceedChargingProfile(chargingProfile
, currentDate
, chargingStation
.logPrefix())) {
1027 // Check if the charging profile is active
1029 isWithinInterval(currentDate
, {
1030 end
: addSeconds(chargingSchedule
.startSchedule
, chargingSchedule
.duration
),
1031 start
: chargingSchedule
.startSchedule
,
1034 if (isNotEmptyArray
<ChargingSchedulePeriod
>(chargingSchedule
.chargingSchedulePeriod
)) {
1035 const chargingSchedulePeriodCompareFn
= (
1036 a
: ChargingSchedulePeriod
,
1037 b
: ChargingSchedulePeriod
1038 ): number => a
.startPeriod
- b
.startPeriod
1040 !isArraySorted
<ChargingSchedulePeriod
>(
1041 chargingSchedule
.chargingSchedulePeriod
,
1042 chargingSchedulePeriodCompareFn
1046 `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} schedule periods are not sorted by start period`
1048 chargingSchedule
.chargingSchedulePeriod
.sort(chargingSchedulePeriodCompareFn
)
1050 // Check if the first schedule period startPeriod property is equal to 0
1051 if (chargingSchedule
.chargingSchedulePeriod
[0].startPeriod
!== 0) {
1053 `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod.toString()} is not equal to 0`
1057 // Handle only one schedule period
1058 if (chargingSchedule
.chargingSchedulePeriod
.length
=== 1) {
1059 const chargingProfilesLimit
: ChargingProfilesLimit
= {
1061 limit
: chargingSchedule
.chargingSchedulePeriod
[0].limit
,
1063 logger
.debug(debugLogMsg
, chargingProfilesLimit
)
1064 return chargingProfilesLimit
1066 let previousChargingSchedulePeriod
: ChargingSchedulePeriod
| undefined
1067 // Search for the right schedule period
1070 chargingSchedulePeriod
,
1071 ] of chargingSchedule
.chargingSchedulePeriod
.entries()) {
1072 // Find the right schedule period
1075 addSeconds(chargingSchedule
.startSchedule
, chargingSchedulePeriod
.startPeriod
),
1079 // Found the schedule period: previous is the correct one
1080 const chargingProfilesLimit
: ChargingProfilesLimit
= {
1081 chargingProfile
: previousActiveChargingProfile
?? chargingProfile
,
1082 limit
: previousChargingSchedulePeriod
?.limit
?? chargingSchedulePeriod
.limit
,
1084 logger
.debug(debugLogMsg
, chargingProfilesLimit
)
1085 return chargingProfilesLimit
1087 // Handle the last schedule period within the charging profile duration
1089 index
=== chargingSchedule
.chargingSchedulePeriod
.length
- 1 ||
1090 (index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1091 differenceInSeconds(
1093 chargingSchedule
.startSchedule
,
1094 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
1096 chargingSchedule
.startSchedule
1097 ) > chargingSchedule
.duration
)
1099 const chargingProfilesLimit
: ChargingProfilesLimit
= {
1101 limit
: chargingSchedulePeriod
.limit
,
1103 logger
.debug(debugLogMsg
, chargingProfilesLimit
)
1104 return chargingProfilesLimit
1106 // Keep a reference to previous charging schedule period
1107 previousChargingSchedulePeriod
= chargingSchedulePeriod
1110 // Keep a reference to previous active charging profile
1111 previousActiveChargingProfile
= chargingProfile
1116 export const prepareChargingProfileKind
= (
1117 connectorStatus
: ConnectorStatus
| undefined,
1118 chargingProfile
: ChargingProfile
,
1119 currentDate
: Date | number | string,
1122 switch (chargingProfile
.chargingProfileKind
) {
1123 case ChargingProfileKindType
.RECURRING
:
1124 if (!canProceedRecurringChargingProfile(chargingProfile
, logPrefix
)) {
1127 prepareRecurringChargingProfile(chargingProfile
, currentDate
, logPrefix
)
1129 case ChargingProfileKindType
.RELATIVE
:
1130 if (chargingProfile
.chargingSchedule
.startSchedule
!= null) {
1132 `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfile.chargingProfileId.toString()} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`
1134 delete chargingProfile
.chargingSchedule
.startSchedule
1136 if (connectorStatus
?.transactionStarted
=== true) {
1137 chargingProfile
.chargingSchedule
.startSchedule
= connectorStatus
.transactionStart
1139 // FIXME: handle relative charging profile duration
1145 export const canProceedChargingProfile
= (
1146 chargingProfile
: ChargingProfile
,
1147 currentDate
: Date | number | string,
1151 (isValidDate(chargingProfile
.validFrom
) && isBefore(currentDate
, chargingProfile
.validFrom
)) ||
1152 (isValidDate(chargingProfile
.validTo
) && isAfter(currentDate
, chargingProfile
.validTo
))
1155 `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} is not valid for the current date ${
1156 isDate(currentDate) ? currentDate.toISOString() : currentDate.toString()
1162 chargingProfile
.chargingSchedule
.startSchedule
== null ||
1163 chargingProfile
.chargingSchedule
.duration
== null
1166 `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule or duration defined`
1170 if (!isValidDate(chargingProfile
.chargingSchedule
.startSchedule
)) {
1172 `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has an invalid startSchedule date defined`
1176 if (!Number.isSafeInteger(chargingProfile
.chargingSchedule
.duration
)) {
1178 `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has non integer duration defined`
1185 const canProceedRecurringChargingProfile
= (
1186 chargingProfile
: ChargingProfile
,
1190 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
1191 chargingProfile
.recurrencyKind
== null
1194 `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId.toString()} has no recurrencyKind defined`
1199 chargingProfile
.chargingProfileKind
=== ChargingProfileKindType
.RECURRING
&&
1200 chargingProfile
.chargingSchedule
.startSchedule
== null
1203 `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule defined`
1211 * Adjust recurring charging profile startSchedule to the current recurrency time interval if needed
1212 * @param chargingProfile -
1213 * @param currentDate -
1214 * @param logPrefix -
1217 const prepareRecurringChargingProfile
= (
1218 chargingProfile
: ChargingProfile
,
1219 currentDate
: Date | number | string,
1222 const chargingSchedule
= chargingProfile
.chargingSchedule
1223 let recurringIntervalTranslated
= false
1224 let recurringInterval
: Interval
| undefined
1225 switch (chargingProfile
.recurrencyKind
) {
1226 case RecurrencyKindType
.DAILY
:
1227 recurringInterval
= {
1228 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1229 end
: addDays(chargingSchedule
.startSchedule
!, 1),
1230 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1231 start
: chargingSchedule
.startSchedule
!,
1233 checkRecurringChargingProfileDuration(chargingProfile
, recurringInterval
, logPrefix
)
1235 !isWithinInterval(currentDate
, recurringInterval
) &&
1236 isBefore(recurringInterval
.end
, currentDate
)
1238 chargingSchedule
.startSchedule
= addDays(
1239 recurringInterval
.start
,
1240 differenceInDays(currentDate
, recurringInterval
.start
)
1242 recurringInterval
= {
1243 end
: addDays(chargingSchedule
.startSchedule
, 1),
1244 start
: chargingSchedule
.startSchedule
,
1246 recurringIntervalTranslated
= true
1249 case RecurrencyKindType
.WEEKLY
:
1250 recurringInterval
= {
1251 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1252 end
: addWeeks(chargingSchedule
.startSchedule
!, 1),
1253 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1254 start
: chargingSchedule
.startSchedule
!,
1256 checkRecurringChargingProfileDuration(chargingProfile
, recurringInterval
, logPrefix
)
1258 !isWithinInterval(currentDate
, recurringInterval
) &&
1259 isBefore(recurringInterval
.end
, currentDate
)
1261 chargingSchedule
.startSchedule
= addWeeks(
1262 recurringInterval
.start
,
1263 differenceInWeeks(currentDate
, recurringInterval
.start
)
1265 recurringInterval
= {
1266 end
: addWeeks(chargingSchedule
.startSchedule
, 1),
1267 start
: chargingSchedule
.startSchedule
,
1269 recurringIntervalTranslated
= true
1274 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1275 `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId.toString()} is not supported`
1278 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1279 if (recurringIntervalTranslated
&& !isWithinInterval(currentDate
, recurringInterval
!)) {
1281 `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${
1282 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1283 chargingProfile.recurrencyKind
1284 } charging profile id ${chargingProfile.chargingProfileId.toString()} recurrency time interval [${toDate(
1285 recurringInterval?.start as Date
1286 ).toISOString()}, ${toDate(
1287 recurringInterval?.end as Date
1288 ).toISOString()}] has not been properly translated to current date ${
1289 isDate(currentDate) ? currentDate.toISOString() : currentDate.toString()
1293 return recurringIntervalTranslated
1296 const checkRecurringChargingProfileDuration
= (
1297 chargingProfile
: ChargingProfile
,
1301 if (chargingProfile
.chargingSchedule
.duration
== null) {
1303 `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
1304 chargingProfile.chargingProfileKind
1305 } charging profile id ${chargingProfile.chargingProfileId.toString()} duration is not defined, set it to the recurrency time interval duration ${differenceInSeconds(
1310 chargingProfile
.chargingSchedule
.duration
= differenceInSeconds(interval
.end
, interval
.start
)
1312 chargingProfile
.chargingSchedule
.duration
> differenceInSeconds(interval
.end
, interval
.start
)
1315 `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
1316 chargingProfile.chargingProfileKind
1317 } charging profile id ${chargingProfile.chargingProfileId.toString()} duration ${chargingProfile.chargingSchedule.duration.toString()} is greater than the recurrency time interval duration ${differenceInSeconds(
1322 chargingProfile
.chargingSchedule
.duration
= differenceInSeconds(interval
.end
, interval
.start
)
1326 const getRandomSerialNumberSuffix
= (params
?: {
1327 randomBytesLength
?: number
1330 const randomSerialNumberSuffix
= randomBytes(params
?.randomBytesLength
?? 16).toString('hex')
1331 if (params
?.upperCase
) {
1332 return randomSerialNumberSuffix
.toUpperCase()
1334 return randomSerialNumberSuffix