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