X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=ecd858c9a78e676b2aa0e9990d9359d593144899;hb=a9671b9e315a2807c2659fdda962203229d35c0a;hp=5860a654e47afc9486653cc9ab1f4f069375f9b6;hpb=041365be4e6cfcec381c895a203815dd933afff5;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 5860a654..ecd858c9 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -1,67 +1,18 @@ -// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. +// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. -import { createHash } from 'node:crypto'; -import { EventEmitter } from 'node:events'; -import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; -import { URL } from 'node:url'; -import { parentPort } from 'node:worker_threads'; +import { createHash, randomInt } from 'node:crypto' +import { EventEmitter } from 'node:events' +import { existsSync, type FSWatcher, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { URL } from 'node:url' +import { parentPort } from 'node:worker_threads' -import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns'; -import merge from 'just-merge'; -import { type RawData, WebSocket } from 'ws'; +import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns' +import { mergeDeepRight, once } from 'rambda' +import { type RawData, WebSocket } from 'ws' -import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator'; -import { ChargingStationWorkerBroadcastChannel } from './broadcast-channel/ChargingStationWorkerBroadcastChannel'; -import { - addConfigurationKey, - deleteConfigurationKey, - getConfigurationKey, - setConfigurationKeyValue, -} from './ConfigurationKeyUtils'; -import { - buildConnectorsMap, - checkChargingStation, - checkConfiguration, - checkConnectorsConfiguration, - checkStationInfoConnectorStatus, - checkTemplate, - createBootNotificationRequest, - createSerialNumber, - getAmperageLimitationUnitDivider, - getBootConnectorStatus, - getChargingStationConnectorChargingProfilesPowerLimit, - getChargingStationId, - getDefaultVoltageOut, - getHashId, - getIdTagsFile, - getMaxNumberOfEvses, - getNumberOfReservableConnectors, - getPhaseRotationValue, - hasReservationExpired, - initializeConnectorsMapStatus, - propagateSerialNumber, - stationTemplateToStationInfo, - warnTemplateKeysDeprecation, -} from './Helpers'; -import { IdTagsCache } from './IdTagsCache'; -import { - OCPP16IncomingRequestService, - OCPP16RequestService, - OCPP16ResponseService, - OCPP16ServiceUtils, - OCPP20IncomingRequestService, - OCPP20RequestService, - OCPP20ResponseService, - type OCPPIncomingRequestService, - type OCPPRequestService, - buildStatusNotificationRequest, - getMessageTypeString, - sendAndSetConnectorStatus, -} from './ocpp'; -import { SharedLRUCache } from './SharedLRUCache'; -import { BaseError, OCPPError } from '../exception'; -import { PerformanceStatistics } from '../performance'; +import { BaseError, OCPPError } from '../exception/index.js' +import { PerformanceStatistics } from '../performance/index.js' import { type AutomaticTransactionGeneratorConfiguration, AvailabilityType, @@ -72,6 +23,7 @@ import { ChargingStationEvents, type ChargingStationInfo, type ChargingStationOcppConfiguration, + type ChargingStationOptions, type ChargingStationTemplate, type ConnectorStatus, ConnectorStatusEnum, @@ -85,14 +37,11 @@ import { FirmwareStatus, type FirmwareStatusNotificationRequest, type FirmwareStatusNotificationResponse, - type FirmwareUpgrade, type HeartbeatRequest, type HeartbeatResponse, type IncomingRequest, type IncomingRequestCommand, - type JsonType, MessageType, - type MeterValue, MeterValueMeasurand, type MeterValuesRequest, type MeterValuesResponse, @@ -107,519 +56,640 @@ import { type Response, StandardParametersKey, type Status, - type StatusNotificationRequest, - type StatusNotificationResponse, - StopTransactionReason, + type StopTransactionReason, type StopTransactionRequest, type StopTransactionResponse, SupervisionUrlDistribution, SupportedFeatureProfiles, - VendorParametersKey, - Voltage, - type WSError, + type Voltage, WebSocketCloseEventStatusCode, - type WsOptions, -} from '../types'; + type WSError, + type WsOptions +} from '../types/index.js' import { ACElectricUtils, AsyncLock, AsyncLockType, - Configuration, - Constants, - DCElectricUtils, + buildAddedMessage, buildChargingStationAutomaticTransactionGeneratorConfiguration, buildConnectorsStatus, + buildDeletedMessage, buildEvsesStatus, buildStartedMessage, buildStoppedMessage, buildUpdatedMessage, - cloneObject, + clone, + Configuration, + Constants, convertToBoolean, + convertToDate, convertToInt, + DCElectricUtils, exponentialDelay, formatDurationMilliSeconds, formatDurationSeconds, - getRandomInteger, getWebSocketCloseEventStatusString, handleFileException, isNotEmptyArray, isNotEmptyString, - isNullOrUndefined, - isUndefined, - logPrefix, logger, + logPrefix, min, - once, roundTo, secureRandom, sleep, - watchJsonFile, -} from '../utils'; + watchJsonFile +} from '../utils/index.js' +import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator.js' +import { ChargingStationWorkerBroadcastChannel } from './broadcast-channel/ChargingStationWorkerBroadcastChannel.js' +import { + addConfigurationKey, + deleteConfigurationKey, + getConfigurationKey, + setConfigurationKeyValue +} from './ConfigurationKeyUtils.js' +import { + buildConnectorsMap, + buildTemplateName, + checkChargingStation, + checkConfiguration, + checkConnectorsConfiguration, + checkStationInfoConnectorStatus, + checkTemplate, + createBootNotificationRequest, + createSerialNumber, + getAmperageLimitationUnitDivider, + getBootConnectorStatus, + getChargingStationConnectorChargingProfilesPowerLimit, + getChargingStationId, + getDefaultVoltageOut, + getHashId, + getIdTagsFile, + getMaxNumberOfEvses, + getNumberOfReservableConnectors, + getPhaseRotationValue, + hasFeatureProfile, + hasReservationExpired, + initializeConnectorsMapStatus, + propagateSerialNumber, + setChargingStationOptions, + stationTemplateToStationInfo, + warnTemplateKeysDeprecation +} from './Helpers.js' +import { IdTagsCache } from './IdTagsCache.js' +import { + buildMeterValue, + buildTransactionEndMeterValue, + getMessageTypeString, + OCPP16IncomingRequestService, + OCPP16RequestService, + OCPP16ResponseService, + OCPP20IncomingRequestService, + OCPP20RequestService, + OCPP20ResponseService, + type OCPPIncomingRequestService, + type OCPPRequestService, + sendAndSetConnectorStatus +} from './ocpp/index.js' +import { SharedLRUCache } from './SharedLRUCache.js' export class ChargingStation extends EventEmitter { - public readonly index: number; - public readonly templateFile: string; - public stationInfo!: ChargingStationInfo; - public started: boolean; - public starting: boolean; - public idTagsCache: IdTagsCache; - public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined; - public ocppConfiguration!: ChargingStationOcppConfiguration | undefined; - public wsConnection: WebSocket | null; - public readonly connectors: Map; - public readonly evses: Map; - public readonly requests: Map; - public performanceStatistics!: PerformanceStatistics | undefined; - public heartbeatSetInterval?: NodeJS.Timeout; - public ocppRequestService!: OCPPRequestService; - public bootNotificationRequest!: BootNotificationRequest; - public bootNotificationResponse!: BootNotificationResponse | undefined; - public powerDivider!: number; - private stopping: boolean; - private configurationFile!: string; - private configurationFileHash!: string; - private connectorsConfigurationHash!: string; - private evsesConfigurationHash!: string; - private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration; - private ocppIncomingRequestService!: OCPPIncomingRequestService; - private readonly messageBuffer: Set; - private configuredSupervisionUrl!: URL; - private autoReconnectRetryCount: number; - private templateFileWatcher!: FSWatcher | undefined; - private templateFileHash!: string; - private readonly sharedLRUCache: SharedLRUCache; - private webSocketPingSetInterval?: NodeJS.Timeout; - private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel; - private flushMessageBufferSetInterval?: NodeJS.Timeout; - - constructor(index: number, templateFile: string) { - super(); - this.started = false; - this.starting = false; - this.stopping = false; - this.wsConnection = null; - this.autoReconnectRetryCount = 0; - this.index = index; - this.templateFile = templateFile; - this.connectors = new Map(); - this.evses = new Map(); - this.requests = new Map(); - this.messageBuffer = new Set(); - this.sharedLRUCache = SharedLRUCache.getInstance(); - this.idTagsCache = IdTagsCache.getInstance(); - this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this); - + public readonly index: number + public readonly templateFile: string + public stationInfo?: ChargingStationInfo + public started: boolean + public starting: boolean + public idTagsCache: IdTagsCache + public automaticTransactionGenerator?: AutomaticTransactionGenerator + public ocppConfiguration?: ChargingStationOcppConfiguration + public wsConnection: WebSocket | null + public readonly connectors: Map + public readonly evses: Map + public readonly requests: Map + public performanceStatistics?: PerformanceStatistics + public heartbeatSetInterval?: NodeJS.Timeout + public ocppRequestService!: OCPPRequestService + public bootNotificationRequest?: BootNotificationRequest + public bootNotificationResponse?: BootNotificationResponse + public powerDivider?: number + private stopping: boolean + private configurationFile!: string + private configurationFileHash!: string + private connectorsConfigurationHash!: string + private evsesConfigurationHash!: string + private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration + private ocppIncomingRequestService!: OCPPIncomingRequestService + private readonly messageBuffer: Set + private configuredSupervisionUrl!: URL + private wsConnectionRetried: boolean + private wsConnectionRetryCount: number + private templateFileWatcher?: FSWatcher + private templateFileHash!: string + private readonly sharedLRUCache: SharedLRUCache + private wsPingSetInterval?: NodeJS.Timeout + private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel + private flushMessageBufferSetInterval?: NodeJS.Timeout + + constructor (index: number, templateFile: string, options?: ChargingStationOptions) { + super() + this.started = false + this.starting = false + this.stopping = false + this.wsConnection = null + this.wsConnectionRetried = false + this.wsConnectionRetryCount = 0 + this.index = index + this.templateFile = templateFile + this.connectors = new Map() + this.evses = new Map() + this.requests = new Map() + this.messageBuffer = new Set() + this.sharedLRUCache = SharedLRUCache.getInstance() + this.idTagsCache = IdTagsCache.getInstance() + this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this) + + this.on(ChargingStationEvents.added, () => { + parentPort?.postMessage(buildAddedMessage(this)) + }) + this.on(ChargingStationEvents.deleted, () => { + parentPort?.postMessage(buildDeletedMessage(this)) + }) this.on(ChargingStationEvents.started, () => { - parentPort?.postMessage(buildStartedMessage(this)); - }); + parentPort?.postMessage(buildStartedMessage(this)) + }) this.on(ChargingStationEvents.stopped, () => { - parentPort?.postMessage(buildStoppedMessage(this)); - }); + parentPort?.postMessage(buildStoppedMessage(this)) + }) this.on(ChargingStationEvents.updated, () => { - parentPort?.postMessage(buildUpdatedMessage(this)); - }); + parentPort?.postMessage(buildUpdatedMessage(this)) + }) + this.on(ChargingStationEvents.accepted, () => { + this.startMessageSequence( + this.wsConnectionRetried + ? true + : this.getAutomaticTransactionGeneratorConfiguration()?.stopAbsoluteDuration + ).catch((error: unknown) => { + logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error) + }) + this.wsConnectionRetried = false + }) + this.on(ChargingStationEvents.rejected, () => { + this.wsConnectionRetried = false + }) + this.on(ChargingStationEvents.disconnected, () => { + try { + this.internalStopMessageSequence() + } catch (error) { + logger.error( + `${this.logPrefix()} Error while stopping the internal message sequence:`, + error + ) + } + }) + + this.initialize(options) - this.initialize(); + this.add() + + if (this.stationInfo?.autoStart === true) { + this.start() + } } - public get hasEvses(): boolean { - return this.connectors.size === 0 && this.evses.size > 0; + public get hasEvses (): boolean { + return this.connectors.size === 0 && this.evses.size > 0 } - private get wsConnectionUrl(): URL { + public get wsConnectionUrl (): URL { return new URL( `${ this.stationInfo?.supervisionUrlOcppConfiguration === true && - isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) && - isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)?.value) - ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)!.value + isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) && + isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value) + ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value : this.configuredSupervisionUrl.href - }/${this.stationInfo.chargingStationId}`, - ); + }/${this.stationInfo?.chargingStationId}` + ) } public logPrefix = (): string => { - if (isNotEmptyString(this?.stationInfo?.chargingStationId)) { - return logPrefix(` ${this?.stationInfo?.chargingStationId} |`); + if ( + this instanceof ChargingStation && + this.stationInfo != null && + isNotEmptyString(this.stationInfo.chargingStationId) + ) { + return logPrefix(` ${this.stationInfo.chargingStationId} |`) } - let stationTemplate: ChargingStationTemplate | undefined; + let stationTemplate: ChargingStationTemplate | undefined try { stationTemplate = JSON.parse( - readFileSync(this.templateFile, 'utf8'), - ) as ChargingStationTemplate; + readFileSync(this.templateFile, 'utf8') + ) as ChargingStationTemplate } catch { - stationTemplate = undefined; + // Ignore } - return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`); - }; + return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`) + } - public hasIdTags(): boolean { - return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo)!)); + public hasIdTags (): boolean { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo!)!)) } - public getNumberOfPhases(stationInfo?: ChargingStationInfo): number { - const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo; + public getNumberOfPhases (stationInfo?: ChargingStationInfo): number { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const localStationInfo = stationInfo ?? this.stationInfo! switch (this.getCurrentOutType(stationInfo)) { case CurrentType.AC: - return localStationInfo.numberOfPhases ?? 3; + return localStationInfo.numberOfPhases ?? 3 case CurrentType.DC: - return 0; + return 0 } } - public isWebSocketConnectionOpened(): boolean { - return this?.wsConnection?.readyState === WebSocket.OPEN; + public isWebSocketConnectionOpened (): boolean { + return this.wsConnection?.readyState === WebSocket.OPEN } - public inUnknownState(): boolean { - return isNullOrUndefined(this?.bootNotificationResponse?.status); + public inUnknownState (): boolean { + return this.bootNotificationResponse?.status == null } - public inPendingState(): boolean { - return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING; + public inPendingState (): boolean { + return this.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING } - public inAcceptedState(): boolean { - return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED; + public inAcceptedState (): boolean { + return this.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED } - public inRejectedState(): boolean { - return this?.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED; + public inRejectedState (): boolean { + return this.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED } - public isRegistered(): boolean { - return ( - this.inUnknownState() === false && - (this.inAcceptedState() === true || this.inPendingState() === true) - ); + public isRegistered (): boolean { + return !this.inUnknownState() && (this.inAcceptedState() || this.inPendingState()) } - public isChargingStationAvailable(): boolean { - return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative; + public isChargingStationAvailable (): boolean { + return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative } - public hasConnector(connectorId: number): boolean { + public hasConnector (connectorId: number): boolean { if (this.hasEvses) { for (const evseStatus of this.evses.values()) { if (evseStatus.connectors.has(connectorId)) { - return true; + return true } } - return false; + return false } - return this.connectors.has(connectorId); + return this.connectors.has(connectorId) } - public isConnectorAvailable(connectorId: number): boolean { + public isConnectorAvailable (connectorId: number): boolean { return ( connectorId > 0 && this.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative - ); + ) } - public getNumberOfConnectors(): number { + public getNumberOfConnectors (): number { if (this.hasEvses) { - let numberOfConnectors = 0; + let numberOfConnectors = 0 for (const [evseId, evseStatus] of this.evses) { if (evseId > 0) { - numberOfConnectors += evseStatus.connectors.size; + numberOfConnectors += evseStatus.connectors.size } } - return numberOfConnectors; + return numberOfConnectors } - return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size; + return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size } - public getNumberOfEvses(): number { - return this.evses.has(0) ? this.evses.size - 1 : this.evses.size; + public getNumberOfEvses (): number { + return this.evses.has(0) ? this.evses.size - 1 : this.evses.size } - public getConnectorStatus(connectorId: number): ConnectorStatus | undefined { + public getConnectorStatus (connectorId: number): ConnectorStatus | undefined { if (this.hasEvses) { for (const evseStatus of this.evses.values()) { if (evseStatus.connectors.has(connectorId)) { - return evseStatus.connectors.get(connectorId); + return evseStatus.connectors.get(connectorId) } } - return undefined; + return undefined } - return this.connectors.get(connectorId); + return this.connectors.get(connectorId) } - public getConnectorMaximumAvailablePower(connectorId: number): number { - let connectorAmperageLimitationPowerLimit: number | undefined; + public getConnectorMaximumAvailablePower (connectorId: number): number { + let connectorAmperageLimitationPowerLimit: number | undefined + const amperageLimitation = this.getAmperageLimitation() if ( - !isNullOrUndefined(this.getAmperageLimitation()) && - this.getAmperageLimitation()! < this.stationInfo.maximumAmperage! + amperageLimitation != null && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + amperageLimitation < this.stationInfo!.maximumAmperage! ) { connectorAmperageLimitationPowerLimit = (this.stationInfo?.currentOutType === CurrentType.AC ? ACElectricUtils.powerTotal( - this.getNumberOfPhases(), - this.stationInfo.voltageOut!, - this.getAmperageLimitation()! * - (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()), - ) - : DCElectricUtils.power(this.stationInfo.voltageOut!, this.getAmperageLimitation()!)) / - this.powerDivider; + this.getNumberOfPhases(), + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.stationInfo.voltageOut!, + amperageLimitation * + (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()) + ) + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + DCElectricUtils.power(this.stationInfo!.voltageOut!, amperageLimitation)) / + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.powerDivider! } - const connectorMaximumPower = this.stationInfo.maximumPower! / this.powerDivider; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const connectorMaximumPower = this.stationInfo!.maximumPower! / this.powerDivider! const connectorChargingProfilesPowerLimit = - getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId); + getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId) return min( isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion isNaN(connectorAmperageLimitationPowerLimit!) ? Infinity - : connectorAmperageLimitationPowerLimit!, - isNaN(connectorChargingProfilesPowerLimit!) ? Infinity : connectorChargingProfilesPowerLimit!, - ); + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + connectorAmperageLimitationPowerLimit!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + isNaN(connectorChargingProfilesPowerLimit!) ? Infinity : connectorChargingProfilesPowerLimit! + ) } - public getTransactionIdTag(transactionId: number): string | undefined { + public getTransactionIdTag (transactionId: number): string | undefined { if (this.hasEvses) { for (const evseStatus of this.evses.values()) { for (const connectorStatus of evseStatus.connectors.values()) { if (connectorStatus.transactionId === transactionId) { - return connectorStatus.transactionIdTag; + return connectorStatus.transactionIdTag } } } } else { for (const connectorId of this.connectors.keys()) { if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) { - return this.getConnectorStatus(connectorId)?.transactionIdTag; + return this.getConnectorStatus(connectorId)?.transactionIdTag } } } } - public getNumberOfRunningTransactions(): number { - let numberOfRunningTransactions = 0; + public getNumberOfRunningTransactions (): number { + let numberOfRunningTransactions = 0 if (this.hasEvses) { for (const [evseId, evseStatus] of this.evses) { if (evseId === 0) { - continue; + continue } for (const connectorStatus of evseStatus.connectors.values()) { if (connectorStatus.transactionStarted === true) { - ++numberOfRunningTransactions; + ++numberOfRunningTransactions } } } } else { for (const connectorId of this.connectors.keys()) { if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) { - ++numberOfRunningTransactions; + ++numberOfRunningTransactions } } } - return numberOfRunningTransactions; + return numberOfRunningTransactions } - public getConnectorIdByTransactionId(transactionId: number): number | undefined { - if (this.hasEvses) { + public getConnectorIdByTransactionId (transactionId: number | undefined): number | undefined { + if (transactionId == null) { + return undefined + } else if (this.hasEvses) { for (const evseStatus of this.evses.values()) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { if (connectorStatus.transactionId === transactionId) { - return connectorId; + return connectorId } } } } else { for (const connectorId of this.connectors.keys()) { if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) { - return connectorId; + return connectorId } } } } - public getEnergyActiveImportRegisterByTransactionId( - transactionId: number, - rounded = false, + public getEnergyActiveImportRegisterByTransactionId ( + transactionId: number | undefined, + rounded = false ): number { return this.getEnergyActiveImportRegister( - this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)!)!, - rounded, - ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)!), + rounded + ) } - public getEnergyActiveImportRegisterByConnectorId(connectorId: number, rounded = false): number { - return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId)!, rounded); + public getEnergyActiveImportRegisterByConnectorId (connectorId: number, rounded = false): number { + return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded) } - public getAuthorizeRemoteTxRequests(): boolean { + public getAuthorizeRemoteTxRequests (): boolean { const authorizeRemoteTxRequests = getConfigurationKey( this, - StandardParametersKey.AuthorizeRemoteTxRequests, - ); - return authorizeRemoteTxRequests !== undefined + StandardParametersKey.AuthorizeRemoteTxRequests + ) + return authorizeRemoteTxRequests != null ? convertToBoolean(authorizeRemoteTxRequests.value) - : false; + : false } - public getLocalAuthListEnabled(): boolean { + public getLocalAuthListEnabled (): boolean { const localAuthListEnabled = getConfigurationKey( this, - StandardParametersKey.LocalAuthListEnabled, - ); - return localAuthListEnabled !== undefined - ? convertToBoolean(localAuthListEnabled.value) - : false; + StandardParametersKey.LocalAuthListEnabled + ) + return localAuthListEnabled != null ? convertToBoolean(localAuthListEnabled.value) : false } - public getHeartbeatInterval(): number { - const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval); - if (HeartbeatInterval !== undefined) { - return secondsToMilliseconds(convertToInt(HeartbeatInterval.value)); + public getHeartbeatInterval (): number { + const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval) + if (HeartbeatInterval != null) { + return secondsToMilliseconds(convertToInt(HeartbeatInterval.value)) } - const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval); - if (HeartBeatInterval !== undefined) { - return secondsToMilliseconds(convertToInt(HeartBeatInterval.value)); + const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval) + if (HeartBeatInterval != null) { + return secondsToMilliseconds(convertToInt(HeartBeatInterval.value)) } this.stationInfo?.autoRegister === false && logger.warn( `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${ Constants.DEFAULT_HEARTBEAT_INTERVAL - }`, - ); - return Constants.DEFAULT_HEARTBEAT_INTERVAL; + }` + ) + return Constants.DEFAULT_HEARTBEAT_INTERVAL } - public setSupervisionUrl(url: string): void { + public setSupervisionUrl (url: string): void { if ( this.stationInfo?.supervisionUrlOcppConfiguration === true && - isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) + isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) ) { - setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey!, url); + setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey, url) } else { - this.stationInfo.supervisionUrls = url; - this.saveStationInfo(); - this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.stationInfo!.supervisionUrls = url + this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl() + this.saveStationInfo() } } - public startHeartbeat(): void { - if (this.getHeartbeatInterval() > 0 && this.heartbeatSetInterval === undefined) { + public startHeartbeat (): void { + if (this.getHeartbeatInterval() > 0 && this.heartbeatSetInterval == null) { this.heartbeatSetInterval = setInterval(() => { this.ocppRequestService .requestHandler(this, RequestCommand.HEARTBEAT) - .catch((error) => { + .catch((error: unknown) => { logger.error( `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`, - error, - ); - }); - }, this.getHeartbeatInterval()); + error + ) + }) + }, this.getHeartbeatInterval()) logger.info( `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds( - this.getHeartbeatInterval(), - )}`, - ); - } else if (this.heartbeatSetInterval !== undefined) { + this.getHeartbeatInterval() + )}` + ) + } else if (this.heartbeatSetInterval != null) { logger.info( `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds( - this.getHeartbeatInterval(), - )}`, - ); + this.getHeartbeatInterval() + )}` + ) } else { logger.error( - `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat`, - ); + `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat` + ) } } - public restartHeartbeat(): void { + public restartHeartbeat (): void { // Stop heartbeat - this.stopHeartbeat(); + this.stopHeartbeat() // Start heartbeat - this.startHeartbeat(); + this.startHeartbeat() } - public restartWebSocketPing(): void { + public restartWebSocketPing (): void { // Stop WebSocket ping - this.stopWebSocketPing(); + this.stopWebSocketPing() // Start WebSocket ping - this.startWebSocketPing(); + this.startWebSocketPing() } - public startMeterValues(connectorId: number, interval: number): void { + public startMeterValues (connectorId: number, interval: number): void { if (connectorId === 0) { - logger.error( - `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}`, - ); - return; + logger.error(`${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}`) + return } - if (!this.getConnectorStatus(connectorId)) { + const connectorStatus = this.getConnectorStatus(connectorId) + if (connectorStatus == null) { logger.error( `${this.logPrefix()} Trying to start MeterValues on non existing connector id - ${connectorId}`, - ); - return; + ${connectorId}` + ) + return } - if (this.getConnectorStatus(connectorId)?.transactionStarted === false) { + if (connectorStatus.transactionStarted === false) { logger.error( - `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction started`, - ); - return; + `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction started` + ) + return } else if ( - this.getConnectorStatus(connectorId)?.transactionStarted === true && - isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId) + connectorStatus.transactionStarted === true && + connectorStatus.transactionId == null ) { logger.error( - `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction id`, - ); - return; + `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction id` + ) + return } if (interval > 0) { - this.getConnectorStatus(connectorId)!.transactionSetInterval = setInterval(() => { - // FIXME: Implement OCPP version agnostic helpers - const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue( + connectorStatus.transactionSetInterval = setInterval(() => { + const meterValue = buildMeterValue( this, connectorId, - this.getConnectorStatus(connectorId)!.transactionId!, - interval, - ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + connectorStatus.transactionId!, + interval + ) this.ocppRequestService .requestHandler( - this, - RequestCommand.METER_VALUES, - { - connectorId, - transactionId: this.getConnectorStatus(connectorId)?.transactionId, - meterValue: [meterValue], - }, - ) - .catch((error) => { + this, + RequestCommand.METER_VALUES, + { + connectorId, + transactionId: connectorStatus.transactionId, + meterValue: [meterValue] + } + ) + .catch((error: unknown) => { logger.error( `${this.logPrefix()} Error while sending '${RequestCommand.METER_VALUES}':`, - error, - ); - }); - }, interval); + error + ) + }) + }, interval) } else { logger.error( `${this.logPrefix()} Charging station ${ StandardParametersKey.MeterValueSampleInterval - } configuration set to ${interval}, not sending MeterValues`, - ); + } configuration set to ${interval}, not sending MeterValues` + ) + } + } + + public stopMeterValues (connectorId: number): void { + const connectorStatus = this.getConnectorStatus(connectorId) + if (connectorStatus?.transactionSetInterval != null) { + clearInterval(connectorStatus.transactionSetInterval) } } - public stopMeterValues(connectorId: number) { - if (this.getConnectorStatus(connectorId)?.transactionSetInterval !== undefined) { - clearInterval(this.getConnectorStatus(connectorId)?.transactionSetInterval); + private add (): void { + this.emit(ChargingStationEvents.added) + } + + public async delete (deleteConfiguration = true): Promise { + if (this.started) { + await this.stop() } + AutomaticTransactionGenerator.deleteInstance(this) + PerformanceStatistics.deleteInstance(this.stationInfo?.hashId) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!) + this.requests.clear() + this.connectors.clear() + this.evses.clear() + this.templateFileWatcher?.unref() + deleteConfiguration && rmSync(this.configurationFile, { force: true }) + this.chargingStationWorkerBroadcastChannel.unref() + this.emit(ChargingStationEvents.deleted) + this.removeAllListeners() } - public start(): void { - if (this.started === false) { - if (this.starting === false) { - this.starting = true; + public start (): void { + if (!this.started) { + if (!this.starting) { + this.starting = true if (this.stationInfo?.enableStatistics === true) { - this.performanceStatistics?.start(); + this.performanceStatistics?.start() } - this.openWSConnection(); + this.openWSConnection() // Monitor charging station template file this.templateFileWatcher = watchJsonFile( this.templateFile, @@ -632,282 +702,290 @@ export class ChargingStation extends EventEmitter { logger.debug( `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${ this.templateFile - } file have changed, reload`, - ); - this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash); + } file have changed, reload` + ) + this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!) // Initialize - this.initialize(); - this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo)!); + this.initialize() // Restart the ATG - this.stopAutomaticTransactionGenerator(); - delete this.automaticTransactionGeneratorConfiguration; - if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) { - this.startAutomaticTransactionGenerator(); + const ATGStarted = this.automaticTransactionGenerator?.started + if (ATGStarted === true) { + this.stopAutomaticTransactionGenerator() + } + delete this.automaticTransactionGeneratorConfiguration + if ( + this.getAutomaticTransactionGeneratorConfiguration()?.enable === true && + ATGStarted === true + ) { + this.startAutomaticTransactionGenerator(undefined, true) } if (this.stationInfo?.enableStatistics === true) { - this.performanceStatistics?.restart(); + this.performanceStatistics?.restart() } else { - this.performanceStatistics?.stop(); + this.performanceStatistics?.stop() } // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed } catch (error) { logger.error( `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`, - error, - ); + error + ) } } - }, - ); - this.started = true; - this.emit(ChargingStationEvents.started); - this.starting = false; + } + ) + this.started = true + this.emit(ChargingStationEvents.started) + this.starting = false } else { - logger.warn(`${this.logPrefix()} Charging station is already starting...`); + logger.warn(`${this.logPrefix()} Charging station is already starting...`) } } else { - logger.warn(`${this.logPrefix()} Charging station is already started...`); + logger.warn(`${this.logPrefix()} Charging station is already started...`) } } - public async stop(reason?: StopTransactionReason, stopTransactions?: boolean): Promise { - if (this.started === true) { - if (this.stopping === false) { - this.stopping = true; - await this.stopMessageSequence(reason, stopTransactions); - this.closeWSConnection(); + public async stop ( + reason?: StopTransactionReason, + stopTransactions = this.stationInfo?.stopTransactionsOnStopped + ): Promise { + if (this.started) { + if (!this.stopping) { + this.stopping = true + await this.stopMessageSequence(reason, stopTransactions) + this.closeWSConnection() if (this.stationInfo?.enableStatistics === true) { - this.performanceStatistics?.stop(); + this.performanceStatistics?.stop() } - this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash); - this.templateFileWatcher?.close(); - this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash); - delete this.bootNotificationResponse; - this.started = false; - this.saveConfiguration(); - this.emit(ChargingStationEvents.stopped); - this.stopping = false; + this.templateFileWatcher?.close() + delete this.bootNotificationResponse + this.started = false + this.saveConfiguration() + this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash) + this.emit(ChargingStationEvents.stopped) + this.stopping = false } else { - logger.warn(`${this.logPrefix()} Charging station is already stopping...`); + logger.warn(`${this.logPrefix()} Charging station is already stopping...`) } } else { - logger.warn(`${this.logPrefix()} Charging station is already stopped...`); + logger.warn(`${this.logPrefix()} Charging station is already stopped...`) } } - public async reset(reason?: StopTransactionReason): Promise { - await this.stop(reason); - await sleep(this.stationInfo.resetTime!); - this.initialize(); - this.start(); + public async reset (reason?: StopTransactionReason): Promise { + await this.stop(reason) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await sleep(this.stationInfo!.resetTime!) + this.initialize() + this.start() } - public saveOcppConfiguration(): void { + public saveOcppConfiguration (): void { if (this.stationInfo?.ocppPersistentConfiguration === true) { - this.saveConfiguration(); + this.saveConfiguration() } } - public bufferMessage(message: string): void { - this.messageBuffer.add(message); - this.setIntervalFlushMessageBuffer(); + public bufferMessage (message: string): void { + this.messageBuffer.add(message) + this.setIntervalFlushMessageBuffer() } - public openWSConnection( + public openWSConnection ( options?: WsOptions, - params?: { closeOpened?: boolean; terminateOpened?: boolean }, + params?: { closeOpened?: boolean, terminateOpened?: boolean } ): void { options = { handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()), ...this.stationInfo?.wsOptions, - ...options, - }; - params = { ...{ closeOpened: false, terminateOpened: false }, ...params }; + ...options + } + params = { ...{ closeOpened: false, terminateOpened: false }, ...params } if (!checkChargingStation(this, this.logPrefix())) { - return; + return } - if ( - !isNullOrUndefined(this.stationInfo.supervisionUser) && - !isNullOrUndefined(this.stationInfo.supervisionPassword) - ) { - options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`; + if (this.stationInfo?.supervisionUser != null && this.stationInfo.supervisionPassword != null) { + options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}` } - if (params?.closeOpened) { - this.closeWSConnection(); + if (params.closeOpened === true) { + this.closeWSConnection() } - if (params?.terminateOpened) { - this.terminateWSConnection(); + if (params.terminateOpened === true) { + this.terminateWSConnection() } - if (this.isWebSocketConnectionOpened() === true) { + if (this.isWebSocketConnectionOpened()) { logger.warn( - `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened`, - ); - return; + `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.href} is already opened` + ) + return } - logger.info( - `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`, - ); + logger.info(`${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.href}`) this.wsConnection = new WebSocket( this.wsConnectionUrl, `ocpp${this.stationInfo?.ocppVersion}`, - options, - ); + options + ) // Handle WebSocket message - this.wsConnection.on( - 'message', - this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void, - ); + this.wsConnection.on('message', data => { + this.onMessage(data).catch(Constants.EMPTY_FUNCTION) + }) // Handle WebSocket error - this.wsConnection.on( - 'error', - this.onError.bind(this) as (this: WebSocket, error: Error) => void, - ); + this.wsConnection.on('error', this.onError.bind(this)) // Handle WebSocket close - this.wsConnection.on( - 'close', - this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void, - ); + this.wsConnection.on('close', this.onClose.bind(this)) // Handle WebSocket open - this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void); + this.wsConnection.on('open', () => { + this.onOpen().catch((error: unknown) => + logger.error(`${this.logPrefix()} Error while opening WebSocket connection:`, error) + ) + }) // Handle WebSocket ping - this.wsConnection.on('ping', this.onPing.bind(this) as (this: WebSocket, data: Buffer) => void); + this.wsConnection.on('ping', this.onPing.bind(this)) // Handle WebSocket pong - this.wsConnection.on('pong', this.onPong.bind(this) as (this: WebSocket, data: Buffer) => void); + this.wsConnection.on('pong', this.onPong.bind(this)) } - public closeWSConnection(): void { - if (this.isWebSocketConnectionOpened() === true) { - this.wsConnection?.close(); - this.wsConnection = null; + public closeWSConnection (): void { + if (this.isWebSocketConnectionOpened()) { + this.wsConnection?.close() + this.wsConnection = null } } - public getAutomaticTransactionGeneratorConfiguration(): AutomaticTransactionGeneratorConfiguration { - if (isNullOrUndefined(this.automaticTransactionGeneratorConfiguration)) { + public getAutomaticTransactionGeneratorConfiguration (): + | AutomaticTransactionGeneratorConfiguration + | undefined { + if (this.automaticTransactionGeneratorConfiguration == null) { let automaticTransactionGeneratorConfiguration: - | AutomaticTransactionGeneratorConfiguration - | undefined; - const stationTemplate = this.getTemplateFromFile(); - const stationConfiguration = this.getConfigurationFromFile(); + | AutomaticTransactionGeneratorConfiguration + | undefined + const stationTemplate = this.getTemplateFromFile() + const stationConfiguration = this.getConfigurationFromFile() if ( this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true && stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash && - stationConfiguration?.automaticTransactionGenerator + stationConfiguration?.automaticTransactionGenerator != null ) { automaticTransactionGeneratorConfiguration = - stationConfiguration?.automaticTransactionGenerator; + stationConfiguration.automaticTransactionGenerator } else { - automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator; + automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator } this.automaticTransactionGeneratorConfiguration = { ...Constants.DEFAULT_ATG_CONFIGURATION, - ...automaticTransactionGeneratorConfiguration, - }; + ...automaticTransactionGeneratorConfiguration + } } - return this.automaticTransactionGeneratorConfiguration!; + return this.automaticTransactionGeneratorConfiguration } - public getAutomaticTransactionGeneratorStatuses(): Status[] | undefined { - return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses; + public getAutomaticTransactionGeneratorStatuses (): Status[] | undefined { + return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses } - public startAutomaticTransactionGenerator(connectorIds?: number[]): void { - this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this); + public startAutomaticTransactionGenerator ( + connectorIds?: number[], + stopAbsoluteDuration?: boolean + ): void { + this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this) if (isNotEmptyArray(connectorIds)) { - for (const connectorId of connectorIds!) { - this.automaticTransactionGenerator?.startConnector(connectorId); + for (const connectorId of connectorIds) { + this.automaticTransactionGenerator?.startConnector(connectorId, stopAbsoluteDuration) } } else { - this.automaticTransactionGenerator?.start(); + this.automaticTransactionGenerator?.start(stopAbsoluteDuration) } - this.saveAutomaticTransactionGeneratorConfiguration(); - this.emit(ChargingStationEvents.updated); + this.saveAutomaticTransactionGeneratorConfiguration() + this.emit(ChargingStationEvents.updated) } - public stopAutomaticTransactionGenerator(connectorIds?: number[]): void { + public stopAutomaticTransactionGenerator (connectorIds?: number[]): void { if (isNotEmptyArray(connectorIds)) { - for (const connectorId of connectorIds!) { - this.automaticTransactionGenerator?.stopConnector(connectorId); + for (const connectorId of connectorIds) { + this.automaticTransactionGenerator?.stopConnector(connectorId) } } else { - this.automaticTransactionGenerator?.stop(); + this.automaticTransactionGenerator?.stop() } - this.saveAutomaticTransactionGeneratorConfiguration(); - this.emit(ChargingStationEvents.updated); + this.saveAutomaticTransactionGeneratorConfiguration() + this.emit(ChargingStationEvents.updated) } - public async stopTransactionOnConnector( + public async stopTransactionOnConnector ( connectorId: number, - reason?: StopTransactionReason, + reason?: StopTransactionReason ): Promise { - const transactionId = this.getConnectorStatus(connectorId)?.transactionId; + const transactionId = this.getConnectorStatus(connectorId)?.transactionId if ( this.stationInfo?.beginEndMeterValues === true && - this.stationInfo?.ocppStrictCompliance === true && - this.stationInfo?.outOfOrderEndMeterValues === false + this.stationInfo.ocppStrictCompliance === true && + this.stationInfo.outOfOrderEndMeterValues === false ) { - // FIXME: Implement OCPP version agnostic helpers - const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue( + const transactionEndMeterValue = buildTransactionEndMeterValue( this, connectorId, - this.getEnergyActiveImportRegisterByTransactionId(transactionId!), - ); + this.getEnergyActiveImportRegisterByTransactionId(transactionId) + ) await this.ocppRequestService.requestHandler( this, RequestCommand.METER_VALUES, { connectorId, transactionId, - meterValue: [transactionEndMeterValue], - }, - ); + meterValue: [transactionEndMeterValue] + } + ) } - return this.ocppRequestService.requestHandler( - this, - RequestCommand.STOP_TRANSACTION, - { - transactionId, - meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId!, true), - ...(isNullOrUndefined(reason) && { reason }), - }, - ); + return await this.ocppRequestService.requestHandler< + StopTransactionRequest, + StopTransactionResponse + >(this, RequestCommand.STOP_TRANSACTION, { + transactionId, + meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true), + ...(reason != null && { reason }) + }) } - public getReserveConnectorZeroSupported(): boolean { + public getReserveConnectorZeroSupported (): boolean { return convertToBoolean( - getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value, - ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value + ) } - public async addReservation(reservation: Reservation): Promise { - const reservationFound = this.getReservationBy('reservationId', reservation.reservationId); - if (reservationFound !== undefined) { - await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING); + public async addReservation (reservation: Reservation): Promise { + const reservationFound = this.getReservationBy('reservationId', reservation.reservationId) + if (reservationFound != null) { + await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING) } - this.getConnectorStatus(reservation.connectorId)!.reservation = reservation; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.getConnectorStatus(reservation.connectorId)!.reservation = reservation await sendAndSetConnectorStatus( this, reservation.connectorId, ConnectorStatusEnum.Reserved, undefined, - { send: reservation.connectorId !== 0 }, - ); + { send: reservation.connectorId !== 0 } + ) } - public async removeReservation( + public async removeReservation ( reservation: Reservation, - reason: ReservationTerminationReason, + reason: ReservationTerminationReason ): Promise { - const connector = this.getConnectorStatus(reservation.connectorId)!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const connector = this.getConnectorStatus(reservation.connectorId)! switch (reason) { case ReservationTerminationReason.CONNECTOR_STATE_CHANGED: case ReservationTerminationReason.TRANSACTION_STARTED: - delete connector.reservation; - break; + delete connector.reservation + break case ReservationTerminationReason.RESERVATION_CANCELED: case ReservationTerminationReason.REPLACE_EXISTING: case ReservationTerminationReason.EXPIRED: @@ -916,756 +994,738 @@ export class ChargingStation extends EventEmitter { reservation.connectorId, ConnectorStatusEnum.Available, undefined, - { send: reservation.connectorId !== 0 }, - ); - delete connector.reservation; - break; + { send: reservation.connectorId !== 0 } + ) + delete connector.reservation + break default: // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - throw new BaseError(`Unknown reservation termination reason '${reason}'`); + throw new BaseError(`Unknown reservation termination reason '${reason}'`) } } - public getReservationBy( + public getReservationBy ( filterKey: ReservationKey, - value: number | string, + value: number | string ): Reservation | undefined { if (this.hasEvses) { for (const evseStatus of this.evses.values()) { for (const connectorStatus of evseStatus.connectors.values()) { - if (connectorStatus?.reservation?.[filterKey] === value) { - return connectorStatus.reservation; + if (connectorStatus.reservation?.[filterKey] === value) { + return connectorStatus.reservation } } } } else { for (const connectorStatus of this.connectors.values()) { - if (connectorStatus?.reservation?.[filterKey] === value) { - return connectorStatus.reservation; + if (connectorStatus.reservation?.[filterKey] === value) { + return connectorStatus.reservation } } } } - public isConnectorReservable( + public isConnectorReservable ( reservationId: number, idTag?: string, - connectorId?: number, + connectorId?: number ): boolean { - const reservation = this.getReservationBy('reservationId', reservationId); - const reservationExists = !isUndefined(reservation) && !hasReservationExpired(reservation!); + const reservation = this.getReservationBy('reservationId', reservationId) + const reservationExists = reservation != null && !hasReservationExpired(reservation) if (arguments.length === 1) { - return !reservationExists; + return !reservationExists } else if (arguments.length > 1) { - const userReservation = !isUndefined(idTag) - ? this.getReservationBy('idTag', idTag!) - : undefined; + const userReservation = idTag != null ? this.getReservationBy('idTag', idTag) : undefined const userReservationExists = - !isUndefined(userReservation) && !hasReservationExpired(userReservation!); - const notConnectorZero = isUndefined(connectorId) ? true : connectorId! > 0; - const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0; + userReservation != null && !hasReservationExpired(userReservation) + const notConnectorZero = connectorId == null ? true : connectorId > 0 + const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0 return ( !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable - ); + ) } - return false; + return false } - private setIntervalFlushMessageBuffer(): void { - if (this.flushMessageBufferSetInterval === undefined) { + private setIntervalFlushMessageBuffer (): void { + if (this.flushMessageBufferSetInterval == null) { this.flushMessageBufferSetInterval = setInterval(() => { - if (this.isWebSocketConnectionOpened() === true && this.inAcceptedState() === true) { - this.flushMessageBuffer(); + if (this.isWebSocketConnectionOpened() && this.inAcceptedState()) { + this.flushMessageBuffer() } if (this.messageBuffer.size === 0) { - this.clearIntervalFlushMessageBuffer(); + this.clearIntervalFlushMessageBuffer() } - }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL); + }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL) } } - private clearIntervalFlushMessageBuffer() { - if (this.flushMessageBufferSetInterval !== undefined) { - clearInterval(this.flushMessageBufferSetInterval); - delete this.flushMessageBufferSetInterval; + private clearIntervalFlushMessageBuffer (): void { + if (this.flushMessageBufferSetInterval != null) { + clearInterval(this.flushMessageBufferSetInterval) + delete this.flushMessageBufferSetInterval } } - private getNumberOfReservableConnectors(): number { - let numberOfReservableConnectors = 0; + private getNumberOfReservableConnectors (): number { + let numberOfReservableConnectors = 0 if (this.hasEvses) { for (const evseStatus of this.evses.values()) { - numberOfReservableConnectors += getNumberOfReservableConnectors(evseStatus.connectors); + numberOfReservableConnectors += getNumberOfReservableConnectors(evseStatus.connectors) } } else { - numberOfReservableConnectors = getNumberOfReservableConnectors(this.connectors); + numberOfReservableConnectors = getNumberOfReservableConnectors(this.connectors) } - return numberOfReservableConnectors - this.getNumberOfReservationsOnConnectorZero(); + return numberOfReservableConnectors - this.getNumberOfReservationsOnConnectorZero() } - private getNumberOfReservationsOnConnectorZero(): number { + private getNumberOfReservationsOnConnectorZero (): number { if ( - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation) || - (!this.hasEvses && this.connectors.get(0)?.reservation) + (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation != null) || + (!this.hasEvses && this.connectors.get(0)?.reservation != null) ) { - return 1; + return 1 } - return 0; + return 0 } - private flushMessageBuffer(): void { + private flushMessageBuffer (): void { if (this.messageBuffer.size > 0) { for (const message of this.messageBuffer.values()) { - let beginId: string | undefined; - let commandName: RequestCommand | undefined; - const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse; - const isRequest = messageType === MessageType.CALL_MESSAGE; + let beginId: string | undefined + let commandName: RequestCommand | undefined + const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse + const isRequest = messageType === MessageType.CALL_MESSAGE if (isRequest) { - [, , commandName] = JSON.parse(message) as OutgoingRequest; - beginId = PerformanceStatistics.beginMeasure(commandName); + [, , commandName] = JSON.parse(message) as OutgoingRequest + beginId = PerformanceStatistics.beginMeasure(commandName) } this.wsConnection?.send(message, (error?: Error) => { - isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!); - if (isNullOrUndefined(error)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!) + if (error == null) { logger.debug( `${this.logPrefix()} >> Buffered ${getMessageTypeString( - messageType, - )} OCPP message sent '${JSON.stringify(message)}'`, - ); - this.messageBuffer.delete(message); + messageType + )} OCPP message sent '${JSON.stringify(message)}'` + ) + this.messageBuffer.delete(message) } else { logger.debug( `${this.logPrefix()} >> Buffered ${getMessageTypeString( - messageType, + messageType )} OCPP message '${JSON.stringify(message)}' send failed:`, - error, - ); + error + ) } - }); + }) } } } - private getTemplateFromFile(): ChargingStationTemplate | undefined { - let template: ChargingStationTemplate | undefined; + private getTemplateFromFile (): ChargingStationTemplate | undefined { + let template: ChargingStationTemplate | undefined try { if (this.sharedLRUCache.hasChargingStationTemplate(this.templateFileHash)) { - template = this.sharedLRUCache.getChargingStationTemplate(this.templateFileHash); + template = this.sharedLRUCache.getChargingStationTemplate(this.templateFileHash) } else { - const measureId = `${FileType.ChargingStationTemplate} read`; - const beginId = PerformanceStatistics.beginMeasure(measureId); - template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate; - PerformanceStatistics.endMeasure(measureId, beginId); + const measureId = `${FileType.ChargingStationTemplate} read` + const beginId = PerformanceStatistics.beginMeasure(measureId) + template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate + PerformanceStatistics.endMeasure(measureId, beginId) template.templateHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) .update(JSON.stringify(template)) - .digest('hex'); - this.sharedLRUCache.setChargingStationTemplate(template); - this.templateFileHash = template.templateHash; + .digest('hex') + this.sharedLRUCache.setChargingStationTemplate(template) + this.templateFileHash = template.templateHash } } catch (error) { handleFileException( this.templateFile, FileType.ChargingStationTemplate, error as NodeJS.ErrnoException, - this.logPrefix(), - ); - } - return template; - } - - private getStationInfoFromTemplate(): ChargingStationInfo { - const stationTemplate: ChargingStationTemplate = this.getTemplateFromFile()!; - checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); - const warnTemplateKeysDeprecationOnce = once(warnTemplateKeysDeprecation, this); - warnTemplateKeysDeprecationOnce(stationTemplate, this.logPrefix(), this.templateFile); - if (stationTemplate?.Connectors) { - checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile); - } - const stationInfo: ChargingStationInfo = stationTemplateToStationInfo(stationTemplate); - stationInfo.hashId = getHashId(this.index, stationTemplate); - stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate); - stationInfo.ocppVersion = stationTemplate?.ocppVersion ?? OCPPVersion.VERSION_16; - createSerialNumber(stationTemplate, stationInfo); - stationInfo.voltageOut = this.getVoltageOut(stationInfo); - if (isNotEmptyArray(stationTemplate?.power)) { - stationTemplate.power = stationTemplate.power as number[]; - const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length); + this.logPrefix() + ) + } + return template + } + + private getStationInfoFromTemplate (): ChargingStationInfo { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const stationTemplate = this.getTemplateFromFile()! + checkTemplate(stationTemplate, this.logPrefix(), this.templateFile) + const warnTemplateKeysDeprecationOnce = once(warnTemplateKeysDeprecation) + warnTemplateKeysDeprecationOnce(stationTemplate, this.logPrefix(), this.templateFile) + if (stationTemplate.Connectors != null) { + checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile) + } + const stationInfo = stationTemplateToStationInfo(stationTemplate) + stationInfo.hashId = getHashId(this.index, stationTemplate) + stationInfo.templateIndex = this.index + stationInfo.templateName = buildTemplateName(this.templateFile) + stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate) + createSerialNumber(stationTemplate, stationInfo) + stationInfo.voltageOut = this.getVoltageOut(stationInfo) + if (isNotEmptyArray(stationTemplate.power)) { + const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length) stationInfo.maximumPower = - stationTemplate?.powerUnit === PowerUnits.KILO_WATT + stationTemplate.powerUnit === PowerUnits.KILO_WATT ? stationTemplate.power[powerArrayRandomIndex] * 1000 - : stationTemplate.power[powerArrayRandomIndex]; + : stationTemplate.power[powerArrayRandomIndex] } else { - stationTemplate.power = stationTemplate?.power as number; stationInfo.maximumPower = - stationTemplate?.powerUnit === PowerUnits.KILO_WATT - ? stationTemplate.power * 1000 - : stationTemplate.power; + stationTemplate.powerUnit === PowerUnits.KILO_WATT + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + stationTemplate.power! * 1000 + : stationTemplate.power } - stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo); - stationInfo.firmwareVersionPattern = - stationTemplate?.firmwareVersionPattern ?? Constants.SEMVER_PATTERN; + stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo) if ( + isNotEmptyString(stationInfo.firmwareVersionPattern) && isNotEmptyString(stationInfo.firmwareVersion) && - new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion!) === false + !new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) ) { logger.warn( `${this.logPrefix()} Firmware version '${stationInfo.firmwareVersion}' in template file ${ this.templateFile - } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`, - ); + } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'` + ) } - stationInfo.firmwareUpgrade = merge( - { - versionUpgrade: { - step: 1, - }, - reset: true, - }, - stationTemplate?.firmwareUpgrade ?? {}, - ); - stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime) - ? secondsToMilliseconds(stationTemplate.resetTime!) - : Constants.CHARGING_STATION_DEFAULT_RESET_TIME; - return stationInfo; - } - - private getStationInfoFromFile( - stationInfoPersistentConfiguration = true, + if (stationTemplate.resetTime != null) { + stationInfo.resetTime = secondsToMilliseconds(stationTemplate.resetTime) + } + return stationInfo + } + + private getStationInfoFromFile ( + stationInfoPersistentConfiguration: boolean | undefined = Constants.DEFAULT_STATION_INFO + .stationInfoPersistentConfiguration ): ChargingStationInfo | undefined { - let stationInfo: ChargingStationInfo | undefined; + let stationInfo: ChargingStationInfo | undefined if (stationInfoPersistentConfiguration === true) { - stationInfo = this.getConfigurationFromFile()?.stationInfo; - if (stationInfo) { - delete stationInfo?.infoHash; + stationInfo = this.getConfigurationFromFile()?.stationInfo + if (stationInfo != null) { + delete stationInfo.infoHash + delete (stationInfo as ChargingStationTemplate).numberOfConnectors + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (stationInfo.templateIndex == null) { + stationInfo.templateIndex = this.index + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (stationInfo.templateName == null) { + stationInfo.templateName = buildTemplateName(this.templateFile) + } } } - return stationInfo; - } - - private getStationInfo(): ChargingStationInfo { - const defaultStationInfo: Partial = { - enableStatistics: false, - remoteAuthorization: true, - currentOutType: CurrentType.AC, - mainVoltageMeterValues: true, - phaseLineToLineVoltageMeterValues: false, - customValueLimitationMeterValues: true, - ocppStrictCompliance: true, - outOfOrderEndMeterValues: false, - beginEndMeterValues: false, - meteringPerTransaction: true, - transactionDataMeterValues: false, - supervisionUrlOcppConfiguration: false, - supervisionUrlOcppKey: VendorParametersKey.ConnectionUrl, - ocppVersion: OCPPVersion.VERSION_16, - ocppPersistentConfiguration: true, - stationInfoPersistentConfiguration: true, - automaticTransactionGeneratorPersistentConfiguration: true, - autoReconnectMaxRetries: -1, - registrationMaxRetries: -1, - reconnectExponentialDelay: false, - stopTransactionsOnStopped: true, - }; - const stationInfoFromTemplate: ChargingStationInfo = this.getStationInfoFromTemplate(); - const stationInfoFromFile: ChargingStationInfo | undefined = this.getStationInfoFromFile( - stationInfoFromTemplate?.stationInfoPersistentConfiguration, - ); + return stationInfo + } + + private getStationInfo (options?: ChargingStationOptions): ChargingStationInfo { + const stationInfoFromTemplate = this.getStationInfoFromTemplate() + options?.persistentConfiguration != null && + (stationInfoFromTemplate.stationInfoPersistentConfiguration = options.persistentConfiguration) + const stationInfoFromFile = this.getStationInfoFromFile( + stationInfoFromTemplate.stationInfoPersistentConfiguration + ) + let stationInfo: ChargingStationInfo // Priority: // 1. charging station info from template // 2. charging station info from configuration file - if (stationInfoFromFile?.templateHash === stationInfoFromTemplate.templateHash) { - return { ...defaultStationInfo, ...stationInfoFromFile! }; + if ( + stationInfoFromFile != null && + stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash + ) { + stationInfo = stationInfoFromFile + } else { + stationInfo = stationInfoFromTemplate + stationInfoFromFile != null && + propagateSerialNumber(this.getTemplateFromFile(), stationInfoFromFile, stationInfo) } - stationInfoFromFile && - propagateSerialNumber( - this.getTemplateFromFile()!, - stationInfoFromFile, - stationInfoFromTemplate, - ); - return { ...defaultStationInfo, ...stationInfoFromTemplate }; + return setChargingStationOptions( + mergeDeepRight(Constants.DEFAULT_STATION_INFO, stationInfo), + options + ) } - private saveStationInfo(): void { + private saveStationInfo (): void { if (this.stationInfo?.stationInfoPersistentConfiguration === true) { - this.saveConfiguration(); + this.saveConfiguration() } } - private handleUnsupportedVersion(version: OCPPVersion | undefined) { - const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); + private handleUnsupportedVersion (version: OCPPVersion | undefined): void { + const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } - private initialize(): void { - const stationTemplate = this.getTemplateFromFile()!; - checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); + private initialize (options?: ChargingStationOptions): void { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const stationTemplate = this.getTemplateFromFile()! + checkTemplate(stationTemplate, this.logPrefix(), this.templateFile) this.configurationFile = join( dirname(this.templateFile.replace('station-templates', 'configurations')), - `${getHashId(this.index, stationTemplate)}.json`, - ); - const stationConfiguration = this.getConfigurationFromFile(); + `${getHashId(this.index, stationTemplate)}.json` + ) + const stationConfiguration = this.getConfigurationFromFile() if ( - stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash && - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (stationConfiguration?.connectorsStatus || stationConfiguration?.evsesStatus) + stationConfiguration?.stationInfo?.templateHash === stationTemplate.templateHash && + (stationConfiguration?.connectorsStatus != null || stationConfiguration?.evsesStatus != null) ) { - checkConfiguration(stationConfiguration, this.logPrefix(), this.configurationFile); - this.initializeConnectorsOrEvsesFromFile(stationConfiguration); + checkConfiguration(stationConfiguration, this.logPrefix(), this.configurationFile) + this.initializeConnectorsOrEvsesFromFile(stationConfiguration) } else { - this.initializeConnectorsOrEvsesFromTemplate(stationTemplate); + this.initializeConnectorsOrEvsesFromTemplate(stationTemplate) } - this.stationInfo = this.getStationInfo(); + this.stationInfo = this.getStationInfo(options) if ( this.stationInfo.firmwareStatus === FirmwareStatus.Installing && - isNotEmptyString(this.stationInfo.firmwareVersion) && - isNotEmptyString(this.stationInfo.firmwareVersionPattern) + isNotEmptyString(this.stationInfo.firmwareVersionPattern) && + isNotEmptyString(this.stationInfo.firmwareVersion) ) { - const patternGroup: number | undefined = + const patternGroup = this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ?? - this.stationInfo.firmwareVersion?.split('.').length; - const match = this.stationInfo - .firmwareVersion!.match(new RegExp(this.stationInfo.firmwareVersionPattern!))! - .slice(1, patternGroup! + 1); - const patchLevelIndex = match.length - 1; - match[patchLevelIndex] = ( - convertToInt(match[patchLevelIndex]) + - this.stationInfo.firmwareUpgrade!.versionUpgrade!.step! - ).toString(); - this.stationInfo.firmwareVersion = match?.join('.'); - } - this.saveStationInfo(); - this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl(); - if (this.stationInfo?.enableStatistics === true) { + this.stationInfo.firmwareVersion.split('.').length + const match = new RegExp(this.stationInfo.firmwareVersionPattern) + .exec(this.stationInfo.firmwareVersion) + ?.slice(1, patternGroup + 1) + if (match != null) { + const patchLevelIndex = match.length - 1 + match[patchLevelIndex] = ( + convertToInt(match[patchLevelIndex]) + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.stationInfo.firmwareUpgrade!.versionUpgrade!.step! + ).toString() + this.stationInfo.firmwareVersion = match.join('.') + } + } + this.saveStationInfo() + this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl() + if (this.stationInfo.enableStatistics === true) { this.performanceStatistics = PerformanceStatistics.getInstance( this.stationInfo.hashId, - this.stationInfo.chargingStationId!, - this.configuredSupervisionUrl, - ); + this.stationInfo.chargingStationId, + this.configuredSupervisionUrl + ) + } + const bootNotificationRequest = createBootNotificationRequest(this.stationInfo) + if (bootNotificationRequest == null) { + const errorMsg = 'Error while creating boot notification request' + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } - this.bootNotificationRequest = createBootNotificationRequest(this.stationInfo); - this.powerDivider = this.getPowerDivider(); + this.bootNotificationRequest = bootNotificationRequest + this.powerDivider = this.getPowerDivider() // OCPP configuration - this.ocppConfiguration = this.getOcppConfiguration(); - this.initializeOcppConfiguration(); - this.initializeOcppServices(); - this.once(ChargingStationEvents.accepted, () => { - this.startMessageSequence().catch((error) => { - logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error); - }); - }); - if (this.stationInfo?.autoRegister === true) { + this.ocppConfiguration = this.getOcppConfiguration(options?.persistentConfiguration) + this.initializeOcppConfiguration() + this.initializeOcppServices() + if (this.stationInfo.autoRegister === true) { this.bootNotificationResponse = { currentTime: new Date(), interval: millisecondsToSeconds(this.getHeartbeatInterval()), - status: RegistrationStatusEnumType.ACCEPTED, - }; + status: RegistrationStatusEnumType.ACCEPTED + } } } - private initializeOcppServices(): void { - const ocppVersion = this.stationInfo?.ocppVersion; + private initializeOcppServices (): void { + const ocppVersion = this.stationInfo?.ocppVersion switch (ocppVersion) { case OCPPVersion.VERSION_16: this.ocppIncomingRequestService = - OCPP16IncomingRequestService.getInstance(); + OCPP16IncomingRequestService.getInstance() this.ocppRequestService = OCPP16RequestService.getInstance( - OCPP16ResponseService.getInstance(), - ); - break; + OCPP16ResponseService.getInstance() + ) + break case OCPPVersion.VERSION_20: case OCPPVersion.VERSION_201: this.ocppIncomingRequestService = - OCPP20IncomingRequestService.getInstance(); + OCPP20IncomingRequestService.getInstance() this.ocppRequestService = OCPP20RequestService.getInstance( - OCPP20ResponseService.getInstance(), - ); - break; + OCPP20ResponseService.getInstance() + ) + break default: - this.handleUnsupportedVersion(ocppVersion); - break; + this.handleUnsupportedVersion(ocppVersion) + break } } - private initializeOcppConfiguration(): void { - if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartbeatInterval))) { - addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0'); + private initializeOcppConfiguration (): void { + if (getConfigurationKey(this, StandardParametersKey.HeartbeatInterval) == null) { + addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0') } - if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.HeartBeatInterval))) { - addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false }); + if (getConfigurationKey(this, StandardParametersKey.HeartBeatInterval) == null) { + addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false }) } if ( this.stationInfo?.supervisionUrlOcppConfiguration === true && - isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) && - isNullOrUndefined(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)) + isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) && + getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) == null ) { addConfigurationKey( this, - this.stationInfo.supervisionUrlOcppKey!, + this.stationInfo.supervisionUrlOcppKey, this.configuredSupervisionUrl.href, - { reboot: true }, - ); + { reboot: true } + ) } else if ( this.stationInfo?.supervisionUrlOcppConfiguration === false && - isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) && - !isNullOrUndefined(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)) + isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) && + getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) != null ) { - deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!, { save: false }); + deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey, { save: false }) } if ( isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && - isNullOrUndefined(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)) + getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) == null ) { addConfigurationKey( this, - this.stationInfo.amperageLimitationOcppKey!, - ( - this.stationInfo.maximumAmperage! * getAmperageLimitationUnitDivider(this.stationInfo) - ).toString(), - ); + this.stationInfo.amperageLimitationOcppKey, + // prettier-ignore + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (this.stationInfo.maximumAmperage! * getAmperageLimitationUnitDivider(this.stationInfo)).toString() + ) } - if ( - isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)) - ) { + if (getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles) == null) { addConfigurationKey( this, StandardParametersKey.SupportedFeatureProfiles, - `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`, - ); + `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}` + ) } addConfigurationKey( this, StandardParametersKey.NumberOfConnectors, this.getNumberOfConnectors().toString(), { readonly: true }, - { overwrite: true }, - ); - if ( - isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData)) - ) { + { overwrite: true } + ) + if (getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData) == null) { addConfigurationKey( this, StandardParametersKey.MeterValuesSampledData, - MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, - ); + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER + ) } - if ( - isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation)) - ) { - const connectorsPhaseRotation: string[] = []; + if (getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation) == null) { + const connectorsPhaseRotation: string[] = [] if (this.hasEvses) { for (const evseStatus of this.evses.values()) { for (const connectorId of evseStatus.connectors.keys()) { connectorsPhaseRotation.push( - getPhaseRotationValue(connectorId, this.getNumberOfPhases())!, - ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + getPhaseRotationValue(connectorId, this.getNumberOfPhases())! + ) } } } else { for (const connectorId of this.connectors.keys()) { connectorsPhaseRotation.push( - getPhaseRotationValue(connectorId, this.getNumberOfPhases())!, - ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + getPhaseRotationValue(connectorId, this.getNumberOfPhases())! + ) } } addConfigurationKey( this, StandardParametersKey.ConnectorPhaseRotation, - connectorsPhaseRotation.toString(), - ); + connectorsPhaseRotation.toString() + ) } - if ( - isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests)) - ) { - addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true'); + if (getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests) == null) { + addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true') } if ( - isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled)) && - getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles)?.value?.includes( - SupportedFeatureProfiles.LocalAuthListManagement, - ) + getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) == null && + hasFeatureProfile(this, SupportedFeatureProfiles.LocalAuthListManagement) === true ) { - addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false'); + addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false') } - if (isNullOrUndefined(getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut))) { + if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) == null) { addConfigurationKey( this, StandardParametersKey.ConnectionTimeOut, - Constants.DEFAULT_CONNECTION_TIMEOUT.toString(), - ); + Constants.DEFAULT_CONNECTION_TIMEOUT.toString() + ) } - this.saveOcppConfiguration(); + this.saveOcppConfiguration() } - private initializeConnectorsOrEvsesFromFile(configuration: ChargingStationConfiguration): void { - if (configuration?.connectorsStatus && !configuration?.evsesStatus) { + private initializeConnectorsOrEvsesFromFile (configuration: ChargingStationConfiguration): void { + if (configuration.connectorsStatus != null && configuration.evsesStatus == null) { for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) { - this.connectors.set(connectorId, cloneObject(connectorStatus)); + this.connectors.set(connectorId, clone(connectorStatus)) } - } else if (configuration?.evsesStatus && !configuration?.connectorsStatus) { + } else if (configuration.evsesStatus != null && configuration.connectorsStatus == null) { for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) { - const evseStatus = cloneObject(evseStatusConfiguration); - delete evseStatus.connectorsStatus; + const evseStatus = clone(evseStatusConfiguration) + delete evseStatus.connectorsStatus this.evses.set(evseId, { ...(evseStatus as EvseStatus), connectors: new Map( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion evseStatusConfiguration.connectorsStatus!.map((connectorStatus, connectorId) => [ connectorId, - connectorStatus, - ]), - ), - }); + connectorStatus + ]) + ) + }) } - } else if (configuration?.evsesStatus && configuration?.connectorsStatus) { - const errorMsg = `Connectors and evses defined at the same time in configuration file ${this.configurationFile}`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); + } else if (configuration.evsesStatus != null && configuration.connectorsStatus != null) { + const errorMsg = `Connectors and evses defined at the same time in configuration file ${this.configurationFile}` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } else { - const errorMsg = `No connectors or evses defined in configuration file ${this.configurationFile}`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); + const errorMsg = `No connectors or evses defined in configuration file ${this.configurationFile}` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } } - private initializeConnectorsOrEvsesFromTemplate(stationTemplate: ChargingStationTemplate) { - if (stationTemplate?.Connectors && !stationTemplate?.Evses) { - this.initializeConnectorsFromTemplate(stationTemplate); - } else if (stationTemplate?.Evses && !stationTemplate?.Connectors) { - this.initializeEvsesFromTemplate(stationTemplate); - } else if (stationTemplate?.Evses && stationTemplate?.Connectors) { - const errorMsg = `Connectors and evses defined at the same time in template file ${this.templateFile}`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); + private initializeConnectorsOrEvsesFromTemplate (stationTemplate: ChargingStationTemplate): void { + if (stationTemplate.Connectors != null && stationTemplate.Evses == null) { + this.initializeConnectorsFromTemplate(stationTemplate) + } else if (stationTemplate.Evses != null && stationTemplate.Connectors == null) { + this.initializeEvsesFromTemplate(stationTemplate) + } else if (stationTemplate.Evses != null && stationTemplate.Connectors != null) { + const errorMsg = `Connectors and evses defined at the same time in template file ${this.templateFile}` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } else { - const errorMsg = `No connectors or evses defined in template file ${this.templateFile}`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); + const errorMsg = `No connectors or evses defined in template file ${this.templateFile}` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } } - private initializeConnectorsFromTemplate(stationTemplate: ChargingStationTemplate): void { - if (!stationTemplate?.Connectors && this.connectors.size === 0) { - const errorMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); + private initializeConnectorsFromTemplate (stationTemplate: ChargingStationTemplate): void { + if (stationTemplate.Connectors == null && this.connectors.size === 0) { + const errorMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } - if (!stationTemplate?.Connectors?.[0]) { + if (stationTemplate.Connectors?.[0] == null) { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with no connector id 0 configuration`, - ); + } with no connector id 0 configuration` + ) } - if (stationTemplate?.Connectors) { + if (stationTemplate.Connectors != null) { const { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors } = - checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile); + checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile) const connectorsConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) .update( - `${JSON.stringify(stationTemplate?.Connectors)}${configuredMaxConnectors.toString()}`, + `${JSON.stringify(stationTemplate.Connectors)}${configuredMaxConnectors.toString()}` ) - .digest('hex'); + .digest('hex') const connectorsConfigChanged = - this.connectors?.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash; - if (this.connectors?.size === 0 || connectorsConfigChanged) { - connectorsConfigChanged && this.connectors.clear(); - this.connectorsConfigurationHash = connectorsConfigHash; + this.connectors.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash + if (this.connectors.size === 0 || connectorsConfigChanged) { + connectorsConfigChanged && this.connectors.clear() + this.connectorsConfigurationHash = connectorsConfigHash if (templateMaxConnectors > 0) { for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) { if ( connectorId === 0 && - (!stationTemplate?.Connectors?.[connectorId] || - this.getUseConnectorId0(stationTemplate) === false) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + (stationTemplate.Connectors[connectorId] == null || + !this.getUseConnectorId0(stationTemplate)) ) { - continue; + continue } const templateConnectorId = - connectorId > 0 && stationTemplate?.randomConnectors - ? getRandomInteger(templateMaxAvailableConnectors, 1) - : connectorId; - const connectorStatus = stationTemplate?.Connectors[templateConnectorId]; + connectorId > 0 && stationTemplate.randomConnectors === true + ? randomInt(1, templateMaxAvailableConnectors) + : connectorId + const connectorStatus = stationTemplate.Connectors[templateConnectorId] checkStationInfoConnectorStatus( templateConnectorId, connectorStatus, this.logPrefix(), - this.templateFile, - ); - this.connectors.set(connectorId, cloneObject(connectorStatus)); + this.templateFile + ) + this.connectors.set(connectorId, clone(connectorStatus)) } - initializeConnectorsMapStatus(this.connectors, this.logPrefix()); - this.saveConnectorsStatus(); + initializeConnectorsMapStatus(this.connectors, this.logPrefix()) + this.saveConnectorsStatus() } else { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with no connectors configuration defined, cannot create connectors`, - ); + } with no connectors configuration defined, cannot create connectors` + ) } } } else { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with no connectors configuration defined, using already defined connectors`, - ); + } with no connectors configuration defined, using already defined connectors` + ) } } - private initializeEvsesFromTemplate(stationTemplate: ChargingStationTemplate): void { - if (!stationTemplate?.Evses && this.evses.size === 0) { - const errorMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(errorMsg); + private initializeEvsesFromTemplate (stationTemplate: ChargingStationTemplate): void { + if (stationTemplate.Evses == null && this.evses.size === 0) { + const errorMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } - if (!stationTemplate?.Evses?.[0]) { + if (stationTemplate.Evses?.[0] == null) { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with no evse id 0 configuration`, - ); + } with no evse id 0 configuration` + ) } - if (!stationTemplate?.Evses?.[0]?.Connectors?.[0]) { + if (stationTemplate.Evses?.[0]?.Connectors[0] == null) { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with evse id 0 with no connector id 0 configuration`, - ); + } with evse id 0 with no connector id 0 configuration` + ) } - if (Object.keys(stationTemplate?.Evses?.[0]?.Connectors as object).length > 1) { + if (Object.keys(stationTemplate.Evses?.[0]?.Connectors as object).length > 1) { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with evse id 0 with more than one connector configuration, only connector id 0 configuration will be used`, - ); + } with evse id 0 with more than one connector configuration, only connector id 0 configuration will be used` + ) } - if (stationTemplate?.Evses) { + if (stationTemplate.Evses != null) { const evsesConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) - .update(JSON.stringify(stationTemplate?.Evses)) - .digest('hex'); + .update(JSON.stringify(stationTemplate.Evses)) + .digest('hex') const evsesConfigChanged = - this.evses?.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash; - if (this.evses?.size === 0 || evsesConfigChanged) { - evsesConfigChanged && this.evses.clear(); - this.evsesConfigurationHash = evsesConfigHash; - const templateMaxEvses = getMaxNumberOfEvses(stationTemplate?.Evses); + this.evses.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash + if (this.evses.size === 0 || evsesConfigChanged) { + evsesConfigChanged && this.evses.clear() + this.evsesConfigurationHash = evsesConfigHash + const templateMaxEvses = getMaxNumberOfEvses(stationTemplate.Evses) if (templateMaxEvses > 0) { for (const evseKey in stationTemplate.Evses) { - const evseId = convertToInt(evseKey); + const evseId = convertToInt(evseKey) this.evses.set(evseId, { connectors: buildConnectorsMap( - stationTemplate?.Evses[evseKey]?.Connectors, + stationTemplate.Evses[evseKey].Connectors, this.logPrefix(), - this.templateFile, + this.templateFile ), - availability: AvailabilityType.Operative, - }); - initializeConnectorsMapStatus(this.evses.get(evseId)!.connectors, this.logPrefix()); + availability: AvailabilityType.Operative + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + initializeConnectorsMapStatus(this.evses.get(evseId)!.connectors, this.logPrefix()) } - this.saveEvsesStatus(); + this.saveEvsesStatus() } else { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with no evses configuration defined, cannot create evses`, - ); + } with no evses configuration defined, cannot create evses` + ) } } } else { logger.warn( `${this.logPrefix()} Charging station information from template ${ this.templateFile - } with no evses configuration defined, using already defined evses`, - ); + } with no evses configuration defined, using already defined evses` + ) } } - private getConfigurationFromFile(): ChargingStationConfiguration | undefined { - let configuration: ChargingStationConfiguration | undefined; + private getConfigurationFromFile (): ChargingStationConfiguration | undefined { + let configuration: ChargingStationConfiguration | undefined if (isNotEmptyString(this.configurationFile) && existsSync(this.configurationFile)) { try { if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) { configuration = this.sharedLRUCache.getChargingStationConfiguration( - this.configurationFileHash, - ); + this.configurationFileHash + ) } else { - const measureId = `${FileType.ChargingStationConfiguration} read`; - const beginId = PerformanceStatistics.beginMeasure(measureId); + const measureId = `${FileType.ChargingStationConfiguration} read` + const beginId = PerformanceStatistics.beginMeasure(measureId) configuration = JSON.parse( - readFileSync(this.configurationFile, 'utf8'), - ) as ChargingStationConfiguration; - PerformanceStatistics.endMeasure(measureId, beginId); - this.sharedLRUCache.setChargingStationConfiguration(configuration); - this.configurationFileHash = configuration.configurationHash!; + readFileSync(this.configurationFile, 'utf8') + ) as ChargingStationConfiguration + PerformanceStatistics.endMeasure(measureId, beginId) + this.sharedLRUCache.setChargingStationConfiguration(configuration) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.configurationFileHash = configuration.configurationHash! } } catch (error) { handleFileException( this.configurationFile, FileType.ChargingStationConfiguration, error as NodeJS.ErrnoException, - this.logPrefix(), - ); + this.logPrefix() + ) } } - return configuration; + return configuration } - private saveAutomaticTransactionGeneratorConfiguration(): void { + private saveAutomaticTransactionGeneratorConfiguration (): void { if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) { - this.saveConfiguration(); + this.saveConfiguration() } } - private saveConnectorsStatus() { - this.saveConfiguration(); + private saveConnectorsStatus (): void { + this.saveConfiguration() } - private saveEvsesStatus() { - this.saveConfiguration(); + private saveEvsesStatus (): void { + this.saveConfiguration() } - private saveConfiguration(): void { + private saveConfiguration (): void { if (isNotEmptyString(this.configurationFile)) { try { if (!existsSync(dirname(this.configurationFile))) { - mkdirSync(dirname(this.configurationFile), { recursive: true }); + mkdirSync(dirname(this.configurationFile), { recursive: true }) } - let configurationData: ChargingStationConfiguration = this.getConfigurationFromFile() - ? cloneObject(this.getConfigurationFromFile()!) - : {}; - if (this.stationInfo?.stationInfoPersistentConfiguration === true && this.stationInfo) { - configurationData.stationInfo = this.stationInfo; + const configurationFromFile = this.getConfigurationFromFile() + let configurationData: ChargingStationConfiguration = + configurationFromFile != null + ? clone(configurationFromFile) + : {} + if (this.stationInfo?.stationInfoPersistentConfiguration === true) { + configurationData.stationInfo = this.stationInfo } else { - delete configurationData.stationInfo; + delete configurationData.stationInfo } if ( this.stationInfo?.ocppPersistentConfiguration === true && Array.isArray(this.ocppConfiguration?.configurationKey) ) { - configurationData.configurationKey = this.ocppConfiguration?.configurationKey; + configurationData.configurationKey = this.ocppConfiguration.configurationKey } else { - delete configurationData.configurationKey; + delete configurationData.configurationKey } - configurationData = merge( + configurationData = mergeDeepRight( configurationData, - buildChargingStationAutomaticTransactionGeneratorConfiguration(this), - ); - if ( - !this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration || - !this.getAutomaticTransactionGeneratorConfiguration() - ) { - delete configurationData.automaticTransactionGenerator; + buildChargingStationAutomaticTransactionGeneratorConfiguration(this) + ) + if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration !== true) { + delete configurationData.automaticTransactionGenerator } if (this.connectors.size > 0) { - configurationData.connectorsStatus = buildConnectorsStatus(this); + configurationData.connectorsStatus = buildConnectorsStatus(this) } else { - delete configurationData.connectorsStatus; + delete configurationData.connectorsStatus } if (this.evses.size > 0) { - configurationData.evsesStatus = buildEvsesStatus(this); + configurationData.evsesStatus = buildEvsesStatus(this) } else { - delete configurationData.evsesStatus; + delete configurationData.evsesStatus } - delete configurationData.configurationHash; + delete configurationData.configurationHash const configurationHash = createHash(Constants.DEFAULT_HASH_ALGORITHM) .update( JSON.stringify({ @@ -1673,259 +1733,290 @@ export class ChargingStation extends EventEmitter { configurationKey: configurationData.configurationKey, automaticTransactionGenerator: configurationData.automaticTransactionGenerator, ...(this.connectors.size > 0 && { - connectorsStatus: configurationData.connectorsStatus, + connectorsStatus: configurationData.connectorsStatus }), - ...(this.evses.size > 0 && { evsesStatus: configurationData.evsesStatus }), - } as ChargingStationConfiguration), + ...(this.evses.size > 0 && { evsesStatus: configurationData.evsesStatus }) + } satisfies ChargingStationConfiguration) ) - .digest('hex'); + .digest('hex') if (this.configurationFileHash !== configurationHash) { AsyncLock.runExclusive(AsyncLockType.configuration, () => { - configurationData.configurationHash = configurationHash; - const measureId = `${FileType.ChargingStationConfiguration} write`; - const beginId = PerformanceStatistics.beginMeasure(measureId); + configurationData.configurationHash = configurationHash + const measureId = `${FileType.ChargingStationConfiguration} write` + const beginId = PerformanceStatistics.beginMeasure(measureId) writeFileSync( this.configurationFile, JSON.stringify(configurationData, undefined, 2), - 'utf8', - ); - PerformanceStatistics.endMeasure(measureId, beginId); - this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash); - this.sharedLRUCache.setChargingStationConfiguration(configurationData); - this.configurationFileHash = configurationHash; - }).catch((error) => { + 'utf8' + ) + PerformanceStatistics.endMeasure(measureId, beginId) + this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash) + this.sharedLRUCache.setChargingStationConfiguration(configurationData) + this.configurationFileHash = configurationHash + }).catch((error: unknown) => { handleFileException( this.configurationFile, FileType.ChargingStationConfiguration, error as NodeJS.ErrnoException, - this.logPrefix(), - ); - }); + this.logPrefix() + ) + }) } else { logger.debug( `${this.logPrefix()} Not saving unchanged charging station configuration file ${ this.configurationFile - }`, - ); + }` + ) } } catch (error) { handleFileException( this.configurationFile, FileType.ChargingStationConfiguration, error as NodeJS.ErrnoException, - this.logPrefix(), - ); + this.logPrefix() + ) } } else { logger.error( - `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`, - ); + `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file` + ) } } - private getOcppConfigurationFromTemplate(): ChargingStationOcppConfiguration | undefined { - return this.getTemplateFromFile()?.Configuration; + private getOcppConfigurationFromTemplate (): ChargingStationOcppConfiguration | undefined { + return this.getTemplateFromFile()?.Configuration } - private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined { - const configurationKey = this.getConfigurationFromFile()?.configurationKey; - if (this.stationInfo?.ocppPersistentConfiguration === true && Array.isArray(configurationKey)) { - return { configurationKey }; + private getOcppConfigurationFromFile ( + ocppPersistentConfiguration?: boolean + ): ChargingStationOcppConfiguration | undefined { + const configurationKey = this.getConfigurationFromFile()?.configurationKey + if (ocppPersistentConfiguration === true && Array.isArray(configurationKey)) { + return { configurationKey } } - return undefined; + return undefined } - private getOcppConfiguration(): ChargingStationOcppConfiguration | undefined { + private getOcppConfiguration ( + ocppPersistentConfiguration: boolean | undefined = this.stationInfo?.ocppPersistentConfiguration + ): ChargingStationOcppConfiguration | undefined { let ocppConfiguration: ChargingStationOcppConfiguration | undefined = - this.getOcppConfigurationFromFile(); - if (!ocppConfiguration) { - ocppConfiguration = this.getOcppConfigurationFromTemplate(); + this.getOcppConfigurationFromFile(ocppPersistentConfiguration) + if (ocppConfiguration == null) { + ocppConfiguration = this.getOcppConfigurationFromTemplate() } - return ocppConfiguration; + return ocppConfiguration } - private async onOpen(): Promise { - if (this.isWebSocketConnectionOpened() === true) { + private async onOpen (): Promise { + if (this.isWebSocketConnectionOpened()) { + this.emit(ChargingStationEvents.updated) logger.info( - `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`, - ); - let registrationRetryCount = 0; - if (this.isRegistered() === false) { + `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} succeeded` + ) + let registrationRetryCount = 0 + if (!this.isRegistered()) { // Send BootNotification do { this.bootNotificationResponse = await this.ocppRequestService.requestHandler< - BootNotificationRequest, - BootNotificationResponse + BootNotificationRequest, + BootNotificationResponse >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, { - skipBufferingOnError: true, - }); - if (this.isRegistered() === false) { - this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount; + skipBufferingOnError: true + }) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (this.bootNotificationResponse?.currentTime != null) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.bootNotificationResponse.currentTime = convertToDate( + this.bootNotificationResponse.currentTime + )! + } + if (!this.isRegistered()) { + this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount await sleep( - this?.bootNotificationResponse?.interval + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + this.bootNotificationResponse?.interval != null ? secondsToMilliseconds(this.bootNotificationResponse.interval) - : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL, - ); + : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL + ) } } while ( - this.isRegistered() === false && - (registrationRetryCount <= this.stationInfo.registrationMaxRetries! || + !this.isRegistered() && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (registrationRetryCount <= this.stationInfo!.registrationMaxRetries! || this.stationInfo?.registrationMaxRetries === -1) - ); + ) } - if (this.isRegistered() === true) { - this.emit(ChargingStationEvents.registered); - if (this.inAcceptedState() === true) { - this.emit(ChargingStationEvents.accepted); + if (this.isRegistered()) { + this.emit(ChargingStationEvents.registered) + if (this.inAcceptedState()) { + this.emit(ChargingStationEvents.accepted) } } else { + if (this.inRejectedState()) { + this.emit(ChargingStationEvents.rejected) + } logger.error( - `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount}) or retry disabled (${this - .stationInfo?.registrationMaxRetries})`, - ); + `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount}) or retry disabled (${ + this.stationInfo?.registrationMaxRetries + })` + ) } - this.autoReconnectRetryCount = 0; - this.emit(ChargingStationEvents.updated); + this.wsConnectionRetryCount = 0 + this.emit(ChargingStationEvents.updated) } else { logger.warn( - `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`, - ); + `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} failed` + ) } } - private async onClose(code: WebSocketCloseEventStatusCode, reason: Buffer): Promise { + private onClose (code: WebSocketCloseEventStatusCode, reason: Buffer): void { + this.emit(ChargingStationEvents.disconnected) + this.emit(ChargingStationEvents.updated) switch (code) { // Normal close case WebSocketCloseEventStatusCode.CLOSE_NORMAL: case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS: logger.info( `${this.logPrefix()} WebSocket normally closed with status '${getWebSocketCloseEventStatusString( - code, - )}' and reason '${reason.toString()}'`, - ); - this.autoReconnectRetryCount = 0; - break; + code + )}' and reason '${reason.toString()}'` + ) + this.wsConnectionRetryCount = 0 + break // Abnormal close default: logger.error( `${this.logPrefix()} WebSocket abnormally closed with status '${getWebSocketCloseEventStatusString( - code, - )}' and reason '${reason.toString()}'`, - ); - this.started === true && (await this.reconnect()); - break; + code + )}' and reason '${reason.toString()}'` + ) + this.started && + this.reconnect() + .then(() => { + this.emit(ChargingStationEvents.updated) + }) + .catch((error: unknown) => + logger.error(`${this.logPrefix()} Error while reconnecting:`, error) + ) + break } - this.emit(ChargingStationEvents.updated); } - private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined { - const cachedRequest = this.requests.get(messageId); - if (Array.isArray(cachedRequest) === true) { - return cachedRequest; + private getCachedRequest ( + messageType: MessageType | undefined, + messageId: string + ): CachedRequest | undefined { + const cachedRequest = this.requests.get(messageId) + if (Array.isArray(cachedRequest)) { + return cachedRequest } throw new OCPPError( ErrorType.PROTOCOL_ERROR, `Cached request for message id ${messageId} ${getMessageTypeString( - messageType, + messageType )} is not an array`, undefined, - cachedRequest as JsonType, - ); + cachedRequest + ) } - private async handleIncomingMessage(request: IncomingRequest): Promise { - const [messageType, messageId, commandName, commandPayload] = request; + private async handleIncomingMessage (request: IncomingRequest): Promise { + const [messageType, messageId, commandName, commandPayload] = request if (this.stationInfo?.enableStatistics === true) { - this.performanceStatistics?.addRequestStatistic(commandName, messageType); + this.performanceStatistics?.addRequestStatistic(commandName, messageType) } logger.debug( `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify( - request, - )}`, - ); + request + )}` + ) // Process the message await this.ocppIncomingRequestService.incomingRequestHandler( this, messageId, commandName, - commandPayload, - ); - this.emit(ChargingStationEvents.updated); + commandPayload + ) + this.emit(ChargingStationEvents.updated) } - private handleResponseMessage(response: Response): void { - const [messageType, messageId, commandPayload] = response; - if (this.requests.has(messageId) === false) { + private handleResponseMessage (response: Response): void { + const [messageType, messageId, commandPayload] = response + if (!this.requests.has(messageId)) { // Error throw new OCPPError( ErrorType.INTERNAL_ERROR, `Response for unknown message id ${messageId}`, undefined, - commandPayload, - ); + commandPayload + ) } // Respond + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest( messageType, - messageId, - )!; + messageId + )! logger.debug( - `${this.logPrefix()} << Command '${ - requestCommandName ?? Constants.UNKNOWN_COMMAND - }' received response payload: ${JSON.stringify(response)}`, - ); - responseCallback(commandPayload, requestPayload); + `${this.logPrefix()} << Command '${requestCommandName}' received response payload: ${JSON.stringify( + response + )}` + ) + responseCallback(commandPayload, requestPayload) } - private handleErrorMessage(errorResponse: ErrorResponse): void { - const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse; - if (this.requests.has(messageId) === false) { + private handleErrorMessage (errorResponse: ErrorResponse): void { + const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse + if (!this.requests.has(messageId)) { // Error throw new OCPPError( ErrorType.INTERNAL_ERROR, `Error response for unknown message id ${messageId}`, undefined, - { errorType, errorMessage, errorDetails }, - ); + { errorType, errorMessage, errorDetails } + ) } - const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)! logger.debug( - `${this.logPrefix()} << Command '${ - requestCommandName ?? Constants.UNKNOWN_COMMAND - }' received error response payload: ${JSON.stringify(errorResponse)}`, - ); - errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails)); + `${this.logPrefix()} << Command '${requestCommandName}' received error response payload: ${JSON.stringify( + errorResponse + )}` + ) + errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails)) } - private async onMessage(data: RawData): Promise { - let request: IncomingRequest | Response | ErrorResponse | undefined; - let messageType: MessageType | undefined; - let errorMsg: string; + private async onMessage (data: RawData): Promise { + let request: IncomingRequest | Response | ErrorResponse | undefined + let messageType: MessageType | undefined + let errorMsg: string try { // eslint-disable-next-line @typescript-eslint/no-base-to-string - request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse; - if (Array.isArray(request) === true) { - [messageType] = request; + request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse + if (Array.isArray(request)) { + [messageType] = request // Check the type of message switch (messageType) { // Incoming Message case MessageType.CALL_MESSAGE: - await this.handleIncomingMessage(request as IncomingRequest); - break; + await this.handleIncomingMessage(request as IncomingRequest) + break // Response Message case MessageType.CALL_RESULT_MESSAGE: - this.handleResponseMessage(request as Response); - break; + this.handleResponseMessage(request as Response) + break // Error Message case MessageType.CALL_ERROR_MESSAGE: - this.handleErrorMessage(request as ErrorResponse); - break; + this.handleErrorMessage(request as ErrorResponse) + break // Unknown Message default: // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - errorMsg = `Wrong message type ${messageType}`; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg); + errorMsg = `Wrong message type ${messageType}` + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg) } } else { throw new OCPPError( @@ -1933,405 +2024,414 @@ export class ChargingStation extends EventEmitter { 'Incoming message is not an array', undefined, { - request, - }, - ); + request + } + ) } } catch (error) { - let commandName: IncomingRequestCommand | undefined; - let requestCommandName: RequestCommand | IncomingRequestCommand | undefined; - let errorCallback: ErrorCallback; - const [, messageId] = request!; + if (!Array.isArray(request)) { + logger.error(`${this.logPrefix()} Incoming message '${request}' parsing error:`, error) + return + } + let commandName: IncomingRequestCommand | undefined + let requestCommandName: RequestCommand | IncomingRequestCommand | undefined + let errorCallback: ErrorCallback + const [, messageId] = request switch (messageType) { case MessageType.CALL_MESSAGE: - [, , commandName] = request as IncomingRequest; + [, , commandName] = request as IncomingRequest // Send error - await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName); - break; + await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName) + break case MessageType.CALL_RESULT_MESSAGE: case MessageType.CALL_ERROR_MESSAGE: - if (this.requests.has(messageId) === true) { - [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!; + if (this.requests.has(messageId)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)! // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op) - errorCallback(error as OCPPError, false); + errorCallback(error as OCPPError, false) } else { // Remove the request from the cache in case of error at response handling - this.requests.delete(messageId); + this.requests.delete(messageId) } - break; + break } - if (error instanceof OCPPError === false) { + if (!(error instanceof OCPPError)) { logger.warn( `${this.logPrefix()} Error thrown at incoming OCPP command '${ - commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND + commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND // eslint-disable-next-line @typescript-eslint/no-base-to-string }' message '${data.toString()}' handling is not an OCPPError:`, - error, - ); + error + ) } logger.error( `${this.logPrefix()} Incoming OCPP command '${ - commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND + commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND // eslint-disable-next-line @typescript-eslint/no-base-to-string }' message '${data.toString()}'${ - messageType !== MessageType.CALL_MESSAGE - ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'` + this.requests.has(messageId) + ? ` matching cached request '${JSON.stringify(this.getCachedRequest(messageType, messageId))}'` : '' } processing error:`, - error, - ); + error + ) } } - private onPing(): void { - logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`); + private onPing (): void { + logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`) } - private onPong(): void { - logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`); + private onPong (): void { + logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`) } - private onError(error: WSError): void { - this.closeWSConnection(); - logger.error(`${this.logPrefix()} WebSocket error:`, error); + private onError (error: WSError): void { + this.closeWSConnection() + logger.error(`${this.logPrefix()} WebSocket error:`, error) } - private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number { + private getEnergyActiveImportRegister ( + connectorStatus: ConnectorStatus | undefined, + rounded = false + ): number { if (this.stationInfo?.meteringPerTransaction === true) { return ( - (rounded === true - ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue!) + (rounded + ? connectorStatus?.transactionEnergyActiveImportRegisterValue != null + ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue) + : undefined : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0 - ); + ) } return ( - (rounded === true - ? Math.round(connectorStatus.energyActiveImportRegisterValue!) + (rounded + ? connectorStatus?.energyActiveImportRegisterValue != null + ? Math.round(connectorStatus.energyActiveImportRegisterValue) + : undefined : connectorStatus?.energyActiveImportRegisterValue) ?? 0 - ); + ) } - private getUseConnectorId0(stationTemplate?: ChargingStationTemplate): boolean { - return stationTemplate?.useConnectorId0 ?? true; + private getUseConnectorId0 (stationTemplate?: ChargingStationTemplate): boolean { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return stationTemplate?.useConnectorId0 ?? Constants.DEFAULT_STATION_INFO.useConnectorId0! } - private async stopRunningTransactions(reason?: StopTransactionReason): Promise { + private async stopRunningTransactions (reason?: StopTransactionReason): Promise { if (this.hasEvses) { for (const [evseId, evseStatus] of this.evses) { if (evseId === 0) { - continue; + continue } for (const [connectorId, connectorStatus] of evseStatus.connectors) { if (connectorStatus.transactionStarted === true) { - await this.stopTransactionOnConnector(connectorId, reason); + await this.stopTransactionOnConnector(connectorId, reason) } } } } else { for (const connectorId of this.connectors.keys()) { if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) { - await this.stopTransactionOnConnector(connectorId, reason); + await this.stopTransactionOnConnector(connectorId, reason) } } } } // 0 for disabling - private getConnectionTimeout(): number { - if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) !== undefined) { + private getConnectionTimeout (): number { + if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) != null) { return convertToInt( - getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)!.value! ?? - Constants.DEFAULT_CONNECTION_TIMEOUT, - ); + getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)?.value ?? + Constants.DEFAULT_CONNECTION_TIMEOUT + ) } - return Constants.DEFAULT_CONNECTION_TIMEOUT; + return Constants.DEFAULT_CONNECTION_TIMEOUT } - private getPowerDivider(): number { - let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors(); + private getPowerDivider (): number { + let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors() if (this.stationInfo?.powerSharedByConnectors === true) { - powerDivider = this.getNumberOfRunningTransactions(); + powerDivider = this.getNumberOfRunningTransactions() } - return powerDivider; + return powerDivider } - private getMaximumAmperage(stationInfo?: ChargingStationInfo): number | undefined { - const maximumPower = (stationInfo ?? this.stationInfo).maximumPower!; + private getMaximumAmperage (stationInfo?: ChargingStationInfo): number | undefined { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const maximumPower = (stationInfo ?? this.stationInfo!).maximumPower! switch (this.getCurrentOutType(stationInfo)) { case CurrentType.AC: return ACElectricUtils.amperagePerPhaseFromPower( this.getNumberOfPhases(stationInfo), maximumPower / (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()), - this.getVoltageOut(stationInfo), - ); + this.getVoltageOut(stationInfo) + ) case CurrentType.DC: - return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo)); + return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo)) } } - private getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType { - return (stationInfo ?? this.stationInfo).currentOutType ?? CurrentType.AC; + private getCurrentOutType (stationInfo?: ChargingStationInfo): CurrentType { + return ( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (stationInfo ?? this.stationInfo!).currentOutType ?? + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + Constants.DEFAULT_STATION_INFO.currentOutType! + ) } - private getVoltageOut(stationInfo?: ChargingStationInfo): Voltage { + private getVoltageOut (stationInfo?: ChargingStationInfo): Voltage { return ( - (stationInfo ?? this.stationInfo).voltageOut ?? + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (stationInfo ?? this.stationInfo!).voltageOut ?? getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile) - ); + ) } - private getAmperageLimitation(): number | undefined { + private getAmperageLimitation (): number | undefined { if ( isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) && - getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!) !== undefined + getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) != null ) { return ( - convertToInt( - getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey!)?.value, - ) / getAmperageLimitationUnitDivider(this.stationInfo) - ); + convertToInt(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey)?.value) / + getAmperageLimitationUnitDivider(this.stationInfo) + ) } } - private async startMessageSequence(): Promise { + private async startMessageSequence (ATGStopAbsoluteDuration?: boolean): Promise { if (this.stationInfo?.autoRegister === true) { await this.ocppRequestService.requestHandler< - BootNotificationRequest, - BootNotificationResponse + BootNotificationRequest, + BootNotificationResponse >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, { - skipBufferingOnError: true, - }); + skipBufferingOnError: true + }) } // Start WebSocket ping - this.startWebSocketPing(); + this.startWebSocketPing() // Start heartbeat - this.startHeartbeat(); + this.startHeartbeat() // Initialize connectors status if (this.hasEvses) { for (const [evseId, evseStatus] of this.evses) { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { - const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus); - await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus, evseId); + await sendAndSetConnectorStatus( + this, + connectorId, + getBootConnectorStatus(this, connectorId, connectorStatus), + evseId + ) } } } } else { for (const connectorId of this.connectors.keys()) { if (connectorId > 0) { - const connectorBootStatus = getBootConnectorStatus( + await sendAndSetConnectorStatus( this, connectorId, - this.getConnectorStatus(connectorId)!, - ); - await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus); + getBootConnectorStatus( + this, + connectorId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.getConnectorStatus(connectorId)! + ) + ) } } } - if (this.stationInfo.firmwareStatus === FirmwareStatus.Installing) { + if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) { await this.ocppRequestService.requestHandler< - FirmwareStatusNotificationRequest, - FirmwareStatusNotificationResponse + FirmwareStatusNotificationRequest, + FirmwareStatusNotificationResponse >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, { - status: FirmwareStatus.Installed, - }); - this.stationInfo.firmwareStatus = FirmwareStatus.Installed; + status: FirmwareStatus.Installed + }) + this.stationInfo.firmwareStatus = FirmwareStatus.Installed } // Start the ATG - if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) { - this.startAutomaticTransactionGenerator(); + if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) { + this.startAutomaticTransactionGenerator(undefined, ATGStopAbsoluteDuration) } - this.flushMessageBuffer(); + this.flushMessageBuffer() } - private async stopMessageSequence( - reason?: StopTransactionReason, - stopTransactions = this.stationInfo?.stopTransactionsOnStopped, - ): Promise { + private internalStopMessageSequence (): void { // Stop WebSocket ping - this.stopWebSocketPing(); + this.stopWebSocketPing() // Stop heartbeat - this.stopHeartbeat(); + this.stopHeartbeat() // Stop the ATG if (this.automaticTransactionGenerator?.started === true) { - this.stopAutomaticTransactionGenerator(); + this.stopAutomaticTransactionGenerator() } + } + + private async stopMessageSequence ( + reason?: StopTransactionReason, + stopTransactions?: boolean + ): Promise { + this.internalStopMessageSequence() // Stop ongoing transactions - stopTransactions && (await this.stopRunningTransactions(reason)); + stopTransactions === true && (await this.stopRunningTransactions(reason)) if (this.hasEvses) { for (const [evseId, evseStatus] of this.evses) { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { - await this.ocppRequestService.requestHandler< - StatusNotificationRequest, - StatusNotificationResponse - >( + await sendAndSetConnectorStatus( this, - RequestCommand.STATUS_NOTIFICATION, - buildStatusNotificationRequest( - this, - connectorId, - ConnectorStatusEnum.Unavailable, - evseId, - ), - ); - delete connectorStatus?.status; + connectorId, + ConnectorStatusEnum.Unavailable, + evseId + ) + delete connectorStatus.status } } } } else { for (const connectorId of this.connectors.keys()) { if (connectorId > 0) { - await this.ocppRequestService.requestHandler< - StatusNotificationRequest, - StatusNotificationResponse - >( - this, - RequestCommand.STATUS_NOTIFICATION, - buildStatusNotificationRequest(this, connectorId, ConnectorStatusEnum.Unavailable), - ); - delete this.getConnectorStatus(connectorId)?.status; + await sendAndSetConnectorStatus(this, connectorId, ConnectorStatusEnum.Unavailable) + delete this.getConnectorStatus(connectorId)?.status } } } } - private startWebSocketPing(): void { - const webSocketPingInterval: number = - getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) !== undefined + private startWebSocketPing (): void { + const webSocketPingInterval = + getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) != null ? convertToInt( - getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value, - ) - : 0; - if (webSocketPingInterval > 0 && this.webSocketPingSetInterval === undefined) { - this.webSocketPingSetInterval = setInterval(() => { - if (this.isWebSocketConnectionOpened() === true) { - this.wsConnection?.ping(); + getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value + ) + : 0 + if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) { + this.wsPingSetInterval = setInterval(() => { + if (this.isWebSocketConnectionOpened()) { + this.wsConnection?.ping() } - }, secondsToMilliseconds(webSocketPingInterval)); + }, secondsToMilliseconds(webSocketPingInterval)) logger.info( `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds( - webSocketPingInterval, - )}`, - ); - } else if (this.webSocketPingSetInterval !== undefined) { + webSocketPingInterval + )}` + ) + } else if (this.wsPingSetInterval != null) { logger.info( `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds( - webSocketPingInterval, - )}`, - ); + webSocketPingInterval + )}` + ) } else { logger.error( - `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval}, not starting the WebSocket ping`, - ); + `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval}, not starting the WebSocket ping` + ) } } - private stopWebSocketPing(): void { - if (this.webSocketPingSetInterval !== undefined) { - clearInterval(this.webSocketPingSetInterval); - delete this.webSocketPingSetInterval; + private stopWebSocketPing (): void { + if (this.wsPingSetInterval != null) { + clearInterval(this.wsPingSetInterval) + delete this.wsPingSetInterval } } - private getConfiguredSupervisionUrl(): URL { - let configuredSupervisionUrl: string; - const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls(); + private getConfiguredSupervisionUrl (): URL { + let configuredSupervisionUrl: string + const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls() if (isNotEmptyArray(supervisionUrls)) { - let configuredSupervisionUrlIndex: number; + let configuredSupervisionUrlIndex: number switch (Configuration.getSupervisionUrlDistribution()) { case SupervisionUrlDistribution.RANDOM: - configuredSupervisionUrlIndex = Math.floor( - secureRandom() * (supervisionUrls as string[]).length, - ); - break; + configuredSupervisionUrlIndex = Math.floor(secureRandom() * supervisionUrls.length) + break case SupervisionUrlDistribution.ROUND_ROBIN: case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY: default: - Object.values(SupervisionUrlDistribution).includes( - Configuration.getSupervisionUrlDistribution()!, - ) === false && - logger.error( + !Object.values(SupervisionUrlDistribution).includes( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + Configuration.getSupervisionUrlDistribution()! + ) && + logger.warn( // eslint-disable-next-line @typescript-eslint/no-base-to-string - `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${ + `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' in configuration from values '${SupervisionUrlDistribution.toString()}', defaulting to '${ SupervisionUrlDistribution.CHARGING_STATION_AFFINITY - }`, - ); - configuredSupervisionUrlIndex = (this.index - 1) % (supervisionUrls as string[]).length; - break; + }'` + ) + configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length + break } - configuredSupervisionUrl = (supervisionUrls as string[])[configuredSupervisionUrlIndex]; + configuredSupervisionUrl = supervisionUrls[configuredSupervisionUrlIndex] } else { - configuredSupervisionUrl = supervisionUrls as string; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + configuredSupervisionUrl = supervisionUrls! } if (isNotEmptyString(configuredSupervisionUrl)) { - return new URL(configuredSupervisionUrl); + return new URL(configuredSupervisionUrl) } - const errorMsg = 'No supervision url(s) configured'; - logger.error(`${this.logPrefix()} ${errorMsg}`); - throw new BaseError(`${errorMsg}`); + const errorMsg = 'No supervision url(s) configured' + logger.error(`${this.logPrefix()} ${errorMsg}`) + throw new BaseError(errorMsg) } - private stopHeartbeat(): void { - if (this.heartbeatSetInterval !== undefined) { - clearInterval(this.heartbeatSetInterval); - delete this.heartbeatSetInterval; + private stopHeartbeat (): void { + if (this.heartbeatSetInterval != null) { + clearInterval(this.heartbeatSetInterval) + delete this.heartbeatSetInterval } } - private terminateWSConnection(): void { - if (this.isWebSocketConnectionOpened() === true) { - this.wsConnection?.terminate(); - this.wsConnection = null; + private terminateWSConnection (): void { + if (this.isWebSocketConnectionOpened()) { + this.wsConnection?.terminate() + this.wsConnection = null } } - private async reconnect(): Promise { - // Stop WebSocket ping - this.stopWebSocketPing(); - // Stop heartbeat - this.stopHeartbeat(); - // Stop the ATG if needed - if (this.getAutomaticTransactionGeneratorConfiguration().stopOnConnectionFailure === true) { - this.stopAutomaticTransactionGenerator(); - } + private async reconnect (): Promise { if ( - this.autoReconnectRetryCount < this.stationInfo.autoReconnectMaxRetries! || + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.wsConnectionRetryCount < this.stationInfo!.autoReconnectMaxRetries! || this.stationInfo?.autoReconnectMaxRetries === -1 ) { - ++this.autoReconnectRetryCount; + this.wsConnectionRetried = true + ++this.wsConnectionRetryCount const reconnectDelay = this.stationInfo?.reconnectExponentialDelay === true - ? exponentialDelay(this.autoReconnectRetryCount) - : secondsToMilliseconds(this.getConnectionTimeout()); - const reconnectDelayWithdraw = 1000; + ? exponentialDelay(this.wsConnectionRetryCount) + : secondsToMilliseconds(this.getConnectionTimeout()) + const reconnectDelayWithdraw = 1000 const reconnectTimeout = - reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0 - ? reconnectDelay - reconnectDelayWithdraw - : 0; + reconnectDelay - reconnectDelayWithdraw > 0 ? reconnectDelay - reconnectDelayWithdraw : 0 logger.error( `${this.logPrefix()} WebSocket connection retry in ${roundTo( reconnectDelay, - 2, - )}ms, timeout ${reconnectTimeout}ms`, - ); - await sleep(reconnectDelay); + 2 + )}ms, timeout ${reconnectTimeout}ms` + ) + await sleep(reconnectDelay) logger.error( - `${this.logPrefix()} WebSocket connection retry #${this.autoReconnectRetryCount.toString()}`, - ); + `${this.logPrefix()} WebSocket connection retry #${this.wsConnectionRetryCount.toString()}` + ) this.openWSConnection( { - handshakeTimeout: reconnectTimeout, + handshakeTimeout: reconnectTimeout }, - { closeOpened: true }, - ); + { closeOpened: true } + ) } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) { logger.error( `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${ - this.autoReconnectRetryCount - }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})`, - ); + this.wsConnectionRetryCount + }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})` + ) } } }