X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2FChargingStation.ts;h=9da4fdbb41934219468867ed824b94fd3fb36cd3;hb=c2be11fddae3fd9c84ab92719e3d479301eb0257;hp=78561276c22cc0df9cc0dc4985258a6dbbda648f;hpb=5dc7c990eff43659bd48589cfc5afe09f9ec6664;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 78561276..9da4fdbb 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -1,9 +1,9 @@ -// 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 { type FSWatcher, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { dirname, join, parse } from 'node:path' import { URL } from 'node:url' import { parentPort } from 'node:worker_threads' @@ -42,6 +42,7 @@ import { hasReservationExpired, initializeConnectorsMapStatus, propagateSerialNumber, + setChargingStationOptions, stationTemplateToStationInfo, warnTemplateKeysDeprecation } from './Helpers.js' @@ -56,7 +57,6 @@ import { type OCPPIncomingRequestService, type OCPPRequestService, buildMeterValue, - buildStatusNotificationRequest, buildTransactionEndMeterValue, getMessageTypeString, sendAndSetConnectorStatus @@ -74,6 +74,7 @@ import { ChargingStationEvents, type ChargingStationInfo, type ChargingStationOcppConfiguration, + type ChargingStationOptions, type ChargingStationTemplate, type ConnectorStatus, ConnectorStatusEnum, @@ -107,8 +108,6 @@ import { type Response, StandardParametersKey, type Status, - type StatusNotificationRequest, - type StatusNotificationResponse, type StopTransactionReason, type StopTransactionRequest, type StopTransactionResponse, @@ -126,13 +125,15 @@ import { Configuration, Constants, DCElectricUtils, + buildAddedMessage, buildChargingStationAutomaticTransactionGeneratorConfiguration, buildConnectorsStatus, + buildDeletedMessage, buildEvsesStatus, buildStartedMessage, buildStoppedMessage, buildUpdatedMessage, - cloneObject, + clone, convertToBoolean, convertToDate, convertToInt, @@ -167,7 +168,7 @@ export class ChargingStation extends EventEmitter { public readonly connectors: Map public readonly evses: Map public readonly requests: Map - public performanceStatistics!: PerformanceStatistics | undefined + public performanceStatistics: PerformanceStatistics | undefined public heartbeatSetInterval?: NodeJS.Timeout public ocppRequestService!: OCPPRequestService public bootNotificationRequest?: BootNotificationRequest @@ -182,21 +183,23 @@ export class ChargingStation extends EventEmitter { private ocppIncomingRequestService!: OCPPIncomingRequestService private readonly messageBuffer: Set private configuredSupervisionUrl!: URL - private autoReconnectRetryCount: number - private templateFileWatcher!: FSWatcher | undefined + private wsConnectionRetried: boolean + private wsConnectionRetryCount: number + private templateFileWatcher: FSWatcher | undefined private templateFileHash!: string private readonly sharedLRUCache: SharedLRUCache - private webSocketPingSetInterval?: NodeJS.Timeout + private wsPingSetInterval?: NodeJS.Timeout private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel private flushMessageBufferSetInterval?: NodeJS.Timeout - constructor (index: number, templateFile: string) { + constructor (index: number, templateFile: string, options?: ChargingStationOptions) { super() this.started = false this.starting = false this.stopping = false this.wsConnection = null - this.autoReconnectRetryCount = 0 + this.wsConnectionRetried = false + this.wsConnectionRetryCount = 0 this.index = index this.templateFile = templateFile this.connectors = new Map() @@ -207,6 +210,12 @@ export class ChargingStation extends EventEmitter { 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)) }) @@ -216,15 +225,49 @@ export class ChargingStation extends EventEmitter { this.on(ChargingStationEvents.updated, () => { parentPort?.postMessage(buildUpdatedMessage(this)) }) + this.on(ChargingStationEvents.accepted, () => { + this.startMessageSequence( + this.wsConnectionRetried + ? true + : this.getAutomaticTransactionGeneratorConfiguration()?.stopAbsoluteDuration + ).catch(error => { + 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() + this.initialize(options) + + this.add() + + if (options?.autoStart != null) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.stationInfo!.autoStart = options.autoStart + } + + if (this.stationInfo?.autoStart === true) { + this.start() + } } 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 && @@ -624,6 +667,28 @@ export class ChargingStation extends EventEmitter { } } + 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) { if (!this.starting) { @@ -647,15 +712,21 @@ export class ChargingStation extends EventEmitter { } file have changed, reload` ) this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash) - // Initialize - this.initialize() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!) + // Initialize + this.initialize() // Restart the ATG - this.stopAutomaticTransactionGenerator() + const ATGStarted = this.automaticTransactionGenerator?.started + if (ATGStarted === true) { + this.stopAutomaticTransactionGenerator() + } delete this.automaticTransactionGeneratorConfiguration - if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) { - this.startAutomaticTransactionGenerator() + if ( + this.getAutomaticTransactionGeneratorConfiguration()?.enable === true && + ATGStarted === true + ) { + this.startAutomaticTransactionGenerator(undefined, true) } if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.restart() @@ -683,7 +754,10 @@ export class ChargingStation extends EventEmitter { } } - public async stop (reason?: StopTransactionReason, stopTransactions?: boolean): Promise { + public async stop ( + reason?: StopTransactionReason, + stopTransactions = this.stationInfo?.stopTransactionsOnStopped + ): Promise { if (this.started) { if (!this.stopping) { this.stopping = true @@ -692,12 +766,11 @@ export class ChargingStation extends EventEmitter { if (this.stationInfo?.enableStatistics === true) { 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.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash) this.emit(ChargingStationEvents.stopped) this.stopping = false } else { @@ -752,14 +825,12 @@ export class ChargingStation extends EventEmitter { if (this.isWebSocketConnectionOpened()) { logger.warn( - `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened` + `${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, @@ -768,26 +839,23 @@ export class ChargingStation extends EventEmitter { ) // 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 => + 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 { @@ -828,14 +896,17 @@ export class ChargingStation extends EventEmitter { return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses } - public startAutomaticTransactionGenerator (connectorIds?: number[]): void { + public startAutomaticTransactionGenerator ( + connectorIds?: number[], + stopAbsoluteDuration?: boolean + ): void { this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this) if (isNotEmptyArray(connectorIds)) { for (const connectorId of connectorIds) { - this.automaticTransactionGenerator?.startConnector(connectorId) + this.automaticTransactionGenerator?.startConnector(connectorId, stopAbsoluteDuration) } } else { - this.automaticTransactionGenerator?.start() + this.automaticTransactionGenerator?.start(stopAbsoluteDuration) } this.saveAutomaticTransactionGeneratorConfiguration() this.emit(ChargingStationEvents.updated) @@ -1098,8 +1169,8 @@ export class ChargingStation extends EventEmitter { } const stationInfo = stationTemplateToStationInfo(stationTemplate) stationInfo.hashId = getHashId(this.index, stationTemplate) + stationInfo.templateName = parse(this.templateFile).name 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)) { @@ -1116,9 +1187,8 @@ export class ChargingStation extends EventEmitter { : stationTemplate.power } stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo) - stationInfo.firmwareVersionPattern = - stationTemplate.firmwareVersionPattern ?? Constants.SEMVER_PATTERN if ( + isNotEmptyString(stationInfo.firmwareVersionPattern) && isNotEmptyString(stationInfo.firmwareVersion) && !new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion) ) { @@ -1137,29 +1207,34 @@ export class ChargingStation extends EventEmitter { }, stationTemplate.firmwareUpgrade ?? {} ) - stationInfo.resetTime = - stationTemplate.resetTime != null - ? secondsToMilliseconds(stationTemplate.resetTime) - : Constants.CHARGING_STATION_DEFAULT_RESET_TIME + if (stationTemplate.resetTime != null) { + stationInfo.resetTime = secondsToMilliseconds(stationTemplate.resetTime) + } return stationInfo } private getStationInfoFromFile ( - stationInfoPersistentConfiguration = true + stationInfoPersistentConfiguration: boolean | undefined = Constants.DEFAULT_STATION_INFO + .stationInfoPersistentConfiguration ): ChargingStationInfo | undefined { let stationInfo: ChargingStationInfo | undefined - if (stationInfoPersistentConfiguration) { + if (stationInfoPersistentConfiguration === true) { stationInfo = this.getConfigurationFromFile()?.stationInfo if (stationInfo != null) { delete stationInfo.infoHash + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (stationInfo.templateName == null) { + stationInfo.templateName = parse(this.templateFile).name + } } } return stationInfo } - private getStationInfo (): ChargingStationInfo { - const defaultStationInfo = Constants.DEFAULT_STATION_INFO + private getStationInfo (options?: ChargingStationOptions): ChargingStationInfo { const stationInfoFromTemplate = this.getStationInfoFromTemplate() + options?.persistentConfiguration != null && + (stationInfoFromTemplate.stationInfoPersistentConfiguration = options.persistentConfiguration) const stationInfoFromFile = this.getStationInfoFromFile( stationInfoFromTemplate.stationInfoPersistentConfiguration ) @@ -1170,7 +1245,10 @@ export class ChargingStation extends EventEmitter { stationInfoFromFile != null && stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash ) { - return { ...defaultStationInfo, ...stationInfoFromFile } + return setChargingStationOptions( + { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromFile }, + options + ) } stationInfoFromFile != null && propagateSerialNumber( @@ -1178,7 +1256,10 @@ export class ChargingStation extends EventEmitter { stationInfoFromFile, stationInfoFromTemplate ) - return { ...defaultStationInfo, ...stationInfoFromTemplate } + return setChargingStationOptions( + { ...Constants.DEFAULT_STATION_INFO, ...stationInfoFromTemplate }, + options + ) } private saveStationInfo (): void { @@ -1193,7 +1274,7 @@ export class ChargingStation extends EventEmitter { throw new BaseError(errorMsg) } - private initialize (): void { + 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) @@ -1211,11 +1292,11 @@ export class ChargingStation extends EventEmitter { } else { 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 = this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ?? @@ -1251,14 +1332,9 @@ export class ChargingStation extends EventEmitter { this.bootNotificationRequest = bootNotificationRequest this.powerDivider = this.getPowerDivider() // OCPP configuration - this.ocppConfiguration = this.getOcppConfiguration() + this.ocppConfiguration = this.getOcppConfiguration(options?.persistentConfiguration) 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.bootNotificationResponse = { currentTime: new Date(), @@ -1397,11 +1473,11 @@ export class ChargingStation extends EventEmitter { 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 != null && configuration.connectorsStatus == null) { for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) { - const evseStatus = cloneObject(evseStatusConfiguration) + const evseStatus = clone(evseStatusConfiguration) delete evseStatus.connectorsStatus this.evses.set(evseId, { ...(evseStatus as EvseStatus), @@ -1488,7 +1564,7 @@ export class ChargingStation extends EventEmitter { this.logPrefix(), this.templateFile ) - this.connectors.set(connectorId, cloneObject(connectorStatus)) + this.connectors.set(connectorId, clone(connectorStatus)) } initializeConnectorsMapStatus(this.connectors, this.logPrefix()) this.saveConnectorsStatus() @@ -1632,7 +1708,7 @@ export class ChargingStation extends EventEmitter { const configurationFromFile = this.getConfigurationFromFile() let configurationData: ChargingStationConfiguration = configurationFromFile != null - ? cloneObject(configurationFromFile) + ? clone(configurationFromFile) : {} if (this.stationInfo?.stationInfoPersistentConfiguration === true) { configurationData.stationInfo = this.stationInfo @@ -1651,10 +1727,7 @@ export class ChargingStation extends EventEmitter { configurationData, buildChargingStationAutomaticTransactionGeneratorConfiguration(this) ) - if ( - this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === false || - this.getAutomaticTransactionGeneratorConfiguration() == null - ) { + if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration !== true) { delete configurationData.automaticTransactionGenerator } if (this.connectors.size > 0) { @@ -1729,17 +1802,21 @@ export class ChargingStation extends EventEmitter { return this.getTemplateFromFile()?.Configuration } - private getOcppConfigurationFromFile (): ChargingStationOcppConfiguration | undefined { + private getOcppConfigurationFromFile ( + ocppPersistentConfiguration?: boolean + ): ChargingStationOcppConfiguration | undefined { const configurationKey = this.getConfigurationFromFile()?.configurationKey - if (this.stationInfo?.ocppPersistentConfiguration === true && Array.isArray(configurationKey)) { + if (ocppPersistentConfiguration === true && Array.isArray(configurationKey)) { return { configurationKey } } return undefined } - private getOcppConfiguration (): ChargingStationOcppConfiguration | undefined { + private getOcppConfiguration ( + ocppPersistentConfiguration: boolean | undefined = this.stationInfo?.ocppPersistentConfiguration + ): ChargingStationOcppConfiguration | undefined { let ocppConfiguration: ChargingStationOcppConfiguration | undefined = - this.getOcppConfigurationFromFile() + this.getOcppConfigurationFromFile(ocppPersistentConfiguration) if (ocppConfiguration == null) { ocppConfiguration = this.getOcppConfigurationFromTemplate() } @@ -1749,7 +1826,7 @@ export class ChargingStation extends EventEmitter { private async onOpen (): Promise { if (this.isWebSocketConnectionOpened()) { logger.info( - `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded` + `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} succeeded` ) let registrationRetryCount = 0 if (!this.isRegistered()) { @@ -1761,10 +1838,13 @@ export class ChargingStation extends EventEmitter { >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, { skipBufferingOnError: true }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.bootNotificationResponse.currentTime = convertToDate( - this.bootNotificationResponse.currentTime - )! + // 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( @@ -1787,22 +1867,26 @@ export class ChargingStation extends EventEmitter { 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.autoReconnectRetryCount = 0 + 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) switch (code) { // Normal close case WebSocketCloseEventStatusCode.CLOSE_NORMAL: @@ -1812,7 +1896,7 @@ export class ChargingStation extends EventEmitter { code )}' and reason '${reason.toString()}'` ) - this.autoReconnectRetryCount = 0 + this.wsConnectionRetryCount = 0 break // Abnormal close default: @@ -1821,13 +1905,19 @@ export class ChargingStation extends EventEmitter { code )}' and reason '${reason.toString()}'` ) - this.started && (await this.reconnect()) + this.started && + this.reconnect().catch(error => + logger.error(`${this.logPrefix()} Error while reconnecting:`, error) + ) break } this.emit(ChargingStationEvents.updated) } - private getCachedRequest (messageType: MessageType, messageId: string): CachedRequest | undefined { + private getCachedRequest ( + messageType: MessageType | undefined, + messageId: string + ): CachedRequest | undefined { const cachedRequest = this.requests.get(messageId) if (Array.isArray(cachedRequest)) { return cachedRequest @@ -1949,11 +2039,14 @@ export class ChargingStation extends EventEmitter { ) } } catch (error) { + 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 - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const [, messageId] = request! + const [, messageId] = request switch (messageType) { case MessageType.CALL_MESSAGE: [, , commandName] = request as IncomingRequest @@ -1976,7 +2069,7 @@ export class ChargingStation extends EventEmitter { 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 @@ -1984,11 +2077,11 @@ export class ChargingStation extends EventEmitter { } 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 @@ -2032,7 +2125,8 @@ export class ChargingStation extends EventEmitter { } private getUseConnectorId0 (stationTemplate?: ChargingStationTemplate): boolean { - return stationTemplate?.useConnectorId0 ?? true + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return stationTemplate?.useConnectorId0 ?? Constants.DEFAULT_STATION_INFO.useConnectorId0! } private async stopRunningTransactions (reason?: StopTransactionReason): Promise { @@ -2091,8 +2185,12 @@ export class ChargingStation extends EventEmitter { } private getCurrentOutType (stationInfo?: ChargingStationInfo): CurrentType { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return (stationInfo ?? this.stationInfo!).currentOutType ?? CurrentType.AC + 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 { @@ -2115,7 +2213,7 @@ export class ChargingStation extends EventEmitter { } } - private async startMessageSequence (): Promise { + private async startMessageSequence (ATGStopAbsoluteDuration?: boolean): Promise { if (this.stationInfo?.autoRegister === true) { await this.ocppRequestService.requestHandler< BootNotificationRequest, @@ -2163,15 +2261,12 @@ export class ChargingStation extends EventEmitter { // Start the ATG if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) { - this.startAutomaticTransactionGenerator() + this.startAutomaticTransactionGenerator(undefined, ATGStopAbsoluteDuration) } this.flushMessageBuffer() } - private async stopMessageSequence ( - reason?: StopTransactionReason, - stopTransactions = this.stationInfo?.stopTransactionsOnStopped - ): Promise { + private internalStopMessageSequence (): void { // Stop WebSocket ping this.stopWebSocketPing() // Stop heartbeat @@ -2180,24 +2275,24 @@ export class ChargingStation extends EventEmitter { if (this.automaticTransactionGenerator?.started === true) { this.stopAutomaticTransactionGenerator() } + } + + private async stopMessageSequence ( + reason?: StopTransactionReason, + stopTransactions?: boolean + ): Promise { + this.internalStopMessageSequence() // Stop ongoing transactions 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 - ) + connectorId, + ConnectorStatusEnum.Unavailable, + evseId ) delete connectorStatus.status } @@ -2206,14 +2301,7 @@ export class ChargingStation extends EventEmitter { } 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) - ) + await sendAndSetConnectorStatus(this, connectorId, ConnectorStatusEnum.Unavailable) delete this.getConnectorStatus(connectorId)?.status } } @@ -2227,8 +2315,8 @@ export class ChargingStation extends EventEmitter { getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value ) : 0 - if (webSocketPingInterval > 0 && this.webSocketPingSetInterval == null) { - this.webSocketPingSetInterval = setInterval(() => { + if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) { + this.wsPingSetInterval = setInterval(() => { if (this.isWebSocketConnectionOpened()) { this.wsConnection?.ping() } @@ -2238,7 +2326,7 @@ export class ChargingStation extends EventEmitter { webSocketPingInterval )}` ) - } else if (this.webSocketPingSetInterval != null) { + } else if (this.wsPingSetInterval != null) { logger.info( `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds( webSocketPingInterval @@ -2252,9 +2340,9 @@ export class ChargingStation extends EventEmitter { } private stopWebSocketPing (): void { - if (this.webSocketPingSetInterval != null) { - clearInterval(this.webSocketPingSetInterval) - delete this.webSocketPingSetInterval + if (this.wsPingSetInterval != null) { + clearInterval(this.wsPingSetInterval) + delete this.wsPingSetInterval } } @@ -2311,23 +2399,16 @@ export class ChargingStation extends EventEmitter { } 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() - } if ( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.autoReconnectRetryCount < this.stationInfo!.autoReconnectMaxRetries! || + 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) + ? exponentialDelay(this.wsConnectionRetryCount) : secondsToMilliseconds(this.getConnectionTimeout()) const reconnectDelayWithdraw = 1000 const reconnectTimeout = @@ -2340,7 +2421,7 @@ export class ChargingStation extends EventEmitter { ) await sleep(reconnectDelay) logger.error( - `${this.logPrefix()} WebSocket connection retry #${this.autoReconnectRetryCount.toString()}` + `${this.logPrefix()} WebSocket connection retry #${this.wsConnectionRetryCount.toString()}` ) this.openWSConnection( { @@ -2351,7 +2432,7 @@ export class ChargingStation extends EventEmitter { } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) { logger.error( `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${ - this.autoReconnectRetryCount + this.wsConnectionRetryCount }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})` ) }