1 import { WorkerBroadcastChannel
} from
'./WorkerBroadcastChannel';
2 import { BaseError
, type OCPPError
} from
'../../exception';
6 type AuthorizeResponse
,
7 type BootNotificationRequest
,
8 type BootNotificationResponse
,
9 BroadcastChannelProcedureName
,
10 type BroadcastChannelRequest
,
11 type BroadcastChannelRequestPayload
,
12 type BroadcastChannelResponsePayload
,
13 type DataTransferRequest
,
14 type DataTransferResponse
,
16 type DiagnosticsStatusNotificationRequest
,
17 type DiagnosticsStatusNotificationResponse
,
19 type FirmwareStatusNotificationRequest
,
20 type FirmwareStatusNotificationResponse
,
21 type HeartbeatRequest
,
22 type HeartbeatResponse
,
24 type MeterValuesRequest
,
25 type MeterValuesResponse
,
26 RegistrationStatusEnumType
,
30 StandardParametersKey
,
31 type StartTransactionRequest
,
32 type StartTransactionResponse
,
33 type StatusNotificationRequest
,
34 type StatusNotificationResponse
,
35 type StopTransactionRequest
,
36 type StopTransactionResponse
,
38 import { Constants
, convertToInt
, isEmptyObject
, isNullOrUndefined
, logger
} from
'../../utils';
39 import type { ChargingStation
} from
'../ChargingStation';
40 import { getConfigurationKey
} from
'../ChargingStationConfigurationUtils';
41 import { OCPP16ServiceUtils
} from
'../ocpp';
43 const moduleName
= 'ChargingStationWorkerBroadcastChannel';
45 type CommandResponse
=
47 | StartTransactionResponse
48 | StopTransactionResponse
50 | BootNotificationResponse
52 | DataTransferResponse
;
54 type CommandHandler
= (
55 requestPayload
?: BroadcastChannelRequestPayload
,
56 ) => Promise
<CommandResponse
| void> | void;
58 export class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
59 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>;
60 private readonly chargingStation
: ChargingStation
;
62 constructor(chargingStation
: ChargingStation
) {
64 const requestParams
: RequestParams
= {
67 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
68 [BroadcastChannelProcedureName
.START_CHARGING_STATION
, () => this.chargingStation
.start()],
70 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
71 async () => this.chargingStation
.stop(),
74 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
75 () => this.chargingStation
.openWSConnection(),
78 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
79 () => this.chargingStation
.closeWSConnection(),
82 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
83 (requestPayload
?: BroadcastChannelRequestPayload
) =>
84 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
),
87 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
88 (requestPayload
?: BroadcastChannelRequestPayload
) =>
89 this.chargingStation
.stopAutomaticTransactionGenerator(requestPayload
?.connectorIds
),
92 BroadcastChannelProcedureName
.SET_SUPERVISION_URL
,
93 (requestPayload
?: BroadcastChannelRequestPayload
) =>
94 this.chargingStation
.setSupervisionUrl(requestPayload
?.url
as string),
97 BroadcastChannelProcedureName
.START_TRANSACTION
,
98 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
99 this.chargingStation
.ocppRequestService
.requestHandler
<
100 StartTransactionRequest
,
101 StartTransactionResponse
102 >(this.chargingStation
, RequestCommand
.START_TRANSACTION
, requestPayload
, requestParams
),
105 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
106 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
107 this.chargingStation
.ocppRequestService
.requestHandler
<
108 StopTransactionRequest
,
109 StartTransactionResponse
111 this.chargingStation
,
112 RequestCommand
.STOP_TRANSACTION
,
114 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
115 requestPayload
!.transactionId
!,
124 BroadcastChannelProcedureName
.AUTHORIZE
,
125 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
126 this.chargingStation
.ocppRequestService
.requestHandler
<
129 >(this.chargingStation
, RequestCommand
.AUTHORIZE
, requestPayload
, requestParams
),
132 BroadcastChannelProcedureName
.BOOT_NOTIFICATION
,
133 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
134 this.chargingStation
.bootNotificationResponse
=
135 await this.chargingStation
.ocppRequestService
.requestHandler
<
136 BootNotificationRequest
,
137 BootNotificationResponse
139 this.chargingStation
,
140 RequestCommand
.BOOT_NOTIFICATION
,
142 ...this.chargingStation
.bootNotificationRequest
,
146 skipBufferingOnError
: true,
150 return this.chargingStation
.bootNotificationResponse
;
154 BroadcastChannelProcedureName
.STATUS_NOTIFICATION
,
155 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
156 this.chargingStation
.ocppRequestService
.requestHandler
<
157 StatusNotificationRequest
,
158 StatusNotificationResponse
160 this.chargingStation
,
161 RequestCommand
.STATUS_NOTIFICATION
,
167 BroadcastChannelProcedureName
.HEARTBEAT
,
168 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
169 this.chargingStation
.ocppRequestService
.requestHandler
<
172 >(this.chargingStation
, RequestCommand
.HEARTBEAT
, requestPayload
, requestParams
),
175 BroadcastChannelProcedureName
.METER_VALUES
,
176 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
177 const configuredMeterValueSampleInterval
= getConfigurationKey(
179 StandardParametersKey
.MeterValueSampleInterval
,
181 return this.chargingStation
.ocppRequestService
.requestHandler
<
185 this.chargingStation
,
186 RequestCommand
.METER_VALUES
,
189 // FIXME: Implement OCPP version agnostic helpers
190 OCPP16ServiceUtils
.buildMeterValue(
191 this.chargingStation
,
192 requestPayload
!.connectorId
!,
193 this.chargingStation
.getConnectorStatus(requestPayload
!.connectorId
!)!
195 configuredMeterValueSampleInterval
196 ? convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
197 : Constants
.DEFAULT_METER_VALUES_INTERVAL
,
207 BroadcastChannelProcedureName
.DATA_TRANSFER
,
208 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
209 this.chargingStation
.ocppRequestService
.requestHandler
<
212 >(this.chargingStation
, RequestCommand
.DATA_TRANSFER
, requestPayload
, requestParams
),
215 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
216 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
217 this.chargingStation
.ocppRequestService
.requestHandler
<
218 DiagnosticsStatusNotificationRequest
,
219 DiagnosticsStatusNotificationResponse
221 this.chargingStation
,
222 RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
228 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
229 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
230 this.chargingStation
.ocppRequestService
.requestHandler
<
231 FirmwareStatusNotificationRequest
,
232 FirmwareStatusNotificationResponse
234 this.chargingStation
,
235 RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
241 this.chargingStation
= chargingStation
;
242 this.onmessage
= this.requestHandler
.bind(this) as (message
: unknown
) => void;
243 this.onmessageerror
= this.messageErrorHandler
.bind(this) as (message
: unknown
) => void;
246 private async requestHandler(messageEvent
: MessageEvent
): Promise
<void> {
247 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
);
248 if (validatedMessageEvent
=== false) {
251 if (this.isResponse(validatedMessageEvent
.data
) === true) {
254 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
;
256 !isNullOrUndefined(requestPayload
.hashIds
) &&
257 requestPayload
.hashIds
?.includes(this.chargingStation
.stationInfo
.hashId
) === false
261 if (!isNullOrUndefined(requestPayload
.hashId
)) {
263 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`,
267 let responsePayload
: BroadcastChannelResponsePayload
| undefined;
268 let commandResponse
: CommandResponse
| void | undefined;
270 commandResponse
= await this.commandHandler(command
, requestPayload
);
271 if (isNullOrUndefined(commandResponse
) || isEmptyObject(commandResponse
as CommandResponse
)) {
273 hashId
: this.chargingStation
.stationInfo
.hashId
,
274 status: ResponseStatus
.SUCCESS
,
277 responsePayload
= this.commandResponseToResponsePayload(
280 commandResponse
as CommandResponse
,
285 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
289 hashId
: this.chargingStation
.stationInfo
.hashId
,
290 status: ResponseStatus
.FAILURE
,
293 commandResponse
: commandResponse
as CommandResponse
,
294 errorMessage
: (error
as Error).message
,
295 errorStack
: (error
as Error).stack
,
296 errorDetails
: (error
as OCPPError
).details
,
299 this.sendResponse([uuid
, responsePayload
!]);
303 private messageErrorHandler(messageEvent
: MessageEvent
): void {
305 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
310 private async commandHandler(
311 command
: BroadcastChannelProcedureName
,
312 requestPayload
: BroadcastChannelRequestPayload
,
313 ): Promise
<CommandResponse
| void> {
314 if (this.commandHandlers
.has(command
) === true) {
315 this.cleanRequestPayload(command
, requestPayload
);
316 return this.commandHandlers
.get(command
)!(requestPayload
);
318 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
321 private cleanRequestPayload(
322 command
: BroadcastChannelProcedureName
,
323 requestPayload
: BroadcastChannelRequestPayload
,
325 delete requestPayload
.hashId
;
326 delete requestPayload
.hashIds
;
328 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
329 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
330 ].includes(command
) === false && delete requestPayload
.connectorIds
;
333 private commandResponseToResponsePayload(
334 command
: BroadcastChannelProcedureName
,
335 requestPayload
: BroadcastChannelRequestPayload
,
336 commandResponse
: CommandResponse
,
337 ): BroadcastChannelResponsePayload
{
338 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
);
339 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
341 hashId
: this.chargingStation
.stationInfo
.hashId
,
342 status: responseStatus
,
346 hashId
: this.chargingStation
.stationInfo
.hashId
,
347 status: responseStatus
,
354 private commandResponseToResponseStatus(
355 command
: BroadcastChannelProcedureName
,
356 commandResponse
: CommandResponse
,
359 case BroadcastChannelProcedureName
.START_TRANSACTION
:
360 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
361 case BroadcastChannelProcedureName
.AUTHORIZE
:
365 | StartTransactionResponse
366 | StopTransactionResponse
368 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
370 return ResponseStatus
.SUCCESS
;
372 return ResponseStatus
.FAILURE
;
373 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
374 if (commandResponse
?.status === RegistrationStatusEnumType
.ACCEPTED
) {
375 return ResponseStatus
.SUCCESS
;
377 return ResponseStatus
.FAILURE
;
378 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
379 if (commandResponse
?.status === DataTransferStatus
.ACCEPTED
) {
380 return ResponseStatus
.SUCCESS
;
382 return ResponseStatus
.FAILURE
;
383 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
384 case BroadcastChannelProcedureName
.METER_VALUES
:
385 if (isEmptyObject(commandResponse
) === true) {
386 return ResponseStatus
.SUCCESS
;
388 return ResponseStatus
.FAILURE
;
389 case BroadcastChannelProcedureName
.HEARTBEAT
:
390 if ('currentTime' in commandResponse
) {
391 return ResponseStatus
.SUCCESS
;
393 return ResponseStatus
.FAILURE
;
395 return ResponseStatus
.FAILURE
;