Apply dependencies update
[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 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 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
117 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
118 requestPayload.transactionId,
119 true
120 ),
121 ...requestPayload,
122 requestParams,
123 }),
124 ],
125 [
126 BroadcastChannelProcedureName.AUTHORIZE,
127 async (requestPayload?: BroadcastChannelRequestPayload) =>
128 this.chargingStation.ocppRequestService.requestHandler<
129 AuthorizeRequest,
130 AuthorizeResponse
131 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams),
132 ],
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,
149 throwError: true,
150 }
151 );
152 return this.chargingStation.bootNotificationResponse;
153 },
154 ],
155 [
156 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
157 async (requestPayload?: BroadcastChannelRequestPayload) =>
158 this.chargingStation.ocppRequestService.requestHandler<
159 StatusNotificationRequest,
160 StatusNotificationResponse
161 >(
162 this.chargingStation,
163 RequestCommand.STATUS_NOTIFICATION,
164 requestPayload,
165 requestParams
166 ),
167 ],
168 [
169 BroadcastChannelProcedureName.HEARTBEAT,
170 async (requestPayload?: BroadcastChannelRequestPayload) =>
171 this.chargingStation.ocppRequestService.requestHandler<
172 HeartbeatRequest,
173 HeartbeatResponse
174 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams),
175 ],
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, {
188 meterValue: [
189 // FIXME: Implement OCPP version agnostic helpers
190 OCPP16ServiceUtils.buildMeterValue(
191 this.chargingStation,
192 requestPayload.connectorId,
193 this.chargingStation.getConnectorStatus(requestPayload.connectorId)?.transactionId,
194 configuredMeterValueSampleInterval
195 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
196 : Constants.DEFAULT_METER_VALUES_INTERVAL
197 ),
198 ],
199 ...requestPayload,
200 requestParams,
201 });
202 },
203 ],
204 [
205 BroadcastChannelProcedureName.DATA_TRANSFER,
206 async (requestPayload?: BroadcastChannelRequestPayload) =>
207 this.chargingStation.ocppRequestService.requestHandler<
208 DataTransferRequest,
209 DataTransferResponse
210 >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams),
211 ],
212 [
213 BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
214 async (requestPayload?: BroadcastChannelRequestPayload) =>
215 this.chargingStation.ocppRequestService.requestHandler<
216 DiagnosticsStatusNotificationRequest,
217 DiagnosticsStatusNotificationResponse
218 >(
219 this.chargingStation,
220 RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
221 requestPayload,
222 requestParams
223 ),
224 ],
225 [
226 BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
227 async (requestPayload?: BroadcastChannelRequestPayload) =>
228 this.chargingStation.ocppRequestService.requestHandler<
229 FirmwareStatusNotificationRequest,
230 FirmwareStatusNotificationResponse
231 >(
232 this.chargingStation,
233 RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
234 requestPayload,
235 requestParams
236 ),
237 ],
238 ]);
239 this.chargingStation = chargingStation;
240 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
241 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
242 }
243
244 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
245 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
246 if (validatedMessageEvent === false) {
247 return;
248 }
249 if (this.isResponse(validatedMessageEvent.data) === true) {
250 return;
251 }
252 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
253 if (
254 requestPayload?.hashIds !== undefined &&
255 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
256 ) {
257 return;
258 }
259 if (requestPayload?.hashId !== undefined) {
260 logger.error(
261 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
262 );
263 return;
264 }
265 let responsePayload: BroadcastChannelResponsePayload;
266 let commandResponse: CommandResponse | void;
267 try {
268 commandResponse = await this.commandHandler(command, requestPayload);
269 if (
270 commandResponse === undefined ||
271 commandResponse === null ||
272 Utils.isEmptyObject(commandResponse as CommandResponse)
273 ) {
274 responsePayload = {
275 hashId: this.chargingStation.stationInfo.hashId,
276 status: ResponseStatus.SUCCESS,
277 };
278 } else {
279 responsePayload = this.commandResponseToResponsePayload(
280 command,
281 requestPayload,
282 commandResponse as CommandResponse
283 );
284 }
285 } catch (error) {
286 logger.error(
287 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
288 error
289 );
290 responsePayload = {
291 hashId: this.chargingStation.stationInfo.hashId,
292 status: ResponseStatus.FAILURE,
293 command,
294 requestPayload,
295 commandResponse: commandResponse as CommandResponse,
296 errorMessage: (error as Error).message,
297 errorStack: (error as Error).stack,
298 errorDetails: (error as OCPPError).details,
299 };
300 } finally {
301 this.sendResponse([uuid, responsePayload]);
302 }
303 }
304
305 private messageErrorHandler(messageEvent: MessageEvent): void {
306 logger.error(
307 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
308 messageEvent
309 );
310 }
311
312 private async commandHandler(
313 command: BroadcastChannelProcedureName,
314 requestPayload: BroadcastChannelRequestPayload
315 ): Promise<CommandResponse | void> {
316 if (this.commandHandlers.has(command) === true) {
317 this.cleanRequestPayload(command, requestPayload);
318 return this.commandHandlers.get(command)(requestPayload);
319 }
320 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
321 }
322
323 private cleanRequestPayload(
324 command: BroadcastChannelProcedureName,
325 requestPayload: BroadcastChannelRequestPayload
326 ): void {
327 delete requestPayload.hashId;
328 delete requestPayload.hashIds;
329 [
330 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
331 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
332 ].includes(command) === false && delete requestPayload.connectorIds;
333 }
334
335 private commandResponseToResponsePayload(
336 command: BroadcastChannelProcedureName,
337 requestPayload: BroadcastChannelRequestPayload,
338 commandResponse: CommandResponse
339 ): BroadcastChannelResponsePayload {
340 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse);
341 if (responseStatus === ResponseStatus.SUCCESS) {
342 return {
343 hashId: this.chargingStation.stationInfo.hashId,
344 status: responseStatus,
345 };
346 }
347 return {
348 hashId: this.chargingStation.stationInfo.hashId,
349 status: responseStatus,
350 command,
351 requestPayload,
352 commandResponse,
353 };
354 }
355
356 private commandResponseToResponseStatus(
357 command: BroadcastChannelProcedureName,
358 commandResponse: CommandResponse
359 ): ResponseStatus {
360 switch (command) {
361 case BroadcastChannelProcedureName.START_TRANSACTION:
362 case BroadcastChannelProcedureName.STOP_TRANSACTION:
363 case BroadcastChannelProcedureName.AUTHORIZE:
364 if (
365 (
366 commandResponse as
367 | StartTransactionResponse
368 | StopTransactionResponse
369 | AuthorizeResponse
370 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
371 ) {
372 return ResponseStatus.SUCCESS;
373 }
374 return ResponseStatus.FAILURE;
375 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
376 if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) {
377 return ResponseStatus.SUCCESS;
378 }
379 return ResponseStatus.FAILURE;
380 case BroadcastChannelProcedureName.DATA_TRANSFER:
381 if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
382 return ResponseStatus.SUCCESS;
383 }
384 return ResponseStatus.FAILURE;
385 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
386 case BroadcastChannelProcedureName.METER_VALUES:
387 if (Utils.isEmptyObject(commandResponse) === true) {
388 return ResponseStatus.SUCCESS;
389 }
390 return ResponseStatus.FAILURE;
391 case BroadcastChannelProcedureName.HEARTBEAT:
392 if ('currentTime' in commandResponse) {
393 return ResponseStatus.SUCCESS;
394 }
395 return ResponseStatus.FAILURE;
396 default:
397 return ResponseStatus.FAILURE;
398 }
399 }
400 }