Cleanup Array.from() usage
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
index 7212f09d0cf7b6832f494ddf0fb0925a47bac440..6fd226fa326632d42ef251ae4e59707207c9590f 100644 (file)
@@ -1,5 +1,7 @@
+import BaseError from '../exception/BaseError';
 import { RequestCommand } from '../types/ocpp/Requests';
 import {
+  AuthorizationStatus,
   StartTransactionRequest,
   StartTransactionResponse,
   StopTransactionReason,
@@ -9,13 +11,18 @@ import {
 import {
   BroadcastChannelProcedureName,
   BroadcastChannelRequest,
+  BroadcastChannelRequestPayload,
+  BroadcastChannelResponsePayload,
+  MessageEvent,
 } from '../types/WorkerBroadcastChannel';
-import ChargingStation from './ChargingStation';
+import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
+import logger from '../utils/Logger';
+import type ChargingStation from './ChargingStation';
 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
 
 const moduleName = 'ChargingStationWorkerBroadcastChannel';
 
-type MessageEvent = { data: unknown };
+type CommandResponse = StartTransactionResponse | StopTransactionResponse;
 
 export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
   private readonly chargingStation: ChargingStation;
@@ -23,46 +30,123 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
   constructor(chargingStation: ChargingStation) {
     super();
     this.chargingStation = chargingStation;
-    this.onmessage = this.handleRequest.bind(this) as (message: MessageEvent) => void;
+    this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
+    this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
   }
 
-  private async handleRequest(messageEvent: MessageEvent): Promise<void> {
-    const [, command, payload] = messageEvent.data as BroadcastChannelRequest;
+  private async requestHandler(messageEvent: MessageEvent): Promise<void> {
+    if (this.isResponse(messageEvent.data)) {
+      return;
+    }
+    this.validateMessageEvent(messageEvent);
+
+    const [uuid, command, requestPayload] = messageEvent.data as BroadcastChannelRequest;
 
-    if (payload.hashId !== this.chargingStation.hashId) {
+    if (
+      requestPayload?.hashId === undefined &&
+      (requestPayload?.hashIds as string[])?.includes(this.chargingStation.hashId) === false
+    ) {
+      return;
+    }
+    if (
+      requestPayload?.hashIds === undefined &&
+      requestPayload?.hashId !== this.chargingStation.hashId
+    ) {
       return;
     }
+    if (requestPayload?.hashId !== undefined) {
+      logger.warn(
+        `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
+      );
+    }
+
+    let responsePayload: BroadcastChannelResponsePayload;
+    let commandResponse: CommandResponse;
+    try {
+      commandResponse = await this.commandHandler(command, requestPayload);
+      if (commandResponse === undefined) {
+        responsePayload = {
+          hashId: this.chargingStation.hashId,
+          status: ResponseStatus.SUCCESS,
+        };
+      } else {
+        responsePayload = {
+          hashId: this.chargingStation.hashId,
+          status: this.commandResponseToResponseStatus(commandResponse),
+        };
+      }
+    } catch (error) {
+      logger.error(
+        `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
+        error
+      );
+      responsePayload = {
+        hashId: this.chargingStation.hashId,
+        status: ResponseStatus.FAILURE,
+        command,
+        requestPayload,
+        commandResponse,
+        errorMessage: (error as Error).message,
+        errorStack: (error as Error).stack,
+      };
+    }
+    this.sendResponse([uuid, responsePayload]);
+  }
 
-    // TODO: return a response stating the command success or failure
+  private messageErrorHandler(messageEvent: MessageEvent): void {
+    logger.error(
+      `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
+      { messageEvent, messageEventData: messageEvent.data }
+    );
+  }
+
+  private async commandHandler(
+    command: BroadcastChannelProcedureName,
+    requestPayload: BroadcastChannelRequestPayload
+  ): Promise<CommandResponse | undefined> {
     switch (command) {
       case BroadcastChannelProcedureName.START_TRANSACTION:
-        await this.chargingStation.ocppRequestService.requestHandler<
+        return this.chargingStation.ocppRequestService.requestHandler<
           StartTransactionRequest,
           StartTransactionResponse
         >(this.chargingStation, RequestCommand.START_TRANSACTION, {
-          connectorId: payload.connectorId,
-          idTag: payload.idTag,
+          connectorId: requestPayload.connectorId,
+          idTag: requestPayload.idTag,
         });
-        break;
       case BroadcastChannelProcedureName.STOP_TRANSACTION:
-        await this.chargingStation.ocppRequestService.requestHandler<
+        return this.chargingStation.ocppRequestService.requestHandler<
           StopTransactionRequest,
           StopTransactionResponse
         >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
-          transactionId: payload.transactionId,
+          transactionId: requestPayload.transactionId,
           meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
-            payload.transactionId
+            requestPayload.transactionId
           ),
-          idTag: this.chargingStation.getTransactionIdTag(payload.transactionId),
+          idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
           reason: StopTransactionReason.NONE,
         });
-        break;
       case BroadcastChannelProcedureName.START_CHARGING_STATION:
         this.chargingStation.start();
         break;
       case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
         await this.chargingStation.stop();
         break;
+      case BroadcastChannelProcedureName.OPEN_CONNECTION:
+        this.chargingStation.openWSConnection();
+        break;
+      case BroadcastChannelProcedureName.CLOSE_CONNECTION:
+        this.chargingStation.closeWSConnection();
+        break;
+      default:
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
+    }
+  }
+
+  private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus {
+    if (commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
+      return ResponseStatus.SUCCESS;
     }
+    return ResponseStatus.FAILURE;
   }
 }