Import cleanup
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
index 7212f09d0cf7b6832f494ddf0fb0925a47bac440..086278784cb5eb7b1f1e53286207c6d41314d03e 100644 (file)
@@ -1,21 +1,27 @@
+import BaseError from '../exception/BaseError';
 import { RequestCommand } from '../types/ocpp/Requests';
 import {
+  AuthorizationStatus,
   StartTransactionRequest,
   StartTransactionResponse,
-  StopTransactionReason,
   StopTransactionRequest,
   StopTransactionResponse,
 } from '../types/ocpp/Transaction';
 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 +29,131 @@ 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;
-
-    if (payload.hashId !== this.chargingStation.hashId) {
+  private async requestHandler(messageEvent: MessageEvent): Promise<void> {
+    if (this.isResponse(messageEvent.data)) {
       return;
     }
+    const [uuid, command, requestPayload] = this.validateMessageEvent(messageEvent)
+      .data as BroadcastChannelRequest;
+
+    if (requestPayload?.hashIds !== undefined || requestPayload?.hashId !== undefined) {
+      if (
+        requestPayload?.hashId === undefined &&
+        requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
+      ) {
+        return;
+      }
+      if (
+        requestPayload?.hashIds === undefined &&
+        requestPayload?.hashId !== this.chargingStation.stationInfo.hashId
+      ) {
+        return;
+      }
+      if (requestPayload?.hashId !== undefined) {
+        logger.warn(
+          `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
+        );
+      }
+    }
 
-    // TODO: return a response stating the command success or failure
+    let responsePayload: BroadcastChannelResponsePayload;
+    let commandResponse: CommandResponse;
+    try {
+      commandResponse = await this.commandHandler(command, requestPayload);
+      if (commandResponse === undefined) {
+        responsePayload = {
+          hashId: this.chargingStation.stationInfo.hashId,
+          status: ResponseStatus.SUCCESS,
+        };
+      } else {
+        responsePayload = {
+          hashId: this.chargingStation.stationInfo.hashId,
+          status: this.commandResponseToResponseStatus(commandResponse),
+        };
+      }
+    } catch (error) {
+      logger.error(
+        `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
+        error
+      );
+      responsePayload = {
+        hashId: this.chargingStation.stationInfo.hashId,
+        status: ResponseStatus.FAILURE,
+        command,
+        requestPayload,
+        commandResponse,
+        errorMessage: (error as Error).message,
+        errorStack: (error as Error).stack,
+      };
+    }
+    this.sendResponse([uuid, responsePayload]);
+  }
+
+  private messageErrorHandler(messageEvent: MessageEvent): void {
+    logger.error(
+      `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
+      { messageEvent }
+    );
+  }
+
+  private async commandHandler(
+    command: BroadcastChannelProcedureName,
+    requestPayload: BroadcastChannelRequestPayload
+  ): Promise<CommandResponse | undefined> {
     switch (command) {
+      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;
       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,
+            true
           ),
-          idTag: this.chargingStation.getTransactionIdTag(payload.transactionId),
-          reason: StopTransactionReason.NONE,
+          idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
+          ...(requestPayload.reason && { reason: requestPayload.reason }),
         });
+      case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR:
+        this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds);
         break;
-      case BroadcastChannelProcedureName.START_CHARGING_STATION:
-        this.chargingStation.start();
-        break;
-      case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
-        await this.chargingStation.stop();
+      case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR:
+        this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds);
         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;
   }
 }