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