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