Silence one eslint warning
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 40b846217556023b671d8f31a0f5d7996cc2bd0c..c4d9abb610f1dd8b73fa1b169a06078d2ff52f64 100644 (file)
@@ -6,7 +6,7 @@ import path from 'path';
 import { URL } from 'url';
 import { parentPort } from 'worker_threads';
 
-import WebSocket, { Data, RawData } from 'ws';
+import WebSocket, { type RawData } from 'ws';
 
 import BaseError from '../exception/BaseError';
 import OCPPError from '../exception/OCPPError';
@@ -42,11 +42,13 @@ import {
   AvailabilityType,
   BootNotificationRequest,
   CachedRequest,
+  ErrorCallback,
   HeartbeatRequest,
   IncomingRequest,
   IncomingRequestCommand,
   MeterValuesRequest,
   RequestCommand,
+  ResponseCallback,
   StatusNotificationRequest,
 } from '../types/ocpp/Requests';
 import {
@@ -89,6 +91,7 @@ export default class ChargingStation {
   public readonly templateFile: string;
   public stationInfo!: ChargingStationInfo;
   public started: boolean;
+  public starting: boolean;
   public authorizedTagsCache: AuthorizedTagsCache;
   public automaticTransactionGenerator!: AutomaticTransactionGenerator;
   public ocppConfiguration!: ChargingStationOcppConfiguration;
@@ -101,7 +104,6 @@ export default class ChargingStation {
   public bootNotificationRequest!: BootNotificationRequest;
   public bootNotificationResponse!: BootNotificationResponse | null;
   public powerDivider!: number;
-  private starting: boolean;
   private stopping: boolean;
   private configurationFile!: string;
   private configurationFileHash!: string;
@@ -216,7 +218,10 @@ export default class ChargingStation {
   }
 
   public isRegistered(): boolean {
-    return !this.isInUnknownState() && (this.isInAcceptedState() || this.isInPendingState());
+    return (
+      this.isInUnknownState() === false &&
+      (this.isInAcceptedState() === true || this.isInPendingState() === true)
+    );
   }
 
   public isChargingStationAvailable(): boolean {
@@ -603,6 +608,12 @@ export default class ChargingStation {
     options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
     params.closeOpened = params?.closeOpened ?? false;
     params.terminateOpened = params?.terminateOpened ?? false;
+    if (this.started === false && this.starting === false) {
+      logger.warn(
+        `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()} on stopped charging station`
+      );
+      return;
+    }
     if (
       !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) &&
       !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)
@@ -704,9 +715,9 @@ export default class ChargingStation {
   ): Promise<StopTransactionResponse> {
     const transactionId = this.getConnectorStatus(connectorId).transactionId;
     if (
-      this.getBeginEndMeterValues() &&
-      this.getOcppStrictCompliance() &&
-      !this.getOutOfOrderEndMeterValues()
+      this.getBeginEndMeterValues() === true &&
+      this.getOcppStrictCompliance() === true &&
+      this.getOutOfOrderEndMeterValues() === false
     ) {
       // FIXME: Implement OCPP version agnostic helpers
       const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
@@ -785,7 +796,7 @@ export default class ChargingStation {
   private getStationInfoFromTemplate(): ChargingStationInfo {
     const stationTemplate: ChargingStationTemplate = this.getTemplateFromFile();
     if (Utils.isNullOrUndefined(stationTemplate)) {
-      const errorMsg = 'Failed to read charging station template file';
+      const errorMsg = `Failed to read charging station template file ${this.templateFile}`;
       logger.error(`${this.logPrefix()} ${errorMsg}`);
       throw new BaseError(errorMsg);
     }
@@ -807,6 +818,21 @@ export default class ChargingStation {
       'supervisionUrl',
       'supervisionUrls'
     );
+    const firmwareVersionRegExp = stationTemplate.firmwareVersionPattern
+      ? new RegExp(stationTemplate.firmwareVersionPattern)
+      : Constants.SEMVER_REGEXP;
+    if (
+      stationTemplate.firmwareVersion &&
+      firmwareVersionRegExp.test(stationTemplate.firmwareVersion) === false
+    ) {
+      logger.warn(
+        `${this.logPrefix()} Firmware version '${
+          stationTemplate.firmwareVersion
+        }' in template file ${
+          this.templateFile
+        } does not match regular expression '${firmwareVersionRegExp.toString()}'`
+      );
+    }
     const stationInfo: ChargingStationInfo =
       ChargingStationUtils.stationTemplateToStationInfo(stationTemplate);
     stationInfo.hashId = ChargingStationUtils.getHashId(this.index, stationTemplate);
@@ -1196,10 +1222,17 @@ export default class ChargingStation {
     }
     // Initialize transaction attributes on connectors
     for (const connectorId of this.connectors.keys()) {
+      if (connectorId > 0 && this.getConnectorStatus(connectorId).transactionStarted === true) {
+        logger.warn(
+          `${this.logPrefix()} Connector ${connectorId} at initialization has a transaction started: ${
+            this.getConnectorStatus(connectorId).transactionId
+          }`
+        );
+      }
       if (
         connectorId > 0 &&
         (this.getConnectorStatus(connectorId).transactionStarted === undefined ||
-          this.getConnectorStatus(connectorId).transactionStarted === false)
+          this.getConnectorStatus(connectorId).transactionStarted === null)
       ) {
         this.initializeConnectorStatus(connectorId);
       }
@@ -1305,7 +1338,7 @@ export default class ChargingStation {
 
   private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | null {
     let configuration: ChargingStationConfiguration = null;
-    if (this.getOcppPersistentConfiguration()) {
+    if (this.getOcppPersistentConfiguration() === true) {
       const configurationFromFile = this.getConfigurationFromFile();
       configuration = configurationFromFile?.configurationKey && configurationFromFile;
     }
@@ -1326,7 +1359,7 @@ export default class ChargingStation {
       logger.info(
         `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} succeeded`
       );
-      if (!this.isRegistered()) {
+      if (this.isRegistered() === false) {
         // Send BootNotification
         let registrationRetryCount = 0;
         do {
@@ -1336,7 +1369,7 @@ export default class ChargingStation {
           >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
             skipBufferingOnError: true,
           });
-          if (!this.isRegistered()) {
+          if (this.isRegistered() === false) {
             this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
             await Utils.sleep(
               this.bootNotificationResponse?.interval
@@ -1345,13 +1378,13 @@ export default class ChargingStation {
             );
           }
         } while (
-          !this.isRegistered() &&
+          this.isRegistered() === false &&
           (registrationRetryCount <= this.getRegistrationMaxRetries() ||
             this.getRegistrationMaxRetries() === -1)
         );
       }
-      if (this.isRegistered()) {
-        if (this.isInAcceptedState()) {
+      if (this.isRegistered() === true) {
+        if (this.isInAcceptedState() === true) {
           await this.startMessageSequence();
         }
       } else {
@@ -1369,7 +1402,7 @@ export default class ChargingStation {
     }
   }
 
-  private async onClose(code: number, reason: string): Promise<void> {
+  private async onClose(code: number, reason: Buffer): Promise<void> {
     switch (code) {
       // Normal close
       case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
@@ -1377,7 +1410,7 @@ export default class ChargingStation {
         logger.info(
           `${this.logPrefix()} WebSocket normally closed with status '${Utils.getWebSocketCloseEventStatusString(
             code
-          )}' and reason '${reason}'`
+          )}' and reason '${reason.toString()}'`
         );
         this.autoReconnectRetryCount = 0;
         break;
@@ -1386,7 +1419,7 @@ export default class ChargingStation {
         logger.error(
           `${this.logPrefix()} WebSocket abnormally closed with status '${Utils.getWebSocketCloseEventStatusString(
             code
-          )}' and reason '${reason}'`
+          )}' and reason '${reason.toString()}'`
         );
         this.started === true && (await this.reconnect());
         break;
@@ -1394,7 +1427,7 @@ export default class ChargingStation {
     parentPort.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
-  private async onMessage(data: Data): Promise<void> {
+  private async onMessage(data: RawData): Promise<void> {
     let messageType: number;
     let messageId: string;
     let commandName: IncomingRequestCommand;
@@ -1402,8 +1435,8 @@ export default class ChargingStation {
     let errorType: ErrorType;
     let errorMessage: string;
     let errorDetails: JsonType;
-    let responseCallback: (payload: JsonType, requestPayload: JsonType) => void;
-    let errorCallback: (error: OCPPError, requestStatistic?: boolean) => void;
+    let responseCallback: ResponseCallback;
+    let errorCallback: ErrorCallback;
     let requestCommandName: RequestCommand | IncomingRequestCommand;
     let requestPayload: JsonType;
     let cachedRequest: CachedRequest;
@@ -1764,8 +1797,7 @@ export default class ChargingStation {
       } else if (
         !this.getConnectorStatus(connectorId)?.status &&
         (this.isChargingStationAvailable() === false ||
-          (this.isChargingStationAvailable() === true &&
-            this.isConnectorAvailable(connectorId) === false))
+          this.isConnectorAvailable(connectorId) === false)
       ) {
         chargePointStatus = ChargePointStatus.UNAVAILABLE;
       } else if (
@@ -1841,9 +1873,7 @@ export default class ChargingStation {
     if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
       this.webSocketPingSetInterval = setInterval(() => {
         if (this.isWebSocketConnectionOpened() === true) {
-          this.wsConnection.ping((): void => {
-            /* This is intentional */
-          });
+          this.wsConnection.ping();
         }
       }, webSocketPingInterval * 1000);
       logger.info(
@@ -1875,9 +1905,7 @@ export default class ChargingStation {
   }
 
   private getConfiguredSupervisionUrl(): URL {
-    const supervisionUrls = Utils.cloneObject<string | string[]>(
-      this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls()
-    );
+    const supervisionUrls = this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls();
     if (!Utils.isEmptyArray(supervisionUrls)) {
       switch (Configuration.getSupervisionUrlDistribution()) {
         case SupervisionUrlDistribution.ROUND_ROBIN: