refactor: cleanup unneeded type casting
[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(
f938317f 132 requestPayload?.transactionId,
66a7748d 133 true
1969f643 134 ),
66a7748d 135 ...requestPayload
1969f643 136 },
66a7748d
JB
137 requestParams
138 )
9d73266c 139 ],
1984f194
JB
140 [
141 BroadcastChannelProcedureName.AUTHORIZE,
142 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
143 await this.chargingStation.ocppRequestService.requestHandler<
144 AuthorizeRequest,
145 AuthorizeResponse
146 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams)
1984f194 147 ],
8bfbc743
JB
148 [
149 BroadcastChannelProcedureName.BOOT_NOTIFICATION,
150 async (requestPayload?: BroadcastChannelRequestPayload) => {
151 this.chargingStation.bootNotificationResponse =
152 await this.chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
153 BootNotificationRequest,
154 BootNotificationResponse
8bfbc743
JB
155 >(
156 this.chargingStation,
157 RequestCommand.BOOT_NOTIFICATION,
158 {
159 ...this.chargingStation.bootNotificationRequest,
66a7748d 160 ...requestPayload
8bfbc743
JB
161 },
162 {
163 skipBufferingOnError: true,
66a7748d
JB
164 throwError: true
165 }
166 )
167 return this.chargingStation.bootNotificationResponse
168 }
8bfbc743 169 ],
9d73266c
JB
170 [
171 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
172 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
173 await this.chargingStation.ocppRequestService.requestHandler<
174 StatusNotificationRequest,
175 StatusNotificationResponse
a223d9be 176 >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload, requestParams)
9d73266c
JB
177 ],
178 [
179 BroadcastChannelProcedureName.HEARTBEAT,
1984f194 180 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
181 await this.chargingStation.ocppRequestService.requestHandler<
182 HeartbeatRequest,
183 HeartbeatResponse
184 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams)
9d73266c 185 ],
d3195f0a
JB
186 [
187 BroadcastChannelProcedureName.METER_VALUES,
188 async (requestPayload?: BroadcastChannelRequestPayload) => {
f2d5e3d9
JB
189 const configuredMeterValueSampleInterval = getConfigurationKey(
190 chargingStation,
66a7748d
JB
191 StandardParametersKey.MeterValueSampleInterval
192 )
193 return await this.chargingStation.ocppRequestService.requestHandler<
194 MeterValuesRequest,
195 MeterValuesResponse
1969f643
JB
196 >(
197 this.chargingStation,
198 RequestCommand.METER_VALUES,
199 {
200 meterValue: [
41f3983a 201 buildMeterValue(
1969f643 202 this.chargingStation,
66a7748d 203 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 204 requestPayload!.connectorId!,
66a7748d 205 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4
JB
206 this.chargingStation.getConnectorStatus(requestPayload!.connectorId!)!
207 .transactionId!,
a807045b 208 configuredMeterValueSampleInterval != null
be4c6702 209 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
66a7748d
JB
210 : Constants.DEFAULT_METER_VALUES_INTERVAL
211 )
1969f643 212 ],
66a7748d 213 ...requestPayload
1969f643 214 },
66a7748d
JB
215 requestParams
216 )
217 }
d3195f0a 218 ],
91a7d3ea
JB
219 [
220 BroadcastChannelProcedureName.DATA_TRANSFER,
221 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
222 await this.chargingStation.ocppRequestService.requestHandler<
223 DataTransferRequest,
224 DataTransferResponse
225 >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams)
91a7d3ea 226 ],
c9a4f9ea
JB
227 [
228 BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
229 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
230 await this.chargingStation.ocppRequestService.requestHandler<
231 DiagnosticsStatusNotificationRequest,
232 DiagnosticsStatusNotificationResponse
8ec8e3d0
JB
233 >(
234 this.chargingStation,
235 RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
236 requestPayload,
66a7748d
JB
237 requestParams
238 )
c9a4f9ea
JB
239 ],
240 [
241 BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
242 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
243 await this.chargingStation.ocppRequestService.requestHandler<
244 FirmwareStatusNotificationRequest,
245 FirmwareStatusNotificationResponse
8ec8e3d0
JB
246 >(
247 this.chargingStation,
248 RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
249 requestPayload,
66a7748d
JB
250 requestParams
251 )
252 ]
253 ])
254 this.chargingStation = chargingStation
255 this.onmessage = this.requestHandler.bind(this) as (message: unknown) => void
256 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: unknown) => void
89b7a234
JB
257 }
258
ba9a56a6 259 private requestHandler (messageEvent: MessageEvent): void {
66a7748d 260 const validatedMessageEvent = this.validateMessageEvent(messageEvent)
5dea4c94 261 if (validatedMessageEvent === false) {
66a7748d 262 return
6c8f5d90 263 }
66a7748d
JB
264 if (this.isResponse(validatedMessageEvent.data)) {
265 return
5dea4c94 266 }
66a7748d 267 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest
2afb4d15 268 if (
401fa922 269 requestPayload.hashIds != null &&
5199f9fd
JB
270 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
271 !requestPayload.hashIds.includes(this.chargingStation.stationInfo!.hashId)
2afb4d15 272 ) {
66a7748d 273 return
2afb4d15 274 }
401fa922 275 if (requestPayload.hashId != null) {
2afb4d15 276 logger.error(
66a7748d
JB
277 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
278 )
279 return
4eca248c 280 }
66a7748d 281 let responsePayload: BroadcastChannelResponsePayload | undefined
a807045b 282 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
cc6845fc 283 let commandResponse: CommandResponse | void
ba9a56a6
JB
284 this.commandHandler(command, requestPayload)
285 .then(commandResponse => {
286 if (commandResponse == null || isEmptyObject(commandResponse)) {
287 responsePayload = {
288 hashId: this.chargingStation.stationInfo?.hashId,
289 status: ResponseStatus.SUCCESS
290 }
291 } else {
292 responsePayload = this.commandResponseToResponsePayload(
293 command,
294 requestPayload,
295 commandResponse
296 )
297 }
298 })
299 .catch(error => {
300 logger.error(
301 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
302 error
303 )
10d244c0 304 responsePayload = {
5199f9fd 305 hashId: this.chargingStation.stationInfo?.hashId,
ba9a56a6 306 status: ResponseStatus.FAILURE,
1984f194 307 command,
d3195f0a 308 requestPayload,
ba9a56a6
JB
309 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
310 commandResponse: commandResponse!,
311 errorMessage: (error as OCPPError).message,
312 errorStack: (error as OCPPError).stack,
313 errorDetails: (error as OCPPError).details
314 }
315 })
316 .finally(() => {
66a7748d 317 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ba9a56a6
JB
318 this.sendResponse([uuid, responsePayload!])
319 })
6c8f5d90
JB
320 }
321
66a7748d 322 private messageErrorHandler (messageEvent: MessageEvent): void {
6c8f5d90
JB
323 logger.error(
324 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
66a7748d
JB
325 messageEvent
326 )
6c8f5d90
JB
327 }
328
66a7748d 329 private async commandHandler (
6c8f5d90 330 command: BroadcastChannelProcedureName,
66a7748d
JB
331 requestPayload: BroadcastChannelRequestPayload
332 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
cc6845fc 333 ): Promise<CommandResponse | void> {
66a7748d
JB
334 if (this.commandHandlers.has(command)) {
335 this.cleanRequestPayload(command, requestPayload)
336 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
337 return await this.commandHandlers.get(command)!(requestPayload)
6c8f5d90 338 }
66a7748d 339 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
6c8f5d90
JB
340 }
341
66a7748d 342 private cleanRequestPayload (
1984f194 343 command: BroadcastChannelProcedureName,
66a7748d 344 requestPayload: BroadcastChannelRequestPayload
1984f194 345 ): void {
66a7748d
JB
346 delete requestPayload.hashId
347 delete requestPayload.hashIds
348 ![
1984f194 349 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
66a7748d
JB
350 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR
351 ].includes(command) && delete requestPayload.connectorIds
1984f194
JB
352 }
353
66a7748d 354 private commandResponseToResponsePayload (
d3195f0a
JB
355 command: BroadcastChannelProcedureName,
356 requestPayload: BroadcastChannelRequestPayload,
66a7748d 357 commandResponse: CommandResponse
d3195f0a 358 ): BroadcastChannelResponsePayload {
66a7748d 359 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse)
cfa257f5 360 if (responseStatus === ResponseStatus.SUCCESS) {
d3195f0a 361 return {
5199f9fd 362 hashId: this.chargingStation.stationInfo?.hashId,
66a7748d
JB
363 status: responseStatus
364 }
d3195f0a
JB
365 }
366 return {
5199f9fd 367 hashId: this.chargingStation.stationInfo?.hashId,
cfa257f5 368 status: responseStatus,
d3195f0a
JB
369 command,
370 requestPayload,
66a7748d
JB
371 commandResponse
372 }
d3195f0a
JB
373 }
374
66a7748d 375 private commandResponseToResponseStatus (
10db00b2 376 command: BroadcastChannelProcedureName,
66a7748d 377 commandResponse: CommandResponse
10db00b2
JB
378 ): ResponseStatus {
379 switch (command) {
380 case BroadcastChannelProcedureName.START_TRANSACTION:
381 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 382 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 383 if (
1984f194
JB
384 (
385 commandResponse as
386 | StartTransactionResponse
387 | StopTransactionResponse
388 | AuthorizeResponse
5199f9fd 389 ).idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2 390 ) {
66a7748d 391 return ResponseStatus.SUCCESS
10db00b2 392 }
66a7748d 393 return ResponseStatus.FAILURE
8bfbc743 394 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
5199f9fd 395 if (commandResponse.status === RegistrationStatusEnumType.ACCEPTED) {
66a7748d 396 return ResponseStatus.SUCCESS
8bfbc743 397 }
66a7748d 398 return ResponseStatus.FAILURE
91a7d3ea 399 case BroadcastChannelProcedureName.DATA_TRANSFER:
5199f9fd 400 if (commandResponse.status === DataTransferStatus.ACCEPTED) {
66a7748d 401 return ResponseStatus.SUCCESS
91a7d3ea 402 }
66a7748d 403 return ResponseStatus.FAILURE
10db00b2 404 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 405 case BroadcastChannelProcedureName.METER_VALUES:
66a7748d
JB
406 if (isEmptyObject(commandResponse)) {
407 return ResponseStatus.SUCCESS
10db00b2 408 }
66a7748d 409 return ResponseStatus.FAILURE
10db00b2
JB
410 case BroadcastChannelProcedureName.HEARTBEAT:
411 if ('currentTime' in commandResponse) {
66a7748d 412 return ResponseStatus.SUCCESS
10db00b2 413 }
66a7748d 414 return ResponseStatus.FAILURE
10db00b2 415 default:
66a7748d 416 return ResponseStatus.FAILURE
89b7a234
JB
417 }
418 }
419}