refactor: cleanup isNullOrdefined usage
[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
JB
275 requestPayload.hashIds != null &&
276 !requestPayload.hashIds.includes(this.chargingStation.stationInfo.hashId)
2afb4d15 277 ) {
66a7748d 278 return
2afb4d15 279 }
401fa922 280 if (requestPayload.hashId != null) {
2afb4d15 281 logger.error(
66a7748d
JB
282 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
283 )
284 return
4eca248c 285 }
66a7748d 286 let responsePayload: BroadcastChannelResponsePayload | undefined
a807045b
JB
287 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
288 let commandResponse: CommandResponse | void | undefined
6c8f5d90 289 try {
66a7748d 290 commandResponse = await this.commandHandler(command, requestPayload)
401fa922 291 if (commandResponse == null || isEmptyObject(commandResponse)) {
10d244c0 292 responsePayload = {
51c83d6f 293 hashId: this.chargingStation.stationInfo.hashId,
66a7748d
JB
294 status: ResponseStatus.SUCCESS
295 }
6c8f5d90 296 } else {
d3195f0a 297 responsePayload = this.commandResponseToResponsePayload(
1984f194 298 command,
d3195f0a 299 requestPayload,
401fa922 300 commandResponse
66a7748d 301 )
6c8f5d90
JB
302 }
303 } catch (error) {
304 logger.error(
305 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
66a7748d
JB
306 error
307 )
6c8f5d90 308 responsePayload = {
51c83d6f 309 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
310 status: ResponseStatus.FAILURE,
311 command,
312 requestPayload,
66a7748d
JB
313 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
314 commandResponse: commandResponse!,
7375968c
JB
315 errorMessage: (error as OCPPError).message,
316 errorStack: (error as OCPPError).stack,
66a7748d
JB
317 errorDetails: (error as OCPPError).details
318 }
623b39b5 319 } finally {
66a7748d
JB
320 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
321 this.sendResponse([uuid, responsePayload!])
6c8f5d90 322 }
6c8f5d90
JB
323 }
324
66a7748d 325 private messageErrorHandler (messageEvent: MessageEvent): void {
6c8f5d90
JB
326 logger.error(
327 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
66a7748d
JB
328 messageEvent
329 )
6c8f5d90
JB
330 }
331
66a7748d 332 private async commandHandler (
6c8f5d90 333 command: BroadcastChannelProcedureName,
66a7748d
JB
334 requestPayload: BroadcastChannelRequestPayload
335 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
336 ): Promise<void | CommandResponse> {
337 if (this.commandHandlers.has(command)) {
338 this.cleanRequestPayload(command, requestPayload)
339 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
340 return await this.commandHandlers.get(command)!(requestPayload)
6c8f5d90 341 }
66a7748d 342 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
6c8f5d90
JB
343 }
344
66a7748d 345 private cleanRequestPayload (
1984f194 346 command: BroadcastChannelProcedureName,
66a7748d 347 requestPayload: BroadcastChannelRequestPayload
1984f194 348 ): void {
66a7748d
JB
349 delete requestPayload.hashId
350 delete requestPayload.hashIds
351 ![
1984f194 352 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
66a7748d
JB
353 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR
354 ].includes(command) && delete requestPayload.connectorIds
1984f194
JB
355 }
356
66a7748d 357 private commandResponseToResponsePayload (
d3195f0a
JB
358 command: BroadcastChannelProcedureName,
359 requestPayload: BroadcastChannelRequestPayload,
66a7748d 360 commandResponse: CommandResponse
d3195f0a 361 ): BroadcastChannelResponsePayload {
66a7748d 362 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse)
cfa257f5 363 if (responseStatus === ResponseStatus.SUCCESS) {
d3195f0a
JB
364 return {
365 hashId: this.chargingStation.stationInfo.hashId,
66a7748d
JB
366 status: responseStatus
367 }
d3195f0a
JB
368 }
369 return {
370 hashId: this.chargingStation.stationInfo.hashId,
cfa257f5 371 status: responseStatus,
d3195f0a
JB
372 command,
373 requestPayload,
66a7748d
JB
374 commandResponse
375 }
d3195f0a
JB
376 }
377
66a7748d 378 private commandResponseToResponseStatus (
10db00b2 379 command: BroadcastChannelProcedureName,
66a7748d 380 commandResponse: CommandResponse
10db00b2
JB
381 ): ResponseStatus {
382 switch (command) {
383 case BroadcastChannelProcedureName.START_TRANSACTION:
384 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 385 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 386 if (
1984f194
JB
387 (
388 commandResponse as
389 | StartTransactionResponse
390 | StopTransactionResponse
391 | AuthorizeResponse
392 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2 393 ) {
66a7748d 394 return ResponseStatus.SUCCESS
10db00b2 395 }
66a7748d 396 return ResponseStatus.FAILURE
8bfbc743 397 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
d270cc87 398 if (commandResponse?.status === RegistrationStatusEnumType.ACCEPTED) {
66a7748d 399 return ResponseStatus.SUCCESS
8bfbc743 400 }
66a7748d 401 return ResponseStatus.FAILURE
91a7d3ea
JB
402 case BroadcastChannelProcedureName.DATA_TRANSFER:
403 if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
66a7748d 404 return ResponseStatus.SUCCESS
91a7d3ea 405 }
66a7748d 406 return ResponseStatus.FAILURE
10db00b2 407 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 408 case BroadcastChannelProcedureName.METER_VALUES:
66a7748d
JB
409 if (isEmptyObject(commandResponse)) {
410 return ResponseStatus.SUCCESS
10db00b2 411 }
66a7748d 412 return ResponseStatus.FAILURE
10db00b2
JB
413 case BroadcastChannelProcedureName.HEARTBEAT:
414 if ('currentTime' in commandResponse) {
66a7748d 415 return ResponseStatus.SUCCESS
10db00b2 416 }
66a7748d 417 return ResponseStatus.FAILURE
10db00b2 418 default:
66a7748d 419 return ResponseStatus.FAILURE
89b7a234
JB
420 }
421 }
422}