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