1 import { secondsToMilliseconds
} from
'date-fns'
2 import { isEmpty
} from
'rambda'
4 import type { ChargingStation
} from
'../ChargingStation.js'
6 import { BaseError
, type OCPPError
} from
'../../exception/index.js'
10 type AuthorizeResponse
,
11 type BootNotificationRequest
,
12 type BootNotificationResponse
,
13 BroadcastChannelProcedureName
,
14 type BroadcastChannelRequest
,
15 type BroadcastChannelRequestPayload
,
16 type BroadcastChannelResponsePayload
,
17 type DataTransferRequest
,
18 type DataTransferResponse
,
20 type DiagnosticsStatusNotificationRequest
,
21 type DiagnosticsStatusNotificationResponse
,
23 type FirmwareStatusNotificationRequest
,
24 type FirmwareStatusNotificationResponse
,
25 type HeartbeatRequest
,
26 type HeartbeatResponse
,
28 type MeterValuesRequest
,
29 type MeterValuesResponse
,
30 RegistrationStatusEnumType
,
34 StandardParametersKey
,
35 type StartTransactionRequest
,
36 type StartTransactionResponse
,
37 type StatusNotificationRequest
,
38 type StatusNotificationResponse
,
39 type StopTransactionRequest
,
40 type StopTransactionResponse
,
41 } from
'../../types/index.js'
42 import { Constants
, convertToInt
, isAsyncFunction
, logger
} from
'../../utils/index.js'
43 import { getConfigurationKey
} from
'../ConfigurationKeyUtils.js'
44 import { buildMeterValue
} from
'../ocpp/index.js'
45 import { WorkerBroadcastChannel
} from
'./WorkerBroadcastChannel.js'
47 const moduleName
= 'ChargingStationWorkerBroadcastChannel'
49 type CommandResponse
=
51 | BootNotificationResponse
52 | DataTransferResponse
55 | StartTransactionResponse
56 | StopTransactionResponse
58 type CommandHandler
= (
59 requestPayload
?: BroadcastChannelRequestPayload
60 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
61 ) => CommandResponse
| Promise
<CommandResponse
| void> | void
63 export class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
64 private readonly chargingStation
: ChargingStation
65 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>
67 constructor (chargingStation
: ChargingStation
) {
69 const requestParams
: RequestParams
= {
72 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
74 BroadcastChannelProcedureName
.AUTHORIZE
,
75 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
76 await this.chargingStation
.ocppRequestService
.requestHandler
<
81 RequestCommand
.AUTHORIZE
,
82 requestPayload
as AuthorizeRequest
,
87 BroadcastChannelProcedureName
.BOOT_NOTIFICATION
,
88 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
89 return await this.chargingStation
.ocppRequestService
.requestHandler
<
90 BootNotificationRequest
,
91 BootNotificationResponse
94 RequestCommand
.BOOT_NOTIFICATION
,
96 ...this.chargingStation
.bootNotificationRequest
,
98 } as BootNotificationRequest
,
100 skipBufferingOnError
: true,
107 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
109 this.chargingStation
.closeWSConnection()
113 BroadcastChannelProcedureName
.DATA_TRANSFER
,
114 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
115 await this.chargingStation
.ocppRequestService
.requestHandler
<
119 this.chargingStation
,
120 RequestCommand
.DATA_TRANSFER
,
121 requestPayload
as DataTransferRequest
,
126 BroadcastChannelProcedureName
.DELETE_CHARGING_STATIONS
,
127 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
128 await this.chargingStation
.delete(requestPayload
?.deleteConfiguration
as boolean)
132 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
133 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
134 await this.chargingStation
.ocppRequestService
.requestHandler
<
135 DiagnosticsStatusNotificationRequest
,
136 DiagnosticsStatusNotificationResponse
138 this.chargingStation
,
139 RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
140 requestPayload
as DiagnosticsStatusNotificationRequest
,
145 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
146 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
147 await this.chargingStation
.ocppRequestService
.requestHandler
<
148 FirmwareStatusNotificationRequest
,
149 FirmwareStatusNotificationResponse
151 this.chargingStation
,
152 RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
153 requestPayload
as FirmwareStatusNotificationRequest
,
158 BroadcastChannelProcedureName
.HEARTBEAT
,
159 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
160 await this.chargingStation
.ocppRequestService
.requestHandler
<
164 this.chargingStation
,
165 RequestCommand
.HEARTBEAT
,
166 requestPayload
as HeartbeatRequest
,
171 BroadcastChannelProcedureName
.METER_VALUES
,
172 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
173 const configuredMeterValueSampleInterval
= getConfigurationKey(
175 StandardParametersKey
.MeterValueSampleInterval
177 return await this.chargingStation
.ocppRequestService
.requestHandler
<
181 this.chargingStation
,
182 RequestCommand
.METER_VALUES
,
186 this.chargingStation
,
187 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
188 requestPayload
!.connectorId
!,
189 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
190 this.chargingStation
.getConnectorStatus(requestPayload
!.connectorId
!)!
192 configuredMeterValueSampleInterval
!= null
193 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval
.value
))
194 : Constants
.DEFAULT_METER_VALUES_INTERVAL
198 } as MeterValuesRequest
,
204 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
206 this.chargingStation
.openWSConnection()
210 BroadcastChannelProcedureName
.SET_SUPERVISION_URL
,
211 (requestPayload
?: BroadcastChannelRequestPayload
) => {
212 this.chargingStation
.setSupervisionUrl(requestPayload
?.url
as string)
216 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
217 (requestPayload
?: BroadcastChannelRequestPayload
) => {
218 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
222 BroadcastChannelProcedureName
.START_CHARGING_STATION
,
224 this.chargingStation
.start()
228 BroadcastChannelProcedureName
.START_TRANSACTION
,
229 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
230 await this.chargingStation
.ocppRequestService
.requestHandler
<
231 StartTransactionRequest
,
232 StartTransactionResponse
234 this.chargingStation
,
235 RequestCommand
.START_TRANSACTION
,
236 requestPayload
as StartTransactionRequest
,
241 BroadcastChannelProcedureName
.STATUS_NOTIFICATION
,
242 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
243 await this.chargingStation
.ocppRequestService
.requestHandler
<
244 StatusNotificationRequest
,
245 StatusNotificationResponse
247 this.chargingStation
,
248 RequestCommand
.STATUS_NOTIFICATION
,
249 requestPayload
as StatusNotificationRequest
,
254 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
255 (requestPayload
?: BroadcastChannelRequestPayload
) => {
256 this.chargingStation
.stopAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
260 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
262 await this.chargingStation
.stop()
266 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
267 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
268 await this.chargingStation
.ocppRequestService
.requestHandler
<
269 StopTransactionRequest
,
270 StartTransactionResponse
272 this.chargingStation
,
273 RequestCommand
.STOP_TRANSACTION
,
275 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
276 requestPayload
?.transactionId
,
280 } as StopTransactionRequest
,
285 this.chargingStation
= chargingStation
286 this.onmessage
= this.requestHandler
.bind(this) as (message
: unknown
) => void
287 this.onmessageerror
= this.messageErrorHandler
.bind(this) as (message
: unknown
) => void
290 private cleanRequestPayload (
291 command
: BroadcastChannelProcedureName
,
292 requestPayload
: BroadcastChannelRequestPayload
294 delete requestPayload
.hashId
295 delete requestPayload
.hashIds
297 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
298 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
299 ].includes(command
) && delete requestPayload
.connectorIds
302 private async commandHandler (
303 command
: BroadcastChannelProcedureName
,
304 requestPayload
: BroadcastChannelRequestPayload
305 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
306 ): Promise
<CommandResponse
| void> {
307 if (this.commandHandlers
.has(command
)) {
308 this.cleanRequestPayload(command
, requestPayload
)
309 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
310 const commandHandler
= this.commandHandlers
.get(command
)!
311 if (isAsyncFunction(commandHandler
)) {
312 return await commandHandler(requestPayload
)
316 requestPayload
?: BroadcastChannelRequestPayload
317 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
318 ) => CommandResponse
| void
321 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
324 private commandResponseToResponsePayload (
325 command
: BroadcastChannelProcedureName
,
326 requestPayload
: BroadcastChannelRequestPayload
,
327 commandResponse
: CommandResponse
328 ): BroadcastChannelResponsePayload
{
329 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
)
330 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
332 hashId
: this.chargingStation
.stationInfo
?.hashId
,
333 status: responseStatus
,
339 hashId
: this.chargingStation
.stationInfo
?.hashId
,
341 status: responseStatus
,
345 private commandResponseToResponseStatus (
346 command
: BroadcastChannelProcedureName
,
347 commandResponse
: CommandResponse
350 case BroadcastChannelProcedureName
.AUTHORIZE
:
351 case BroadcastChannelProcedureName
.START_TRANSACTION
:
352 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
357 | StartTransactionResponse
358 | StopTransactionResponse
359 ).idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
361 return ResponseStatus
.SUCCESS
363 return ResponseStatus
.FAILURE
364 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
365 if (commandResponse
.status === RegistrationStatusEnumType
.ACCEPTED
) {
366 return ResponseStatus
.SUCCESS
368 return ResponseStatus
.FAILURE
369 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
370 if (commandResponse
.status === DataTransferStatus
.ACCEPTED
) {
371 return ResponseStatus
.SUCCESS
373 return ResponseStatus
.FAILURE
374 case BroadcastChannelProcedureName
.HEARTBEAT
:
375 if ('currentTime' in commandResponse
) {
376 return ResponseStatus
.SUCCESS
378 return ResponseStatus
.FAILURE
379 case BroadcastChannelProcedureName
.METER_VALUES
:
380 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
381 if (isEmpty(commandResponse
)) {
382 return ResponseStatus
.SUCCESS
384 return ResponseStatus
.FAILURE
386 return ResponseStatus
.FAILURE
390 private messageErrorHandler (messageEvent
: MessageEvent
): void {
392 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
397 private requestHandler (messageEvent
: MessageEvent
): void {
398 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
)
399 if (validatedMessageEvent
=== false) {
402 if (this.isResponse(validatedMessageEvent
.data
)) {
405 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
407 requestPayload
.hashIds
!= null &&
408 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
409 !requestPayload
.hashIds
.includes(this.chargingStation
.stationInfo
!.hashId
)
413 if (requestPayload
.hashId
!= null) {
415 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
419 let responsePayload
: BroadcastChannelResponsePayload
| undefined
420 this.commandHandler(command
, requestPayload
)
421 .then(commandResponse
=> {
422 if (commandResponse
== null || isEmpty(commandResponse
)) {
424 hashId
: this.chargingStation
.stationInfo
?.hashId
,
425 status: ResponseStatus
.SUCCESS
,
428 responsePayload
= this.commandResponseToResponsePayload(
437 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
438 this.sendResponse([uuid
, responsePayload
!])
440 .catch((error
: unknown
) => {
442 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
447 errorDetails
: (error
as OCPPError
).details
,
448 errorMessage
: (error
as OCPPError
).message
,
449 errorStack
: (error
as OCPPError
).stack
,
450 hashId
: this.chargingStation
.stationInfo
?.hashId
,
452 status: ResponseStatus
.FAILURE
,
453 } satisfies BroadcastChannelResponsePayload