fix: ensure the ATG is properly restored after disconnection to CSMS
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 28 Jan 2024 12:21:01 +0000 (13:21 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 28 Jan 2024 12:21:01 +0000 (13:21 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
18 files changed:
README.md
src/assets/station-templates/abb-atg.station-template.json
src/assets/station-templates/abb.station-template.json
src/assets/station-templates/chargex.station-template.json
src/assets/station-templates/evlink.station-template.json
src/assets/station-templates/keba.station-template.json
src/assets/station-templates/schneider-evses.station-template.json
src/assets/station-templates/schneider-imredd.station-template.json
src/assets/station-templates/schneider.station-template.json
src/assets/station-templates/siemens.station-template.json
src/assets/station-templates/virtual-simple-atg.station-template.json
src/assets/station-templates/virtual-simple.station-template.json
src/assets/station-templates/virtual.station-template.json
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/types/AutomaticTransactionGenerator.ts
src/types/ChargingStationEvents.ts
src/utils/Constants.ts

index dcb4a23c408c96987cb40bde4db789ea23a1db3c..7077bf32f661f4d31443b707abd930145e4686e2 100644 (file)
--- 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"
   }
index 456089bfd0ef584960b086c8bb3d0da13cb6f7fe..d9921ebbd332cff999edc9a7f2bc15b3c6b31ef7 100644 (file)
@@ -61,7 +61,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index a95517e7d67a12b65bfb4fb479a324377eea327d..e3df2ba7c861640c255eea0b39e6f9b4c0203955 100644 (file)
@@ -61,7 +61,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": true,
     "requireAuthorize": true
   },
   "Connectors": {
index fca570940e21de8fa7206c78592e3d930cd9ffbf..fe87192e88a01a90ae3d32c3f14b8af169e1d745 100644 (file)
@@ -85,7 +85,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 5b9f9eacfe813d6a672c96cce6a092aa60c436da..920d5cc1f9ac2e3aef3f86680ea95305db58f50a 100644 (file)
@@ -62,7 +62,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 6c0e3437269f4fd3790c4d5d9ddd50a0a7b73487..d51cc7423a99ae2d568e2ff0a79218ff6816ee85 100644 (file)
@@ -59,7 +59,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 79ce114c9c1c8e70d22688db6c4bd2bd76c48df4..aceb747c69c4a31fb6db47e7cd7395871ed7609b 100644 (file)
@@ -59,7 +59,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true,
     "idTagDistribution": "round-robin"
   },
index a4411047355b4b2da68f324b65dc679ad02d7b0a..029fb214d04223bb66c984ed9487737af8e84f18 100644 (file)
@@ -61,7 +61,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 8fb25abec7f96d5b2e7814f5a9c4690d2a0e475c..5d3d798e343900663a2c4355710d52f6e6ca909d 100644 (file)
@@ -61,7 +61,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 439b5f92c53c2f67000d8c7a1c0bdbc6245bfeab..5a51acb0e347735bbca327ad7e1553afc199ef86 100644 (file)
@@ -56,7 +56,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index aedd27cbbeda051d9ee1d86708ac62d36f75c819..6743965796c09af9d2a39f87d317fc415c3ca7a1 100644 (file)
@@ -56,7 +56,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 37cbe8ad4236a868d18ff1c42e911cfb7545d265..24fce690a32e366b9e9996757c53a60c55e44b1c 100644 (file)
@@ -56,7 +56,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 09086f8e4901a9015ec9e4d4c58883cdfdeca0d2..3692355d5644bb0a32bf36fb37384a81888785e7 100644 (file)
@@ -56,7 +56,6 @@
     "maxDelayBetweenTwoTransactions": 30,
     "probabilityOfStart": 1,
     "stopAfterHours": 0.3,
-    "stopOnConnectionFailure": false,
     "requireAuthorize": true
   },
   "Connectors": {
index 4bff9031eb1f401a216e58e1a0f2032929b20e76..2380c967beada92d179262a2c71abc30640758f6 100644 (file)
@@ -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<void> {
-    this.setStartConnectorStatus(connectorId)
+  private async internalStartConnector (
+    connectorId: number,
+    stopAbsoluteDuration?: boolean
+  ): Promise<void> {
+    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)
     ) {
index 31b8cf794b4e5fd0b041eb20a468294d91715ba0..ba2f30c89f47312fe2f49580b4cc9298d81a806d 100644 (file)
@@ -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<void> {
+  private async startMessageSequence (ATGStopAbsoluteDuration?: boolean): Promise<void> {
     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<void> {
+  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<void> {
+    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<void> {
-    // 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! ||
index 2625f8ad485993a54702fb4eacf23b3e3254cafd..1592c5507fb4d8b7629dd51d1e22cba31971fd88 100644 (file)
@@ -13,7 +13,6 @@ export interface AutomaticTransactionGeneratorConfiguration {
   probabilityOfStart: number
   stopAfterHours: number
   stopAbsoluteDuration: boolean
-  stopOnConnectionFailure: boolean
   requireAuthorize?: boolean
   idTagDistribution?: IdTagDistribution
 }
index 5a0fc8fd6ee7d5a2007fa1a80d4aeacf37756234..a24fd17095b91c25ff2f839ff4ff584f10f07833 100644 (file)
@@ -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'
 }
index 45d864754f9198f819698dd4e6e2bccb3310fab7..9b8fd1c517a7199465febcb0bb9df792ff4d3500 100644 (file)
@@ -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