UI Server: factor out responses handling logic in abstract class
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
CommitLineData
6c8f5d90 1import BaseError from '../exception/BaseError';
a9ed42b2 2import type OCPPError from '../exception/OCPPError';
10db00b2
JB
3import {
4 HeartbeatRequest,
5 RequestCommand,
6 type StatusNotificationRequest,
7} from '../types/ocpp/Requests';
8import type { HeartbeatResponse, StatusNotificationResponse } from '../types/ocpp/Responses';
89b7a234 9import {
6c8f5d90 10 AuthorizationStatus,
89b7a234
JB
11 StartTransactionRequest,
12 StartTransactionResponse,
89b7a234
JB
13 StopTransactionRequest,
14 StopTransactionResponse,
15} from '../types/ocpp/Transaction';
16import {
17 BroadcastChannelProcedureName,
18 BroadcastChannelRequest,
6c8f5d90
JB
19 BroadcastChannelRequestPayload,
20 BroadcastChannelResponsePayload,
21 MessageEvent,
89b7a234 22} from '../types/WorkerBroadcastChannel';
f27eb751 23import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
6c8f5d90 24import logger from '../utils/Logger';
a9ed42b2 25import Utils from '../utils/Utils';
db2336d9 26import type ChargingStation from './ChargingStation';
1598b27c 27import WorkerBroadcastChannel from './WorkerBroadcastChannel';
89b7a234 28
4e3ff94d
JB
29const moduleName = 'ChargingStationWorkerBroadcastChannel';
30
a9ed42b2
JB
31type CommandResponse =
32 | StartTransactionResponse
33 | StopTransactionResponse
10db00b2
JB
34 | StatusNotificationResponse
35 | HeartbeatResponse;
89b7a234 36
1598b27c 37export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
89b7a234
JB
38 private readonly chargingStation: ChargingStation;
39
40 constructor(chargingStation: ChargingStation) {
1598b27c 41 super();
89b7a234 42 this.chargingStation = chargingStation;
02a6943a 43 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
6c8f5d90 44 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
89b7a234
JB
45 }
46
02a6943a 47 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
5dea4c94
JB
48 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
49 if (validatedMessageEvent === false) {
6c8f5d90
JB
50 return;
51 }
5dea4c94
JB
52 if (this.isResponse(validatedMessageEvent.data) === true) {
53 return;
54 }
55 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
6c8f5d90 56
2afb4d15
JB
57 if (
58 requestPayload?.hashIds !== undefined &&
59 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
60 ) {
61 return;
62 }
63 if (requestPayload?.hashId !== undefined) {
64 logger.error(
65 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
66 );
67 return;
4eca248c 68 }
89b7a234 69
6c8f5d90
JB
70 let responsePayload: BroadcastChannelResponsePayload;
71 let commandResponse: CommandResponse;
72 try {
73 commandResponse = await this.commandHandler(command, requestPayload);
74 if (commandResponse === undefined) {
10d244c0 75 responsePayload = {
51c83d6f 76 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
77 status: ResponseStatus.SUCCESS,
78 };
6c8f5d90 79 } else {
10d244c0 80 responsePayload = {
51c83d6f 81 hashId: this.chargingStation.stationInfo.hashId,
10db00b2 82 status: this.commandResponseToResponseStatus(command, commandResponse),
10d244c0 83 };
6c8f5d90
JB
84 }
85 } catch (error) {
86 logger.error(
87 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
88 error
89 );
90 responsePayload = {
51c83d6f 91 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
92 status: ResponseStatus.FAILURE,
93 command,
94 requestPayload,
95 commandResponse,
96 errorMessage: (error as Error).message,
97 errorStack: (error as Error).stack,
a9ed42b2 98 errorDetails: (error as OCPPError).details,
6c8f5d90
JB
99 };
100 }
101 this.sendResponse([uuid, responsePayload]);
102 }
103
104 private messageErrorHandler(messageEvent: MessageEvent): void {
105 logger.error(
106 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
5e3cb728 107 { messageEvent }
6c8f5d90
JB
108 );
109 }
110
111 private async commandHandler(
112 command: BroadcastChannelProcedureName,
113 requestPayload: BroadcastChannelRequestPayload
114 ): Promise<CommandResponse | undefined> {
89b7a234 115 switch (command) {
4f69be04
JB
116 case BroadcastChannelProcedureName.START_CHARGING_STATION:
117 this.chargingStation.start();
118 break;
119 case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
120 await this.chargingStation.stop();
121 break;
122 case BroadcastChannelProcedureName.OPEN_CONNECTION:
123 this.chargingStation.openWSConnection();
124 break;
125 case BroadcastChannelProcedureName.CLOSE_CONNECTION:
126 this.chargingStation.closeWSConnection();
127 break;
89b7a234 128 case BroadcastChannelProcedureName.START_TRANSACTION:
6c8f5d90 129 return this.chargingStation.ocppRequestService.requestHandler<
89b7a234
JB
130 StartTransactionRequest,
131 StartTransactionResponse
132 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
6c8f5d90
JB
133 connectorId: requestPayload.connectorId,
134 idTag: requestPayload.idTag,
89b7a234 135 });
89b7a234 136 case BroadcastChannelProcedureName.STOP_TRANSACTION:
6c8f5d90 137 return this.chargingStation.ocppRequestService.requestHandler<
89b7a234
JB
138 StopTransactionRequest,
139 StopTransactionResponse
140 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
6c8f5d90 141 transactionId: requestPayload.transactionId,
89b7a234 142 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
07989fad
JB
143 requestPayload.transactionId,
144 true
89b7a234 145 ),
cf058664
JB
146 idTag: requestPayload.idTag,
147 reason: requestPayload.reason,
89b7a234 148 });
4f69be04 149 case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR:
a5e9befc 150 this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds);
89b7a234 151 break;
4f69be04 152 case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR:
a5e9befc 153 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds);
db2336d9 154 break;
a9ed42b2
JB
155 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
156 return this.chargingStation.ocppRequestService.requestHandler<
157 StatusNotificationRequest,
158 StatusNotificationResponse
159 >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, {
160 connectorId: requestPayload.connectorId,
161 errorCode: requestPayload.errorCode,
162 status: requestPayload.status,
163 ...(requestPayload.info && { info: requestPayload.info }),
164 ...(requestPayload.timestamp && { timestamp: requestPayload.timestamp }),
165 ...(requestPayload.vendorId && { vendorId: requestPayload.vendorId }),
166 ...(requestPayload.vendorErrorCode && {
167 vendorErrorCode: requestPayload.vendorErrorCode,
168 }),
169 });
10db00b2
JB
170 case BroadcastChannelProcedureName.HEARTBEAT:
171 delete requestPayload.hashId;
172 delete requestPayload.hashIds;
173 delete requestPayload.connectorIds;
174 return this.chargingStation.ocppRequestService.requestHandler<
175 HeartbeatRequest,
176 HeartbeatResponse
177 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload);
6c8f5d90
JB
178 default:
179 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
ce7a4fc3 180 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
181 }
182 }
183
10db00b2
JB
184 private commandResponseToResponseStatus(
185 command: BroadcastChannelProcedureName,
186 commandResponse: CommandResponse
187 ): ResponseStatus {
188 switch (command) {
189 case BroadcastChannelProcedureName.START_TRANSACTION:
190 case BroadcastChannelProcedureName.STOP_TRANSACTION:
191 if (
192 (commandResponse as StartTransactionResponse | StopTransactionResponse)?.idTagInfo
193 ?.status === AuthorizationStatus.ACCEPTED
194 ) {
195 return ResponseStatus.SUCCESS;
196 }
197 return ResponseStatus.FAILURE;
198 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
199 if (Utils.isEmptyObject(commandResponse) === true) {
200 return ResponseStatus.SUCCESS;
201 }
202 return ResponseStatus.FAILURE;
203 case BroadcastChannelProcedureName.HEARTBEAT:
204 if ('currentTime' in commandResponse) {
205 return ResponseStatus.SUCCESS;
206 }
207 return ResponseStatus.FAILURE;
208 default:
209 return ResponseStatus.FAILURE;
89b7a234
JB
210 }
211 }
212}