refactor: cleanup log messages
[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,
346b47e0 18 type EmptyObject,
268a74bb 19 type FirmwareStatusNotificationRequest,
c9a4f9ea 20 type FirmwareStatusNotificationResponse,
268a74bb 21 type HeartbeatRequest,
8bfbc743 22 type HeartbeatResponse,
268a74bb
JB
23 type MessageEvent,
24 type MeterValuesRequest,
8bfbc743 25 type MeterValuesResponse,
d270cc87 26 RegistrationStatusEnumType,
268a74bb
JB
27 RequestCommand,
28 type RequestParams,
29 ResponseStatus,
30 StandardParametersKey,
8bfbc743
JB
31 type StartTransactionRequest,
32 type StartTransactionResponse,
268a74bb
JB
33 type StatusNotificationRequest,
34 type StatusNotificationResponse,
8bfbc743
JB
35 type StopTransactionRequest,
36 type StopTransactionResponse,
7671fa0b 37} from '../../types';
9bf0ef23 38import { Constants, convertToInt, isEmptyObject, isNullOrUndefined, logger } from '../../utils';
7671fa0b 39import type { ChargingStation } from '../ChargingStation';
f2d5e3d9 40import { getConfigurationKey } from '../ChargingStationConfigurationUtils';
7671fa0b 41import { OCPP16ServiceUtils } from '../ocpp';
89b7a234 42
4e3ff94d
JB
43const moduleName = 'ChargingStationWorkerBroadcastChannel';
44
a9ed42b2 45type CommandResponse =
346b47e0 46 | EmptyObject
a9ed42b2
JB
47 | StartTransactionResponse
48 | StopTransactionResponse
1984f194 49 | AuthorizeResponse
8bfbc743 50 | BootNotificationResponse
d3195f0a 51 | HeartbeatResponse
e1d9a0f4 52 | DataTransferResponse;
89b7a234 53
d273692c 54type CommandHandler = (
5edd8ba0 55 requestPayload?: BroadcastChannelRequestPayload,
d273692c
JB
56) => Promise<CommandResponse | void> | void;
57
268a74bb 58export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
d273692c 59 private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>;
89b7a234
JB
60 private readonly chargingStation: ChargingStation;
61
62 constructor(chargingStation: ChargingStation) {
1598b27c 63 super();
8ec8e3d0
JB
64 const requestParams: RequestParams = {
65 throwError: true,
66 };
d273692c 67 this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
9d73266c
JB
68 [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()],
69 [
70 BroadcastChannelProcedureName.STOP_CHARGING_STATION,
d273692c 71 async () => this.chargingStation.stop(),
9d73266c
JB
72 ],
73 [
74 BroadcastChannelProcedureName.OPEN_CONNECTION,
75 () => this.chargingStation.openWSConnection(),
76 ],
77 [
78 BroadcastChannelProcedureName.CLOSE_CONNECTION,
79 () => this.chargingStation.closeWSConnection(),
80 ],
623b39b5
JB
81 [
82 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
83 (requestPayload?: BroadcastChannelRequestPayload) =>
72092cfc 84 this.chargingStation.startAutomaticTransactionGenerator(requestPayload?.connectorIds),
623b39b5
JB
85 ],
86 [
87 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
88 (requestPayload?: BroadcastChannelRequestPayload) =>
72092cfc 89 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds),
623b39b5 90 ],
269de583
JB
91 [
92 BroadcastChannelProcedureName.SET_SUPERVISION_URL,
93 (requestPayload?: BroadcastChannelRequestPayload) =>
94 this.chargingStation.setSupervisionUrl(requestPayload?.url as string),
95 ],
9d73266c
JB
96 [
97 BroadcastChannelProcedureName.START_TRANSACTION,
98 async (requestPayload?: BroadcastChannelRequestPayload) =>
99 this.chargingStation.ocppRequestService.requestHandler<
100 StartTransactionRequest,
101 StartTransactionResponse
8ec8e3d0 102 >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload, requestParams),
9d73266c
JB
103 ],
104 [
105 BroadcastChannelProcedureName.STOP_TRANSACTION,
106 async (requestPayload?: BroadcastChannelRequestPayload) =>
107 this.chargingStation.ocppRequestService.requestHandler<
108 StopTransactionRequest,
109 StartTransactionResponse
1969f643
JB
110 >(
111 this.chargingStation,
112 RequestCommand.STOP_TRANSACTION,
113 {
114 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
e1d9a0f4 115 requestPayload!.transactionId!,
5edd8ba0 116 true,
1969f643
JB
117 ),
118 ...requestPayload,
119 },
5edd8ba0 120 requestParams,
1969f643 121 ),
9d73266c 122 ],
1984f194
JB
123 [
124 BroadcastChannelProcedureName.AUTHORIZE,
125 async (requestPayload?: BroadcastChannelRequestPayload) =>
126 this.chargingStation.ocppRequestService.requestHandler<
127 AuthorizeRequest,
128 AuthorizeResponse
8ec8e3d0 129 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams),
1984f194 130 ],
8bfbc743
JB
131 [
132 BroadcastChannelProcedureName.BOOT_NOTIFICATION,
133 async (requestPayload?: BroadcastChannelRequestPayload) => {
134 this.chargingStation.bootNotificationResponse =
135 await this.chargingStation.ocppRequestService.requestHandler<
136 BootNotificationRequest,
137 BootNotificationResponse
138 >(
139 this.chargingStation,
140 RequestCommand.BOOT_NOTIFICATION,
141 {
142 ...this.chargingStation.bootNotificationRequest,
143 ...requestPayload,
144 },
145 {
146 skipBufferingOnError: true,
8ec8e3d0 147 throwError: true,
5edd8ba0 148 },
8bfbc743
JB
149 );
150 return this.chargingStation.bootNotificationResponse;
151 },
152 ],
9d73266c
JB
153 [
154 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
155 async (requestPayload?: BroadcastChannelRequestPayload) =>
156 this.chargingStation.ocppRequestService.requestHandler<
157 StatusNotificationRequest,
158 StatusNotificationResponse
8ec8e3d0
JB
159 >(
160 this.chargingStation,
161 RequestCommand.STATUS_NOTIFICATION,
162 requestPayload,
5edd8ba0 163 requestParams,
8ec8e3d0 164 ),
9d73266c
JB
165 ],
166 [
167 BroadcastChannelProcedureName.HEARTBEAT,
1984f194
JB
168 async (requestPayload?: BroadcastChannelRequestPayload) =>
169 this.chargingStation.ocppRequestService.requestHandler<
9d73266c
JB
170 HeartbeatRequest,
171 HeartbeatResponse
8ec8e3d0 172 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams),
9d73266c 173 ],
d3195f0a
JB
174 [
175 BroadcastChannelProcedureName.METER_VALUES,
176 async (requestPayload?: BroadcastChannelRequestPayload) => {
f2d5e3d9
JB
177 const configuredMeterValueSampleInterval = getConfigurationKey(
178 chargingStation,
179 StandardParametersKey.MeterValueSampleInterval,
180 );
d3195f0a
JB
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;
a37fc6dc
JB
242 this.onmessage = this.requestHandler.bind(this) as (message: unknown) => void;
243 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: unknown) => 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 (
4a3807d1
JB
256 !isNullOrUndefined(requestPayload.hashIds) &&
257 requestPayload.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
2afb4d15
JB
258 ) {
259 return;
260 }
4a3807d1 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}