Apply dependencies update
[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';
d3195f0a 3import { StandardParametersKey } from '../types/ocpp/Configuration';
10db00b2 4import {
8bfbc743 5 type BootNotificationRequest,
91a7d3ea 6 type DataTransferRequest,
c9a4f9ea
JB
7 type DiagnosticsStatusNotificationRequest,
8 type FirmwareStatusNotificationRequest,
8bfbc743
JB
9 type HeartbeatRequest,
10 type MeterValuesRequest,
10db00b2 11 RequestCommand,
8ec8e3d0 12 RequestParams,
10db00b2
JB
13 type StatusNotificationRequest,
14} from '../types/ocpp/Requests';
8bfbc743
JB
15import {
16 type BootNotificationResponse,
91a7d3ea
JB
17 type DataTransferResponse,
18 DataTransferStatus,
c9a4f9ea
JB
19 type DiagnosticsStatusNotificationResponse,
20 type FirmwareStatusNotificationResponse,
8bfbc743
JB
21 type HeartbeatResponse,
22 type MeterValuesResponse,
d270cc87 23 RegistrationStatusEnumType,
8bfbc743 24 type StatusNotificationResponse,
d3195f0a 25} from '../types/ocpp/Responses';
89b7a234 26import {
6c8f5d90 27 AuthorizationStatus,
8bfbc743
JB
28 type AuthorizeRequest,
29 type AuthorizeResponse,
30 type StartTransactionRequest,
31 type StartTransactionResponse,
32 type StopTransactionRequest,
33 type StopTransactionResponse,
89b7a234 34} from '../types/ocpp/Transaction';
c91bd158 35import { ResponseStatus } from '../types/UIProtocol';
89b7a234
JB
36import {
37 BroadcastChannelProcedureName,
8bfbc743
JB
38 type BroadcastChannelRequest,
39 type BroadcastChannelRequestPayload,
40 type BroadcastChannelResponsePayload,
41 type MessageEvent,
89b7a234 42} from '../types/WorkerBroadcastChannel';
d3195f0a 43import Constants from '../utils/Constants';
6c8f5d90 44import logger from '../utils/Logger';
a9ed42b2 45import Utils from '../utils/Utils';
db2336d9 46import type ChargingStation from './ChargingStation';
d3195f0a
JB
47import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
48import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
1598b27c 49import WorkerBroadcastChannel from './WorkerBroadcastChannel';
89b7a234 50
4e3ff94d
JB
51const moduleName = 'ChargingStationWorkerBroadcastChannel';
52
a9ed42b2
JB
53type CommandResponse =
54 | StartTransactionResponse
55 | StopTransactionResponse
1984f194 56 | AuthorizeResponse
8bfbc743 57 | BootNotificationResponse
10db00b2 58 | StatusNotificationResponse
d3195f0a 59 | HeartbeatResponse
91a7d3ea 60 | MeterValuesResponse
c9a4f9ea
JB
61 | DataTransferResponse
62 | DiagnosticsStatusNotificationResponse
63 | FirmwareStatusNotificationResponse;
89b7a234 64
d273692c
JB
65type CommandHandler = (
66 requestPayload?: BroadcastChannelRequestPayload
67) => Promise<CommandResponse | void> | void;
68
1598b27c 69export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
d273692c 70 private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>;
89b7a234
JB
71 private readonly chargingStation: ChargingStation;
72
73 constructor(chargingStation: ChargingStation) {
1598b27c 74 super();
8ec8e3d0
JB
75 const requestParams: RequestParams = {
76 throwError: true,
77 };
d273692c 78 this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
9d73266c
JB
79 [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()],
80 [
81 BroadcastChannelProcedureName.STOP_CHARGING_STATION,
d273692c 82 async () => this.chargingStation.stop(),
9d73266c
JB
83 ],
84 [
85 BroadcastChannelProcedureName.OPEN_CONNECTION,
86 () => this.chargingStation.openWSConnection(),
87 ],
88 [
89 BroadcastChannelProcedureName.CLOSE_CONNECTION,
90 () => this.chargingStation.closeWSConnection(),
91 ],
623b39b5
JB
92 [
93 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
94 (requestPayload?: BroadcastChannelRequestPayload) =>
95 this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds),
96 ],
97 [
98 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
99 (requestPayload?: BroadcastChannelRequestPayload) =>
100 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds),
101 ],
9d73266c
JB
102 [
103 BroadcastChannelProcedureName.START_TRANSACTION,
104 async (requestPayload?: BroadcastChannelRequestPayload) =>
105 this.chargingStation.ocppRequestService.requestHandler<
106 StartTransactionRequest,
107 StartTransactionResponse
8ec8e3d0 108 >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload, requestParams),
9d73266c
JB
109 ],
110 [
111 BroadcastChannelProcedureName.STOP_TRANSACTION,
112 async (requestPayload?: BroadcastChannelRequestPayload) =>
113 this.chargingStation.ocppRequestService.requestHandler<
114 StopTransactionRequest,
115 StartTransactionResponse
116 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
2212319a
JB
117 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
118 requestPayload.transactionId,
119 true
120 ),
8bfbc743 121 ...requestPayload,
8ec8e3d0 122 requestParams,
9d73266c
JB
123 }),
124 ],
1984f194
JB
125 [
126 BroadcastChannelProcedureName.AUTHORIZE,
127 async (requestPayload?: BroadcastChannelRequestPayload) =>
128 this.chargingStation.ocppRequestService.requestHandler<
129 AuthorizeRequest,
130 AuthorizeResponse
8ec8e3d0 131 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams),
1984f194 132 ],
8bfbc743
JB
133 [
134 BroadcastChannelProcedureName.BOOT_NOTIFICATION,
135 async (requestPayload?: BroadcastChannelRequestPayload) => {
136 this.chargingStation.bootNotificationResponse =
137 await this.chargingStation.ocppRequestService.requestHandler<
138 BootNotificationRequest,
139 BootNotificationResponse
140 >(
141 this.chargingStation,
142 RequestCommand.BOOT_NOTIFICATION,
143 {
144 ...this.chargingStation.bootNotificationRequest,
145 ...requestPayload,
146 },
147 {
148 skipBufferingOnError: true,
8ec8e3d0 149 throwError: true,
8bfbc743
JB
150 }
151 );
152 return this.chargingStation.bootNotificationResponse;
153 },
154 ],
9d73266c
JB
155 [
156 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
157 async (requestPayload?: BroadcastChannelRequestPayload) =>
158 this.chargingStation.ocppRequestService.requestHandler<
159 StatusNotificationRequest,
160 StatusNotificationResponse
8ec8e3d0
JB
161 >(
162 this.chargingStation,
163 RequestCommand.STATUS_NOTIFICATION,
164 requestPayload,
165 requestParams
166 ),
9d73266c
JB
167 ],
168 [
169 BroadcastChannelProcedureName.HEARTBEAT,
1984f194
JB
170 async (requestPayload?: BroadcastChannelRequestPayload) =>
171 this.chargingStation.ocppRequestService.requestHandler<
9d73266c
JB
172 HeartbeatRequest,
173 HeartbeatResponse
8ec8e3d0 174 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams),
9d73266c 175 ],
d3195f0a
JB
176 [
177 BroadcastChannelProcedureName.METER_VALUES,
178 async (requestPayload?: BroadcastChannelRequestPayload) => {
179 const configuredMeterValueSampleInterval =
180 ChargingStationConfigurationUtils.getConfigurationKey(
181 chargingStation,
182 StandardParametersKey.MeterValueSampleInterval
183 );
184 return this.chargingStation.ocppRequestService.requestHandler<
185 MeterValuesRequest,
186 MeterValuesResponse
187 >(this.chargingStation, RequestCommand.METER_VALUES, {
8bfbc743 188 meterValue: [
d3195f0a
JB
189 OCPP16ServiceUtils.buildMeterValue(
190 this.chargingStation,
191 requestPayload.connectorId,
192 this.chargingStation.getConnectorStatus(requestPayload.connectorId)?.transactionId,
193 configuredMeterValueSampleInterval
194 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
195 : Constants.DEFAULT_METER_VALUES_INTERVAL
196 ),
197 ],
8bfbc743 198 ...requestPayload,
8ec8e3d0 199 requestParams,
d3195f0a
JB
200 });
201 },
202 ],
91a7d3ea
JB
203 [
204 BroadcastChannelProcedureName.DATA_TRANSFER,
205 async (requestPayload?: BroadcastChannelRequestPayload) =>
206 this.chargingStation.ocppRequestService.requestHandler<
207 DataTransferRequest,
208 DataTransferResponse
8ec8e3d0 209 >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams),
91a7d3ea 210 ],
c9a4f9ea
JB
211 [
212 BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
213 async (requestPayload?: BroadcastChannelRequestPayload) =>
214 this.chargingStation.ocppRequestService.requestHandler<
215 DiagnosticsStatusNotificationRequest,
216 DiagnosticsStatusNotificationResponse
8ec8e3d0
JB
217 >(
218 this.chargingStation,
219 RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
220 requestPayload,
221 requestParams
222 ),
c9a4f9ea
JB
223 ],
224 [
225 BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
226 async (requestPayload?: BroadcastChannelRequestPayload) =>
227 this.chargingStation.ocppRequestService.requestHandler<
228 FirmwareStatusNotificationRequest,
229 FirmwareStatusNotificationResponse
8ec8e3d0
JB
230 >(
231 this.chargingStation,
232 RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
233 requestPayload,
234 requestParams
235 ),
c9a4f9ea 236 ],
9d73266c 237 ]);
89b7a234 238 this.chargingStation = chargingStation;
02a6943a 239 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
6c8f5d90 240 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
89b7a234
JB
241 }
242
02a6943a 243 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
5dea4c94
JB
244 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
245 if (validatedMessageEvent === false) {
6c8f5d90
JB
246 return;
247 }
5dea4c94
JB
248 if (this.isResponse(validatedMessageEvent.data) === true) {
249 return;
250 }
251 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
2afb4d15
JB
252 if (
253 requestPayload?.hashIds !== undefined &&
254 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
255 ) {
256 return;
257 }
258 if (requestPayload?.hashId !== undefined) {
259 logger.error(
edd13439 260 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
2afb4d15
JB
261 );
262 return;
4eca248c 263 }
6c8f5d90 264 let responsePayload: BroadcastChannelResponsePayload;
9d73266c 265 let commandResponse: CommandResponse | void;
6c8f5d90
JB
266 try {
267 commandResponse = await this.commandHandler(command, requestPayload);
c9a4f9ea
JB
268 if (
269 commandResponse === undefined ||
270 commandResponse === null ||
271 Utils.isEmptyObject(commandResponse as CommandResponse)
272 ) {
10d244c0 273 responsePayload = {
51c83d6f 274 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
275 status: ResponseStatus.SUCCESS,
276 };
6c8f5d90 277 } else {
d3195f0a 278 responsePayload = this.commandResponseToResponsePayload(
1984f194 279 command,
d3195f0a 280 requestPayload,
1984f194
JB
281 commandResponse as CommandResponse
282 );
6c8f5d90
JB
283 }
284 } catch (error) {
285 logger.error(
286 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
287 error
288 );
289 responsePayload = {
51c83d6f 290 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
291 status: ResponseStatus.FAILURE,
292 command,
293 requestPayload,
9d73266c 294 commandResponse: commandResponse as CommandResponse,
6c8f5d90
JB
295 errorMessage: (error as Error).message,
296 errorStack: (error as Error).stack,
a9ed42b2 297 errorDetails: (error as OCPPError).details,
6c8f5d90 298 };
623b39b5
JB
299 } finally {
300 this.sendResponse([uuid, responsePayload]);
6c8f5d90 301 }
6c8f5d90
JB
302 }
303
304 private messageErrorHandler(messageEvent: MessageEvent): void {
305 logger.error(
306 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
5083d31a 307 messageEvent
6c8f5d90
JB
308 );
309 }
310
311 private async commandHandler(
312 command: BroadcastChannelProcedureName,
313 requestPayload: BroadcastChannelRequestPayload
9d73266c
JB
314 ): Promise<CommandResponse | void> {
315 if (this.commandHandlers.has(command) === true) {
1984f194 316 this.cleanRequestPayload(command, requestPayload);
9d73266c 317 return this.commandHandlers.get(command)(requestPayload);
6c8f5d90 318 }
9d73266c 319 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
320 }
321
1984f194
JB
322 private cleanRequestPayload(
323 command: BroadcastChannelProcedureName,
324 requestPayload: BroadcastChannelRequestPayload
325 ): void {
326 delete requestPayload.hashId;
327 delete requestPayload.hashIds;
328 [
329 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
330 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
8f879946 331 ].includes(command) === false && delete requestPayload.connectorIds;
1984f194
JB
332 }
333
d3195f0a
JB
334 private commandResponseToResponsePayload(
335 command: BroadcastChannelProcedureName,
336 requestPayload: BroadcastChannelRequestPayload,
337 commandResponse: CommandResponse
338 ): BroadcastChannelResponsePayload {
cfa257f5
JB
339 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse);
340 if (responseStatus === ResponseStatus.SUCCESS) {
d3195f0a
JB
341 return {
342 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 343 status: responseStatus,
d3195f0a
JB
344 };
345 }
346 return {
347 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 348 status: responseStatus,
d3195f0a
JB
349 command,
350 requestPayload,
351 commandResponse,
352 };
353 }
354
cfa257f5 355 private commandResponseToResponseStatus(
10db00b2
JB
356 command: BroadcastChannelProcedureName,
357 commandResponse: CommandResponse
358 ): ResponseStatus {
359 switch (command) {
360 case BroadcastChannelProcedureName.START_TRANSACTION:
361 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 362 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 363 if (
1984f194
JB
364 (
365 commandResponse as
366 | StartTransactionResponse
367 | StopTransactionResponse
368 | AuthorizeResponse
369 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2
JB
370 ) {
371 return ResponseStatus.SUCCESS;
372 }
373 return ResponseStatus.FAILURE;
8bfbc743 374 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
d270cc87 375 if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) {
8bfbc743
JB
376 return ResponseStatus.SUCCESS;
377 }
378 return ResponseStatus.FAILURE;
91a7d3ea
JB
379 case BroadcastChannelProcedureName.DATA_TRANSFER:
380 if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
381 return ResponseStatus.SUCCESS;
382 }
383 return ResponseStatus.FAILURE;
10db00b2 384 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 385 case BroadcastChannelProcedureName.METER_VALUES:
10db00b2
JB
386 if (Utils.isEmptyObject(commandResponse) === true) {
387 return ResponseStatus.SUCCESS;
388 }
389 return ResponseStatus.FAILURE;
390 case BroadcastChannelProcedureName.HEARTBEAT:
391 if ('currentTime' in commandResponse) {
392 return ResponseStatus.SUCCESS;
393 }
394 return ResponseStatus.FAILURE;
395 default:
396 return ResponseStatus.FAILURE;
89b7a234
JB
397 }
398 }
399}