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