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