Apply dependencies update
[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,
8ec8e3d0 16 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) =>
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: [
12052fe9 189 // FIXME: Implement OCPP version agnostic helpers
d3195f0a
JB
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 ],
8bfbc743 199 ...requestPayload,
8ec8e3d0 200 requestParams,
d3195f0a
JB
201 });
202 },
203 ],
91a7d3ea
JB
204 [
205 BroadcastChannelProcedureName.DATA_TRANSFER,
206 async (requestPayload?: BroadcastChannelRequestPayload) =>
207 this.chargingStation.ocppRequestService.requestHandler<
208 DataTransferRequest,
209 DataTransferResponse
8ec8e3d0 210 >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams),
91a7d3ea 211 ],
c9a4f9ea
JB
212 [
213 BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
214 async (requestPayload?: BroadcastChannelRequestPayload) =>
215 this.chargingStation.ocppRequestService.requestHandler<
216 DiagnosticsStatusNotificationRequest,
217 DiagnosticsStatusNotificationResponse
8ec8e3d0
JB
218 >(
219 this.chargingStation,
220 RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
221 requestPayload,
222 requestParams
223 ),
c9a4f9ea
JB
224 ],
225 [
226 BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
227 async (requestPayload?: BroadcastChannelRequestPayload) =>
228 this.chargingStation.ocppRequestService.requestHandler<
229 FirmwareStatusNotificationRequest,
230 FirmwareStatusNotificationResponse
8ec8e3d0
JB
231 >(
232 this.chargingStation,
233 RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
234 requestPayload,
235 requestParams
236 ),
c9a4f9ea 237 ],
9d73266c 238 ]);
89b7a234 239 this.chargingStation = chargingStation;
02a6943a 240 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
6c8f5d90 241 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
89b7a234
JB
242 }
243
02a6943a 244 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
5dea4c94
JB
245 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
246 if (validatedMessageEvent === false) {
6c8f5d90
JB
247 return;
248 }
5dea4c94
JB
249 if (this.isResponse(validatedMessageEvent.data) === true) {
250 return;
251 }
252 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
2afb4d15
JB
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(
edd13439 261 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
2afb4d15
JB
262 );
263 return;
4eca248c 264 }
6c8f5d90 265 let responsePayload: BroadcastChannelResponsePayload;
9d73266c 266 let commandResponse: CommandResponse | void;
6c8f5d90
JB
267 try {
268 commandResponse = await this.commandHandler(command, requestPayload);
c9a4f9ea
JB
269 if (
270 commandResponse === undefined ||
271 commandResponse === null ||
272 Utils.isEmptyObject(commandResponse as CommandResponse)
273 ) {
10d244c0 274 responsePayload = {
51c83d6f 275 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
276 status: ResponseStatus.SUCCESS,
277 };
6c8f5d90 278 } else {
d3195f0a 279 responsePayload = this.commandResponseToResponsePayload(
1984f194 280 command,
d3195f0a 281 requestPayload,
1984f194
JB
282 commandResponse as CommandResponse
283 );
6c8f5d90
JB
284 }
285 } catch (error) {
286 logger.error(
287 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
288 error
289 );
290 responsePayload = {
51c83d6f 291 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
292 status: ResponseStatus.FAILURE,
293 command,
294 requestPayload,
9d73266c 295 commandResponse: commandResponse as CommandResponse,
6c8f5d90
JB
296 errorMessage: (error as Error).message,
297 errorStack: (error as Error).stack,
a9ed42b2 298 errorDetails: (error as OCPPError).details,
6c8f5d90 299 };
623b39b5
JB
300 } finally {
301 this.sendResponse([uuid, responsePayload]);
6c8f5d90 302 }
6c8f5d90
JB
303 }
304
305 private messageErrorHandler(messageEvent: MessageEvent): void {
306 logger.error(
307 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
5083d31a 308 messageEvent
6c8f5d90
JB
309 );
310 }
311
312 private async commandHandler(
313 command: BroadcastChannelProcedureName,
314 requestPayload: BroadcastChannelRequestPayload
9d73266c
JB
315 ): Promise<CommandResponse | void> {
316 if (this.commandHandlers.has(command) === true) {
1984f194 317 this.cleanRequestPayload(command, requestPayload);
9d73266c 318 return this.commandHandlers.get(command)(requestPayload);
6c8f5d90 319 }
9d73266c 320 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
321 }
322
1984f194
JB
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,
8f879946 332 ].includes(command) === false && delete requestPayload.connectorIds;
1984f194
JB
333 }
334
d3195f0a
JB
335 private commandResponseToResponsePayload(
336 command: BroadcastChannelProcedureName,
337 requestPayload: BroadcastChannelRequestPayload,
338 commandResponse: CommandResponse
339 ): BroadcastChannelResponsePayload {
cfa257f5
JB
340 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse);
341 if (responseStatus === ResponseStatus.SUCCESS) {
d3195f0a
JB
342 return {
343 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 344 status: responseStatus,
d3195f0a
JB
345 };
346 }
347 return {
348 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 349 status: responseStatus,
d3195f0a
JB
350 command,
351 requestPayload,
352 commandResponse,
353 };
354 }
355
cfa257f5 356 private commandResponseToResponseStatus(
10db00b2
JB
357 command: BroadcastChannelProcedureName,
358 commandResponse: CommandResponse
359 ): ResponseStatus {
360 switch (command) {
361 case BroadcastChannelProcedureName.START_TRANSACTION:
362 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 363 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 364 if (
1984f194
JB
365 (
366 commandResponse as
367 | StartTransactionResponse
368 | StopTransactionResponse
369 | AuthorizeResponse
370 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2
JB
371 ) {
372 return ResponseStatus.SUCCESS;
373 }
374 return ResponseStatus.FAILURE;
8bfbc743 375 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
d270cc87 376 if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) {
8bfbc743
JB
377 return ResponseStatus.SUCCESS;
378 }
379 return ResponseStatus.FAILURE;
91a7d3ea
JB
380 case BroadcastChannelProcedureName.DATA_TRANSFER:
381 if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
382 return ResponseStatus.SUCCESS;
383 }
384 return ResponseStatus.FAILURE;
10db00b2 385 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 386 case BroadcastChannelProcedureName.METER_VALUES:
10db00b2
JB
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;
89b7a234
JB
398 }
399 }
400}