-import { createHash, randomBytes } from 'node:crypto'
import type { EventEmitter } from 'node:events'
-import { basename, dirname, join } from 'node:path'
-import { env } from 'node:process'
-import { fileURLToPath } from 'node:url'
import chalk from 'chalk'
import {
- type Interval,
addDays,
addSeconds,
addWeeks,
differenceInDays,
differenceInSeconds,
differenceInWeeks,
+ type Interval,
isAfter,
isBefore,
isDate,
isPast,
isWithinInterval,
- toDate
+ toDate,
} from 'date-fns'
import { maxTime } from 'date-fns/constants'
+import { createHash, randomBytes } from 'node:crypto'
+import { basename, dirname, isAbsolute, join, parse, relative, resolve } from 'node:path'
+import { env } from 'node:process'
+import { fileURLToPath } from 'node:url'
+import { isEmpty } from 'rambda'
import type { ChargingStation } from './ChargingStation.js'
-import { getConfigurationKey } from './ConfigurationKeyUtils.js'
+
import { BaseError } from '../exception/index.js'
import {
AmpereUnits,
BootReasonEnumType,
type ChargingProfile,
ChargingProfileKindType,
+ ChargingProfilePurposeType,
ChargingRateUnitType,
type ChargingSchedulePeriod,
type ChargingStationConfiguration,
type ChargingStationInfo,
+ type ChargingStationOptions,
type ChargingStationTemplate,
type ChargingStationWorkerMessageEvents,
ConnectorPhaseRotation,
ReservationTerminationReason,
StandardParametersKey,
type SupportedFeatureProfiles,
- Voltage
+ Voltage,
} from '../types/index.js'
import {
ACElectricUtils,
+ clone,
Constants,
- DCElectricUtils,
- cloneObject,
convertToDate,
convertToInt,
+ DCElectricUtils,
isArraySorted,
- isEmptyObject,
- isEmptyString,
isNotEmptyArray,
isNotEmptyString,
- isValidTime,
+ isValidDate,
logger,
- secureRandom
+ secureRandom,
} from '../utils/index.js'
+import { getConfigurationKey } from './ConfigurationKeyUtils.js'
const moduleName = 'Helpers'
+export const buildTemplateName = (templateFile: string): string => {
+ if (isAbsolute(templateFile)) {
+ templateFile = relative(
+ resolve(join(dirname(fileURLToPath(import.meta.url)), 'assets', 'station-templates')),
+ templateFile
+ )
+ }
+ const templateFileParsedPath = parse(templateFile)
+ return join(templateFileParsedPath.dir, templateFileParsedPath.name)
+}
+
export const getChargingStationId = (
index: number,
stationTemplate: ChargingStationTemplate | undefined
const chargingStationInfo = {
chargePointModel: stationTemplate.chargePointModel,
chargePointVendor: stationTemplate.chargePointVendor,
- ...(stationTemplate.chargeBoxSerialNumberPrefix !== undefined && {
- chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix
+ ...(stationTemplate.chargeBoxSerialNumberPrefix != null && {
+ chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
}),
- ...(stationTemplate.chargePointSerialNumberPrefix !== undefined && {
- chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix
+ ...(stationTemplate.chargePointSerialNumberPrefix != null && {
+ chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
}),
- ...(stationTemplate.meterSerialNumberPrefix !== undefined && {
- meterSerialNumber: stationTemplate.meterSerialNumberPrefix
+ ...(stationTemplate.meterSerialNumberPrefix != null && {
+ meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
+ }),
+ ...(stationTemplate.meterType != null && {
+ meterType: stationTemplate.meterType,
}),
- ...(stationTemplate.meterType !== undefined && {
- meterType: stationTemplate.meterType
- })
}
return createHash(Constants.DEFAULT_HASH_ALGORITHM)
.update(`${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`)
.digest('hex')
}
-export const checkChargingStation = (
+export const validateStationInfo = (chargingStation: ChargingStation): void => {
+ if (chargingStation.stationInfo == null || isEmpty(chargingStation.stationInfo)) {
+ throw new BaseError('Missing charging station information')
+ }
+ if (
+ chargingStation.stationInfo.chargingStationId == null ||
+ isEmpty(chargingStation.stationInfo.chargingStationId.trim())
+ ) {
+ throw new BaseError('Missing chargingStationId in stationInfo properties')
+ }
+ const chargingStationId = chargingStation.stationInfo.chargingStationId
+ if (
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ chargingStation.stationInfo.hashId == null ||
+ isEmpty(chargingStation.stationInfo.hashId.trim())
+ ) {
+ throw new BaseError(`${chargingStationId}: Missing hashId in stationInfo properties`)
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (chargingStation.stationInfo.templateIndex == null) {
+ throw new BaseError(`${chargingStationId}: Missing templateIndex in stationInfo properties`)
+ }
+ if (chargingStation.stationInfo.templateIndex <= 0) {
+ throw new BaseError(
+ `${chargingStationId}: Invalid templateIndex value in stationInfo properties`
+ )
+ }
+ if (
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ chargingStation.stationInfo.templateName == null ||
+ isEmpty(chargingStation.stationInfo.templateName.trim())
+ ) {
+ throw new BaseError(`${chargingStationId}: Missing templateName in stationInfo properties`)
+ }
+ if (chargingStation.stationInfo.maximumPower == null) {
+ throw new BaseError(`${chargingStationId}: Missing maximumPower in stationInfo properties`)
+ }
+ if (chargingStation.stationInfo.maximumPower <= 0) {
+ throw new RangeError(
+ `${chargingStationId}: Invalid maximumPower value in stationInfo properties`
+ )
+ }
+ if (chargingStation.stationInfo.maximumAmperage == null) {
+ throw new BaseError(`${chargingStationId}: Missing maximumAmperage in stationInfo properties`)
+ }
+ if (chargingStation.stationInfo.maximumAmperage <= 0) {
+ throw new RangeError(
+ `${chargingStationId}: Invalid maximumAmperage value in stationInfo properties`
+ )
+ }
+ switch (chargingStation.stationInfo.ocppVersion) {
+ case OCPPVersion.VERSION_20:
+ case OCPPVersion.VERSION_201:
+ if (chargingStation.evses.size === 0) {
+ throw new BaseError(
+ `${chargingStationId}: OCPP 2.0 or superior requires at least one EVSE defined in the charging station template/configuration`
+ )
+ }
+ }
+}
+
+export const checkChargingStationState = (
chargingStation: ChargingStation,
logPrefix: string
): boolean => {
): string | undefined => {
// AC/DC
if (connectorId === 0 && numberOfPhases === 0) {
- return `${connectorId}.${ConnectorPhaseRotation.RST}`
+ return `${connectorId.toString()}.${ConnectorPhaseRotation.RST}`
} else if (connectorId > 0 && numberOfPhases === 0) {
- return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`
+ return `${connectorId.toString()}.${ConnectorPhaseRotation.NotApplicable}`
// AC
} else if (connectorId >= 0 && numberOfPhases === 1) {
- return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`
+ return `${connectorId.toString()}.${ConnectorPhaseRotation.NotApplicable}`
} else if (connectorId >= 0 && numberOfPhases === 3) {
- return `${connectorId}.${ConnectorPhaseRotation.RST}`
+ return `${connectorId.toString()}.${ConnectorPhaseRotation.RST}`
}
}
logger.error(`${logPrefix} ${errorMsg}`)
throw new BaseError(errorMsg)
}
- if (isEmptyObject(stationTemplate)) {
+ if (isEmpty(stationTemplate)) {
const errorMsg = `Empty charging station information from template file ${templateFile}`
logger.error(`${logPrefix} ${errorMsg}`)
throw new BaseError(errorMsg)
}
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- if (isEmptyObject(stationTemplate.AutomaticTransactionGenerator!)) {
- stationTemplate.AutomaticTransactionGenerator = Constants.DEFAULT_ATG_CONFIGURATION
- logger.warn(
- `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
- Constants.DEFAULT_ATG_CONFIGURATION
- )
- }
- if (stationTemplate.idTagsFile == null || isEmptyString(stationTemplate.idTagsFile)) {
+ if (stationTemplate.idTagsFile == null || isEmpty(stationTemplate.idTagsFile)) {
logger.warn(
`${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`
)
logger.error(`${logPrefix} ${errorMsg}`)
throw new BaseError(errorMsg)
}
- if (isEmptyObject(stationConfiguration)) {
+ if (isEmpty(stationConfiguration)) {
const errorMsg = `Empty charging station configuration from file ${configurationFile}`
logger.error(`${logPrefix} ${errorMsg}`)
throw new BaseError(errorMsg)
templateFile: string
): {
configuredMaxConnectors: number
- templateMaxConnectors: number
templateMaxAvailableConnectors: number
+ templateMaxConnectors: number
} => {
const configuredMaxConnectors = getConfiguredMaxNumberOfConnectors(stationTemplate)
checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile)
)
stationTemplate.randomConnectors = true
}
- return { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors }
+ return {
+ configuredMaxConnectors,
+ templateMaxAvailableConnectors,
+ templateMaxConnectors,
+ }
}
export const checkStationInfoConnectorStatus = (
): void => {
if (connectorStatus.status != null) {
logger.warn(
- `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
+ `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId.toString()} status configuration defined, undefine it`
)
delete connectorStatus.status
}
}
+export const setChargingStationOptions = (
+ stationInfo: ChargingStationInfo,
+ options?: ChargingStationOptions
+): ChargingStationInfo => {
+ if (options?.supervisionUrls != null) {
+ stationInfo.supervisionUrls = options.supervisionUrls
+ }
+ if (options?.persistentConfiguration != null) {
+ stationInfo.stationInfoPersistentConfiguration = options.persistentConfiguration
+ stationInfo.ocppPersistentConfiguration = options.persistentConfiguration
+ stationInfo.automaticTransactionGeneratorPersistentConfiguration =
+ options.persistentConfiguration
+ }
+ if (options?.autoStart != null) {
+ stationInfo.autoStart = options.autoStart
+ }
+ if (options?.autoRegister != null) {
+ stationInfo.autoRegister = options.autoRegister
+ }
+ if (options?.enableStatistics != null) {
+ stationInfo.enableStatistics = options.enableStatistics
+ }
+ if (options?.ocppStrictCompliance != null) {
+ stationInfo.ocppStrictCompliance = options.ocppStrictCompliance
+ }
+ if (options?.stopTransactionsOnStopped != null) {
+ stationInfo.stopTransactionsOnStopped = options.stopTransactionsOnStopped
+ }
+ return stationInfo
+}
+
export const buildConnectorsMap = (
connectors: Record<string, ConnectorStatus>,
logPrefix: string,
const connectorStatus = connectors[connector]
const connectorId = convertToInt(connector)
checkStationInfoConnectorStatus(connectorId, connectorStatus, logPrefix, templateFile)
- connectorsMap.set(connectorId, cloneObject<ConnectorStatus>(connectorStatus))
+ connectorsMap.set(connectorId, clone<ConnectorStatus>(connectorStatus))
}
} else {
logger.warn(
for (const connectorId of connectors.keys()) {
if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
logger.warn(
- `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
- connectors.get(connectorId)?.transactionId
- }`
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ `${logPrefix} Connector id ${connectorId.toString()} at initialization has a transaction started with id ${connectors
+ .get(connectorId)
+ ?.transactionId?.toString()}`
)
}
if (connectorId === 0) {
}
}
+export const resetAuthorizeConnectorStatus = (connectorStatus: ConnectorStatus): void => {
+ connectorStatus.idTagLocalAuthorized = false
+ connectorStatus.idTagAuthorized = false
+ delete connectorStatus.localAuthorizeIdTag
+ delete connectorStatus.authorizeIdTag
+}
+
export const resetConnectorStatus = (connectorStatus: ConnectorStatus | undefined): void => {
if (connectorStatus == null) {
return
}
- connectorStatus.chargingProfiles =
- connectorStatus.transactionId != null && isNotEmptyArray(connectorStatus.chargingProfiles)
- ? connectorStatus.chargingProfiles?.filter(
- (chargingProfile) => chargingProfile.transactionId !== connectorStatus.transactionId
- )
- : []
- connectorStatus.idTagLocalAuthorized = false
- connectorStatus.idTagAuthorized = false
+ if (isNotEmptyArray(connectorStatus.chargingProfiles)) {
+ connectorStatus.chargingProfiles = connectorStatus.chargingProfiles.filter(
+ chargingProfile =>
+ (chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE &&
+ chargingProfile.transactionId != null &&
+ connectorStatus.transactionId != null &&
+ chargingProfile.transactionId !== connectorStatus.transactionId) ||
+ chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE
+ )
+ }
+ resetAuthorizeConnectorStatus(connectorStatus)
connectorStatus.transactionRemoteStarted = false
connectorStatus.transactionStarted = false
delete connectorStatus.transactionStart
delete connectorStatus.transactionId
- delete connectorStatus.localAuthorizeIdTag
- delete connectorStatus.authorizeIdTag
delete connectorStatus.transactionIdTag
connectorStatus.transactionEnergyActiveImportRegisterValue = 0
delete connectorStatus.transactionBeginMeterValue
}
+export const prepareConnectorStatus = (connectorStatus: ConnectorStatus): ConnectorStatus => {
+ if (connectorStatus.reservation != null) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ connectorStatus.reservation.expiryDate = convertToDate(connectorStatus.reservation.expiryDate)!
+ }
+ if (isNotEmptyArray(connectorStatus.chargingProfiles)) {
+ connectorStatus.chargingProfiles = connectorStatus.chargingProfiles
+ .filter(
+ chargingProfile =>
+ chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE
+ )
+ .map(chargingProfile => {
+ chargingProfile.chargingSchedule.startSchedule = convertToDate(
+ chargingProfile.chargingSchedule.startSchedule
+ )
+ chargingProfile.validFrom = convertToDate(chargingProfile.validFrom)
+ chargingProfile.validTo = convertToDate(chargingProfile.validTo)
+ return chargingProfile
+ })
+ }
+ return connectorStatus
+}
+
export const createBootNotificationRequest = (
stationInfo: ChargingStationInfo,
bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
return {
chargePointModel: stationInfo.chargePointModel,
chargePointVendor: stationInfo.chargePointVendor,
- ...(stationInfo.chargeBoxSerialNumber !== undefined && {
- chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber
+ ...(stationInfo.chargeBoxSerialNumber != null && {
+ chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
+ }),
+ ...(stationInfo.chargePointSerialNumber != null && {
+ chargePointSerialNumber: stationInfo.chargePointSerialNumber,
}),
- ...(stationInfo.chargePointSerialNumber !== undefined && {
- chargePointSerialNumber: stationInfo.chargePointSerialNumber
+ ...(stationInfo.firmwareVersion != null && {
+ firmwareVersion: stationInfo.firmwareVersion,
}),
- ...(stationInfo.firmwareVersion !== undefined && {
- firmwareVersion: stationInfo.firmwareVersion
+ ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+ ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+ ...(stationInfo.meterSerialNumber != null && {
+ meterSerialNumber: stationInfo.meterSerialNumber,
}),
- ...(stationInfo.iccid !== undefined && { iccid: stationInfo.iccid }),
- ...(stationInfo.imsi !== undefined && { imsi: stationInfo.imsi }),
- ...(stationInfo.meterSerialNumber !== undefined && {
- meterSerialNumber: stationInfo.meterSerialNumber
+ ...(stationInfo.meterType != null && {
+ meterType: stationInfo.meterType,
}),
- ...(stationInfo.meterType !== undefined && {
- meterType: stationInfo.meterType
- })
} satisfies OCPP16BootNotificationRequest
case OCPPVersion.VERSION_20:
case OCPPVersion.VERSION_201:
return {
- reason: bootReason,
chargingStation: {
model: stationInfo.chargePointModel,
vendorName: stationInfo.chargePointVendor,
- ...(stationInfo.firmwareVersion !== undefined && {
- firmwareVersion: stationInfo.firmwareVersion
+ ...(stationInfo.firmwareVersion != null && {
+ firmwareVersion: stationInfo.firmwareVersion,
}),
- ...(stationInfo.chargeBoxSerialNumber !== undefined && {
- serialNumber: stationInfo.chargeBoxSerialNumber
+ ...(stationInfo.chargeBoxSerialNumber != null && {
+ serialNumber: stationInfo.chargeBoxSerialNumber,
}),
- ...((stationInfo.iccid !== undefined || stationInfo.imsi !== undefined) && {
+ ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
modem: {
- ...(stationInfo.iccid !== undefined && { iccid: stationInfo.iccid }),
- ...(stationInfo.imsi !== undefined && { imsi: stationInfo.imsi })
- }
- })
- }
+ ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+ ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+ },
+ }),
+ },
+ reason: bootReason,
} satisfies OCPP20BootNotificationRequest
}
}
logPrefix: string,
templateFile: string
): void => {
- const templateKeys: Array<{ deprecatedKey: string, key?: string }> = [
+ const templateKeys: { deprecatedKey: string; key?: string }[] = [
{ deprecatedKey: 'supervisionUrl', key: 'supervisionUrls' },
{ deprecatedKey: 'authorizationFile', key: 'idTagsFile' },
{ deprecatedKey: 'payloadSchemaValidation', key: 'ocppStrictCompliance' },
- { deprecatedKey: 'mustAuthorizeAtRemoteStart', key: 'remoteAuthorization' }
+ { deprecatedKey: 'mustAuthorizeAtRemoteStart', key: 'remoteAuthorization' },
]
for (const templateKey of templateKeys) {
warnDeprecatedTemplateKey(
templateKey.deprecatedKey,
logPrefix,
templateFile,
- templateKey.key !== undefined ? `Use '${templateKey.key}' instead` : undefined
+ templateKey.key != null ? `Use '${templateKey.key}' instead` : undefined
)
convertDeprecatedTemplateKey(stationTemplate, templateKey.deprecatedKey, templateKey.key)
}
export const stationTemplateToStationInfo = (
stationTemplate: ChargingStationTemplate
): ChargingStationInfo => {
- stationTemplate = cloneObject<ChargingStationTemplate>(stationTemplate)
+ stationTemplate = clone<ChargingStationTemplate>(stationTemplate)
delete stationTemplate.power
delete stationTemplate.powerUnit
delete stationTemplate.Connectors
delete stationTemplate.Evses
delete stationTemplate.Configuration
delete stationTemplate.AutomaticTransactionGenerator
+ delete stationTemplate.numberOfConnectors
delete stationTemplate.chargeBoxSerialNumberPrefix
delete stationTemplate.chargePointSerialNumberPrefix
delete stationTemplate.meterSerialNumberPrefix
stationTemplate: ChargingStationTemplate,
stationInfo: ChargingStationInfo,
params?: {
- randomSerialNumberUpperCase?: boolean
randomSerialNumber?: boolean
+ randomSerialNumberUpperCase?: boolean
}
): void => {
- params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params }
- const serialNumberSuffix =
- params.randomSerialNumber === true
- ? getRandomSerialNumberSuffix({
- upperCase: params.randomSerialNumberUpperCase
- })
- : ''
+ params = {
+ ...{ randomSerialNumber: true, randomSerialNumberUpperCase: true },
+ ...params,
+ }
+ const serialNumberSuffix = params.randomSerialNumber
+ ? getRandomSerialNumberSuffix({
+ upperCase: params.randomSerialNumberUpperCase,
+ })
+ : ''
isNotEmptyString(stationTemplate.chargePointSerialNumberPrefix) &&
(stationInfo.chargePointSerialNumber = `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`)
isNotEmptyString(stationTemplate.chargeBoxSerialNumberPrefix) &&
return unitDivider
}
+const getChargingStationChargingProfiles = (
+ chargingStation: ChargingStation
+): ChargingProfile[] => {
+ return (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [])
+ .filter(
+ chargingProfile =>
+ chargingProfile.chargingProfilePurpose ===
+ ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE
+ )
+ .sort((a, b) => b.stackLevel - a.stackLevel)
+}
+
+export const getChargingStationChargingProfilesLimit = (
+ chargingStation: ChargingStation
+): number | undefined => {
+ const chargingProfiles = getChargingStationChargingProfiles(chargingStation)
+ if (isNotEmptyArray(chargingProfiles)) {
+ const chargingProfilesLimit = getChargingProfilesLimit(chargingStation, 0, chargingProfiles)
+ if (chargingProfilesLimit != null) {
+ const limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit)
+ const chargingStationMaximumPower =
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ chargingStation.stationInfo!.maximumPower!
+ if (limit > chargingStationMaximumPower) {
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingStationChargingProfilesLimit: Charging profile id ${chargingProfilesLimit.chargingProfile.chargingProfileId.toString()} limit ${limit.toString()} is greater than charging station maximum ${chargingStationMaximumPower.toString()}: %j`,
+ chargingProfilesLimit
+ )
+ return chargingStationMaximumPower
+ }
+ return limit
+ }
+ }
+}
+
/**
- * Gets the connector cloned charging profiles applying a power limitation
- * and sorted by connector id descending then stack level descending
- *
- * @param chargingStation -
- * @param connectorId -
+ * Gets the connector charging profiles relevant for power limitation shallow cloned
+ * and sorted by priorities
+ * @param chargingStation - Charging station
+ * @param connectorId - Connector id
* @returns connector charging profiles array
*/
export const getConnectorChargingProfiles = (
chargingStation: ChargingStation,
connectorId: number
): ChargingProfile[] => {
- return cloneObject<ChargingProfile[]>(
- (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? [])
- .sort((a, b) => b.stackLevel - a.stackLevel)
- .concat(
- (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? []).sort(
- (a, b) => b.stackLevel - a.stackLevel
+ return (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? [])
+ .slice()
+ .sort((a, b) => {
+ if (
+ a.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE &&
+ b.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE
+ ) {
+ return -1
+ } else if (
+ a.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE &&
+ b.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE
+ ) {
+ return 1
+ }
+ return b.stackLevel - a.stackLevel
+ })
+ .concat(
+ (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [])
+ .filter(
+ chargingProfile =>
+ chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE
)
- )
- )
+ .sort((a, b) => b.stackLevel - a.stackLevel)
+ )
}
-export const getChargingStationConnectorChargingProfilesPowerLimit = (
+export const getConnectorChargingProfilesLimit = (
chargingStation: ChargingStation,
connectorId: number
): number | undefined => {
- let limit: number | undefined, chargingProfile: ChargingProfile | undefined
- // Get charging profiles sorted by connector id then stack level
const chargingProfiles = getConnectorChargingProfiles(chargingStation, connectorId)
if (isNotEmptyArray(chargingProfiles)) {
- const result = getLimitFromChargingProfiles(
+ const chargingProfilesLimit = getChargingProfilesLimit(
chargingStation,
connectorId,
- chargingProfiles,
- chargingStation.logPrefix()
+ chargingProfiles
)
- if (result != null) {
- limit = result.limit
- chargingProfile = result.chargingProfile
- switch (chargingStation.stationInfo?.currentOutType) {
- case CurrentType.AC:
- limit =
- chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
- ? limit
- : ACElectricUtils.powerTotal(
- chargingStation.getNumberOfPhases(),
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- chargingStation.stationInfo.voltageOut!,
- limit
- )
- break
- case CurrentType.DC:
- limit =
- chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
- ? limit
- : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit)
- }
+ if (chargingProfilesLimit != null) {
+ const limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit)
const connectorMaximumPower =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
chargingStation.stationInfo!.maximumPower! / chargingStation.powerDivider!
if (limit > connectorMaximumPower) {
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${
- chargingProfile.chargingProfileId
- } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
- result
+ `${chargingStation.logPrefix()} ${moduleName}.getConnectorChargingProfilesLimit: Charging profile id ${chargingProfilesLimit.chargingProfile.chargingProfileId.toString()} limit ${limit.toString()} is greater than connector ${connectorId.toString()} maximum ${connectorMaximumPower.toString()}: %j`,
+ chargingProfilesLimit
)
- limit = connectorMaximumPower
+ return connectorMaximumPower
}
+ return limit
}
}
- return limit
+}
+
+const buildChargingProfilesLimit = (
+ chargingStation: ChargingStation,
+ chargingProfilesLimit: ChargingProfilesLimit
+): number => {
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ const errorMsg = `Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in charging station information, cannot build charging profiles limit`
+ const { chargingProfile, limit } = chargingProfilesLimit
+ switch (chargingStation.stationInfo?.currentOutType) {
+ case CurrentType.AC:
+ return chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+ ? limit
+ : ACElectricUtils.powerTotal(
+ chargingStation.getNumberOfPhases(),
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ chargingStation.stationInfo.voltageOut!,
+ limit
+ )
+ case CurrentType.DC:
+ return chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+ ? limit
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit)
+ default:
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.buildChargingProfilesLimit: ${errorMsg}`
+ )
+ throw new BaseError(errorMsg)
+ }
}
export const getDefaultVoltageOut = (
event: ChargingStationWorkerMessageEvents,
eventsToWait: number
): Promise<number> => {
- return await new Promise<number>((resolve) => {
+ return await new Promise<number>(resolve => {
let events = 0
if (eventsToWait === 0) {
resolve(events)
const getConfiguredMaxNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => {
let configuredMaxNumberOfConnectors = 0
- if (isNotEmptyArray(stationTemplate.numberOfConnectors)) {
- const numberOfConnectors = stationTemplate.numberOfConnectors as number[]
+ if (isNotEmptyArray<number>(stationTemplate.numberOfConnectors)) {
+ const numberOfConnectors = stationTemplate.numberOfConnectors
configuredMaxNumberOfConnectors =
numberOfConnectors[Math.floor(secureRandom() * numberOfConnectors.length)]
- } else if (stationTemplate.numberOfConnectors != null) {
- configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors as number
+ } else if (typeof stationTemplate.numberOfConnectors === 'number') {
+ configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors
} else if (stationTemplate.Connectors != null && stationTemplate.Evses == null) {
configuredMaxNumberOfConnectors =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
): void => {
if (configuredMaxConnectors <= 0) {
logger.warn(
- `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
+ `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors.toString()} connectors`
)
}
}
templateFile: string,
logMsgToAppend = ''
): void => {
- if (template[key as keyof ChargingStationTemplate] !== undefined) {
+ if (template[key as keyof ChargingStationTemplate] != null) {
const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
}`
deprecatedKey: string,
key?: string
): void => {
- if (template[deprecatedKey as keyof ChargingStationTemplate] !== undefined) {
- if (key !== undefined) {
- (template as unknown as Record<string, unknown>)[key] =
+ if (template[deprecatedKey as keyof ChargingStationTemplate] != null) {
+ if (key != null) {
+ ;(template as unknown as Record<string, unknown>)[key] =
template[deprecatedKey as keyof ChargingStationTemplate]
}
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
}
interface ChargingProfilesLimit {
- limit: number
chargingProfile: ChargingProfile
+ limit: number
}
/**
- * Charging profiles shall already be sorted by connector id descending then stack level descending
- *
+ * Get the charging profiles limit for a connector
+ * Charging profiles shall already be sorted by priorities
* @param chargingStation -
* @param connectorId -
* @param chargingProfiles -
- * @param logPrefix -
* @returns ChargingProfilesLimit
*/
-const getLimitFromChargingProfiles = (
+const getChargingProfilesLimit = (
chargingStation: ChargingStation,
connectorId: number,
- chargingProfiles: ChargingProfile[],
- logPrefix: string
+ chargingProfiles: ChargingProfile[]
): ChargingProfilesLimit | undefined => {
- const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`
+ const debugLogMsg = `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profiles limit found: %j`
const currentDate = new Date()
const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ let previousActiveChargingProfile: ChargingProfile | undefined
for (const chargingProfile of chargingProfiles) {
const chargingSchedule = chargingProfile.chargingSchedule
if (chargingSchedule.startSchedule == null) {
logger.debug(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined. Trying to set it to the connector current transaction start date`
+ `${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`
)
// OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
chargingSchedule.startSchedule = connectorStatus?.transactionStart
}
if (!isDate(chargingSchedule.startSchedule)) {
logger.warn(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance`
+ `${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`
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
chargingSchedule.startSchedule = convertToDate(chargingSchedule.startSchedule)!
}
if (chargingSchedule.duration == null) {
logger.debug(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined and will be set to the maximum time allowed`
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no duration defined and will be set to the maximum time allowed`
)
// OCPP specifies that if duration is not defined, it should be infinite
chargingSchedule.duration = differenceInSeconds(maxTime, chargingSchedule.startSchedule)
}
- if (!prepareChargingProfileKind(connectorStatus, chargingProfile, currentDate, logPrefix)) {
+ if (
+ !prepareChargingProfileKind(
+ connectorStatus,
+ chargingProfile,
+ currentDate,
+ chargingStation.logPrefix()
+ )
+ ) {
continue
}
- if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) {
+ if (!canProceedChargingProfile(chargingProfile, currentDate, chargingStation.logPrefix())) {
continue
}
// Check if the charging profile is active
if (
isWithinInterval(currentDate, {
+ end: addSeconds(chargingSchedule.startSchedule, chargingSchedule.duration),
start: chargingSchedule.startSchedule,
- end: addSeconds(chargingSchedule.startSchedule, chargingSchedule.duration)
})
) {
- if (isNotEmptyArray(chargingSchedule.chargingSchedulePeriod)) {
+ if (isNotEmptyArray<ChargingSchedulePeriod>(chargingSchedule.chargingSchedulePeriod)) {
const chargingSchedulePeriodCompareFn = (
a: ChargingSchedulePeriod,
b: ChargingSchedulePeriod
)
) {
logger.warn(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} schedule periods are not sorted by start period`
)
chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn)
}
// Check if the first schedule period startPeriod property is equal to 0
if (chargingSchedule.chargingSchedulePeriod[0].startPeriod !== 0) {
logger.error(
- `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0`
+ `${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`
)
continue
}
// Handle only one schedule period
if (chargingSchedule.chargingSchedulePeriod.length === 1) {
- const result: ChargingProfilesLimit = {
+ const chargingProfilesLimit: ChargingProfilesLimit = {
+ chargingProfile,
limit: chargingSchedule.chargingSchedulePeriod[0].limit,
- chargingProfile
}
- logger.debug(debugLogMsg, result)
- return result
+ logger.debug(debugLogMsg, chargingProfilesLimit)
+ return chargingProfilesLimit
}
let previousChargingSchedulePeriod: ChargingSchedulePeriod | undefined
// Search for the right schedule period
for (const [
index,
- chargingSchedulePeriod
+ chargingSchedulePeriod,
] of chargingSchedule.chargingSchedulePeriod.entries()) {
// Find the right schedule period
if (
)
) {
// Found the schedule period: previous is the correct one
- const result: ChargingProfilesLimit = {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- limit: previousChargingSchedulePeriod!.limit,
- chargingProfile
+ const chargingProfilesLimit: ChargingProfilesLimit = {
+ chargingProfile: previousActiveChargingProfile ?? chargingProfile,
+ limit: previousChargingSchedulePeriod?.limit ?? chargingSchedulePeriod.limit,
}
- logger.debug(debugLogMsg, result)
- return result
+ logger.debug(debugLogMsg, chargingProfilesLimit)
+ return chargingProfilesLimit
}
- // Keep a reference to previous one
- previousChargingSchedulePeriod = chargingSchedulePeriod
// Handle the last schedule period within the charging profile duration
if (
index === chargingSchedule.chargingSchedulePeriod.length - 1 ||
chargingSchedule.startSchedule
) > chargingSchedule.duration)
) {
- const result: ChargingProfilesLimit = {
- limit: previousChargingSchedulePeriod.limit,
- chargingProfile
+ const chargingProfilesLimit: ChargingProfilesLimit = {
+ chargingProfile,
+ limit: chargingSchedulePeriod.limit,
}
- logger.debug(debugLogMsg, result)
- return result
+ logger.debug(debugLogMsg, chargingProfilesLimit)
+ return chargingProfilesLimit
}
+ // Keep a reference to previous charging schedule period
+ previousChargingSchedulePeriod = chargingSchedulePeriod
}
}
+ // Keep a reference to previous active charging profile
+ previousActiveChargingProfile = chargingProfile
}
}
}
export const prepareChargingProfileKind = (
connectorStatus: ConnectorStatus | undefined,
chargingProfile: ChargingProfile,
- currentDate: string | number | Date,
+ currentDate: Date | number | string,
logPrefix: string
): boolean => {
switch (chargingProfile.chargingProfileKind) {
case ChargingProfileKindType.RELATIVE:
if (chargingProfile.chargingSchedule.startSchedule != null) {
logger.warn(
- `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfile.chargingProfileId} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`
+ `${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`
)
delete chargingProfile.chargingSchedule.startSchedule
}
if (connectorStatus?.transactionStarted === true) {
chargingProfile.chargingSchedule.startSchedule = connectorStatus.transactionStart
}
- // FIXME: Handle relative charging profile duration
+ // FIXME: handle relative charging profile duration
break
}
return true
export const canProceedChargingProfile = (
chargingProfile: ChargingProfile,
- currentDate: string | number | Date,
+ currentDate: Date | number | string,
logPrefix: string
): boolean => {
if (
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- (isValidTime(chargingProfile.validFrom) && isBefore(currentDate, chargingProfile.validFrom!)) ||
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- (isValidTime(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo!))
+ (isValidDate(chargingProfile.validFrom) && isBefore(currentDate, chargingProfile.validFrom)) ||
+ (isValidDate(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo))
) {
logger.debug(
- `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${
- chargingProfile.chargingProfileId
- } is not valid for the current date ${
- currentDate instanceof Date ? currentDate.toISOString() : currentDate
+ `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} is not valid for the current date ${
+ isDate(currentDate) ? currentDate.toISOString() : currentDate.toString()
}`
)
return false
chargingProfile.chargingSchedule.duration == null
) {
logger.error(
- `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule or duration defined`
+ `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule or duration defined`
)
return false
}
- if (!isValidTime(chargingProfile.chargingSchedule.startSchedule)) {
+ if (!isValidDate(chargingProfile.chargingSchedule.startSchedule)) {
logger.error(
- `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has an invalid startSchedule date defined`
+ `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has an invalid startSchedule date defined`
)
return false
}
if (!Number.isSafeInteger(chargingProfile.chargingSchedule.duration)) {
logger.error(
- `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has non integer duration defined`
+ `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has non integer duration defined`
)
return false
}
chargingProfile.recurrencyKind == null
) {
logger.error(
- `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`
+ `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId.toString()} has no recurrencyKind defined`
)
return false
}
chargingProfile.chargingSchedule.startSchedule == null
) {
logger.error(
- `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined`
+ `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule defined`
)
return false
}
/**
* Adjust recurring charging profile startSchedule to the current recurrency time interval if needed
- *
* @param chargingProfile -
* @param currentDate -
* @param logPrefix -
+ * @returns boolean
*/
const prepareRecurringChargingProfile = (
chargingProfile: ChargingProfile,
- currentDate: string | number | Date,
+ currentDate: Date | number | string,
logPrefix: string
): boolean => {
const chargingSchedule = chargingProfile.chargingSchedule
let recurringIntervalTranslated = false
- let recurringInterval: Interval
+ let recurringInterval: Interval | undefined
switch (chargingProfile.recurrencyKind) {
case RecurrencyKindType.DAILY:
recurringInterval = {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- start: chargingSchedule.startSchedule!,
+ end: addDays(chargingSchedule.startSchedule!, 1),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- end: addDays(chargingSchedule.startSchedule!, 1)
+ start: chargingSchedule.startSchedule!,
}
checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix)
if (
differenceInDays(currentDate, recurringInterval.start)
)
recurringInterval = {
+ end: addDays(chargingSchedule.startSchedule, 1),
start: chargingSchedule.startSchedule,
- end: addDays(chargingSchedule.startSchedule, 1)
}
recurringIntervalTranslated = true
}
case RecurrencyKindType.WEEKLY:
recurringInterval = {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- start: chargingSchedule.startSchedule!,
+ end: addWeeks(chargingSchedule.startSchedule!, 1),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- end: addWeeks(chargingSchedule.startSchedule!, 1)
+ start: chargingSchedule.startSchedule!,
}
checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix)
if (
differenceInWeeks(currentDate, recurringInterval.start)
)
recurringInterval = {
+ end: addWeeks(chargingSchedule.startSchedule, 1),
start: chargingSchedule.startSchedule,
- end: addWeeks(chargingSchedule.startSchedule, 1)
}
recurringIntervalTranslated = true
}
break
default:
logger.error(
- `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId} is not supported`
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId.toString()} is not supported`
)
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (recurringIntervalTranslated && !isWithinInterval(currentDate, recurringInterval!)) {
logger.error(
`${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
chargingProfile.recurrencyKind
- } charging profile id ${chargingProfile.chargingProfileId} recurrency time interval [${toDate(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- recurringInterval!.start
+ } charging profile id ${chargingProfile.chargingProfileId.toString()} recurrency time interval [${toDate(
+ recurringInterval?.start as Date
).toISOString()}, ${toDate(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- recurringInterval!.end
+ recurringInterval?.end as Date
).toISOString()}] has not been properly translated to current date ${
- currentDate instanceof Date ? currentDate.toISOString() : currentDate
+ isDate(currentDate) ? currentDate.toISOString() : currentDate.toString()
} `
)
}
logger.warn(
`${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
chargingProfile.chargingProfileKind
- } charging profile id ${
- chargingProfile.chargingProfileId
- } duration is not defined, set it to the recurrency time interval duration ${differenceInSeconds(
+ } charging profile id ${chargingProfile.chargingProfileId.toString()} duration is not defined, set it to the recurrency time interval duration ${differenceInSeconds(
interval.end,
interval.start
- )}`
+ ).toString()}`
)
chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start)
} else if (
logger.warn(
`${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
chargingProfile.chargingProfileKind
- } charging profile id ${chargingProfile.chargingProfileId} duration ${
- chargingProfile.chargingSchedule.duration
- } is greater than the recurrency time interval duration ${differenceInSeconds(
+ } charging profile id ${chargingProfile.chargingProfileId.toString()} duration ${chargingProfile.chargingSchedule.duration.toString()} is greater than the recurrency time interval duration ${differenceInSeconds(
interval.end,
interval.start
- )}`
+ ).toString()}`
)
chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start)
}
upperCase?: boolean
}): string => {
const randomSerialNumberSuffix = randomBytes(params?.randomBytesLength ?? 16).toString('hex')
- if (params?.upperCase === true) {
+ if (params?.upperCase) {
return randomSerialNumberSuffix.toUpperCase()
}
return randomSerialNumberSuffix