Strict null check fixes
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
1 import type ChargingStation from './ChargingStation';
2 import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
3 import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
4 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
5 import BaseError from '../exception/BaseError';
6 import type OCPPError from '../exception/OCPPError';
7 import { StandardParametersKey } from '../types/ocpp/Configuration';
8 import {
9 type BootNotificationRequest,
10 type DataTransferRequest,
11 type DiagnosticsStatusNotificationRequest,
12 type FirmwareStatusNotificationRequest,
13 type HeartbeatRequest,
14 type MeterValuesRequest,
15 RequestCommand,
16 type RequestParams,
17 type StatusNotificationRequest,
18 } from '../types/ocpp/Requests';
19 import {
20 type BootNotificationResponse,
21 type DataTransferResponse,
22 DataTransferStatus,
23 type DiagnosticsStatusNotificationResponse,
24 type FirmwareStatusNotificationResponse,
25 type HeartbeatResponse,
26 type MeterValuesResponse,
27 RegistrationStatusEnumType,
28 type StatusNotificationResponse,
29 } from '../types/ocpp/Responses';
30 import {
31 AuthorizationStatus,
32 type AuthorizeRequest,
33 type AuthorizeResponse,
34 type StartTransactionRequest,
35 type StartTransactionResponse,
36 type StopTransactionRequest,
37 type StopTransactionResponse,
38 } from '../types/ocpp/Transaction';
39 import { ResponseStatus } from '../types/UIProtocol';
40 import {
41 BroadcastChannelProcedureName,
42 type BroadcastChannelRequest,
43 type BroadcastChannelRequestPayload,
44 type BroadcastChannelResponsePayload,
45 type MessageEvent,
46 } from '../types/WorkerBroadcastChannel';
47 import Constants from '../utils/Constants';
48 import logger from '../utils/Logger';
49 import Utils from '../utils/Utils';
50
51 const moduleName = 'ChargingStationWorkerBroadcastChannel';
52
53 type CommandResponse =
54 | StartTransactionResponse
55 | StopTransactionResponse
56 | AuthorizeResponse
57 | BootNotificationResponse
58 | StatusNotificationResponse
59 | HeartbeatResponse
60 | MeterValuesResponse
61 | DataTransferResponse
62 | DiagnosticsStatusNotificationResponse
63 | FirmwareStatusNotificationResponse;
64
65 type CommandHandler = (
66 requestPayload?: BroadcastChannelRequestPayload
67 ) => Promise<CommandResponse | void> | void;
68
69 export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
70 private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>;
71 private readonly chargingStation: ChargingStation;
72
73 constructor(chargingStation: ChargingStation) {
74 super();
75 const requestParams: RequestParams = {
76 throwError: true,
77 };
78 this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
79 [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()],
80 [
81 BroadcastChannelProcedureName.STOP_CHARGING_STATION,
82 async () => this.chargingStation.stop(),
83 ],
84 [
85 BroadcastChannelProcedureName.OPEN_CONNECTION,
86 () => this.chargingStation.openWSConnection(),
87 ],
88 [
89 BroadcastChannelProcedureName.CLOSE_CONNECTION,
90 () => this.chargingStation.closeWSConnection(),
91 ],
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 ],
102 [
103 BroadcastChannelProcedureName.START_TRANSACTION,
104 async (requestPayload?: BroadcastChannelRequestPayload) =>
105 this.chargingStation.ocppRequestService.requestHandler<
106 StartTransactionRequest,
107 StartTransactionResponse
108 >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload, requestParams),
109 ],
110 [
111 BroadcastChannelProcedureName.STOP_TRANSACTION,
112 async (requestPayload?: BroadcastChannelRequestPayload) =>
113 this.chargingStation.ocppRequestService.requestHandler<
114 StopTransactionRequest,
115 StartTransactionResponse
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 ),
128 ],
129 [
130 BroadcastChannelProcedureName.AUTHORIZE,
131 async (requestPayload?: BroadcastChannelRequestPayload) =>
132 this.chargingStation.ocppRequestService.requestHandler<
133 AuthorizeRequest,
134 AuthorizeResponse
135 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams),
136 ],
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,
153 throwError: true,
154 }
155 );
156 return this.chargingStation.bootNotificationResponse;
157 },
158 ],
159 [
160 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
161 async (requestPayload?: BroadcastChannelRequestPayload) =>
162 this.chargingStation.ocppRequestService.requestHandler<
163 StatusNotificationRequest,
164 StatusNotificationResponse
165 >(
166 this.chargingStation,
167 RequestCommand.STATUS_NOTIFICATION,
168 requestPayload,
169 requestParams
170 ),
171 ],
172 [
173 BroadcastChannelProcedureName.HEARTBEAT,
174 async (requestPayload?: BroadcastChannelRequestPayload) =>
175 this.chargingStation.ocppRequestService.requestHandler<
176 HeartbeatRequest,
177 HeartbeatResponse
178 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams),
179 ],
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
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 );
211 },
212 ],
213 [
214 BroadcastChannelProcedureName.DATA_TRANSFER,
215 async (requestPayload?: BroadcastChannelRequestPayload) =>
216 this.chargingStation.ocppRequestService.requestHandler<
217 DataTransferRequest,
218 DataTransferResponse
219 >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams),
220 ],
221 [
222 BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
223 async (requestPayload?: BroadcastChannelRequestPayload) =>
224 this.chargingStation.ocppRequestService.requestHandler<
225 DiagnosticsStatusNotificationRequest,
226 DiagnosticsStatusNotificationResponse
227 >(
228 this.chargingStation,
229 RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
230 requestPayload,
231 requestParams
232 ),
233 ],
234 [
235 BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
236 async (requestPayload?: BroadcastChannelRequestPayload) =>
237 this.chargingStation.ocppRequestService.requestHandler<
238 FirmwareStatusNotificationRequest,
239 FirmwareStatusNotificationResponse
240 >(
241 this.chargingStation,
242 RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
243 requestPayload,
244 requestParams
245 ),
246 ],
247 ]);
248 this.chargingStation = chargingStation;
249 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
250 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
251 }
252
253 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
254 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
255 if (validatedMessageEvent === false) {
256 return;
257 }
258 if (this.isResponse(validatedMessageEvent.data) === true) {
259 return;
260 }
261 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
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(
270 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
271 );
272 return;
273 }
274 let responsePayload: BroadcastChannelResponsePayload;
275 let commandResponse: CommandResponse | void;
276 try {
277 commandResponse = await this.commandHandler(command, requestPayload);
278 if (
279 commandResponse === undefined ||
280 commandResponse === null ||
281 Utils.isEmptyObject(commandResponse)
282 ) {
283 responsePayload = {
284 hashId: this.chargingStation.stationInfo.hashId,
285 status: ResponseStatus.SUCCESS,
286 };
287 } else {
288 responsePayload = this.commandResponseToResponsePayload(
289 command,
290 requestPayload,
291 commandResponse
292 );
293 }
294 } catch (error) {
295 logger.error(
296 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
297 error
298 );
299 responsePayload = {
300 hashId: this.chargingStation.stationInfo.hashId,
301 status: ResponseStatus.FAILURE,
302 command,
303 requestPayload,
304 commandResponse: commandResponse as CommandResponse,
305 errorMessage: (error as Error).message,
306 errorStack: (error as Error).stack,
307 errorDetails: (error as OCPPError).details,
308 };
309 } finally {
310 this.sendResponse([uuid, responsePayload]);
311 }
312 }
313
314 private messageErrorHandler(messageEvent: MessageEvent): void {
315 logger.error(
316 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
317 messageEvent
318 );
319 }
320
321 private async commandHandler(
322 command: BroadcastChannelProcedureName,
323 requestPayload: BroadcastChannelRequestPayload
324 ): Promise<CommandResponse | void> {
325 if (this.commandHandlers.has(command) === true) {
326 this.cleanRequestPayload(command, requestPayload);
327 return this.commandHandlers.get(command)(requestPayload);
328 }
329 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
330 }
331
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,
341 ].includes(command) === false && delete requestPayload.connectorIds;
342 }
343
344 private commandResponseToResponsePayload(
345 command: BroadcastChannelProcedureName,
346 requestPayload: BroadcastChannelRequestPayload,
347 commandResponse: CommandResponse
348 ): BroadcastChannelResponsePayload {
349 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse);
350 if (responseStatus === ResponseStatus.SUCCESS) {
351 return {
352 hashId: this.chargingStation.stationInfo.hashId,
353 status: responseStatus,
354 };
355 }
356 return {
357 hashId: this.chargingStation.stationInfo.hashId,
358 status: responseStatus,
359 command,
360 requestPayload,
361 commandResponse,
362 };
363 }
364
365 private commandResponseToResponseStatus(
366 command: BroadcastChannelProcedureName,
367 commandResponse: CommandResponse
368 ): ResponseStatus {
369 switch (command) {
370 case BroadcastChannelProcedureName.START_TRANSACTION:
371 case BroadcastChannelProcedureName.STOP_TRANSACTION:
372 case BroadcastChannelProcedureName.AUTHORIZE:
373 if (
374 (
375 commandResponse as
376 | StartTransactionResponse
377 | StopTransactionResponse
378 | AuthorizeResponse
379 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
380 ) {
381 return ResponseStatus.SUCCESS;
382 }
383 return ResponseStatus.FAILURE;
384 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
385 if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) {
386 return ResponseStatus.SUCCESS;
387 }
388 return ResponseStatus.FAILURE;
389 case BroadcastChannelProcedureName.DATA_TRANSFER:
390 if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
391 return ResponseStatus.SUCCESS;
392 }
393 return ResponseStatus.FAILURE;
394 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
395 case BroadcastChannelProcedureName.METER_VALUES:
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;
407 }
408 }
409 }