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