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
e1d9a0f4 51 | DataTransferResponse;
89b7a234 52
d273692c 53type CommandHandler = (
5edd8ba0 54 requestPayload?: BroadcastChannelRequestPayload,
d273692c
JB
55) => Promise<CommandResponse | void> | void;
56
268a74bb 57export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
d273692c 58 private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>;
89b7a234
JB
59 private readonly chargingStation: ChargingStation;
60
61 constructor(chargingStation: ChargingStation) {
1598b27c 62 super();
8ec8e3d0
JB
63 const requestParams: RequestParams = {
64 throwError: true,
65 };
d273692c 66 this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
9d73266c
JB
67 [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()],
68 [
69 BroadcastChannelProcedureName.STOP_CHARGING_STATION,
d273692c 70 async () => this.chargingStation.stop(),
9d73266c
JB
71 ],
72 [
73 BroadcastChannelProcedureName.OPEN_CONNECTION,
74 () => this.chargingStation.openWSConnection(),
75 ],
76 [
77 BroadcastChannelProcedureName.CLOSE_CONNECTION,
78 () => this.chargingStation.closeWSConnection(),
79 ],
623b39b5
JB
80 [
81 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
82 (requestPayload?: BroadcastChannelRequestPayload) =>
72092cfc 83 this.chargingStation.startAutomaticTransactionGenerator(requestPayload?.connectorIds),
623b39b5
JB
84 ],
85 [
86 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
87 (requestPayload?: BroadcastChannelRequestPayload) =>
72092cfc 88 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds),
623b39b5 89 ],
269de583
JB
90 [
91 BroadcastChannelProcedureName.SET_SUPERVISION_URL,
92 (requestPayload?: BroadcastChannelRequestPayload) =>
93 this.chargingStation.setSupervisionUrl(requestPayload?.url as string),
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(
e1d9a0f4 114 requestPayload!.transactionId!,
5edd8ba0 115 true,
1969f643
JB
116 ),
117 ...requestPayload,
118 },
5edd8ba0 119 requestParams,
1969f643 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,
5edd8ba0 147 },
8bfbc743
JB
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,
5edd8ba0 162 requestParams,
8ec8e3d0 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,
5edd8ba0 179 StandardParametersKey.MeterValueSampleInterval,
d3195f0a
JB
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,
e1d9a0f4
JB
192 requestPayload!.connectorId!,
193 this.chargingStation.getConnectorStatus(requestPayload!.connectorId!)!
194 .transactionId!,
1969f643 195 configuredMeterValueSampleInterval
9bf0ef23 196 ? convertToInt(configuredMeterValueSampleInterval.value) * 1000
5edd8ba0 197 : Constants.DEFAULT_METER_VALUES_INTERVAL,
1969f643
JB
198 ),
199 ],
200 ...requestPayload,
201 },
5edd8ba0 202 requestParams,
1969f643 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,
5edd8ba0 224 requestParams,
8ec8e3d0 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,
5edd8ba0 237 requestParams,
8ec8e3d0 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 255 if (
9bf0ef23 256 !isNullOrUndefined(requestPayload?.hashIds) &&
2afb4d15
JB
257 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
258 ) {
259 return;
260 }
9bf0ef23 261 if (!isNullOrUndefined(requestPayload?.hashId)) {
2afb4d15 262 logger.error(
5edd8ba0 263 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`,
2afb4d15
JB
264 );
265 return;
4eca248c 266 }
e1d9a0f4
JB
267 let responsePayload: BroadcastChannelResponsePayload | undefined;
268 let commandResponse: CommandResponse | void | undefined;
6c8f5d90
JB
269 try {
270 commandResponse = await this.commandHandler(command, requestPayload);
9bf0ef23 271 if (isNullOrUndefined(commandResponse) || isEmptyObject(commandResponse as CommandResponse)) {
10d244c0 272 responsePayload = {
51c83d6f 273 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
274 status: ResponseStatus.SUCCESS,
275 };
6c8f5d90 276 } else {
d3195f0a 277 responsePayload = this.commandResponseToResponsePayload(
1984f194 278 command,
d3195f0a 279 requestPayload,
5edd8ba0 280 commandResponse as CommandResponse,
1984f194 281 );
6c8f5d90
JB
282 }
283 } catch (error) {
284 logger.error(
285 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
5edd8ba0 286 error,
6c8f5d90
JB
287 );
288 responsePayload = {
51c83d6f 289 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
290 status: ResponseStatus.FAILURE,
291 command,
292 requestPayload,
9d73266c 293 commandResponse: commandResponse as CommandResponse,
6c8f5d90
JB
294 errorMessage: (error as Error).message,
295 errorStack: (error as Error).stack,
a9ed42b2 296 errorDetails: (error as OCPPError).details,
6c8f5d90 297 };
623b39b5 298 } finally {
e1d9a0f4 299 this.sendResponse([uuid, responsePayload!]);
6c8f5d90 300 }
6c8f5d90
JB
301 }
302
303 private messageErrorHandler(messageEvent: MessageEvent): void {
304 logger.error(
305 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
5edd8ba0 306 messageEvent,
6c8f5d90
JB
307 );
308 }
309
310 private async commandHandler(
311 command: BroadcastChannelProcedureName,
5edd8ba0 312 requestPayload: BroadcastChannelRequestPayload,
9d73266c
JB
313 ): Promise<CommandResponse | void> {
314 if (this.commandHandlers.has(command) === true) {
1984f194 315 this.cleanRequestPayload(command, requestPayload);
e1d9a0f4 316 return this.commandHandlers.get(command)!(requestPayload);
6c8f5d90 317 }
9d73266c 318 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
319 }
320
1984f194
JB
321 private cleanRequestPayload(
322 command: BroadcastChannelProcedureName,
5edd8ba0 323 requestPayload: BroadcastChannelRequestPayload,
1984f194
JB
324 ): void {
325 delete requestPayload.hashId;
326 delete requestPayload.hashIds;
327 [
328 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
329 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
8f879946 330 ].includes(command) === false && delete requestPayload.connectorIds;
1984f194
JB
331 }
332
d3195f0a
JB
333 private commandResponseToResponsePayload(
334 command: BroadcastChannelProcedureName,
335 requestPayload: BroadcastChannelRequestPayload,
5edd8ba0 336 commandResponse: CommandResponse,
d3195f0a 337 ): BroadcastChannelResponsePayload {
cfa257f5
JB
338 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse);
339 if (responseStatus === ResponseStatus.SUCCESS) {
d3195f0a
JB
340 return {
341 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 342 status: responseStatus,
d3195f0a
JB
343 };
344 }
345 return {
346 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 347 status: responseStatus,
d3195f0a
JB
348 command,
349 requestPayload,
350 commandResponse,
351 };
352 }
353
cfa257f5 354 private commandResponseToResponseStatus(
10db00b2 355 command: BroadcastChannelProcedureName,
5edd8ba0 356 commandResponse: CommandResponse,
10db00b2
JB
357 ): ResponseStatus {
358 switch (command) {
359 case BroadcastChannelProcedureName.START_TRANSACTION:
360 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 361 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 362 if (
1984f194
JB
363 (
364 commandResponse as
365 | StartTransactionResponse
366 | StopTransactionResponse
367 | AuthorizeResponse
368 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2
JB
369 ) {
370 return ResponseStatus.SUCCESS;
371 }
372 return ResponseStatus.FAILURE;
8bfbc743 373 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
d270cc87 374 if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) {
8bfbc743
JB
375 return ResponseStatus.SUCCESS;
376 }
377 return ResponseStatus.FAILURE;
91a7d3ea
JB
378 case BroadcastChannelProcedureName.DATA_TRANSFER:
379 if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
380 return ResponseStatus.SUCCESS;
381 }
382 return ResponseStatus.FAILURE;
10db00b2 383 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 384 case BroadcastChannelProcedureName.METER_VALUES:
9bf0ef23 385 if (isEmptyObject(commandResponse) === true) {
10db00b2
JB
386 return ResponseStatus.SUCCESS;
387 }
388 return ResponseStatus.FAILURE;
389 case BroadcastChannelProcedureName.HEARTBEAT:
390 if ('currentTime' in commandResponse) {
391 return ResponseStatus.SUCCESS;
392 }
393 return ResponseStatus.FAILURE;
394 default:
395 return ResponseStatus.FAILURE;
89b7a234
JB
396 }
397 }
398}