Move hashId to stationInfo
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
1 import BaseError from '../exception/BaseError';
2 import { RequestCommand } from '../types/ocpp/Requests';
3 import {
4 AuthorizationStatus,
5 StartTransactionRequest,
6 StartTransactionResponse,
7 StopTransactionReason,
8 StopTransactionRequest,
9 StopTransactionResponse,
10 } from '../types/ocpp/Transaction';
11 import {
12 BroadcastChannelProcedureName,
13 BroadcastChannelRequest,
14 BroadcastChannelRequestPayload,
15 BroadcastChannelResponsePayload,
16 MessageEvent,
17 } from '../types/WorkerBroadcastChannel';
18 import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
19 import logger from '../utils/Logger';
20 import type ChargingStation from './ChargingStation';
21 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
22
23 const moduleName = 'ChargingStationWorkerBroadcastChannel';
24
25 type CommandResponse = StartTransactionResponse | StopTransactionResponse;
26
27 export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
28 private readonly chargingStation: ChargingStation;
29
30 constructor(chargingStation: ChargingStation) {
31 super();
32 this.chargingStation = chargingStation;
33 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
34 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
35 }
36
37 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
38 if (this.isResponse(messageEvent.data)) {
39 return;
40 }
41 this.validateMessageEvent(messageEvent);
42
43 const [uuid, command, requestPayload] = messageEvent.data as BroadcastChannelRequest;
44
45 if (
46 requestPayload?.hashId === undefined &&
47 (requestPayload?.hashIds as string[])?.includes(this.chargingStation.stationInfo.hashId) ===
48 false
49 ) {
50 return;
51 }
52 if (
53 requestPayload?.hashIds === undefined &&
54 requestPayload?.hashId !== this.chargingStation.stationInfo.hashId
55 ) {
56 return;
57 }
58 if (requestPayload?.hashId !== undefined) {
59 logger.warn(
60 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
61 );
62 }
63
64 let responsePayload: BroadcastChannelResponsePayload;
65 let commandResponse: CommandResponse;
66 try {
67 commandResponse = await this.commandHandler(command, requestPayload);
68 if (commandResponse === undefined) {
69 responsePayload = {
70 hashId: this.chargingStation.stationInfo.hashId,
71 status: ResponseStatus.SUCCESS,
72 };
73 } else {
74 responsePayload = {
75 hashId: this.chargingStation.stationInfo.hashId,
76 status: this.commandResponseToResponseStatus(commandResponse),
77 };
78 }
79 } catch (error) {
80 logger.error(
81 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
82 error
83 );
84 responsePayload = {
85 hashId: this.chargingStation.stationInfo.hashId,
86 status: ResponseStatus.FAILURE,
87 command,
88 requestPayload,
89 commandResponse,
90 errorMessage: (error as Error).message,
91 errorStack: (error as Error).stack,
92 };
93 }
94 this.sendResponse([uuid, responsePayload]);
95 }
96
97 private messageErrorHandler(messageEvent: MessageEvent): void {
98 logger.error(
99 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
100 { messageEvent, messageEventData: messageEvent.data }
101 );
102 }
103
104 private async commandHandler(
105 command: BroadcastChannelProcedureName,
106 requestPayload: BroadcastChannelRequestPayload
107 ): Promise<CommandResponse | undefined> {
108 switch (command) {
109 case BroadcastChannelProcedureName.START_CHARGING_STATION:
110 this.chargingStation.start();
111 break;
112 case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
113 await this.chargingStation.stop();
114 break;
115 case BroadcastChannelProcedureName.OPEN_CONNECTION:
116 this.chargingStation.openWSConnection();
117 break;
118 case BroadcastChannelProcedureName.CLOSE_CONNECTION:
119 this.chargingStation.closeWSConnection();
120 break;
121 case BroadcastChannelProcedureName.START_TRANSACTION:
122 return this.chargingStation.ocppRequestService.requestHandler<
123 StartTransactionRequest,
124 StartTransactionResponse
125 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
126 connectorId: requestPayload.connectorId,
127 idTag: requestPayload.idTag,
128 });
129 case BroadcastChannelProcedureName.STOP_TRANSACTION:
130 return this.chargingStation.ocppRequestService.requestHandler<
131 StopTransactionRequest,
132 StopTransactionResponse
133 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
134 transactionId: requestPayload.transactionId,
135 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
136 requestPayload.transactionId
137 ),
138 idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
139 reason: StopTransactionReason.NONE,
140 });
141 case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR:
142 this.chargingStation.startAutomaticTransactionGenerator();
143 break;
144 case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR:
145 this.chargingStation.stopAutomaticTransactionGenerator();
146 break;
147 default:
148 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
149 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
150 }
151 }
152
153 private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus {
154 if (commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
155 return ResponseStatus.SUCCESS;
156 }
157 return ResponseStatus.FAILURE;
158 }
159 }