From e054fc1cc8912765435d03729d0104e9e2316949 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 28 Jan 2024 13:21:01 +0100 Subject: [PATCH] fix: ensure the ATG is properly restored after disconnection to CSMS MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- README.md | 2 - .../abb-atg.station-template.json | 1 - .../abb.station-template.json | 1 - .../chargex.station-template.json | 1 - .../evlink.station-template.json | 1 - .../keba.station-template.json | 1 - .../schneider-evses.station-template.json | 1 - .../schneider-imredd.station-template.json | 1 - .../schneider.station-template.json | 1 - .../siemens.station-template.json | 1 - .../virtual-simple-atg.station-template.json | 1 - .../virtual-simple.station-template.json | 1 - .../virtual.station-template.json | 1 - .../AutomaticTransactionGenerator.ts | 30 +++--- src/charging-station/ChargingStation.ts | 92 ++++++++++--------- src/types/AutomaticTransactionGenerator.ts | 1 - src/types/ChargingStationEvents.ts | 4 +- src/utils/Constants.ts | 3 +- 18 files changed, 71 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index dcb4a23c..7077bf32 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,6 @@ type AutomaticTransactionGeneratorConfiguration = { probabilityOfStart: number stopAfterHours: number stopAbsoluteDuration: boolean - stopOnConnectionFailure: boolean requireAuthorize?: boolean idTagDistribution?: 'random' | 'round-robin' | 'connector-affinity' } @@ -241,7 +240,6 @@ type AutomaticTransactionGeneratorConfiguration = { "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": true, "requireAuthorize": true, "idTagDistribution": "random" } diff --git a/src/assets/station-templates/abb-atg.station-template.json b/src/assets/station-templates/abb-atg.station-template.json index 456089bf..d9921ebb 100644 --- a/src/assets/station-templates/abb-atg.station-template.json +++ b/src/assets/station-templates/abb-atg.station-template.json @@ -61,7 +61,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/abb.station-template.json b/src/assets/station-templates/abb.station-template.json index a95517e7..e3df2ba7 100644 --- a/src/assets/station-templates/abb.station-template.json +++ b/src/assets/station-templates/abb.station-template.json @@ -61,7 +61,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": true, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/chargex.station-template.json b/src/assets/station-templates/chargex.station-template.json index fca57094..fe87192e 100644 --- a/src/assets/station-templates/chargex.station-template.json +++ b/src/assets/station-templates/chargex.station-template.json @@ -85,7 +85,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/evlink.station-template.json b/src/assets/station-templates/evlink.station-template.json index 5b9f9eac..920d5cc1 100644 --- a/src/assets/station-templates/evlink.station-template.json +++ b/src/assets/station-templates/evlink.station-template.json @@ -62,7 +62,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/keba.station-template.json b/src/assets/station-templates/keba.station-template.json index 6c0e3437..d51cc742 100644 --- a/src/assets/station-templates/keba.station-template.json +++ b/src/assets/station-templates/keba.station-template.json @@ -59,7 +59,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/schneider-evses.station-template.json b/src/assets/station-templates/schneider-evses.station-template.json index 79ce114c..aceb747c 100644 --- a/src/assets/station-templates/schneider-evses.station-template.json +++ b/src/assets/station-templates/schneider-evses.station-template.json @@ -59,7 +59,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true, "idTagDistribution": "round-robin" }, diff --git a/src/assets/station-templates/schneider-imredd.station-template.json b/src/assets/station-templates/schneider-imredd.station-template.json index a4411047..029fb214 100644 --- a/src/assets/station-templates/schneider-imredd.station-template.json +++ b/src/assets/station-templates/schneider-imredd.station-template.json @@ -61,7 +61,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/schneider.station-template.json b/src/assets/station-templates/schneider.station-template.json index 8fb25abe..5d3d798e 100644 --- a/src/assets/station-templates/schneider.station-template.json +++ b/src/assets/station-templates/schneider.station-template.json @@ -61,7 +61,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/siemens.station-template.json b/src/assets/station-templates/siemens.station-template.json index 439b5f92..5a51acb0 100644 --- a/src/assets/station-templates/siemens.station-template.json +++ b/src/assets/station-templates/siemens.station-template.json @@ -56,7 +56,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/virtual-simple-atg.station-template.json b/src/assets/station-templates/virtual-simple-atg.station-template.json index aedd27cb..67439657 100644 --- a/src/assets/station-templates/virtual-simple-atg.station-template.json +++ b/src/assets/station-templates/virtual-simple-atg.station-template.json @@ -56,7 +56,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/virtual-simple.station-template.json b/src/assets/station-templates/virtual-simple.station-template.json index 37cbe8ad..24fce690 100644 --- a/src/assets/station-templates/virtual-simple.station-template.json +++ b/src/assets/station-templates/virtual-simple.station-template.json @@ -56,7 +56,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/assets/station-templates/virtual.station-template.json b/src/assets/station-templates/virtual.station-template.json index 09086f8e..3692355d 100644 --- a/src/assets/station-templates/virtual.station-template.json +++ b/src/assets/station-templates/virtual.station-template.json @@ -56,7 +56,6 @@ "maxDelayBetweenTwoTransactions": 30, "probabilityOfStart": 1, "stopAfterHours": 0.3, - "stopOnConnectionFailure": false, "requireAuthorize": true }, "Connectors": { diff --git a/src/charging-station/AutomaticTransactionGenerator.ts b/src/charging-station/AutomaticTransactionGenerator.ts index 4bff9031..2380c967 100644 --- a/src/charging-station/AutomaticTransactionGenerator.ts +++ b/src/charging-station/AutomaticTransactionGenerator.ts @@ -66,7 +66,7 @@ export class AutomaticTransactionGenerator { return AutomaticTransactionGenerator.instances.get(chargingStation.stationInfo!.hashId) } - public start (): void { + public start (stopAbsoluteDuration?: boolean): void { if (!checkChargingStation(this.chargingStation, this.logPrefix())) { return } @@ -79,7 +79,7 @@ export class AutomaticTransactionGenerator { return } this.starting = true - this.startConnectors() + this.startConnectors(stopAbsoluteDuration) this.started = true this.starting = false } @@ -99,7 +99,7 @@ export class AutomaticTransactionGenerator { this.stopping = false } - public startConnector (connectorId: number): void { + public startConnector (connectorId: number, stopAbsoluteDuration?: boolean): void { if (!checkChargingStation(this.chargingStation, this.logPrefix(connectorId))) { return } @@ -108,7 +108,7 @@ export class AutomaticTransactionGenerator { throw new BaseError(`Connector ${connectorId} does not exist`) } if (this.connectorsStatus.get(connectorId)?.start === false) { - this.internalStartConnector(connectorId).catch(Constants.EMPTY_FUNCTION) + this.internalStartConnector(connectorId, stopAbsoluteDuration).catch(Constants.EMPTY_FUNCTION) } else if (this.connectorsStatus.get(connectorId)?.start === true) { logger.warn(`${this.logPrefix(connectorId)} is already started on connector`) } @@ -127,7 +127,7 @@ export class AutomaticTransactionGenerator { } } - private startConnectors (): void { + private startConnectors (stopAbsoluteDuration?: boolean): void { if ( this.connectorsStatus.size > 0 && this.connectorsStatus.size !== this.chargingStation.getNumberOfConnectors() @@ -139,14 +139,14 @@ export class AutomaticTransactionGenerator { for (const [evseId, evseStatus] of this.chargingStation.evses) { if (evseId > 0) { for (const connectorId of evseStatus.connectors.keys()) { - this.startConnector(connectorId) + this.startConnector(connectorId, stopAbsoluteDuration) } } } } else { for (const connectorId of this.chargingStation.connectors.keys()) { if (connectorId > 0) { - this.startConnector(connectorId) + this.startConnector(connectorId, stopAbsoluteDuration) } } } @@ -170,8 +170,11 @@ export class AutomaticTransactionGenerator { } } - private async internalStartConnector (connectorId: number): Promise { - this.setStartConnectorStatus(connectorId) + private async internalStartConnector ( + connectorId: number, + stopAbsoluteDuration?: boolean + ): Promise { + this.setStartConnectorStatus(connectorId, stopAbsoluteDuration) logger.info( `${this.logPrefix( connectorId @@ -257,12 +260,15 @@ export class AutomaticTransactionGenerator { ) } - private setStartConnectorStatus (connectorId: number): void { + private setStartConnectorStatus ( + connectorId: number, + stopAbsoluteDuration = this.chargingStation.getAutomaticTransactionGeneratorConfiguration() + ?.stopAbsoluteDuration + ): void { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.connectorsStatus.get(connectorId)!.startDate = new Date() if ( - this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.stopAbsoluteDuration === - false || + stopAbsoluteDuration === false || // eslint-disable-next-line @typescript-eslint/no-non-null-assertion !isValidDate(this.connectorsStatus.get(connectorId)!.stopDate) ) { diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 31b8cf79..ba2f30c8 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -56,7 +56,6 @@ import { type OCPPIncomingRequestService, type OCPPRequestService, buildMeterValue, - buildStatusNotificationRequest, buildTransactionEndMeterValue, getMessageTypeString, sendAndSetConnectorStatus @@ -107,8 +106,6 @@ import { type Response, StandardParametersKey, type Status, - type StatusNotificationRequest, - type StatusNotificationResponse, type StopTransactionReason, type StopTransactionRequest, type StopTransactionResponse, @@ -217,10 +214,24 @@ export class ChargingStation extends EventEmitter { parentPort?.postMessage(buildUpdatedMessage(this)) }) this.on(ChargingStationEvents.accepted, () => { - this.startMessageSequence().catch(error => { + this.startMessageSequence( + this.autoReconnectRetryCount > 0 + ? true + : this.getAutomaticTransactionGeneratorConfiguration()?.stopAbsoluteDuration + ).catch(error => { logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error) }) }) + this.on(ChargingStationEvents.disconnected, () => { + try { + this.internalStopMessageSequence() + } catch (error) { + logger.error( + `${this.logPrefix()} Error while stopping the internal message sequence:`, + error + ) + } + }) this.initialize() } @@ -657,10 +668,16 @@ export class ChargingStation extends EventEmitter { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!) // 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() @@ -830,14 +847,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) @@ -1787,6 +1807,9 @@ 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 @@ -1803,6 +1826,7 @@ export class ChargingStation extends EventEmitter { } private onClose (code: WebSocketCloseEventStatusCode, reason: Buffer): void { + this.emit(ChargingStationEvents.disconnected) switch (code) { // Normal close case WebSocketCloseEventStatusCode.CLOSE_NORMAL: @@ -2121,7 +2145,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, @@ -2169,15 +2193,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 @@ -2186,24 +2207,24 @@ export class ChargingStation extends EventEmitter { if (this.automaticTransactionGenerator?.started === true) { this.stopAutomaticTransactionGenerator() } + } + + private async stopMessageSequence ( + reason?: StopTransactionReason, + stopTransactions = this.stationInfo?.stopTransactionsOnStopped + ): 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 } @@ -2212,14 +2233,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 } } @@ -2317,14 +2331,6 @@ 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! || diff --git a/src/types/AutomaticTransactionGenerator.ts b/src/types/AutomaticTransactionGenerator.ts index 2625f8ad..1592c550 100644 --- a/src/types/AutomaticTransactionGenerator.ts +++ b/src/types/AutomaticTransactionGenerator.ts @@ -13,7 +13,6 @@ export interface AutomaticTransactionGeneratorConfiguration { probabilityOfStart: number stopAfterHours: number stopAbsoluteDuration: boolean - stopOnConnectionFailure: boolean requireAuthorize?: boolean idTagDistribution?: IdTagDistribution } diff --git a/src/types/ChargingStationEvents.ts b/src/types/ChargingStationEvents.ts index 5a0fc8fd..a24fd170 100644 --- a/src/types/ChargingStationEvents.ts +++ b/src/types/ChargingStationEvents.ts @@ -1,8 +1,10 @@ export enum ChargingStationEvents { started = 'started', stopped = 'stopped', + updated = 'updated', registered = 'registered', accepted = 'accepted', - updated = 'updated', + rejected = 'rejected', + disconnected = 'disconnected', connectorStatusChanged = 'connectorStatusChanged' } diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 45d86475..9b8fd1c5 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -51,8 +51,7 @@ export class Constants { maxDelayBetweenTwoTransactions: 30, probabilityOfStart: 1, stopAfterHours: 0.25, - stopAbsoluteDuration: false, - stopOnConnectionFailure: true + stopAbsoluteDuration: false }) // See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string -- 2.34.1