refactor: add helper to test connector
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPRequestService.ts
index 0d95ffa5493f8f4b06677c43f7cf47231ff9ee30..a50500dc24047f7a7eb3276a03d725cedf69fb44 100644 (file)
@@ -1,34 +1,33 @@
 import Ajv, { type JSONSchemaType } from 'ajv';
 import ajvFormats from 'ajv-formats';
 
-import OCPPError from '../../exception/OCPPError';
-import PerformanceStatistics from '../../performance/PerformanceStatistics';
-import type { EmptyObject } from '../../types/EmptyObject';
-import type { HandleErrorParams } from '../../types/Error';
-import type { JsonObject, JsonType } from '../../types/JsonType';
-import { ErrorType } from '../../types/ocpp/ErrorType';
-import { MessageType } from '../../types/ocpp/MessageType';
-import type { OCPPVersion } from '../../types/ocpp/OCPPVersion';
+import { OCPPConstants, type OCPPResponseService, OCPPServiceUtils } from './internal';
+import type { ChargingStation } from '../../charging-station';
+import { OCPPError } from '../../exception';
+import { PerformanceStatistics } from '../../performance';
 import {
+  type EmptyObject,
   type ErrorCallback,
+  type ErrorResponse,
+  ErrorType,
+  type HandleErrorParams,
   type IncomingRequestCommand,
+  type JsonObject,
+  type JsonType,
+  MessageType,
+  type OCPPVersion,
   type OutgoingRequest,
   RequestCommand,
   type RequestParams,
+  type Response,
   type ResponseCallback,
   type ResponseType,
-} from '../../types/ocpp/Requests';
-import type { ErrorResponse, Response } from '../../types/ocpp/Responses';
-import Constants from '../../utils/Constants';
-import logger from '../../utils/Logger';
-import Utils from '../../utils/Utils';
-import type ChargingStation from '../ChargingStation';
-import type OCPPResponseService from './OCPPResponseService';
-import { OCPPServiceUtils } from './OCPPServiceUtils';
+} from '../../types';
+import { Constants, Utils, logger } from '../../utils';
 
 const moduleName = 'OCPPRequestService';
 
-export default abstract class OCPPRequestService {
+export abstract class OCPPRequestService {
   private static instance: OCPPRequestService | null = null;
   private readonly version: OCPPVersion;
   private readonly ajv: Ajv;
@@ -43,14 +42,63 @@ export default abstract class OCPPRequestService {
     });
     ajvFormats(this.ajv);
     this.ocppResponseService = ocppResponseService;
-    this.requestHandler.bind(this);
-    this.sendMessage.bind(this);
-    this.sendResponse.bind(this);
-    this.sendError.bind(this);
-    this.internalSendMessage.bind(this);
-    this.buildMessageToSend.bind(this);
-    this.validateRequestPayload.bind(this);
-    this.validateIncomingRequestResponsePayload.bind(this);
+    this.requestHandler = this.requestHandler.bind(this) as <
+      ReqType extends JsonType,
+      ResType extends JsonType
+    >(
+      chargingStation: ChargingStation,
+      commandName: RequestCommand,
+      commandParams?: JsonType,
+      params?: RequestParams
+    ) => Promise<ResType>;
+    this.sendMessage = this.sendMessage.bind(this) as (
+      chargingStation: ChargingStation,
+      messageId: string,
+      messagePayload: JsonType,
+      commandName: RequestCommand,
+      params?: RequestParams
+    ) => Promise<ResponseType>;
+    this.sendResponse = this.sendResponse.bind(this) as (
+      chargingStation: ChargingStation,
+      messageId: string,
+      messagePayload: JsonType,
+      commandName: IncomingRequestCommand
+    ) => Promise<ResponseType>;
+    this.sendError = this.sendError.bind(this) as (
+      chargingStation: ChargingStation,
+      messageId: string,
+      ocppError: OCPPError,
+      commandName: RequestCommand | IncomingRequestCommand
+    ) => Promise<ResponseType>;
+    this.internalSendMessage = this.internalSendMessage.bind(this) as (
+      chargingStation: ChargingStation,
+      messageId: string,
+      messagePayload: JsonType | OCPPError,
+      messageType: MessageType,
+      commandName: RequestCommand | IncomingRequestCommand,
+      params?: RequestParams
+    ) => Promise<ResponseType>;
+    this.buildMessageToSend = this.buildMessageToSend.bind(this) as (
+      chargingStation: ChargingStation,
+      messageId: string,
+      messagePayload: JsonType | OCPPError,
+      messageType: MessageType,
+      commandName: RequestCommand | IncomingRequestCommand,
+      responseCallback: ResponseCallback,
+      errorCallback: ErrorCallback
+    ) => string;
+    this.validateRequestPayload = this.validateRequestPayload.bind(this) as <T extends JsonObject>(
+      chargingStation: ChargingStation,
+      commandName: RequestCommand | IncomingRequestCommand,
+      payload: T
+    ) => boolean;
+    this.validateIncomingRequestResponsePayload = this.validateIncomingRequestResponsePayload.bind(
+      this
+    ) as <T extends JsonObject>(
+      chargingStation: ChargingStation,
+      commandName: RequestCommand | IncomingRequestCommand,
+      payload: T
+    ) => boolean;
   }
 
   public static getInstance<T extends OCPPRequestService>(
@@ -113,6 +161,7 @@ export default abstract class OCPPRequestService {
     params: RequestParams = {
       skipBufferingOnError: false,
       triggerMessage: false,
+      throwError: false,
     }
   ): Promise<ResponseType> {
     try {
@@ -125,7 +174,9 @@ export default abstract class OCPPRequestService {
         params
       );
     } catch (error) {
-      this.handleSendMessageError(chargingStation, commandName, error as Error);
+      this.handleSendMessageError(chargingStation, commandName, error as Error, {
+        throwError: params.throwError,
+      });
     }
   }
 
@@ -208,7 +259,7 @@ export default abstract class OCPPRequestService {
     messageId: string,
     messagePayload: JsonType | OCPPError,
     messageType: MessageType,
-    commandName?: RequestCommand | IncomingRequestCommand,
+    commandName: RequestCommand | IncomingRequestCommand,
     params: RequestParams = {
       skipBufferingOnError: false,
       triggerMessage: false,
@@ -228,76 +279,15 @@ export default abstract class OCPPRequestService {
       // Send a message through wsConnection
       return Utils.promiseWithTimeout(
         new Promise((resolve, reject) => {
-          const messageToSend = this.buildMessageToSend(
-            chargingStation,
-            messageId,
-            messagePayload,
-            messageType,
-            commandName,
-            responseCallback,
-            errorCallback
-          );
-          if (chargingStation.getEnableStatistics() === true) {
-            chargingStation.performanceStatistics.addRequestStatistic(commandName, messageType);
-          }
-          let sendError = false;
-          // Check if wsConnection opened
-          if (chargingStation.isWebSocketConnectionOpened() === true) {
-            const beginId = PerformanceStatistics.beginMeasure(commandName as string);
-            try {
-              chargingStation.wsConnection.send(messageToSend);
-            } catch (error) {
-              sendError = true;
-            }
-            PerformanceStatistics.endMeasure(commandName as string, beginId);
-            logger.debug(
-              `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
-                messageType
-              )} payload: ${messageToSend}`
-            );
-          }
-          const wsClosedOrErrored =
-            chargingStation.isWebSocketConnectionOpened() === false || sendError === true;
-          if (wsClosedOrErrored && params.skipBufferingOnError === false) {
-            // Buffer
-            chargingStation.bufferMessage(messageToSend);
-            // Reject and keep request in the cache
-            return reject(
-              new OCPPError(
-                ErrorType.GENERIC_ERROR,
-                `WebSocket closed or errored for buffered message id '${messageId}' with content '${messageToSend}'`,
-                commandName,
-                (messagePayload as JsonObject)?.details ?? {}
-              )
-            );
-          } else if (wsClosedOrErrored) {
-            const ocppError = new OCPPError(
-              ErrorType.GENERIC_ERROR,
-              `WebSocket closed or errored for non buffered message id '${messageId}' with content '${messageToSend}'`,
-              commandName,
-              (messagePayload as JsonObject)?.details ?? {}
-            );
-            // Reject response
-            if (messageType !== MessageType.CALL_MESSAGE) {
-              return reject(ocppError);
-            }
-            // Reject and remove request from the cache
-            return errorCallback(ocppError, false);
-          }
-          // Resolve response
-          if (messageType !== MessageType.CALL_MESSAGE) {
-            return resolve(messagePayload);
-          }
-
           /**
            * Function that will receive the request's response
            *
            * @param payload -
            * @param requestPayload -
            */
-          function responseCallback(payload: JsonType, requestPayload: JsonType): void {
+          const responseCallback = (payload: JsonType, requestPayload: JsonType): void => {
             if (chargingStation.getEnableStatistics() === true) {
-              chargingStation.performanceStatistics.addRequestStatistic(
+              chargingStation.performanceStatistics?.addRequestStatistic(
                 commandName,
                 MessageType.CALL_RESULT_MESSAGE
               );
@@ -319,7 +309,7 @@ export default abstract class OCPPRequestService {
               .finally(() => {
                 chargingStation.requests.delete(messageId);
               });
-          }
+          };
 
           /**
            * Function that will receive the request's error response
@@ -327,15 +317,15 @@ export default abstract class OCPPRequestService {
            * @param error -
            * @param requestStatistic -
            */
-          function errorCallback(error: OCPPError, requestStatistic = true): void {
+          const errorCallback = (error: OCPPError, requestStatistic = true): void => {
             if (requestStatistic === true && chargingStation.getEnableStatistics() === true) {
-              chargingStation.performanceStatistics.addRequestStatistic(
+              chargingStation.performanceStatistics?.addRequestStatistic(
                 commandName,
                 MessageType.CALL_ERROR_MESSAGE
               );
             }
             logger.error(
-              `${chargingStation.logPrefix()} Error occurred at ${self.getMessageTypeString(
+              `${chargingStation.logPrefix()} Error occurred at ${OCPPServiceUtils.getMessageTypeString(
                 messageType
               )} command ${commandName} with PDU %j:`,
               messagePayload,
@@ -343,14 +333,81 @@ export default abstract class OCPPRequestService {
             );
             chargingStation.requests.delete(messageId);
             reject(error);
+          };
+
+          if (chargingStation.getEnableStatistics() === true) {
+            chargingStation.performanceStatistics?.addRequestStatistic(commandName, messageType);
+          }
+          const messageToSend = this.buildMessageToSend(
+            chargingStation,
+            messageId,
+            messagePayload,
+            messageType,
+            commandName,
+            responseCallback,
+            errorCallback
+          );
+          let sendError = false;
+          // Check if wsConnection opened
+          const wsOpened = chargingStation.isWebSocketConnectionOpened() === true;
+          if (wsOpened) {
+            const beginId = PerformanceStatistics.beginMeasure(commandName);
+            try {
+              chargingStation.wsConnection?.send(messageToSend);
+              logger.debug(
+                `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${OCPPServiceUtils.getMessageTypeString(
+                  messageType
+                )} payload: ${messageToSend}`
+              );
+            } catch (error) {
+              logger.error(
+                `${chargingStation.logPrefix()} >> Command '${commandName}' failed to send ${OCPPServiceUtils.getMessageTypeString(
+                  messageType
+                )} payload: ${messageToSend}:`,
+                error
+              );
+              sendError = true;
+            }
+            PerformanceStatistics.endMeasure(commandName, beginId);
+          }
+          const wsClosedOrErrored = !wsOpened || sendError === true;
+          if (wsClosedOrErrored && params.skipBufferingOnError === false) {
+            // Buffer
+            chargingStation.bufferMessage(messageToSend);
+            // Reject and keep request in the cache
+            return reject(
+              new OCPPError(
+                ErrorType.GENERIC_ERROR,
+                `WebSocket closed or errored for buffered message id '${messageId}' with content '${messageToSend}'`,
+                commandName,
+                (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FREEZED_OBJECT
+              )
+            );
+          } else if (wsClosedOrErrored) {
+            const ocppError = new OCPPError(
+              ErrorType.GENERIC_ERROR,
+              `WebSocket closed or errored for non buffered message id '${messageId}' with content '${messageToSend}'`,
+              commandName,
+              (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FREEZED_OBJECT
+            );
+            // Reject response
+            if (messageType !== MessageType.CALL_MESSAGE) {
+              return reject(ocppError);
+            }
+            // Reject and remove request from the cache
+            return errorCallback(ocppError, false);
+          }
+          // Resolve response
+          if (messageType !== MessageType.CALL_MESSAGE) {
+            return resolve(messagePayload);
           }
         }),
-        Constants.OCPP_WEBSOCKET_TIMEOUT,
+        OCPPConstants.OCPP_WEBSOCKET_TIMEOUT,
         new OCPPError(
           ErrorType.GENERIC_ERROR,
           `Timeout for message id '${messageId}'`,
           commandName,
-          (messagePayload as JsonObject)?.details ?? {}
+          (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FREEZED_OBJECT
         ),
         () => {
           messageType === MessageType.CALL_MESSAGE && chargingStation.requests.delete(messageId);
@@ -369,9 +426,9 @@ export default abstract class OCPPRequestService {
     messageId: string,
     messagePayload: JsonType | OCPPError,
     messageType: MessageType,
-    commandName?: RequestCommand | IncomingRequestCommand,
-    responseCallback?: ResponseCallback,
-    errorCallback?: ErrorCallback
+    commandName: RequestCommand | IncomingRequestCommand,
+    responseCallback: ResponseCallback,
+    errorCallback: ErrorCallback
   ): string {
     let messageToSend: string;
     // Type of message
@@ -379,13 +436,13 @@ export default abstract class OCPPRequestService {
       // Request
       case MessageType.CALL_MESSAGE:
         // Build request
+        this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonObject);
         chargingStation.requests.set(messageId, [
           responseCallback,
           errorCallback,
           commandName,
           messagePayload as JsonType,
         ]);
-        this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonObject);
         messageToSend = JSON.stringify([
           messageType,
           messageId,
@@ -418,17 +475,6 @@ export default abstract class OCPPRequestService {
     return messageToSend;
   }
 
-  private getMessageTypeString(messageType: MessageType): string {
-    switch (messageType) {
-      case MessageType.CALL_MESSAGE:
-        return 'request';
-      case MessageType.CALL_RESULT_MESSAGE:
-        return 'response';
-      case MessageType.CALL_ERROR_MESSAGE:
-        return 'error';
-    }
-  }
-
   private handleSendMessageError(
     chargingStation: ChargingStation,
     commandName: RequestCommand | IncomingRequestCommand,