Fix siemens charging station template
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 79c9e65afc219c2672f6fc49a964f0333b618a0e..1626a9b3e74fa286d0ce68cb8f2d7e43425f35a7 100644 (file)
@@ -5,20 +5,22 @@ import { ConnectorPhaseRotation, StandardParametersKey, SupportedFeatureProfiles
 import Connectors, { Connector, SampledValueTemplate } from '../types/Connectors';
 import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues';
 import Requests, { AvailabilityType, BootNotificationRequest, IncomingRequest, IncomingRequestCommand } from '../types/ocpp/Requests';
-import WebSocket, { MessageEvent } from 'ws';
+import WebSocket, { ClientOptions, MessageEvent } from 'ws';
 
 import AutomaticTransactionGenerator from './AutomaticTransactionGenerator';
 import { ChargePointStatus } from '../types/ocpp/ChargePointStatus';
 import { ChargingProfile } from '../types/ocpp/ChargingProfile';
 import ChargingStationInfo from '../types/ChargingStationInfo';
+import { ClientRequestArgs } from 'http';
 import Configuration from '../utils/Configuration';
 import Constants from '../utils/Constants';
+import { ErrorType } from '../types/ocpp/ErrorType';
 import FileUtils from '../utils/FileUtils';
 import { MessageType } from '../types/ocpp/MessageType';
-import OCPP16IncomingRequestService from './ocpp/1.6/OCCP16IncomingRequestService';
+import OCPP16IncomingRequestService from './ocpp/1.6/OCPP16IncomingRequestService';
 import OCPP16RequestService from './ocpp/1.6/OCPP16RequestService';
 import OCPP16ResponseService from './ocpp/1.6/OCPP16ResponseService';
-import OCPPError from './OCPPError';
+import OCPPError from './ocpp/OCPPError';
 import OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
 import OCPPRequestService from './ocpp/OCPPRequestService';
 import { OCPPVersion } from '../types/ocpp/OCPPVersion';
@@ -102,7 +104,7 @@ export default class ChargingStation {
     }
   }
 
-  public isWebSocketOpen(): boolean {
+  public isWebSocketConnectionOpened(): boolean {
     return this.wsConnection?.readyState === WebSocket.OPEN;
   }
 
@@ -298,6 +300,9 @@ export default class ChargingStation {
   }
 
   public start(): void {
+    if (this.getEnableStatistics()) {
+      this.performanceStatistics.start();
+    }
     this.openWSConnection();
     // Monitor authorization file
     this.startAuthorizationFileMonitoring();
@@ -326,21 +331,23 @@ export default class ChargingStation {
         this.getConnector(Utils.convertToInt(connector)).status = ChargePointStatus.UNAVAILABLE;
       }
     }
-    if (this.isWebSocketOpen()) {
+    if (this.isWebSocketConnectionOpened()) {
       this.wsConnection.close();
     }
+    if (this.getEnableStatistics()) {
+      this.performanceStatistics.stop();
+    }
     this.bootNotificationResponse = null;
     this.hasStopped = true;
   }
 
   public getConfigurationKey(key: string | StandardParametersKey, caseInsensitive = false): ConfigurationKey | undefined {
-    const configurationKey: ConfigurationKey | undefined = this.configuration.configurationKey.find((configElement) => {
+    return this.configuration.configurationKey.find((configElement) => {
       if (caseInsensitive) {
         return configElement.key.toLowerCase() === key.toLowerCase();
       }
       return configElement.key === key;
     });
-    return configurationKey;
   }
 
   public addConfigurationKey(key: string | StandardParametersKey, value: string, readonly = false, visible = true, reboot = false): void {
@@ -549,7 +556,7 @@ export default class ChargingStation {
     }
     this.stationInfo.powerDivider = this.getPowerDivider();
     if (this.getEnableStatistics()) {
-      this.performanceStatistics = new PerformanceStatistics(this.stationInfo.chargingStationId);
+      this.performanceStatistics = new PerformanceStatistics(this.stationInfo.chargingStationId, this.wsConnectionUrl);
     }
   }
 
@@ -607,7 +614,7 @@ export default class ChargingStation {
     if (this.isRegistered()) {
       await this.startMessageSequence();
       this.hasStopped && (this.hasStopped = false);
-      if (this.hasSocketRestarted && this.isWebSocketOpen()) {
+      if (this.hasSocketRestarted && this.isWebSocketConnectionOpened()) {
         this.flushMessageQueue();
       }
     } else {
@@ -643,7 +650,7 @@ export default class ChargingStation {
         // Parse the message
         [messageType, messageId, commandName, commandPayload, errorDetails] = request;
       } else {
-        throw new Error('Incoming request is not iterable');
+        throw new OCPPError(ErrorType.PROTOCOL_ERROR, 'Incoming request is not iterable');
       }
       // Check the Type of message
       switch (messageType) {
@@ -661,11 +668,11 @@ export default class ChargingStation {
           if (Utils.isIterable(this.requests[messageId])) {
             [responseCallback, , requestPayload] = this.requests[messageId];
           } else {
-            throw new Error(`Response request for message id ${messageId} is not iterable`);
+            throw new OCPPError(ErrorType.PROTOCOL_ERROR, `Response request for message id ${messageId} is not iterable`);
           }
           if (!responseCallback) {
             // Error
-            throw new Error(`Response request for unknown message id ${messageId}`);
+            throw new OCPPError(ErrorType.INTERNAL_ERROR, `Response request for unknown message id ${messageId}`);
           }
           delete this.requests[messageId];
           responseCallback(commandName, requestPayload);
@@ -674,12 +681,12 @@ export default class ChargingStation {
         case MessageType.CALL_ERROR_MESSAGE:
           if (!this.requests[messageId]) {
             // Error
-            throw new Error(`Error request for unknown message id ${messageId}`);
+            throw new OCPPError(ErrorType.INTERNAL_ERROR, `Error request for unknown message id ${messageId}`);
           }
           if (Utils.isIterable(this.requests[messageId])) {
             [, rejectCallback] = this.requests[messageId];
           } else {
-            throw new Error(`Error request for message id ${messageId} is not iterable`);
+            throw new OCPPError(ErrorType.PROTOCOL_ERROR, `Error request for message id ${messageId} is not iterable`);
           }
           delete this.requests[messageId];
           rejectCallback(new OCPPError(commandName, commandPayload.toString(), errorDetails));
@@ -688,7 +695,7 @@ export default class ChargingStation {
         default:
           errMsg = `${this.logPrefix()} Wrong message type ${messageType}`;
           logger.error(errMsg);
-          throw new Error(errMsg);
+          throw new OCPPError(ErrorType.PROTOCOL_ERROR, errMsg);
       }
     } catch (error) {
       // Log
@@ -716,7 +723,7 @@ export default class ChargingStation {
   }
 
   private getTemplateChargingStationConfiguration(): ChargingStationConfiguration {
-    return this.stationInfo.Configuration ? this.stationInfo.Configuration : {} as ChargingStationConfiguration;
+    return this.stationInfo.Configuration ?? {} as ChargingStationConfiguration;
   }
 
   private getAuthorizationFile(): string | undefined {
@@ -840,9 +847,6 @@ export default class ChargingStation {
     }
     // Start the ATG
     this.startAutomaticTransactionGenerator();
-    if (this.getEnableStatistics()) {
-      this.performanceStatistics.start();
-    }
   }
 
   private startAutomaticTransactionGenerator() {
@@ -884,7 +888,7 @@ export default class ChargingStation {
       : 0;
     if (webSocketPingInterval > 0 && !this.webSocketPingSetInterval) {
       this.webSocketPingSetInterval = setInterval(() => {
-        if (this.isWebSocketOpen()) {
+        if (this.isWebSocketConnectionOpened()) {
           this.wsConnection.ping((): void => { });
         }
       }, webSocketPingInterval * 1000);
@@ -936,10 +940,13 @@ export default class ChargingStation {
     }
   }
 
-  private openWSConnection(options?: WebSocket.ClientOptions, forceCloseOpened = false): void {
-    options ?? {} as WebSocket.ClientOptions;
-    options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
-    if (this.isWebSocketOpen() && forceCloseOpened) {
+  private openWSConnection(options?: ClientOptions & ClientRequestArgs, forceCloseOpened = false): void {
+    options = options ?? {};
+    options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000;
+    if (!Utils.isNullOrUndefined(this.stationInfo.supervisionUser) && !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword)) {
+      options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`;
+    }
+    if (this.isWebSocketConnectionOpened() && forceCloseOpened) {
       this.wsConnection.close();
     }
     let protocol;
@@ -990,13 +997,17 @@ export default class ChargingStation {
           logger.debug(this.logPrefix() + ' Template file ' + this.stationTemplateFile + ' have changed, reload');
           // Initialize
           this.initialize();
-          // Stop the ATG
+          // Restart the ATG
           if (!this.stationInfo.AutomaticTransactionGenerator.enable &&
           this.automaticTransactionGeneration) {
             await this.automaticTransactionGeneration.stop();
           }
-          // Start the ATG
           this.startAutomaticTransactionGenerator();
+          if (this.getEnableStatistics()) {
+            this.performanceStatistics.restart();
+          } else {
+            this.performanceStatistics.stop();
+          }
           // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
         } catch (error) {
           logger.error(this.logPrefix() + ' Charging station template file monitoring error: %j', error);
@@ -1011,7 +1022,9 @@ export default class ChargingStation {
     return !Utils.isUndefined(this.stationInfo.reconnectExponentialDelay) ? this.stationInfo.reconnectExponentialDelay : false;
   }
 
-  private async reconnect(error: any): Promise<void> {
+  private async reconnect(error: unknown): Promise<void> {
+    // Stop WebSocket ping
+    this.stopWebSocketPing();
     // Stop heartbeat
     this.stopHeartbeat();
     // Stop the ATG if needed