1 import { secondsToMilliseconds
} from
'date-fns'
2 import { isEmpty
} from
'rambda'
4 import { BaseError
, type OCPPError
} from
'../../exception/index.js'
8 type AuthorizeResponse
,
9 type BootNotificationRequest
,
10 type BootNotificationResponse
,
11 BroadcastChannelProcedureName
,
12 type BroadcastChannelRequest
,
13 type BroadcastChannelRequestPayload
,
14 type BroadcastChannelResponsePayload
,
15 type DataTransferRequest
,
16 type DataTransferResponse
,
18 type DiagnosticsStatusNotificationRequest
,
19 type DiagnosticsStatusNotificationResponse
,
21 type FirmwareStatusNotificationRequest
,
22 type FirmwareStatusNotificationResponse
,
23 type HeartbeatRequest
,
24 type HeartbeatResponse
,
26 type MeterValuesRequest
,
27 type MeterValuesResponse
,
28 RegistrationStatusEnumType
,
32 StandardParametersKey
,
33 type StartTransactionRequest
,
34 type StartTransactionResponse
,
35 type StatusNotificationRequest
,
36 type StatusNotificationResponse
,
37 type StopTransactionRequest
,
38 type StopTransactionResponse
39 } from
'../../types/index.js'
40 import { Constants
, convertToInt
, isAsyncFunction
, logger
} from
'../../utils/index.js'
41 import type { ChargingStation
} from
'../ChargingStation.js'
42 import { getConfigurationKey
} from
'../ConfigurationKeyUtils.js'
43 import { buildMeterValue
} from
'../ocpp/index.js'
44 import { WorkerBroadcastChannel
} from
'./WorkerBroadcastChannel.js'
46 const moduleName
= 'ChargingStationWorkerBroadcastChannel'
48 type CommandResponse
=
50 | StartTransactionResponse
51 | StopTransactionResponse
53 | BootNotificationResponse
55 | DataTransferResponse
57 type CommandHandler
= (
58 requestPayload
?: BroadcastChannelRequestPayload
59 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
60 ) => Promise
<CommandResponse
| void> | CommandResponse
| void
62 export class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
63 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>
64 private readonly chargingStation
: ChargingStation
66 constructor (chargingStation
: ChargingStation
) {
68 const requestParams
: RequestParams
= {
71 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
73 BroadcastChannelProcedureName
.START_CHARGING_STATION
,
75 this.chargingStation
.start()
79 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
81 await this.chargingStation
.stop()
85 BroadcastChannelProcedureName
.DELETE_CHARGING_STATIONS
,
86 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
87 await this.chargingStation
.delete(requestPayload
?.deleteConfiguration
as boolean)
91 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
93 this.chargingStation
.openWSConnection()
97 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
99 this.chargingStation
.closeWSConnection()
103 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
104 (requestPayload
?: BroadcastChannelRequestPayload
) => {
105 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
109 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
110 (requestPayload
?: BroadcastChannelRequestPayload
) => {
111 this.chargingStation
.stopAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
115 BroadcastChannelProcedureName
.SET_SUPERVISION_URL
,
116 (requestPayload
?: BroadcastChannelRequestPayload
) => {
117 this.chargingStation
.setSupervisionUrl(requestPayload
?.url
as string)
121 BroadcastChannelProcedureName
.START_TRANSACTION
,
122 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
123 await this.chargingStation
.ocppRequestService
.requestHandler
<
124 StartTransactionRequest
,
125 StartTransactionResponse
126 >(this.chargingStation
, RequestCommand
.START_TRANSACTION
, requestPayload
, requestParams
)
129 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
130 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
131 await this.chargingStation
.ocppRequestService
.requestHandler
<
132 StopTransactionRequest
,
133 StartTransactionResponse
135 this.chargingStation
,
136 RequestCommand
.STOP_TRANSACTION
,
138 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
139 requestPayload
?.transactionId
,
148 BroadcastChannelProcedureName
.AUTHORIZE
,
149 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
150 await this.chargingStation
.ocppRequestService
.requestHandler
<
153 >(this.chargingStation
, RequestCommand
.AUTHORIZE
, requestPayload
, requestParams
)
156 BroadcastChannelProcedureName
.BOOT_NOTIFICATION
,
157 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
158 this.chargingStation
.bootNotificationResponse
=
159 await this.chargingStation
.ocppRequestService
.requestHandler
<
160 BootNotificationRequest
,
161 BootNotificationResponse
163 this.chargingStation
,
164 RequestCommand
.BOOT_NOTIFICATION
,
166 ...this.chargingStation
.bootNotificationRequest
,
170 skipBufferingOnError
: true,
174 return this.chargingStation
.bootNotificationResponse
178 BroadcastChannelProcedureName
.STATUS_NOTIFICATION
,
179 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
180 await this.chargingStation
.ocppRequestService
.requestHandler
<
181 StatusNotificationRequest
,
182 StatusNotificationResponse
183 >(this.chargingStation
, RequestCommand
.STATUS_NOTIFICATION
, requestPayload
, requestParams
)
186 BroadcastChannelProcedureName
.HEARTBEAT
,
187 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
188 await this.chargingStation
.ocppRequestService
.requestHandler
<
191 >(this.chargingStation
, RequestCommand
.HEARTBEAT
, requestPayload
, requestParams
)
194 BroadcastChannelProcedureName
.METER_VALUES
,
195 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
196 const configuredMeterValueSampleInterval
= getConfigurationKey(
198 StandardParametersKey
.MeterValueSampleInterval
200 return await this.chargingStation
.ocppRequestService
.requestHandler
<
204 this.chargingStation
,
205 RequestCommand
.METER_VALUES
,
209 this.chargingStation
,
210 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
211 requestPayload
!.connectorId
!,
212 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
213 this.chargingStation
.getConnectorStatus(requestPayload
!.connectorId
!)!
215 configuredMeterValueSampleInterval
!= null
216 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval
.value
))
217 : Constants
.DEFAULT_METER_VALUES_INTERVAL
227 BroadcastChannelProcedureName
.DATA_TRANSFER
,
228 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
229 await this.chargingStation
.ocppRequestService
.requestHandler
<
232 >(this.chargingStation
, RequestCommand
.DATA_TRANSFER
, requestPayload
, requestParams
)
235 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
236 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
237 await this.chargingStation
.ocppRequestService
.requestHandler
<
238 DiagnosticsStatusNotificationRequest
,
239 DiagnosticsStatusNotificationResponse
241 this.chargingStation
,
242 RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
248 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
249 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
250 await this.chargingStation
.ocppRequestService
.requestHandler
<
251 FirmwareStatusNotificationRequest
,
252 FirmwareStatusNotificationResponse
254 this.chargingStation
,
255 RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
261 this.chargingStation
= chargingStation
262 this.onmessage
= this.requestHandler
.bind(this) as (message
: unknown
) => void
263 this.onmessageerror
= this.messageErrorHandler
.bind(this) as (message
: unknown
) => void
266 private requestHandler (messageEvent
: MessageEvent
): void {
267 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
)
268 if (validatedMessageEvent
=== false) {
271 if (this.isResponse(validatedMessageEvent
.data
)) {
274 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
276 requestPayload
.hashIds
!= null &&
277 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
278 !requestPayload
.hashIds
.includes(this.chargingStation
.stationInfo
!.hashId
)
282 if (requestPayload
.hashId
!= null) {
284 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
288 let responsePayload
: BroadcastChannelResponsePayload
| undefined
289 this.commandHandler(command
, requestPayload
)
290 .then(commandResponse
=> {
291 if (commandResponse
== null || isEmpty(commandResponse
)) {
293 hashId
: this.chargingStation
.stationInfo
?.hashId
,
294 status: ResponseStatus
.SUCCESS
297 responsePayload
= this.commandResponseToResponsePayload(
304 .catch((error
: unknown
) => {
306 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
310 hashId
: this.chargingStation
.stationInfo
?.hashId
,
311 status: ResponseStatus
.FAILURE
,
314 errorMessage
: (error
as OCPPError
).message
,
315 errorStack
: (error
as OCPPError
).stack
,
316 errorDetails
: (error
as OCPPError
).details
317 } satisfies BroadcastChannelResponsePayload
320 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
321 this.sendResponse([uuid
, responsePayload
!])
325 private messageErrorHandler (messageEvent
: MessageEvent
): void {
327 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
332 private async commandHandler (
333 command
: BroadcastChannelProcedureName
,
334 requestPayload
: BroadcastChannelRequestPayload
335 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
336 ): Promise
<CommandResponse
| void> {
337 if (this.commandHandlers
.has(command
)) {
338 this.cleanRequestPayload(command
, requestPayload
)
339 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
340 const commandHandler
= this.commandHandlers
.get(command
)!
341 if (isAsyncFunction(commandHandler
)) {
342 return await commandHandler(requestPayload
)
346 requestPayload
?: BroadcastChannelRequestPayload
347 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
348 ) => CommandResponse
| void
351 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
354 private cleanRequestPayload (
355 command
: BroadcastChannelProcedureName
,
356 requestPayload
: BroadcastChannelRequestPayload
358 delete requestPayload
.hashId
359 delete requestPayload
.hashIds
361 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
362 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
363 ].includes(command
) && delete requestPayload
.connectorIds
366 private commandResponseToResponsePayload (
367 command
: BroadcastChannelProcedureName
,
368 requestPayload
: BroadcastChannelRequestPayload
,
369 commandResponse
: CommandResponse
370 ): BroadcastChannelResponsePayload
{
371 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
)
372 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
374 hashId
: this.chargingStation
.stationInfo
?.hashId
,
375 status: responseStatus
379 hashId
: this.chargingStation
.stationInfo
?.hashId
,
380 status: responseStatus
,
387 private commandResponseToResponseStatus (
388 command
: BroadcastChannelProcedureName
,
389 commandResponse
: CommandResponse
392 case BroadcastChannelProcedureName
.START_TRANSACTION
:
393 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
394 case BroadcastChannelProcedureName
.AUTHORIZE
:
398 | StartTransactionResponse
399 | StopTransactionResponse
401 ).idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
403 return ResponseStatus
.SUCCESS
405 return ResponseStatus
.FAILURE
406 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
407 if (commandResponse
.status === RegistrationStatusEnumType
.ACCEPTED
) {
408 return ResponseStatus
.SUCCESS
410 return ResponseStatus
.FAILURE
411 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
412 if (commandResponse
.status === DataTransferStatus
.ACCEPTED
) {
413 return ResponseStatus
.SUCCESS
415 return ResponseStatus
.FAILURE
416 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
417 case BroadcastChannelProcedureName
.METER_VALUES
:
418 if (isEmpty(commandResponse
)) {
419 return ResponseStatus
.SUCCESS
421 return ResponseStatus
.FAILURE
422 case BroadcastChannelProcedureName
.HEARTBEAT
:
423 if ('currentTime' in commandResponse
) {
424 return ResponseStatus
.SUCCESS
426 return ResponseStatus
.FAILURE
428 return ResponseStatus
.FAILURE