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