refactor: cleanup type definition in internal messaging
[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'
4b9332af
JB
40import {
41 Constants,
42 convertToInt,
43 isAsyncFunction,
44 isEmptyObject,
45 logger
46} from '../../utils/index.js'
66a7748d
JB
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
4b9332af 65) => Promise<CommandResponse | void> | CommandResponse | 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(
f938317f 138 requestPayload?.transactionId,
66a7748d 139 true
1969f643 140 ),
66a7748d 141 ...requestPayload
1969f643 142 },
66a7748d
JB
143 requestParams
144 )
9d73266c 145 ],
1984f194
JB
146 [
147 BroadcastChannelProcedureName.AUTHORIZE,
148 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
149 await this.chargingStation.ocppRequestService.requestHandler<
150 AuthorizeRequest,
151 AuthorizeResponse
152 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams)
1984f194 153 ],
8bfbc743
JB
154 [
155 BroadcastChannelProcedureName.BOOT_NOTIFICATION,
156 async (requestPayload?: BroadcastChannelRequestPayload) => {
157 this.chargingStation.bootNotificationResponse =
158 await this.chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
159 BootNotificationRequest,
160 BootNotificationResponse
8bfbc743
JB
161 >(
162 this.chargingStation,
163 RequestCommand.BOOT_NOTIFICATION,
164 {
165 ...this.chargingStation.bootNotificationRequest,
66a7748d 166 ...requestPayload
8bfbc743
JB
167 },
168 {
169 skipBufferingOnError: true,
66a7748d
JB
170 throwError: true
171 }
172 )
173 return this.chargingStation.bootNotificationResponse
174 }
8bfbc743 175 ],
9d73266c
JB
176 [
177 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
178 async (requestPayload?: BroadcastChannelRequestPayload) =>
66a7748d
JB
179 await this.chargingStation.ocppRequestService.requestHandler<
180 StatusNotificationRequest,
181 StatusNotificationResponse
a223d9be 182 >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload, requestParams)
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
ba9a56a6 265 private requestHandler (messageEvent: MessageEvent): void {
66a7748d 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
ae2f7bc0 288 let commandResponse: CommandResponse | undefined
ba9a56a6
JB
289 this.commandHandler(command, requestPayload)
290 .then(commandResponse => {
291 if (commandResponse == null || isEmptyObject(commandResponse)) {
292 responsePayload = {
293 hashId: this.chargingStation.stationInfo?.hashId,
294 status: ResponseStatus.SUCCESS
295 }
296 } else {
297 responsePayload = this.commandResponseToResponsePayload(
298 command,
299 requestPayload,
300 commandResponse
301 )
302 }
303 })
304 .catch(error => {
305 logger.error(
306 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
307 error
308 )
10d244c0 309 responsePayload = {
5199f9fd 310 hashId: this.chargingStation.stationInfo?.hashId,
ba9a56a6 311 status: ResponseStatus.FAILURE,
1984f194 312 command,
d3195f0a 313 requestPayload,
ba9a56a6
JB
314 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
315 commandResponse: commandResponse!,
316 errorMessage: (error as OCPPError).message,
317 errorStack: (error as OCPPError).stack,
318 errorDetails: (error as OCPPError).details
319 }
320 })
321 .finally(() => {
66a7748d 322 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ba9a56a6
JB
323 this.sendResponse([uuid, responsePayload!])
324 })
6c8f5d90
JB
325 }
326
66a7748d 327 private messageErrorHandler (messageEvent: MessageEvent): void {
6c8f5d90
JB
328 logger.error(
329 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
66a7748d
JB
330 messageEvent
331 )
6c8f5d90
JB
332 }
333
66a7748d 334 private async commandHandler (
6c8f5d90 335 command: BroadcastChannelProcedureName,
66a7748d
JB
336 requestPayload: BroadcastChannelRequestPayload
337 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
cc6845fc 338 ): Promise<CommandResponse | void> {
66a7748d
JB
339 if (this.commandHandlers.has(command)) {
340 this.cleanRequestPayload(command, requestPayload)
341 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4b9332af
JB
342 const commandHandler = this.commandHandlers.get(command)!
343 if (isAsyncFunction(commandHandler)) {
344 return await commandHandler(requestPayload)
345 }
346 return (
7f79ef45
JB
347 commandHandler as (
348 requestPayload?: BroadcastChannelRequestPayload
349 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
350 ) => CommandResponse | void
4b9332af 351 )(requestPayload)
6c8f5d90 352 }
66a7748d 353 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
6c8f5d90
JB
354 }
355
66a7748d 356 private cleanRequestPayload (
1984f194 357 command: BroadcastChannelProcedureName,
66a7748d 358 requestPayload: BroadcastChannelRequestPayload
1984f194 359 ): void {
66a7748d
JB
360 delete requestPayload.hashId
361 delete requestPayload.hashIds
362 ![
1984f194 363 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
66a7748d
JB
364 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR
365 ].includes(command) && delete requestPayload.connectorIds
1984f194
JB
366 }
367
66a7748d 368 private commandResponseToResponsePayload (
d3195f0a
JB
369 command: BroadcastChannelProcedureName,
370 requestPayload: BroadcastChannelRequestPayload,
66a7748d 371 commandResponse: CommandResponse
d3195f0a 372 ): BroadcastChannelResponsePayload {
66a7748d 373 const responseStatus = this.commandResponseToResponseStatus(command, commandResponse)
cfa257f5 374 if (responseStatus === ResponseStatus.SUCCESS) {
d3195f0a 375 return {
5199f9fd 376 hashId: this.chargingStation.stationInfo?.hashId,
66a7748d
JB
377 status: responseStatus
378 }
d3195f0a
JB
379 }
380 return {
5199f9fd 381 hashId: this.chargingStation.stationInfo?.hashId,
cfa257f5 382 status: responseStatus,
d3195f0a
JB
383 command,
384 requestPayload,
66a7748d
JB
385 commandResponse
386 }
d3195f0a
JB
387 }
388
66a7748d 389 private commandResponseToResponseStatus (
10db00b2 390 command: BroadcastChannelProcedureName,
66a7748d 391 commandResponse: CommandResponse
10db00b2
JB
392 ): ResponseStatus {
393 switch (command) {
394 case BroadcastChannelProcedureName.START_TRANSACTION:
395 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 396 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 397 if (
1984f194
JB
398 (
399 commandResponse as
400 | StartTransactionResponse
401 | StopTransactionResponse
402 | AuthorizeResponse
5199f9fd 403 ).idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2 404 ) {
66a7748d 405 return ResponseStatus.SUCCESS
10db00b2 406 }
66a7748d 407 return ResponseStatus.FAILURE
8bfbc743 408 case BroadcastChannelProcedureName.BOOT_NOTIFICATION:
5199f9fd 409 if (commandResponse.status === RegistrationStatusEnumType.ACCEPTED) {
66a7748d 410 return ResponseStatus.SUCCESS
8bfbc743 411 }
66a7748d 412 return ResponseStatus.FAILURE
91a7d3ea 413 case BroadcastChannelProcedureName.DATA_TRANSFER:
5199f9fd 414 if (commandResponse.status === DataTransferStatus.ACCEPTED) {
66a7748d 415 return ResponseStatus.SUCCESS
91a7d3ea 416 }
66a7748d 417 return ResponseStatus.FAILURE
10db00b2 418 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 419 case BroadcastChannelProcedureName.METER_VALUES:
66a7748d
JB
420 if (isEmptyObject(commandResponse)) {
421 return ResponseStatus.SUCCESS
10db00b2 422 }
66a7748d 423 return ResponseStatus.FAILURE
10db00b2
JB
424 case BroadcastChannelProcedureName.HEARTBEAT:
425 if ('currentTime' in commandResponse) {
66a7748d 426 return ResponseStatus.SUCCESS
10db00b2 427 }
66a7748d 428 return ResponseStatus.FAILURE
10db00b2 429 default:
66a7748d 430 return ResponseStatus.FAILURE
89b7a234
JB
431 }
432 }
433}