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