refactor(simulator): remove unneeded type casting
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index c74daceb008b3195adda78261a6d0e3a66ad1fa9..ac5aaa93c451d8c877c9d93b0345550b74dd37e8 100644 (file)
@@ -4,32 +4,38 @@ import crypto from 'node:crypto';
 import fs from 'node:fs';
 import path from 'node:path';
 import { URL } from 'node:url';
-import { parentPort } from 'worker_threads';
+import { parentPort } from 'node:worker_threads';
 
 import merge from 'just-merge';
 import WebSocket, { type RawData } from 'ws';
 
-import { AuthorizedTagsCache } from './AuthorizedTagsCache';
-import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator';
-import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
-import { ChargingStationUtils } from './ChargingStationUtils';
-import { ChargingStationWorkerBroadcastChannel } from './ChargingStationWorkerBroadcastChannel';
-import { MessageChannelUtils } from './MessageChannelUtils';
 import {
-  OCPP16IncomingRequestService,
+  AuthorizedTagsCache,
+  AutomaticTransactionGenerator,
+  ChargingStationConfigurationUtils,
+  ChargingStationUtils,
+  ChargingStationWorkerBroadcastChannel,
+  MessageChannelUtils,
+  SharedLRUCache,
+} from './internal';
+import {
+  // OCPP16IncomingRequestService,
   OCPP16RequestService,
-  OCPP16ResponseService,
+  // OCPP16ResponseService,
   OCPP16ServiceUtils,
   OCPP20IncomingRequestService,
   OCPP20RequestService,
-  OCPP20ResponseService,
+  // OCPP20ResponseService,
   type OCPPIncomingRequestService,
   type OCPPRequestService,
-  OCPPServiceUtils,
+  // OCPPServiceUtils,
 } from './ocpp';
-import { SharedLRUCache } from './SharedLRUCache';
+import { OCPP16IncomingRequestService } from './ocpp/1.6/OCPP16IncomingRequestService';
+import { OCPP16ResponseService } from './ocpp/1.6/OCPP16ResponseService';
+import { OCPP20ResponseService } from './ocpp/2.0/OCPP20ResponseService';
+import { OCPPServiceUtils } from './ocpp/OCPPServiceUtils';
 import { BaseError, OCPPError } from '../exception';
-import { PerformanceStatistics } from '../performance/PerformanceStatistics';
+import { PerformanceStatistics } from '../performance';
 import {
   type AutomaticTransactionGeneratorConfiguration,
   AvailabilityType,
@@ -77,17 +83,20 @@ import {
   type StopTransactionResponse,
   SupervisionUrlDistribution,
   SupportedFeatureProfiles,
-  VendorDefaultParametersKey,
+  VendorParametersKey,
   type WSError,
   WebSocketCloseEventStatusCode,
   type WsOptions,
 } from '../types';
-import { Configuration } from '../utils/Configuration';
-import { Constants } from '../utils/Constants';
-import { ACElectricUtils, DCElectricUtils } from '../utils/ElectricUtils';
-import { FileUtils } from '../utils/FileUtils';
-import { logger } from '../utils/Logger';
-import { Utils } from '../utils/Utils';
+import {
+  ACElectricUtils,
+  Configuration,
+  Constants,
+  DCElectricUtils,
+  FileUtils,
+  Utils,
+  logger,
+} from '../utils';
 
 export class ChargingStation {
   public readonly index: number;
@@ -564,7 +573,7 @@ export class ChargingStation {
         this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
         this.templateFileWatcher?.close();
         this.sharedLRUCache.deleteChargingStationTemplate(this.stationInfo?.templateHash);
-        this.bootNotificationResponse = undefined;
+        delete this.bootNotificationResponse;
         this.started = false;
         parentPort?.postMessage(MessageChannelUtils.buildStoppedMessage(this));
         this.stopping = false;
@@ -616,7 +625,7 @@ export class ChargingStation {
   }
 
   public openWSConnection(
-    options: WsOptions = this.stationInfo?.wsOptions ?? {},
+    options: WsOptions = this.stationInfo?.wsOptions ?? Constants.EMPTY_OBJECT,
     params: { closeOpened?: boolean; terminateOpened?: boolean } = {
       closeOpened: false,
       terminateOpened: false,
@@ -643,18 +652,6 @@ export class ChargingStation {
     if (params?.terminateOpened) {
       this.terminateWSConnection();
     }
-    const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
-    let protocol: string;
-    switch (ocppVersion) {
-      case OCPPVersion.VERSION_16:
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        protocol = `ocpp${ocppVersion}`;
-        break;
-      default:
-        this.handleUnsupportedVersion(ocppVersion);
-        break;
-    }
 
     if (this.isWebSocketConnectionOpened() === true) {
       logger.warn(
@@ -667,7 +664,11 @@ export class ChargingStation {
       `${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.toString()}`
     );
 
-    this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options);
+    this.wsConnection = new WebSocket(
+      this.wsConnectionUrl,
+      `ocpp${this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16}`,
+      options
+    );
 
     // Handle WebSocket message
     this.wsConnection.on(
@@ -794,7 +795,7 @@ export class ChargingStation {
   }
 
   private getSupervisionUrlOcppKey(): string {
-    return this.stationInfo.supervisionUrlOcppKey ?? VendorDefaultParametersKey.ConnectionUrl;
+    return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl;
   }
 
   private getTemplateFromFile(): ChargingStationTemplate | undefined {
@@ -893,7 +894,7 @@ export class ChargingStation {
         },
         reset: true,
       },
-      stationTemplate?.firmwareUpgrade ?? {}
+      stationTemplate?.firmwareUpgrade ?? Constants.EMPTY_OBJECT
     );
     stationInfo.resetTime = !Utils.isNullOrUndefined(stationTemplate?.resetTime)
       ? stationTemplate.resetTime * 1000
@@ -986,7 +987,7 @@ export class ChargingStation {
     this.stationInfo = this.getStationInfo();
     this.saveStationInfo();
     // Avoid duplication of connectors related information in RAM
-    this.stationInfo?.Connectors && delete this.stationInfo.Connectors;
+    delete this.stationInfo?.Connectors;
     this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
     if (this.getEnableStatistics() === true) {
       this.performanceStatistics = PerformanceStatistics.getInstance(
@@ -1002,27 +1003,7 @@ export class ChargingStation {
     // OCPP configuration
     this.ocppConfiguration = this.getOcppConfiguration();
     this.initializeOcppConfiguration();
-    const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
-    switch (ocppVersion) {
-      case OCPPVersion.VERSION_16:
-        this.ocppIncomingRequestService =
-          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>();
-        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
-          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
-        );
-        break;
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        this.ocppIncomingRequestService =
-          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>();
-        this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
-          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
-        );
-        break;
-      default:
-        this.handleUnsupportedVersion(ocppVersion);
-        break;
-    }
+    this.initializeOcppServices();
     if (this.stationInfo?.autoRegister === true) {
       this.bootNotificationResponse = {
         currentTime: new Date(),
@@ -1050,6 +1031,30 @@ export class ChargingStation {
     }
   }
 
+  private initializeOcppServices(): void {
+    const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
+    switch (ocppVersion) {
+      case OCPPVersion.VERSION_16:
+        this.ocppIncomingRequestService =
+          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>();
+        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
+          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
+        );
+        break;
+      case OCPPVersion.VERSION_20:
+      case OCPPVersion.VERSION_201:
+        this.ocppIncomingRequestService =
+          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>();
+        this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
+          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
+        );
+        break;
+      default:
+        this.handleUnsupportedVersion(ocppVersion);
+        break;
+    }
+  }
+
   private initializeOcppConfiguration(): void {
     if (
       !ChargingStationConfigurationUtils.getConfigurationKey(
@@ -1312,7 +1317,7 @@ export class ChargingStation {
           this.templateFile
         } with connector ${connectorId} status configuration defined, undefine it`
       );
-      connectorStatus.status = undefined;
+      delete connectorStatus.status;
     }
   }
 
@@ -1353,7 +1358,7 @@ export class ChargingStation {
           fs.mkdirSync(path.dirname(this.configurationFile), { recursive: true });
         }
         const configurationData: ChargingStationConfiguration =
-          this.getConfigurationFromFile() ?? {};
+          Utils.cloneObject(this.getConfigurationFromFile()) ?? Constants.EMPTY_OBJECT;
         this.ocppConfiguration?.configurationKey &&
           (configurationData.configurationKey = this.ocppConfiguration.configurationKey);
         this.stationInfo && (configurationData.stationInfo = this.stationInfo);
@@ -1405,7 +1410,10 @@ export class ChargingStation {
       const configurationFromFile = this.getConfigurationFromFile();
       configuration = configurationFromFile?.configurationKey && configurationFromFile;
     }
-    configuration && delete configuration.stationInfo;
+    if (!Utils.isNullOrUndefined(configuration)) {
+      delete configuration.stationInfo;
+      delete configuration.configurationHash;
+    }
     return configuration;
   }
 
@@ -1491,107 +1499,107 @@ export class ChargingStation {
     parentPort?.postMessage(MessageChannelUtils.buildUpdatedMessage(this));
   }
 
+  private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined {
+    const cachedRequest = this.requests.get(messageId);
+    if (Array.isArray(cachedRequest) === true) {
+      return cachedRequest;
+    }
+    throw new OCPPError(
+      ErrorType.PROTOCOL_ERROR,
+      `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString(
+        messageType
+      )} is not an array`,
+      undefined,
+      cachedRequest as JsonType
+    );
+  }
+
+  private async handleIncomingMessage(request: IncomingRequest): Promise<void> {
+    const [messageType, messageId, commandName, commandPayload] = request;
+    if (this.getEnableStatistics() === true) {
+      this.performanceStatistics?.addRequestStatistic(commandName, messageType);
+    }
+    logger.debug(
+      `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
+        request
+      )}`
+    );
+    // Process the message
+    await this.ocppIncomingRequestService.incomingRequestHandler(
+      this,
+      messageId,
+      commandName,
+      commandPayload
+    );
+  }
+
+  private handleResponseMessage(response: Response): void {
+    const [messageType, messageId, commandPayload] = response;
+    if (this.requests.has(messageId) === false) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Response for unknown message id ${messageId}`,
+        undefined,
+        commandPayload
+      );
+    }
+    // Respond
+    const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
+      messageType,
+      messageId
+    );
+    logger.debug(
+      `${this.logPrefix()} << Command '${
+        requestCommandName ?? Constants.UNKNOWN_COMMAND
+      }' received response payload: ${JSON.stringify(response)}`
+    );
+    responseCallback(commandPayload, requestPayload);
+  }
+
+  private handleErrorMessage(errorResponse: ErrorResponse): void {
+    const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse;
+    if (this.requests.has(messageId) === false) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Error response for unknown message id ${messageId}`,
+        undefined,
+        { errorType, errorMessage, errorDetails }
+      );
+    }
+    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId);
+    logger.debug(
+      `${this.logPrefix()} << Command '${
+        requestCommandName ?? Constants.UNKNOWN_COMMAND
+      }' received error response payload: ${JSON.stringify(errorResponse)}`
+    );
+    errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
+  }
+
   private async onMessage(data: RawData): Promise<void> {
+    let request: IncomingRequest | Response | ErrorResponse;
     let messageType: number;
-    let messageId: string;
-    let commandName: IncomingRequestCommand;
-    let commandPayload: JsonType;
-    let errorType: ErrorType;
-    let errorMessage: string;
-    let errorDetails: JsonType;
-    let responseCallback: ResponseCallback;
-    let errorCallback: ErrorCallback;
-    let requestCommandName: RequestCommand | IncomingRequestCommand;
-    let requestPayload: JsonType;
-    let cachedRequest: CachedRequest;
     let errMsg: string;
     try {
-      const request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
+      request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse;
       if (Array.isArray(request) === true) {
-        [messageType, messageId] = request;
+        [messageType] = request;
         // Check the type of message
         switch (messageType) {
           // Incoming Message
           case MessageType.CALL_MESSAGE:
-            [, , commandName, commandPayload] = request as IncomingRequest;
-            if (this.getEnableStatistics() === true) {
-              this.performanceStatistics?.addRequestStatistic(commandName, messageType);
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
-                request
-              )}`
-            );
-            // Process the message
-            await this.ocppIncomingRequestService.incomingRequestHandler(
-              this,
-              messageId,
-              commandName,
-              commandPayload
-            );
+            await this.handleIncomingMessage(request as IncomingRequest);
             break;
-          // Outcome Message
+          // Response Message
           case MessageType.CALL_RESULT_MESSAGE:
-            [, , commandPayload] = request as Response;
-            if (this.requests.has(messageId) === false) {
-              // Error
-              throw new OCPPError(
-                ErrorType.INTERNAL_ERROR,
-                `Response for unknown message id ${messageId}`,
-                undefined,
-                commandPayload
-              );
-            }
-            // Respond
-            cachedRequest = this.requests.get(messageId);
-            if (Array.isArray(cachedRequest) === true) {
-              [responseCallback, errorCallback, requestCommandName, requestPayload] = cachedRequest;
-            } else {
-              throw new OCPPError(
-                ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} response is not an array`,
-                undefined,
-                cachedRequest as unknown as JsonType
-              );
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${
-                requestCommandName ?? Constants.UNKNOWN_COMMAND
-              }' received response payload: ${JSON.stringify(request)}`
-            );
-            responseCallback(commandPayload, requestPayload);
+            this.handleResponseMessage(request as Response);
             break;
           // Error Message
           case MessageType.CALL_ERROR_MESSAGE:
-            [, , errorType, errorMessage, errorDetails] = request as ErrorResponse;
-            if (this.requests.has(messageId) === false) {
-              // Error
-              throw new OCPPError(
-                ErrorType.INTERNAL_ERROR,
-                `Error response for unknown message id ${messageId}`,
-                undefined,
-                { errorType, errorMessage, errorDetails }
-              );
-            }
-            cachedRequest = this.requests.get(messageId);
-            if (Array.isArray(cachedRequest) === true) {
-              [, errorCallback, requestCommandName] = cachedRequest;
-            } else {
-              throw new OCPPError(
-                ErrorType.PROTOCOL_ERROR,
-                `Cached request for message id ${messageId} error response is not an array`,
-                undefined,
-                cachedRequest as unknown as JsonType
-              );
-            }
-            logger.debug(
-              `${this.logPrefix()} << Command '${
-                requestCommandName ?? Constants.UNKNOWN_COMMAND
-              }' received error response payload: ${JSON.stringify(request)}`
-            );
-            errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails));
+            this.handleErrorMessage(request as ErrorResponse);
             break;
-          // Error
+          // Unknown Message
           default:
             // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
             errMsg = `Wrong message type ${messageType}`;
@@ -1605,38 +1613,20 @@ export class ChargingStation {
         });
       }
     } catch (error) {
-      // Log
-      logger.error(
-        `${this.logPrefix()} Incoming OCPP command '${
-          commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
-        }' message '${data.toString()}'${
-          messageType !== MessageType.CALL_MESSAGE
-            ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
-            : ''
-        } processing error:`,
-        error
-      );
-      if (error instanceof OCPPError === false) {
-        logger.warn(
-          `${this.logPrefix()} Error thrown at incoming OCPP command '${
-            commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
-          }' message '${data.toString()}' handling is not an OCPPError:`,
-          error
-        );
-      }
+      let commandName: IncomingRequestCommand;
+      let requestCommandName: RequestCommand | IncomingRequestCommand;
+      let errorCallback: ErrorCallback;
+      const [, messageId] = request;
       switch (messageType) {
         case MessageType.CALL_MESSAGE:
+          [, , commandName] = request as IncomingRequest;
           // Send error
-          await this.ocppRequestService.sendError(
-            this,
-            messageId,
-            error as OCPPError,
-            commandName ?? requestCommandName ?? null
-          );
+          await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName);
           break;
         case MessageType.CALL_RESULT_MESSAGE:
         case MessageType.CALL_ERROR_MESSAGE:
-          if (errorCallback) {
+          if (this.requests.has(messageId) === true) {
+            [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId);
             // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
             errorCallback(error as OCPPError, false);
           } else {
@@ -1645,6 +1635,24 @@ export class ChargingStation {
           }
           break;
       }
+      if (error instanceof OCPPError === false) {
+        logger.warn(
+          `${this.logPrefix()} Error thrown at incoming OCPP command '${
+            commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+          }' message '${data.toString()}' handling is not an OCPPError:`,
+          error
+        );
+      }
+      logger.error(
+        `${this.logPrefix()} Incoming OCPP command '${
+          commandName ?? requestCommandName ?? Constants.UNKNOWN_COMMAND
+        }' message '${data.toString()}'${
+          messageType !== MessageType.CALL_MESSAGE
+            ? ` matching cached request '${JSON.stringify(this.requests.get(messageId))}'`
+            : ''
+        } processing error:`,
+        error
+      );
     }
   }
 
@@ -1802,7 +1810,7 @@ export class ChargingStation {
         (this.isChargingStationAvailable() === false ||
           this.isConnectorAvailable(connectorId) === false)
       ) {
-        connectorStatus = ConnectorStatusEnum.UNAVAILABLE;
+        connectorStatus = ConnectorStatusEnum.Unavailable;
       } else if (
         !this.getConnectorStatus(connectorId)?.status &&
         this.getConnectorStatus(connectorId)?.bootStatus
@@ -1814,7 +1822,7 @@ export class ChargingStation {
         connectorStatus = this.getConnectorStatus(connectorId)?.status;
       } else {
         // Set default status
-        connectorStatus = ConnectorStatusEnum.AVAILABLE;
+        connectorStatus = ConnectorStatusEnum.Available;
       }
       await this.ocppRequestService.requestHandler<
         StatusNotificationRequest,
@@ -1867,10 +1875,10 @@ export class ChargingStation {
           OCPPServiceUtils.buildStatusNotificationRequest(
             this,
             connectorId,
-            ConnectorStatusEnum.UNAVAILABLE
+            ConnectorStatusEnum.Unavailable
           )
         );
-        this.getConnectorStatus(connectorId).status = undefined;
+        delete this.getConnectorStatus(connectorId)?.status;
       }
     }
   }
@@ -1925,24 +1933,22 @@ export class ChargingStation {
     const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls();
     if (Utils.isNotEmptyArray(supervisionUrls)) {
       switch (Configuration.getSupervisionUrlDistribution()) {
-        case SupervisionUrlDistribution.ROUND_ROBIN:
-          // FIXME
-          this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
-          break;
         case SupervisionUrlDistribution.RANDOM:
           this.configuredSupervisionUrlIndex = Math.floor(
             Utils.secureRandom() * supervisionUrls.length
           );
           break;
+        case SupervisionUrlDistribution.ROUND_ROBIN:
         case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
-          this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
-          break;
         default:
-          logger.error(
-            `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${
-              SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
-            }`
-          );
+          Object.values(SupervisionUrlDistribution).includes(
+            Configuration.getSupervisionUrlDistribution()
+          ) === false &&
+            logger.error(
+              `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' from values '${SupervisionUrlDistribution.toString()}', defaulting to ${
+                SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
+              }`
+            );
           this.configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length;
           break;
       }
@@ -2031,7 +2037,10 @@ export class ChargingStation {
         `${this.logPrefix()} WebSocket connection retry #${this.autoReconnectRetryCount.toString()}`
       );
       this.openWSConnection(
-        { ...(this.stationInfo?.wsOptions ?? {}), handshakeTimeout: reconnectTimeout },
+        {
+          ...(this.stationInfo?.wsOptions ?? Constants.EMPTY_OBJECT),
+          handshakeTimeout: reconnectTimeout,
+        },
         { closeOpened: true }
       );
       this.wsConnectionRestarted = true;