1 import { secondsToMilliseconds
} from
'date-fns'
3 import { WorkerBroadcastChannel
} from
'./WorkerBroadcastChannel.js'
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
, isEmptyObject
, 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'
45 const moduleName
= 'ChargingStationWorkerBroadcastChannel'
47 type CommandResponse
=
49 | StartTransactionResponse
50 | StopTransactionResponse
52 | BootNotificationResponse
54 | DataTransferResponse
56 type CommandHandler
= (
57 requestPayload
?: BroadcastChannelRequestPayload
58 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
59 ) => Promise
<CommandResponse
| void> | void
61 export class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
62 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>
63 private readonly chargingStation
: ChargingStation
65 constructor (chargingStation
: ChargingStation
) {
67 const requestParams
: RequestParams
= {
70 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
72 BroadcastChannelProcedureName
.START_CHARGING_STATION
,
74 this.chargingStation
.start()
78 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
80 await this.chargingStation
.stop()
84 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
86 this.chargingStation
.openWSConnection()
90 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
92 this.chargingStation
.closeWSConnection()
96 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
97 (requestPayload
?: BroadcastChannelRequestPayload
) => {
98 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
102 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
103 (requestPayload
?: BroadcastChannelRequestPayload
) => {
104 this.chargingStation
.stopAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
108 BroadcastChannelProcedureName
.SET_SUPERVISION_URL
,
109 (requestPayload
?: BroadcastChannelRequestPayload
) => {
110 this.chargingStation
.setSupervisionUrl(requestPayload
?.url
as string)
114 BroadcastChannelProcedureName
.START_TRANSACTION
,
115 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
116 await this.chargingStation
.ocppRequestService
.requestHandler
<
117 StartTransactionRequest
,
118 StartTransactionResponse
119 >(this.chargingStation
, RequestCommand
.START_TRANSACTION
, requestPayload
, requestParams
)
122 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
123 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
124 await this.chargingStation
.ocppRequestService
.requestHandler
<
125 StopTransactionRequest
,
126 StartTransactionResponse
128 this.chargingStation
,
129 RequestCommand
.STOP_TRANSACTION
,
131 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
132 requestPayload
?.transactionId
,
141 BroadcastChannelProcedureName
.AUTHORIZE
,
142 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
143 await this.chargingStation
.ocppRequestService
.requestHandler
<
146 >(this.chargingStation
, RequestCommand
.AUTHORIZE
, requestPayload
, requestParams
)
149 BroadcastChannelProcedureName
.BOOT_NOTIFICATION
,
150 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
151 this.chargingStation
.bootNotificationResponse
=
152 await this.chargingStation
.ocppRequestService
.requestHandler
<
153 BootNotificationRequest
,
154 BootNotificationResponse
156 this.chargingStation
,
157 RequestCommand
.BOOT_NOTIFICATION
,
159 ...this.chargingStation
.bootNotificationRequest
,
163 skipBufferingOnError
: true,
167 return this.chargingStation
.bootNotificationResponse
171 BroadcastChannelProcedureName
.STATUS_NOTIFICATION
,
172 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
173 await this.chargingStation
.ocppRequestService
.requestHandler
<
174 StatusNotificationRequest
,
175 StatusNotificationResponse
176 >(this.chargingStation
, RequestCommand
.STATUS_NOTIFICATION
, requestPayload
, requestParams
)
179 BroadcastChannelProcedureName
.HEARTBEAT
,
180 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
181 await this.chargingStation
.ocppRequestService
.requestHandler
<
184 >(this.chargingStation
, RequestCommand
.HEARTBEAT
, requestPayload
, requestParams
)
187 BroadcastChannelProcedureName
.METER_VALUES
,
188 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
189 const configuredMeterValueSampleInterval
= getConfigurationKey(
191 StandardParametersKey
.MeterValueSampleInterval
193 return await this.chargingStation
.ocppRequestService
.requestHandler
<
197 this.chargingStation
,
198 RequestCommand
.METER_VALUES
,
202 this.chargingStation
,
203 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204 requestPayload
!.connectorId
!,
205 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
206 this.chargingStation
.getConnectorStatus(requestPayload
!.connectorId
!)!
208 configuredMeterValueSampleInterval
!= null
209 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval
.value
))
210 : Constants
.DEFAULT_METER_VALUES_INTERVAL
220 BroadcastChannelProcedureName
.DATA_TRANSFER
,
221 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
222 await this.chargingStation
.ocppRequestService
.requestHandler
<
225 >(this.chargingStation
, RequestCommand
.DATA_TRANSFER
, requestPayload
, requestParams
)
228 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
229 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
230 await this.chargingStation
.ocppRequestService
.requestHandler
<
231 DiagnosticsStatusNotificationRequest
,
232 DiagnosticsStatusNotificationResponse
234 this.chargingStation
,
235 RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
241 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
242 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
243 await this.chargingStation
.ocppRequestService
.requestHandler
<
244 FirmwareStatusNotificationRequest
,
245 FirmwareStatusNotificationResponse
247 this.chargingStation
,
248 RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
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
259 private requestHandler (messageEvent
: MessageEvent
): void {
260 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
)
261 if (validatedMessageEvent
=== false) {
264 if (this.isResponse(validatedMessageEvent
.data
)) {
267 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
269 requestPayload
.hashIds
!= null &&
270 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
271 !requestPayload
.hashIds
.includes(this.chargingStation
.stationInfo
!.hashId
)
275 if (requestPayload
.hashId
!= null) {
277 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
281 let responsePayload
: BroadcastChannelResponsePayload
| undefined
282 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
283 let commandResponse
: CommandResponse
| void
284 this.commandHandler(command
, requestPayload
)
285 .then(commandResponse
=> {
286 if (commandResponse
== null || isEmptyObject(commandResponse
)) {
288 hashId
: this.chargingStation
.stationInfo
?.hashId
,
289 status: ResponseStatus
.SUCCESS
292 responsePayload
= this.commandResponseToResponsePayload(
301 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
305 hashId
: this.chargingStation
.stationInfo
?.hashId
,
306 status: ResponseStatus
.FAILURE
,
309 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
310 commandResponse
: commandResponse
!,
311 errorMessage
: (error
as OCPPError
).message
,
312 errorStack
: (error
as OCPPError
).stack
,
313 errorDetails
: (error
as OCPPError
).details
317 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
318 this.sendResponse([uuid
, responsePayload
!])
322 private messageErrorHandler (messageEvent
: MessageEvent
): void {
324 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
329 private async commandHandler (
330 command
: BroadcastChannelProcedureName
,
331 requestPayload
: BroadcastChannelRequestPayload
332 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
333 ): Promise
<CommandResponse
| void> {
334 if (this.commandHandlers
.has(command
)) {
335 this.cleanRequestPayload(command
, requestPayload
)
336 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
337 return await this.commandHandlers
.get(command
)!(requestPayload
)
339 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
342 private cleanRequestPayload (
343 command
: BroadcastChannelProcedureName
,
344 requestPayload
: BroadcastChannelRequestPayload
346 delete requestPayload
.hashId
347 delete requestPayload
.hashIds
349 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
350 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
351 ].includes(command
) && delete requestPayload
.connectorIds
354 private commandResponseToResponsePayload (
355 command
: BroadcastChannelProcedureName
,
356 requestPayload
: BroadcastChannelRequestPayload
,
357 commandResponse
: CommandResponse
358 ): BroadcastChannelResponsePayload
{
359 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
)
360 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
362 hashId
: this.chargingStation
.stationInfo
?.hashId
,
363 status: responseStatus
367 hashId
: this.chargingStation
.stationInfo
?.hashId
,
368 status: responseStatus
,
375 private commandResponseToResponseStatus (
376 command
: BroadcastChannelProcedureName
,
377 commandResponse
: CommandResponse
380 case BroadcastChannelProcedureName
.START_TRANSACTION
:
381 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
382 case BroadcastChannelProcedureName
.AUTHORIZE
:
386 | StartTransactionResponse
387 | StopTransactionResponse
389 ).idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
391 return ResponseStatus
.SUCCESS
393 return ResponseStatus
.FAILURE
394 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
395 if (commandResponse
.status === RegistrationStatusEnumType
.ACCEPTED
) {
396 return ResponseStatus
.SUCCESS
398 return ResponseStatus
.FAILURE
399 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
400 if (commandResponse
.status === DataTransferStatus
.ACCEPTED
) {
401 return ResponseStatus
.SUCCESS
403 return ResponseStatus
.FAILURE
404 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
405 case BroadcastChannelProcedureName
.METER_VALUES
:
406 if (isEmptyObject(commandResponse
)) {
407 return ResponseStatus
.SUCCESS
409 return ResponseStatus
.FAILURE
410 case BroadcastChannelProcedureName
.HEARTBEAT
:
411 if ('currentTime' in commandResponse
) {
412 return ResponseStatus
.SUCCESS
414 return ResponseStatus
.FAILURE
416 return ResponseStatus
.FAILURE