feat: set supervision url through the UI protocol
[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 ],
269de583
JB
95 [
96 BroadcastChannelProcedureName.SET_SUPERVISION_URL,
97 (requestPayload?: BroadcastChannelRequestPayload) =>
98 this.chargingStation.setSupervisionUrl(requestPayload?.url as string),
99 ],
9d73266c
JB
100 [
101 BroadcastChannelProcedureName.START_TRANSACTION,
102 async (requestPayload?: BroadcastChannelRequestPayload) =>
103 this.chargingStation.ocppRequestService.requestHandler<
104 StartTransactionRequest,
105 StartTransactionResponse
8ec8e3d0 106 >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload, requestParams),
9d73266c
JB
107 ],
108 [
109 BroadcastChannelProcedureName.STOP_TRANSACTION,
110 async (requestPayload?: BroadcastChannelRequestPayload) =>
111 this.chargingStation.ocppRequestService.requestHandler<
112 StopTransactionRequest,
113 StartTransactionResponse
1969f643
JB
114 >(
115 this.chargingStation,
116 RequestCommand.STOP_TRANSACTION,
117 {
118 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
119 requestPayload.transactionId,
120 true
121 ),
122 ...requestPayload,
123 },
124 requestParams
125 ),
9d73266c 126 ],
1984f194
JB
127 [
128 BroadcastChannelProcedureName.AUTHORIZE,
129 async (requestPayload?: BroadcastChannelRequestPayload) =>
130 this.chargingStation.ocppRequestService.requestHandler<
131 AuthorizeRequest,
132 AuthorizeResponse
8ec8e3d0 133 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams),
1984f194 134 ],
8bfbc743
JB
135 [
136 BroadcastChannelProcedureName.BOOT_NOTIFICATION,
137 async (requestPayload?: BroadcastChannelRequestPayload) => {
138 this.chargingStation.bootNotificationResponse =
139 await this.chargingStation.ocppRequestService.requestHandler<
140 BootNotificationRequest,
141 BootNotificationResponse
142 >(
143 this.chargingStation,
144 RequestCommand.BOOT_NOTIFICATION,
145 {
146 ...this.chargingStation.bootNotificationRequest,
147 ...requestPayload,
148 },
149 {
150 skipBufferingOnError: true,
8ec8e3d0 151 throwError: true,
8bfbc743
JB
152 }
153 );
154 return this.chargingStation.bootNotificationResponse;
155 },
156 ],
9d73266c
JB
157 [
158 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
159 async (requestPayload?: BroadcastChannelRequestPayload) =>
160 this.chargingStation.ocppRequestService.requestHandler<
161 StatusNotificationRequest,
162 StatusNotificationResponse
8ec8e3d0
JB
163 >(
164 this.chargingStation,
165 RequestCommand.STATUS_NOTIFICATION,
166 requestPayload,
167 requestParams
168 ),
9d73266c
JB
169 ],
170 [
171 BroadcastChannelProcedureName.HEARTBEAT,
1984f194
JB
172 async (requestPayload?: BroadcastChannelRequestPayload) =>
173 this.chargingStation.ocppRequestService.requestHandler<
9d73266c
JB
174 HeartbeatRequest,
175 HeartbeatResponse
8ec8e3d0 176 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams),
9d73266c 177 ],
d3195f0a
JB
178 [
179 BroadcastChannelProcedureName.METER_VALUES,
180 async (requestPayload?: BroadcastChannelRequestPayload) => {
181 const configuredMeterValueSampleInterval =
182 ChargingStationConfigurationUtils.getConfigurationKey(
183 chargingStation,
184 StandardParametersKey.MeterValueSampleInterval
185 );
186 return this.chargingStation.ocppRequestService.requestHandler<
187 MeterValuesRequest,
188 MeterValuesResponse
1969f643
JB
189 >(
190 this.chargingStation,
191 RequestCommand.METER_VALUES,
192 {
193 meterValue: [
194 // FIXME: Implement OCPP version agnostic helpers
195 OCPP16ServiceUtils.buildMeterValue(
196 this.chargingStation,
197 requestPayload.connectorId,
198 this.chargingStation.getConnectorStatus(requestPayload.connectorId)
199 ?.transactionId,
200 configuredMeterValueSampleInterval
201 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
202 : Constants.DEFAULT_METER_VALUES_INTERVAL
203 ),
204 ],
205 ...requestPayload,
206 },
207 requestParams
208 );
d3195f0a
JB
209 },
210 ],
91a7d3ea
JB
211 [
212 BroadcastChannelProcedureName.DATA_TRANSFER,
213 async (requestPayload?: BroadcastChannelRequestPayload) =>
214 this.chargingStation.ocppRequestService.requestHandler<
215 DataTransferRequest,
216 DataTransferResponse
8ec8e3d0 217 >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams),
91a7d3ea 218 ],
c9a4f9ea
JB
219 [
220 BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
221 async (requestPayload?: BroadcastChannelRequestPayload) =>
222 this.chargingStation.ocppRequestService.requestHandler<
223 DiagnosticsStatusNotificationRequest,
224 DiagnosticsStatusNotificationResponse
8ec8e3d0
JB
225 >(
226 this.chargingStation,
227 RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
228 requestPayload,
229 requestParams
230 ),
c9a4f9ea
JB
231 ],
232 [
233 BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
234 async (requestPayload?: BroadcastChannelRequestPayload) =>
235 this.chargingStation.ocppRequestService.requestHandler<
236 FirmwareStatusNotificationRequest,
237 FirmwareStatusNotificationResponse
8ec8e3d0
JB
238 >(
239 this.chargingStation,
240 RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
241 requestPayload,
242 requestParams
243 ),
c9a4f9ea 244 ],
9d73266c 245 ]);
89b7a234 246 this.chargingStation = chargingStation;
02a6943a 247 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
6c8f5d90 248 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
89b7a234
JB
249 }
250
02a6943a 251 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
5dea4c94
JB
252 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
253 if (validatedMessageEvent === false) {
6c8f5d90
JB
254 return;
255 }
5dea4c94
JB
256 if (this.isResponse(validatedMessageEvent.data) === true) {
257 return;
258 }
259 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
2afb4d15
JB
260 if (
261 requestPayload?.hashIds !== undefined &&
262 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
263 ) {
264 return;
265 }
266 if (requestPayload?.hashId !== undefined) {
267 logger.error(
edd13439 268 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
2afb4d15
JB
269 );
270 return;
4eca248c 271 }
6c8f5d90 272 let responsePayload: BroadcastChannelResponsePayload;
9d73266c 273 let commandResponse: CommandResponse | void;
6c8f5d90
JB
274 try {
275 commandResponse = await this.commandHandler(command, requestPayload);
c9a4f9ea
JB
276 if (
277 commandResponse === undefined ||
278 commandResponse === null ||
e92fdb8c 279 Utils.isEmptyObject(commandResponse as CommandResponse)
c9a4f9ea 280 ) {
10d244c0 281 responsePayload = {
51c83d6f 282 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
283 status: ResponseStatus.SUCCESS,
284 };
6c8f5d90 285 } else {
d3195f0a 286 responsePayload = this.commandResponseToResponsePayload(
1984f194 287 command,
d3195f0a 288 requestPayload,
e92fdb8c 289 commandResponse as CommandResponse
1984f194 290 );
6c8f5d90
JB
291 }
292 } catch (error) {
293 logger.error(
294 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
295 error
296 );
297 responsePayload = {
51c83d6f 298 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
299 status: ResponseStatus.FAILURE,
300 command,
301 requestPayload,
9d73266c 302 commandResponse: commandResponse as CommandResponse,
6c8f5d90
JB
303 errorMessage: (error as Error).message,
304 errorStack: (error as Error).stack,
a9ed42b2 305 errorDetails: (error as OCPPError).details,
6c8f5d90 306 };
623b39b5
JB
307 } finally {
308 this.sendResponse([uuid, responsePayload]);
6c8f5d90 309 }
6c8f5d90
JB
310 }
311
312 private messageErrorHandler(messageEvent: MessageEvent): void {
313 logger.error(
314 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
5083d31a 315 messageEvent
6c8f5d90
JB
316 );
317 }
318
319 private async commandHandler(
320 command: BroadcastChannelProcedureName,
321 requestPayload: BroadcastChannelRequestPayload
9d73266c
JB
322 ): Promise<CommandResponse | void> {
323 if (this.commandHandlers.has(command) === true) {
1984f194 324 this.cleanRequestPayload(command, requestPayload);
9d73266c 325 return this.commandHandlers.get(command)(requestPayload);
6c8f5d90 326 }
9d73266c 327 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
328 }
329
1984f194
JB
330 private cleanRequestPayload(
331 command: BroadcastChannelProcedureName,
332 requestPayload: BroadcastChannelRequestPayload
333 ): void {
334 delete requestPayload.hashId;
335 delete requestPayload.hashIds;
336 [
337 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
338 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
8f879946 339 ].includes(command) === false && delete requestPayload.connectorIds;
1984f194
JB
340 }
341
d3195f0a
JB
342 private commandResponseToResponsePayload(
343 command: BroadcastChannelProcedureName,
344 requestPayload: BroadcastChannelRequestPayload,
345 commandResponse: CommandResponse
346 ): BroadcastChannelResponsePayload {
cfa257f5
JB
347 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse);
348 if (responseStatus === ResponseStatus.SUCCESS) {
d3195f0a
JB
349 return {
350 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 351 status: responseStatus,
d3195f0a
JB
352 };
353 }
354 return {
355 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 356 status: responseStatus,
d3195f0a
JB
357 command,
358 requestPayload,
359 commandResponse,
360 };
361 }
362
cfa257f5 363 private commandResponseToResponseStatus(
10db00b2
JB
364 command: BroadcastChannelProcedureName,
365 commandResponse: CommandResponse
366 ): ResponseStatus {
367 switch (command) {
368 case BroadcastChannelProcedureName.START_TRANSACTION:
369 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 370 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 371 if (
1984f194
JB
372 (
373 commandResponse as
374 | StartTransactionResponse
375 | StopTransactionResponse
376 | AuthorizeResponse
377 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2
JB
378 ) {
379 return ResponseStatus.SUCCESS;
380 }
381 return ResponseStatus.FAILURE;
8bfbc743 382 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
d270cc87 383 if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) {
8bfbc743
JB
384 return ResponseStatus.SUCCESS;
385 }
386 return ResponseStatus.FAILURE;
91a7d3ea
JB
387 case BroadcastChannelProcedureName.DATA_TRANSFER:
388 if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
389 return ResponseStatus.SUCCESS;
390 }
391 return ResponseStatus.FAILURE;
10db00b2 392 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 393 case BroadcastChannelProcedureName.METER_VALUES:
10db00b2
JB
394 if (Utils.isEmptyObject(commandResponse) === true) {
395 return ResponseStatus.SUCCESS;
396 }
397 return ResponseStatus.FAILURE;
398 case BroadcastChannelProcedureName.HEARTBEAT:
399 if ('currentTime' in commandResponse) {
400 return ResponseStatus.SUCCESS;
401 }
402 return ResponseStatus.FAILURE;
403 default:
404 return ResponseStatus.FAILURE;
89b7a234
JB
405 }
406 }
407}