fix: ensure running transactions are stopped at CS stop
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 10 Nov 2023 11:36:58 +0000 (12:36 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 10 Nov 2023 11:36:58 +0000 (12:36 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/OCPPIncomingRequestService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPResponseService.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/types/ocpp/1.6/Transaction.ts

index e15ce4accd220b98c3658f05d724f79dc5404dc1..38f4901cda48f2e2722a076875ef156d482144e0 100644 (file)
@@ -86,7 +86,7 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     this.starting = false;
   }
 
-  public async stop(): Promise<void> {
+  public stop(): void {
     if (this.started === false) {
       logger.warn(`${this.logPrefix()} is already stopped`);
       return;
@@ -96,7 +96,7 @@ export class AutomaticTransactionGenerator extends AsyncResource {
       return;
     }
     this.stopping = true;
-    await this.stopConnectors();
+    this.stopConnectors();
     this.started = false;
     this.stopping = false;
   }
@@ -123,14 +123,13 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     }
   }
 
-  public async stopConnector(connectorId: number): Promise<void> {
+  public stopConnector(connectorId: number): void {
     if (this.connectorsStatus.has(connectorId) === false) {
       logger.error(`${this.logPrefix(connectorId)} stopping on non existing connector`);
       throw new BaseError(`Connector ${connectorId} does not exist`);
     }
     if (this.connectorsStatus.get(connectorId)?.start === true) {
       this.connectorsStatus.get(connectorId)!.start = false;
-      await this.stopTransaction(connectorId);
     } else if (this.connectorsStatus.get(connectorId)?.start === false) {
       logger.warn(`${this.logPrefix(connectorId)} is already stopped on connector`);
     }
@@ -161,19 +160,19 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     }
   }
 
-  private async stopConnectors(): Promise<void> {
+  private stopConnectors(): void {
     if (this.chargingStation.hasEvses) {
       for (const [evseId, evseStatus] of this.chargingStation.evses) {
         if (evseId > 0) {
           for (const connectorId of evseStatus.connectors.keys()) {
-            await this.stopConnector(connectorId);
+            this.stopConnector(connectorId);
           }
         }
       }
     } else {
       for (const connectorId of this.chargingStation.connectors.keys()) {
         if (connectorId > 0) {
-          await this.stopConnector(connectorId);
+          this.stopConnector(connectorId);
         }
       }
     }
@@ -191,7 +190,7 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     );
     while (this.connectorsStatus.get(connectorId)?.start === true) {
       if (!this.canStartConnector(connectorId)) {
-        await this.stopConnector(connectorId);
+        this.stopConnector(connectorId);
         break;
       }
       if (!this.chargingStation?.ocppRequestService) {
@@ -439,7 +438,7 @@ export class AutomaticTransactionGenerator extends AsyncResource {
 
   private async stopTransaction(
     connectorId: number,
-    reason: StopTransactionReason = StopTransactionReason.LOCAL,
+    reason = StopTransactionReason.LOCAL,
   ): Promise<StopTransactionResponse | undefined> {
     const measureId = 'StopTransaction with ATG';
     const beginId = PerformanceStatistics.beginMeasure(measureId);
index 134ccff6d3e7a3aadcb5170a17e0f2e979ec4775..441c5d1e95652a8d7cfc67174cf1b2834848cea5 100644 (file)
@@ -724,11 +724,11 @@ export class ChargingStation {
     }
   }
 
-  public async stop(reason?: StopTransactionReason): Promise<void> {
+  public async stop(reason?: StopTransactionReason, stopTransactions?: boolean): Promise<void> {
     if (this.started === true) {
       if (this.stopping === false) {
         this.stopping = true;
-        await this.stopMessageSequence(reason);
+        await this.stopMessageSequence(reason, stopTransactions);
         this.closeWSConnection();
         if (this.getEnableStatistics() === true) {
           this.performanceStatistics?.stop();
@@ -884,13 +884,13 @@ export class ChargingStation {
     parentPort?.postMessage(buildUpdatedMessage(this));
   }
 
-  public async stopAutomaticTransactionGenerator(connectorIds?: number[]): Promise<void> {
+  public stopAutomaticTransactionGenerator(connectorIds?: number[]): void {
     if (isNotEmptyArray(connectorIds)) {
       for (const connectorId of connectorIds!) {
-        await this.automaticTransactionGenerator?.stopConnector(connectorId);
+        this.automaticTransactionGenerator?.stopConnector(connectorId);
       }
     } else {
-      await this.automaticTransactionGenerator?.stop();
+      this.automaticTransactionGenerator?.stop();
     }
     this.saveAutomaticTransactionGeneratorConfiguration();
     parentPort?.postMessage(buildUpdatedMessage(this));
@@ -898,7 +898,7 @@ export class ChargingStation {
 
   public async stopTransactionOnConnector(
     connectorId: number,
-    reason = StopTransactionReason.NONE,
+    reason?: StopTransactionReason,
   ): Promise<StopTransactionResponse> {
     const transactionId = this.getConnectorStatus(connectorId)?.transactionId;
     if (
@@ -928,7 +928,7 @@ export class ChargingStation {
       {
         transactionId,
         meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId!, true),
-        reason,
+        ...(isNullOrUndefined(reason) && { reason }),
       },
     );
   }
@@ -2043,7 +2043,7 @@ export class ChargingStation {
     return stationTemplate?.useConnectorId0 ?? true;
   }
 
-  private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise<void> {
+  private async stopRunningTransactions(reason?: StopTransactionReason): Promise<void> {
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
         if (evseId === 0) {
@@ -2178,17 +2178,18 @@ export class ChargingStation {
   }
 
   private async stopMessageSequence(
-    reason: StopTransactionReason = StopTransactionReason.NONE,
+    reason?: StopTransactionReason,
+    stopTransactions = true,
   ): Promise<void> {
     // Stop WebSocket ping
     this.stopWebSocketPing();
     // Stop heartbeat
     this.stopHeartbeat();
     // Stop ongoing transactions
+    stopTransactions && (await this.stopRunningTransactions(reason));
+    // Stop the ATG
     if (this.automaticTransactionGenerator?.started === true) {
-      await this.stopAutomaticTransactionGenerator();
-    } else {
-      await this.stopRunningTransactions(reason);
+      this.stopAutomaticTransactionGenerator();
     }
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
@@ -2333,7 +2334,7 @@ export class ChargingStation {
     this.stopHeartbeat();
     // Stop the ATG if needed
     if (this.getAutomaticTransactionGeneratorConfiguration().stopOnConnectionFailure === true) {
-      await this.stopAutomaticTransactionGenerator();
+      this.stopAutomaticTransactionGenerator();
     }
     if (
       this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries()! ||
index ee423e1e1a8b06037bd9b59c6697218b77edf96e..fff823c09b22989f441c6b88968018a6acc933a6 100644 (file)
@@ -87,10 +87,8 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
       ],
       [
         BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
-        async (requestPayload?: BroadcastChannelRequestPayload) =>
-          await this.chargingStation.stopAutomaticTransactionGenerator(
-            requestPayload?.connectorIds,
-          ),
+        (requestPayload?: BroadcastChannelRequestPayload) =>
+          this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds),
       ],
       [
         BroadcastChannelProcedureName.SET_SUPERVISION_URL,
index d2e1937590db905d4107be2a81d7da0860f0e325..03bf136720ef590d3bfc4d74934b4ea420ce9754 100644 (file)
@@ -559,7 +559,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
     }
     if (connectorStatus?.transactionStarted === true) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${connectorStatus?.transactionIdTag}}`,
+        `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${connectorStatus?.transactionIdTag}`,
       );
       return;
     }
@@ -569,7 +569,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
           for (const [id, status] of evseStatus.connectors) {
             if (id !== connectorId && status?.transactionStarted === true) {
               logger.error(
-                `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${status?.transactionIdTag}}`,
+                `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${status?.transactionIdTag}`,
               );
               await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
               return;
index 17da98c08a6333c14a5fb83eba3725ad5321775a..04cf899f2cd01437a89592b0c4dc7de63fb72bfa 100644 (file)
@@ -123,7 +123,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
           }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
             meterValue.sampledValue[sampledValuesIndex].value
-          }/${socMaximumValue}}`,
+          }/${socMaximumValue}`,
         );
       }
     }
index 0ccf92fe78990be826da78d6da556df98c00dfc8..fd4d8807daa155662a82ce2d1f55e7ae9e659695 100644 (file)
@@ -104,7 +104,7 @@ export abstract class OCPPIncomingRequestService extends AsyncResource {
       validate.errors,
     );
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors!),
+      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
       'Incoming request PDU is invalid',
       commandName,
       JSON.stringify(validate.errors, undefined, 2),
index b95e9d239e6864701b41166a47b6a0d19e81094d..bcbbe639b8dcc8920855f83097ec71afc0353576 100644 (file)
@@ -230,7 +230,7 @@ export abstract class OCPPRequestService {
     );
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors!),
+      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
       'Request PDU is invalid',
       commandName,
       JSON.stringify(validate.errors, undefined, 2),
@@ -285,7 +285,7 @@ export abstract class OCPPRequestService {
     );
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors!),
+      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
       'Response PDU is invalid',
       commandName,
       JSON.stringify(validate.errors, undefined, 2),
index b76fde6a4b3067d907064bf46ccdb79c76e75f25..1bc73bc83cd8071f3c42af848deb44d1ac10af03 100644 (file)
@@ -83,7 +83,7 @@ export abstract class OCPPResponseService {
       validate.errors,
     );
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors!),
+      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
       'Response PDU is invalid',
       commandName,
       JSON.stringify(validate.errors, undefined, 2),
index 7c459809f45f2df877fcb69a2db54953c5e4a8a6..ef16bfb8703302c74488af11ff635b8c6da4dd2d 100644 (file)
@@ -48,17 +48,19 @@ export class OCPPServiceUtils {
     // This is intentional
   }
 
-  public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType {
-    for (const error of errors as DefinedError[]) {
-      switch (error.keyword) {
-        case 'type':
-          return ErrorType.TYPE_CONSTRAINT_VIOLATION;
-        case 'dependencies':
-        case 'required':
-          return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION;
-        case 'pattern':
-        case 'format':
-          return ErrorType.PROPERTY_CONSTRAINT_VIOLATION;
+  public static ajvErrorsToErrorType(errors: ErrorObject[] | null | undefined): ErrorType {
+    if (isNotEmptyArray(errors) === true) {
+      for (const error of errors as DefinedError[]) {
+        switch (error.keyword) {
+          case 'type':
+            return ErrorType.TYPE_CONSTRAINT_VIOLATION;
+          case 'dependencies':
+          case 'required':
+            return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION;
+          case 'pattern':
+          case 'format':
+            return ErrorType.PROPERTY_CONSTRAINT_VIOLATION;
+        }
       }
     }
     return ErrorType.FORMAT_VIOLATION;
index 4af42747ede0040b50fffa78410a943d4909b4b2..586367d769df006dc01aae280002ea42291e17fd 100644 (file)
@@ -2,7 +2,6 @@ import type { OCPP16MeterValue } from './MeterValues';
 import type { JsonObject } from '../../JsonType';
 
 export enum OCPP16StopTransactionReason {
-  NONE = '',
   EMERGENCY_STOP = 'EmergencyStop',
   EV_DISCONNECTED = 'EVDisconnected',
   HARD_RESET = 'HardReset',