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'
46 } from
'../../utils/index.js'
47 import type { ChargingStation
} from
'../ChargingStation.js'
48 import { getConfigurationKey
} from
'../ConfigurationKeyUtils.js'
49 import { buildMeterValue
} from
'../ocpp/index.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> | 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
.OPEN_CONNECTION
,
92 this.chargingStation
.openWSConnection()
96 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
98 this.chargingStation
.closeWSConnection()
102 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
103 (requestPayload
?: BroadcastChannelRequestPayload
) => {
104 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
108 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
109 (requestPayload
?: BroadcastChannelRequestPayload
) => {
110 this.chargingStation
.stopAutomaticTransactionGenerator(requestPayload
?.connectorIds
)
114 BroadcastChannelProcedureName
.SET_SUPERVISION_URL
,
115 (requestPayload
?: BroadcastChannelRequestPayload
) => {
116 this.chargingStation
.setSupervisionUrl(requestPayload
?.url
as string)
120 BroadcastChannelProcedureName
.START_TRANSACTION
,
121 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
122 await this.chargingStation
.ocppRequestService
.requestHandler
<
123 StartTransactionRequest
,
124 StartTransactionResponse
125 >(this.chargingStation
, RequestCommand
.START_TRANSACTION
, requestPayload
, requestParams
)
128 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
129 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
130 await this.chargingStation
.ocppRequestService
.requestHandler
<
131 StopTransactionRequest
,
132 StartTransactionResponse
134 this.chargingStation
,
135 RequestCommand
.STOP_TRANSACTION
,
137 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
138 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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
184 this.chargingStation
,
185 RequestCommand
.STATUS_NOTIFICATION
,
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 async requestHandler (messageEvent
: MessageEvent
): Promise
<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 !isNullOrUndefined(requestPayload
.hashIds
) &&
282 requestPayload
.hashIds
?.includes(this.chargingStation
.stationInfo
.hashId
) === false
286 if (!isNullOrUndefined(requestPayload
.hashId
)) {
288 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
292 let responsePayload
: BroadcastChannelResponsePayload
| undefined
293 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
294 let commandResponse
: CommandResponse
| void | undefined
296 commandResponse
= await this.commandHandler(command
, requestPayload
)
297 if (isNullOrUndefined(commandResponse
) || isEmptyObject(commandResponse
as CommandResponse
)) {
299 hashId
: this.chargingStation
.stationInfo
.hashId
,
300 status: ResponseStatus
.SUCCESS
303 responsePayload
= this.commandResponseToResponsePayload(
306 commandResponse
as CommandResponse
311 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
315 hashId
: this.chargingStation
.stationInfo
.hashId
,
316 status: ResponseStatus
.FAILURE
,
319 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
320 commandResponse
: commandResponse
!,
321 errorMessage
: (error
as OCPPError
).message
,
322 errorStack
: (error
as OCPPError
).stack
,
323 errorDetails
: (error
as OCPPError
).details
326 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
327 this.sendResponse([uuid
, responsePayload
!])
331 private messageErrorHandler (messageEvent
: MessageEvent
): void {
333 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
338 private async commandHandler (
339 command
: BroadcastChannelProcedureName
,
340 requestPayload
: BroadcastChannelRequestPayload
341 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
342 ): Promise
<void | CommandResponse
> {
343 if (this.commandHandlers
.has(command
)) {
344 this.cleanRequestPayload(command
, requestPayload
)
345 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
346 return await this.commandHandlers
.get(command
)!(requestPayload
)
348 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
351 private cleanRequestPayload (
352 command
: BroadcastChannelProcedureName
,
353 requestPayload
: BroadcastChannelRequestPayload
355 delete requestPayload
.hashId
356 delete requestPayload
.hashIds
358 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
359 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
360 ].includes(command
) && delete requestPayload
.connectorIds
363 private commandResponseToResponsePayload (
364 command
: BroadcastChannelProcedureName
,
365 requestPayload
: BroadcastChannelRequestPayload
,
366 commandResponse
: CommandResponse
367 ): BroadcastChannelResponsePayload
{
368 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
)
369 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
371 hashId
: this.chargingStation
.stationInfo
.hashId
,
372 status: responseStatus
376 hashId
: this.chargingStation
.stationInfo
.hashId
,
377 status: responseStatus
,
384 private commandResponseToResponseStatus (
385 command
: BroadcastChannelProcedureName
,
386 commandResponse
: CommandResponse
389 case BroadcastChannelProcedureName
.START_TRANSACTION
:
390 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
391 case BroadcastChannelProcedureName
.AUTHORIZE
:
395 | StartTransactionResponse
396 | StopTransactionResponse
398 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
400 return ResponseStatus
.SUCCESS
402 return ResponseStatus
.FAILURE
403 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
404 if (commandResponse
?.status === RegistrationStatusEnumType
.ACCEPTED
) {
405 return ResponseStatus
.SUCCESS
407 return ResponseStatus
.FAILURE
408 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
409 if (commandResponse
?.status === DataTransferStatus
.ACCEPTED
) {
410 return ResponseStatus
.SUCCESS
412 return ResponseStatus
.FAILURE
413 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
414 case BroadcastChannelProcedureName
.METER_VALUES
:
415 if (isEmptyObject(commandResponse
)) {
416 return ResponseStatus
.SUCCESS
418 return ResponseStatus
.FAILURE
419 case BroadcastChannelProcedureName
.HEARTBEAT
:
420 if ('currentTime' in commandResponse
) {
421 return ResponseStatus
.SUCCESS
423 return ResponseStatus
.FAILURE
425 return ResponseStatus
.FAILURE