import { createHash, randomBytes } from 'node:crypto'
import type { EventEmitter } from 'node:events'
-import { basename, dirname, join } from 'node:path'
+import { basename, dirname, isAbsolute, join, parse, relative, resolve } 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,
toDate
} from 'date-fns'
import { maxTime } from 'date-fns/constants'
+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,
} from '../types/index.js'
import {
ACElectricUtils,
+ clone,
Constants,
- DCElectricUtils,
- cloneObject,
convertToDate,
convertToInt,
+ DCElectricUtils,
isArraySorted,
- isEmptyObject,
- isEmptyString,
isNotEmptyArray,
isNotEmptyString,
- isValidTime,
+ isValidDate,
logger,
secureRandom
} from '../utils/index.js'
+import type { ChargingStation } from './ChargingStation.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
}
// In case of multiple instances: add instance index to charging station id
const instanceIndex = env.CF_INSTANCE_INDEX ?? 0
- const idSuffix = stationTemplate?.nameSuffix ?? ''
+ const idSuffix = stationTemplate.nameSuffix ?? ''
const idStr = `000000000${index.toString()}`
- return stationTemplate?.fixedName === true
+ return stationTemplate.fixedName === true
? stationTemplate.baseName
: `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
idStr.length - 4
const chargingStationInfo = {
chargePointModel: stationTemplate.chargePointModel,
chargePointVendor: stationTemplate.chargePointVendor,
- ...(stationTemplate.chargeBoxSerialNumberPrefix !== undefined && {
+ ...(stationTemplate.chargeBoxSerialNumberPrefix != null && {
chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix
}),
- ...(stationTemplate.chargePointSerialNumberPrefix !== undefined && {
+ ...(stationTemplate.chargePointSerialNumberPrefix != null && {
chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix
}),
- ...(stationTemplate.meterSerialNumberPrefix !== undefined && {
+ ...(stationTemplate.meterSerialNumberPrefix != null && {
meterSerialNumber: stationTemplate.meterSerialNumberPrefix
}),
- ...(stationTemplate.meterType !== undefined && {
+ ...(stationTemplate.meterType != null && {
meterType: stationTemplate.meterType
})
}
}
}
-export const getMaxNumberOfEvses = (evses: Record<string, EvseTemplate>): number => {
+export const getMaxNumberOfEvses = (evses: Record<string, EvseTemplate> | undefined): number => {
if (evses == null) {
return -1
}
return Object.keys(evses).length
}
-const getMaxNumberOfConnectors = (connectors: Record<string, ConnectorStatus>): number => {
+const getMaxNumberOfConnectors = (
+ connectors: Record<string, ConnectorStatus> | undefined
+): number => {
if (connectors == null) {
return -1
}
): ConnectorStatusEnum => {
let connectorBootStatus: ConnectorStatusEnum
if (
- connectorStatus?.status == null &&
+ connectorStatus.status == null &&
(!chargingStation.isChargingStationAvailable() ||
!chargingStation.isConnectorAvailable(connectorId))
) {
connectorBootStatus = ConnectorStatusEnum.Unavailable
- } else if (connectorStatus?.status == null && connectorStatus?.bootStatus != null) {
+ } else if (connectorStatus.status == null && connectorStatus.bootStatus != null) {
// Set boot status in template at startup
- connectorBootStatus = connectorStatus?.bootStatus
- } else if (connectorStatus?.status != null) {
+ connectorBootStatus = connectorStatus.bootStatus
+ } else if (connectorStatus.status != null) {
// Set previous status at startup
- connectorBootStatus = connectorStatus?.status
+ connectorBootStatus = connectorStatus.status
} else {
// Set default status
connectorBootStatus = ConnectorStatusEnum.Available
}
export const checkTemplate = (
- stationTemplate: ChargingStationTemplate,
+ stationTemplate: ChargingStationTemplate | undefined,
logPrefix: string,
templateFile: string
): void => {
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)
} => {
const configuredMaxConnectors = getConfiguredMaxNumberOfConnectors(stationTemplate)
checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile)
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors!)
+ const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors)
checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile)
const templateMaxAvailableConnectors =
stationTemplate.Connectors?.[0] != null ? templateMaxConnectors - 1 : templateMaxConnectors
if (
configuredMaxConnectors > templateMaxAvailableConnectors &&
- stationTemplate?.randomConnectors !== true
+ stationTemplate.randomConnectors !== true
) {
logger.warn(
`${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`
)
stationTemplate.randomConnectors = true
}
- return { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors }
+ return {
+ configuredMaxConnectors,
+ templateMaxConnectors,
+ templateMaxAvailableConnectors
+ }
}
export const checkStationInfoConnectorStatus = (
logPrefix: string,
templateFile: string
): void => {
- if (connectorStatus?.status != null) {
+ if (connectorStatus.status != null) {
logger.warn(
`${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
)
}
}
+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}`
+ `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
+ connectors.get(connectorId)?.transactionId
+ }`
)
}
if (connectorId === 0) {
}
}
-export const resetConnectorStatus = (connectorStatus: ConnectorStatus): void => {
- connectorStatus.chargingProfiles =
- connectorStatus.transactionId != null && isNotEmptyArray(connectorStatus.chargingProfiles)
- ? connectorStatus.chargingProfiles?.filter(
- (chargingProfile) => chargingProfile.transactionId !== connectorStatus.transactionId
- )
- : []
+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
+ }
+ 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
+ delete connectorStatus.transactionStart
+ delete connectorStatus.transactionId
+ delete connectorStatus.transactionIdTag
connectorStatus.transactionEnergyActiveImportRegisterValue = 0
- delete connectorStatus?.transactionBeginMeterValue
+ 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
-): BootNotificationRequest => {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const ocppVersion = stationInfo.ocppVersion!
+): BootNotificationRequest | undefined => {
+ const ocppVersion = stationInfo.ocppVersion
switch (ocppVersion) {
case OCPPVersion.VERSION_16:
return {
chargePointModel: stationInfo.chargePointModel,
chargePointVendor: stationInfo.chargePointVendor,
- ...(stationInfo.chargeBoxSerialNumber !== undefined && {
+ ...(stationInfo.chargeBoxSerialNumber != null && {
chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber
}),
- ...(stationInfo.chargePointSerialNumber !== undefined && {
+ ...(stationInfo.chargePointSerialNumber != null && {
chargePointSerialNumber: stationInfo.chargePointSerialNumber
}),
- ...(stationInfo.firmwareVersion !== undefined && {
+ ...(stationInfo.firmwareVersion != null && {
firmwareVersion: stationInfo.firmwareVersion
}),
- ...(stationInfo.iccid !== undefined && { iccid: stationInfo.iccid }),
- ...(stationInfo.imsi !== undefined && { imsi: stationInfo.imsi }),
- ...(stationInfo.meterSerialNumber !== undefined && {
+ ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+ ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+ ...(stationInfo.meterSerialNumber != null && {
meterSerialNumber: stationInfo.meterSerialNumber
}),
- ...(stationInfo.meterType !== undefined && {
+ ...(stationInfo.meterType != null && {
meterType: stationInfo.meterType
})
} satisfies OCPP16BootNotificationRequest
chargingStation: {
model: stationInfo.chargePointModel,
vendorName: stationInfo.chargePointVendor,
- ...(stationInfo.firmwareVersion !== undefined && {
+ ...(stationInfo.firmwareVersion != null && {
firmwareVersion: stationInfo.firmwareVersion
}),
- ...(stationInfo.chargeBoxSerialNumber !== undefined && {
+ ...(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 })
}
})
}
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
randomSerialNumber?: boolean
}
): void => {
- params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params }
+ params = {
+ ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true },
+ ...params
+ }
const serialNumberSuffix =
- params?.randomSerialNumber === true
+ params.randomSerialNumber === true
? getRandomSerialNumberSuffix({
upperCase: params.randomSerialNumberUpperCase
})
: ''
- isNotEmptyString(stationTemplate?.chargePointSerialNumberPrefix) &&
+ isNotEmptyString(stationTemplate.chargePointSerialNumberPrefix) &&
(stationInfo.chargePointSerialNumber = `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`)
- isNotEmptyString(stationTemplate?.chargeBoxSerialNumberPrefix) &&
+ isNotEmptyString(stationTemplate.chargeBoxSerialNumberPrefix) &&
(stationInfo.chargeBoxSerialNumber = `${stationTemplate.chargeBoxSerialNumberPrefix}${serialNumberSuffix}`)
- isNotEmptyString(stationTemplate?.meterSerialNumberPrefix) &&
+ isNotEmptyString(stationTemplate.meterSerialNumberPrefix) &&
(stationInfo.meterSerialNumber = `${stationTemplate.meterSerialNumberPrefix}${serialNumberSuffix}`)
}
export const propagateSerialNumber = (
- stationTemplate: ChargingStationTemplate,
- stationInfoSrc: ChargingStationInfo,
+ stationTemplate: ChargingStationTemplate | undefined,
+ stationInfoSrc: ChargingStationInfo | undefined,
stationInfoDst: ChargingStationInfo
): void => {
if (stationInfoSrc == null || stationTemplate == null) {
'Missing charging station template or existing configuration to propagate serial number'
)
}
- stationTemplate?.chargePointSerialNumberPrefix != null &&
- stationInfoSrc?.chargePointSerialNumber != null
+ stationTemplate.chargePointSerialNumberPrefix != null &&
+ stationInfoSrc.chargePointSerialNumber != null
? (stationInfoDst.chargePointSerialNumber = stationInfoSrc.chargePointSerialNumber)
- : stationInfoDst?.chargePointSerialNumber != null &&
+ : stationInfoDst.chargePointSerialNumber != null &&
delete stationInfoDst.chargePointSerialNumber
- stationTemplate?.chargeBoxSerialNumberPrefix != null &&
- stationInfoSrc?.chargeBoxSerialNumber != null
+ stationTemplate.chargeBoxSerialNumberPrefix != null &&
+ stationInfoSrc.chargeBoxSerialNumber != null
? (stationInfoDst.chargeBoxSerialNumber = stationInfoSrc.chargeBoxSerialNumber)
- : stationInfoDst?.chargeBoxSerialNumber != null && delete stationInfoDst.chargeBoxSerialNumber
- stationTemplate?.meterSerialNumberPrefix != null && stationInfoSrc?.meterSerialNumber != null
+ : stationInfoDst.chargeBoxSerialNumber != null && delete stationInfoDst.chargeBoxSerialNumber
+ stationTemplate.meterSerialNumberPrefix != null && stationInfoSrc.meterSerialNumber != null
? (stationInfoDst.meterSerialNumber = stationInfoSrc.meterSerialNumber)
- : stationInfoDst?.meterSerialNumber != null && delete stationInfoDst.meterSerialNumber
+ : stationInfoDst.meterSerialNumber != null && delete stationInfoDst.meterSerialNumber
}
export const hasFeatureProfile = (
}
/**
- * Gets the connector cloned charging profiles applying a power limitation
- * and sorted by connector id descending then stack level descending
+ * Gets the connector charging profiles relevant for power limitation shallow cloned and sorted by priorities
*
- * @param chargingStation -
- * @param connectorId -
+ * @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
+ // FIXME: handle charging profile purpose CHARGE_POINT_MAX_PROFILE
+ 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 = (
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(
chargingStation.logPrefix()
)
if (result != null) {
- limit = result?.limit
- chargingProfile = result?.chargingProfile
+ limit = result.limit
+ chargingProfile = result.chargingProfile
switch (chargingStation.stationInfo?.currentOutType) {
case CurrentType.AC:
limit =
- chargingProfile?.chargingSchedule?.chargingRateUnit === ChargingRateUnitType.WATT
+ chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
? limit
: ACElectricUtils.powerTotal(
chargingStation.getNumberOfPhases(),
break
case CurrentType.DC:
limit =
- chargingProfile?.chargingSchedule?.chargingRateUnit === ChargingRateUnitType.WATT
+ chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
? limit
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit)
}
const connectorMaximumPower =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- chargingStation.stationInfo.maximumPower! / chargingStation.powerDivider
+ 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`,
+ `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${
+ chargingProfile.chargingProfileId
+ } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
result
)
limit = connectorMaximumPower
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[]
+ const numberOfConnectors = stationTemplate.numberOfConnectors
configuredMaxNumberOfConnectors =
numberOfConnectors[Math.floor(secureRandom() * numberOfConnectors.length)]
} else if (stationTemplate.numberOfConnectors != null) {
- configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors as number
+ configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors
} else if (stationTemplate.Connectors != null && stationTemplate.Evses == null) {
configuredMaxNumberOfConnectors =
- stationTemplate.Connectors?.[0] != null
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ stationTemplate.Connectors[0] != null
? getMaxNumberOfConnectors(stationTemplate.Connectors) - 1
: getMaxNumberOfConnectors(stationTemplate.Connectors)
} else if (stationTemplate.Evses != null && stationTemplate.Connectors == null) {
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) {
+ if (template[deprecatedKey as keyof ChargingStationTemplate] != null) {
+ if (key != null) {
(template as unknown as Record<string, unknown>)[key] =
template[deprecatedKey as keyof ChargingStationTemplate]
}
): ChargingProfilesLimit | undefined => {
const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`
const currentDate = new Date()
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+ let previousActiveChargingProfile: ChargingProfile | undefined
for (const chargingProfile of chargingProfiles) {
const chargingSchedule = chargingProfile.chargingSchedule
- if (chargingSchedule?.startSchedule == null && connectorStatus?.transactionStarted === true) {
+ 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`
)
// OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
chargingSchedule.startSchedule = connectorStatus?.transactionStart
}
- if (chargingSchedule?.startSchedule != null && !isDate(chargingSchedule?.startSchedule)) {
+ 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`
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!
+ chargingSchedule.startSchedule = convertToDate(chargingSchedule.startSchedule)!
}
- if (chargingSchedule?.startSchedule != null && chargingSchedule?.duration == null) {
+ 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`
)
// Check if the charging profile is active
if (
isWithinInterval(currentDate, {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- start: chargingSchedule.startSchedule!,
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!)
+ start: chargingSchedule.startSchedule,
+ end: addSeconds(chargingSchedule.startSchedule, chargingSchedule.duration)
})
) {
if (isNotEmptyArray(chargingSchedule.chargingSchedulePeriod)) {
// Find the right schedule period
if (
isAfter(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- addSeconds(chargingSchedule.startSchedule!, chargingSchedulePeriod.startPeriod),
+ addSeconds(chargingSchedule.startSchedule, chargingSchedulePeriod.startPeriod),
currentDate
)
) {
// 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
+ limit: previousChargingSchedulePeriod?.limit ?? chargingSchedulePeriod.limit,
+ chargingProfile: previousActiveChargingProfile ?? chargingProfile
}
logger.debug(debugLogMsg, result)
return result
}
- // Keep a reference to previous one
- previousChargingSchedulePeriod = chargingSchedulePeriod
// Handle the last schedule period within the charging profile duration
if (
index === chargingSchedule.chargingSchedulePeriod.length - 1 ||
(index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
differenceInSeconds(
addSeconds(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- chargingSchedule.startSchedule!,
+ chargingSchedule.startSchedule,
chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod
),
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- chargingSchedule.startSchedule!
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- ) > chargingSchedule.duration!)
+ chargingSchedule.startSchedule
+ ) > chargingSchedule.duration)
) {
const result: ChargingProfilesLimit = {
- limit: previousChargingSchedulePeriod.limit,
+ limit: chargingSchedulePeriod.limit,
chargingProfile
}
logger.debug(debugLogMsg, result)
return result
}
+ // 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,
+ connectorStatus: ConnectorStatus | undefined,
chargingProfile: ChargingProfile,
- currentDate: Date,
+ currentDate: string | number | Date,
logPrefix: string
): boolean => {
switch (chargingProfile.chargingProfileKind) {
delete chargingProfile.chargingSchedule.startSchedule
}
if (connectorStatus?.transactionStarted === true) {
- chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
+ 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: Date,
+ currentDate: string | number | Date,
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.toISOString()}`
+ } is not valid for the current date ${
+ isDate(currentDate) ? currentDate.toISOString() : currentDate
+ }`
)
return false
}
)
return false
}
- if (
- chargingProfile.chargingSchedule.startSchedule != null &&
- !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`
)
return false
}
- if (
- chargingProfile.chargingSchedule.duration != null &&
- !Number.isSafeInteger(chargingProfile.chargingSchedule.duration)
- ) {
+ if (!Number.isSafeInteger(chargingProfile.chargingSchedule.duration)) {
logger.error(
`${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has non integer duration defined`
)
*/
const prepareRecurringChargingProfile = (
chargingProfile: ChargingProfile,
- currentDate: Date,
+ currentDate: string | number | Date,
logPrefix: string
): boolean => {
const chargingSchedule = chargingProfile.chargingSchedule
let recurringIntervalTranslated = false
- let recurringInterval: Interval
+ let recurringInterval: Interval | undefined
switch (chargingProfile.recurrencyKind) {
case RecurrencyKindType.DAILY:
recurringInterval = {
`${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${
chargingProfile.recurrencyKind
} charging profile id ${chargingProfile.chargingProfileId} recurrency time interval [${toDate(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- recurringInterval!.start
+ recurringInterval?.start as Date
).toISOString()}, ${toDate(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- recurringInterval!.end
- ).toISOString()}] has not been properly translated to current date ${currentDate.toISOString()} `
+ recurringInterval?.end as Date
+ ).toISOString()}] has not been properly translated to current date ${
+ isDate(currentDate) ? currentDate.toISOString() : currentDate
+ } `
)
}
return recurringIntervalTranslated