1 import { secondsToMilliseconds
} from
'date-fns'
3 import { BaseError
, type OCPPError
} from
'../../exception/index.js'
7 type AuthorizeResponse
,
8 type BootNotificationRequest
,
9 type BootNotificationResponse
,
10 BroadcastChannelProcedureName
,
11 type BroadcastChannelRequest
,
12 type BroadcastChannelRequestPayload
,
13 type BroadcastChannelResponsePayload
,
14 type DataTransferRequest
,
15 type DataTransferResponse
,
17 type DiagnosticsStatusNotificationRequest
,
18 type DiagnosticsStatusNotificationResponse
,
20 type FirmwareStatusNotificationRequest
,
21 type FirmwareStatusNotificationResponse
,
22 type HeartbeatRequest
,
23 type HeartbeatResponse
,
25 type MeterValuesRequest
,
26 type MeterValuesResponse
,
27 RegistrationStatusEnumType
,
31 StandardParametersKey
,
32 type StartTransactionRequest
,
33 type StartTransactionResponse
,
34 type StatusNotificationRequest
,
35 type StatusNotificationResponse
,
36 type StopTransactionRequest
,
37 type StopTransactionResponse
38 } from
'../../types/index.js'
45 } from
'../../utils/index.js'
46 import type { ChargingStation
} from
'../ChargingStation.js'
47 import { getConfigurationKey
} from
'../ConfigurationKeyUtils.js'
48 import { buildMeterValue
} from
'../ocpp/index.js'
49 import { WorkerBroadcastChannel
} from
'./WorkerBroadcastChannel.js'
51 const moduleName
= 'ChargingStationWorkerBroadcastChannel'
53 type CommandResponse
=
55 | StartTransactionResponse
56 | StopTransactionResponse
58 | BootNotificationResponse
60 | DataTransferResponse
62 type CommandHandler
= (
63 requestPayload
?: BroadcastChannelRequestPayload
64 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
65 ) => Promise
<CommandResponse
| void> | CommandResponse
| void
67 export class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
68 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>
69 private readonly chargingStation
: ChargingStation
71 constructor (chargingStation
: ChargingStation
) {
73 const requestParams
: RequestParams
= {
76 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
78 BroadcastChannelProcedureName
.START_CHARGING_STATION
,
80 this.chargingStation
.start()
84 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
86 await this.chargingStation
.stop()
90 BroadcastChannelProcedureName
.DELETE_CHARGING_STATIONS
,
91 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
92 await this.chargingStation
.delete(requestPayload
?.deleteConfiguration
as boolean)
96 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
98 this.chargingStation
.openWSConnection()
102 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
104 this.chargingStation
.closeWSConnection()
108 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
109 (requestPayload
?: BroadcastChannelRequestPayload
) => {
110 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
114 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
115 (requestPayload
?: BroadcastChannelRequestPayload
) => {
116 this.chargingStation
.stopAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
120 BroadcastChannelProcedureName
.SET_SUPERVISION_URL
,
121 (requestPayload
?: BroadcastChannelRequestPayload
) => {
122 this.chargingStation
.setSupervisionUrl(requestPayload
?.url
as string)
126 BroadcastChannelProcedureName
.START_TRANSACTION
,
127 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
128 await this.chargingStation
.ocppRequestService
.requestHandler
<
129 StartTransactionRequest
,
130 StartTransactionResponse
131 >(this.chargingStation
, RequestCommand
.START_TRANSACTION
, requestPayload
, requestParams
)
134 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
135 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
136 await this.chargingStation
.ocppRequestService
.requestHandler
<
137 StopTransactionRequest
,
138 StartTransactionResponse
140 this.chargingStation
,
141 RequestCommand
.STOP_TRANSACTION
,
143 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
144 requestPayload
?.transactionId
,
153 BroadcastChannelProcedureName
.AUTHORIZE
,
154 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
155 await this.chargingStation
.ocppRequestService
.requestHandler
<
158 >(this.chargingStation
, RequestCommand
.AUTHORIZE
, requestPayload
, requestParams
)
161 BroadcastChannelProcedureName
.BOOT_NOTIFICATION
,
162 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
163 this.chargingStation
.bootNotificationResponse
=
164 await this.chargingStation
.ocppRequestService
.requestHandler
<
165 BootNotificationRequest
,
166 BootNotificationResponse
168 this.chargingStation
,
169 RequestCommand
.BOOT_NOTIFICATION
,
171 ...this.chargingStation
.bootNotificationRequest
,
175 skipBufferingOnError
: true,
179 return this.chargingStation
.bootNotificationResponse
183 BroadcastChannelProcedureName
.STATUS_NOTIFICATION
,
184 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
185 await this.chargingStation
.ocppRequestService
.requestHandler
<
186 StatusNotificationRequest
,
187 StatusNotificationResponse
188 >(this.chargingStation
, RequestCommand
.STATUS_NOTIFICATION
, requestPayload
, requestParams
)
191 BroadcastChannelProcedureName
.HEARTBEAT
,
192 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
193 await this.chargingStation
.ocppRequestService
.requestHandler
<
196 >(this.chargingStation
, RequestCommand
.HEARTBEAT
, requestPayload
, requestParams
)
199 BroadcastChannelProcedureName
.METER_VALUES
,
200 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
201 const configuredMeterValueSampleInterval
= getConfigurationKey(
203 StandardParametersKey
.MeterValueSampleInterval
205 return await this.chargingStation
.ocppRequestService
.requestHandler
<
209 this.chargingStation
,
210 RequestCommand
.METER_VALUES
,
214 this.chargingStation
,
215 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
216 requestPayload
!.connectorId
!,
217 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
218 this.chargingStation
.getConnectorStatus(requestPayload
!.connectorId
!)!
220 configuredMeterValueSampleInterval
!= null
221 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval
.value
))
222 : Constants
.DEFAULT_METER_VALUES_INTERVAL
232 BroadcastChannelProcedureName
.DATA_TRANSFER
,
233 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
234 await this.chargingStation
.ocppRequestService
.requestHandler
<
237 >(this.chargingStation
, RequestCommand
.DATA_TRANSFER
, requestPayload
, requestParams
)
240 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
241 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
242 await this.chargingStation
.ocppRequestService
.requestHandler
<
243 DiagnosticsStatusNotificationRequest
,
244 DiagnosticsStatusNotificationResponse
246 this.chargingStation
,
247 RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
253 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
254 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
255 await this.chargingStation
.ocppRequestService
.requestHandler
<
256 FirmwareStatusNotificationRequest
,
257 FirmwareStatusNotificationResponse
259 this.chargingStation
,
260 RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
266 this.chargingStation
= chargingStation
267 this.onmessage
= this.requestHandler
.bind(this) as (message
: unknown
) => void
268 this.onmessageerror
= this.messageErrorHandler
.bind(this) as (message
: unknown
) => void
271 private requestHandler (messageEvent
: MessageEvent
): void {
272 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
)
273 if (validatedMessageEvent
=== false) {
276 if (this.isResponse(validatedMessageEvent
.data
)) {
279 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
281 requestPayload
.hashIds
!= null &&
282 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
283 !requestPayload
.hashIds
.includes(this.chargingStation
.stationInfo
!.hashId
)
287 if (requestPayload
.hashId
!= null) {
289 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
293 let responsePayload
: BroadcastChannelResponsePayload
| undefined
294 this.commandHandler(command
, requestPayload
)
295 .then(commandResponse
=> {
296 if (commandResponse
== null || isEmptyObject(commandResponse
)) {
298 hashId
: this.chargingStation
.stationInfo
?.hashId
,
299 status: ResponseStatus
.SUCCESS
302 responsePayload
= this.commandResponseToResponsePayload(
311 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
315 hashId
: this.chargingStation
.stationInfo
?.hashId
,
316 status: ResponseStatus
.FAILURE
,
319 errorMessage
: (error
as OCPPError
).message
,
320 errorStack
: (error
as OCPPError
).stack
,
321 errorDetails
: (error
as OCPPError
).details
322 } satisfies BroadcastChannelResponsePayload
325 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
326 this.sendResponse([uuid
, responsePayload
!])
330 private messageErrorHandler (messageEvent
: MessageEvent
): void {
332 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
337 private async commandHandler (
338 command
: BroadcastChannelProcedureName
,
339 requestPayload
: BroadcastChannelRequestPayload
340 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
341 ): Promise
<CommandResponse
| void> {
342 if (this.commandHandlers
.has(command
)) {
343 this.cleanRequestPayload(command
, requestPayload
)
344 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
345 const commandHandler
= this.commandHandlers
.get(command
)!
346 if (isAsyncFunction(commandHandler
)) {
347 return await commandHandler(requestPayload
)
351 requestPayload
?: BroadcastChannelRequestPayload
352 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
353 ) => CommandResponse
| void
356 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
359 private cleanRequestPayload (
360 command
: BroadcastChannelProcedureName
,
361 requestPayload
: BroadcastChannelRequestPayload
363 delete requestPayload
.hashId
364 delete requestPayload
.hashIds
366 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
367 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
368 ].includes(command
) && delete requestPayload
.connectorIds
371 private commandResponseToResponsePayload (
372 command
: BroadcastChannelProcedureName
,
373 requestPayload
: BroadcastChannelRequestPayload
,
374 commandResponse
: CommandResponse
375 ): BroadcastChannelResponsePayload
{
376 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
)
377 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
379 hashId
: this.chargingStation
.stationInfo
?.hashId
,
380 status: responseStatus
384 hashId
: this.chargingStation
.stationInfo
?.hashId
,
385 status: responseStatus
,
392 private commandResponseToResponseStatus (
393 command
: BroadcastChannelProcedureName
,
394 commandResponse
: CommandResponse
397 case BroadcastChannelProcedureName
.START_TRANSACTION
:
398 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
399 case BroadcastChannelProcedureName
.AUTHORIZE
:
403 | StartTransactionResponse
404 | StopTransactionResponse
406 ).idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
408 return ResponseStatus
.SUCCESS
410 return ResponseStatus
.FAILURE
411 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
412 if (commandResponse
.status === RegistrationStatusEnumType
.ACCEPTED
) {
413 return ResponseStatus
.SUCCESS
415 return ResponseStatus
.FAILURE
416 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
417 if (commandResponse
.status === DataTransferStatus
.ACCEPTED
) {
418 return ResponseStatus
.SUCCESS
420 return ResponseStatus
.FAILURE
421 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
422 case BroadcastChannelProcedureName
.METER_VALUES
:
423 if (isEmptyObject(commandResponse
)) {
424 return ResponseStatus
.SUCCESS
426 return ResponseStatus
.FAILURE
427 case BroadcastChannelProcedureName
.HEARTBEAT
:
428 if ('currentTime' in commandResponse
) {
429 return ResponseStatus
.SUCCESS
431 return ResponseStatus
.FAILURE
433 return ResponseStatus
.FAILURE