1 import { secondsToMilliseconds
} from
'date-fns';
3 import { WorkerBroadcastChannel
} from
'./WorkerBroadcastChannel';
4 import { BaseError
, type OCPPError
} from
'../../exception';
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
,
40 import { Constants
, convertToInt
, isEmptyObject
, isNullOrUndefined
, logger
} from
'../../utils';
41 import type { ChargingStation
} from
'../ChargingStation';
42 import { getConfigurationKey
} from
'../ConfigurationKeyUtils';
43 import { OCPP16ServiceUtils
} from
'../ocpp';
45 const moduleName
= 'ChargingStationWorkerBroadcastChannel';
47 type CommandResponse
=
49 | StartTransactionResponse
50 | StopTransactionResponse
52 | BootNotificationResponse
54 | DataTransferResponse
;
56 type CommandHandler
= (
57 requestPayload
?: BroadcastChannelRequestPayload
,
58 ) => Promise
<CommandResponse
| void> | void;
60 export class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
61 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>;
62 private readonly chargingStation
: ChargingStation
;
64 constructor(chargingStation
: ChargingStation
) {
66 const requestParams
: RequestParams
= {
69 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
70 [BroadcastChannelProcedureName
.START_CHARGING_STATION
, () => this.chargingStation
.start()],
72 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
73 async () => this.chargingStation
.stop(),
76 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
77 () => this.chargingStation
.openWSConnection(),
80 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
81 () => this.chargingStation
.closeWSConnection(),
84 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
85 (requestPayload
?: BroadcastChannelRequestPayload
) =>
86 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
),
89 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
90 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
91 await this.chargingStation
.stopAutomaticTransactionGenerator(
92 requestPayload
?.connectorIds
,
96 BroadcastChannelProcedureName
.SET_SUPERVISION_URL
,
97 (requestPayload
?: BroadcastChannelRequestPayload
) =>
98 this.chargingStation
.setSupervisionUrl(requestPayload
?.url
as string),
101 BroadcastChannelProcedureName
.START_TRANSACTION
,
102 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
103 this.chargingStation
.ocppRequestService
.requestHandler
<
104 StartTransactionRequest
,
105 StartTransactionResponse
106 >(this.chargingStation
, RequestCommand
.START_TRANSACTION
, requestPayload
, requestParams
),
109 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
110 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
111 this.chargingStation
.ocppRequestService
.requestHandler
<
112 StopTransactionRequest
,
113 StartTransactionResponse
115 this.chargingStation
,
116 RequestCommand
.STOP_TRANSACTION
,
118 meterStop
: this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
119 requestPayload
!.transactionId
!,
128 BroadcastChannelProcedureName
.AUTHORIZE
,
129 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
130 this.chargingStation
.ocppRequestService
.requestHandler
<
133 >(this.chargingStation
, RequestCommand
.AUTHORIZE
, requestPayload
, requestParams
),
136 BroadcastChannelProcedureName
.BOOT_NOTIFICATION
,
137 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
138 this.chargingStation
.bootNotificationResponse
=
139 await this.chargingStation
.ocppRequestService
.requestHandler
<
140 BootNotificationRequest
,
141 BootNotificationResponse
143 this.chargingStation
,
144 RequestCommand
.BOOT_NOTIFICATION
,
146 ...this.chargingStation
.bootNotificationRequest
,
150 skipBufferingOnError
: true,
154 return this.chargingStation
.bootNotificationResponse
;
158 BroadcastChannelProcedureName
.STATUS_NOTIFICATION
,
159 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
160 this.chargingStation
.ocppRequestService
.requestHandler
<
161 StatusNotificationRequest
,
162 StatusNotificationResponse
164 this.chargingStation
,
165 RequestCommand
.STATUS_NOTIFICATION
,
171 BroadcastChannelProcedureName
.HEARTBEAT
,
172 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
173 this.chargingStation
.ocppRequestService
.requestHandler
<
176 >(this.chargingStation
, RequestCommand
.HEARTBEAT
, requestPayload
, requestParams
),
179 BroadcastChannelProcedureName
.METER_VALUES
,
180 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
181 const configuredMeterValueSampleInterval
= getConfigurationKey(
183 StandardParametersKey
.MeterValueSampleInterval
,
185 return this.chargingStation
.ocppRequestService
.requestHandler
<
189 this.chargingStation
,
190 RequestCommand
.METER_VALUES
,
193 // FIXME: Implement OCPP version agnostic helpers
194 OCPP16ServiceUtils
.buildMeterValue(
195 this.chargingStation
,
196 requestPayload
!.connectorId
!,
197 this.chargingStation
.getConnectorStatus(requestPayload
!.connectorId
!)!
199 configuredMeterValueSampleInterval
200 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval
.value
))
201 : Constants
.DEFAULT_METER_VALUES_INTERVAL
,
211 BroadcastChannelProcedureName
.DATA_TRANSFER
,
212 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
213 this.chargingStation
.ocppRequestService
.requestHandler
<
216 >(this.chargingStation
, RequestCommand
.DATA_TRANSFER
, requestPayload
, requestParams
),
219 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
220 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
221 this.chargingStation
.ocppRequestService
.requestHandler
<
222 DiagnosticsStatusNotificationRequest
,
223 DiagnosticsStatusNotificationResponse
225 this.chargingStation
,
226 RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
232 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
233 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
234 this.chargingStation
.ocppRequestService
.requestHandler
<
235 FirmwareStatusNotificationRequest
,
236 FirmwareStatusNotificationResponse
238 this.chargingStation
,
239 RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
245 this.chargingStation
= chargingStation
;
246 this.onmessage
= this.requestHandler
.bind(this) as (message
: unknown
) => void;
247 this.onmessageerror
= this.messageErrorHandler
.bind(this) as (message
: unknown
) => void;
250 private async requestHandler(messageEvent
: MessageEvent
): Promise
<void> {
251 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
);
252 if (validatedMessageEvent
=== false) {
255 if (this.isResponse(validatedMessageEvent
.data
) === true) {
258 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
;
260 !isNullOrUndefined(requestPayload
.hashIds
) &&
261 requestPayload
.hashIds
?.includes(this.chargingStation
.stationInfo
.hashId
) === false
265 if (!isNullOrUndefined(requestPayload
.hashId
)) {
267 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`,
271 let responsePayload
: BroadcastChannelResponsePayload
| undefined;
272 let commandResponse
: CommandResponse
| void | undefined;
274 commandResponse
= await this.commandHandler(command
, requestPayload
);
275 if (isNullOrUndefined(commandResponse
) || isEmptyObject(commandResponse
as CommandResponse
)) {
277 hashId
: this.chargingStation
.stationInfo
.hashId
,
278 status: ResponseStatus
.SUCCESS
,
281 responsePayload
= this.commandResponseToResponsePayload(
284 commandResponse
as CommandResponse
,
289 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
293 hashId
: this.chargingStation
.stationInfo
.hashId
,
294 status: ResponseStatus
.FAILURE
,
297 commandResponse
: commandResponse
as CommandResponse
,
298 errorMessage
: (error
as Error).message
,
299 errorStack
: (error
as Error).stack
,
300 errorDetails
: (error
as OCPPError
).details
,
303 this.sendResponse([uuid
, responsePayload
!]);
307 private messageErrorHandler(messageEvent
: MessageEvent
): void {
309 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
314 private async commandHandler(
315 command
: BroadcastChannelProcedureName
,
316 requestPayload
: BroadcastChannelRequestPayload
,
317 ): Promise
<CommandResponse
| void> {
318 if (this.commandHandlers
.has(command
) === true) {
319 this.cleanRequestPayload(command
, requestPayload
);
320 return this.commandHandlers
.get(command
)!(requestPayload
);
322 throw new BaseError(`Unknown worker broadcast channel command: '${command}'`);
325 private cleanRequestPayload(
326 command
: BroadcastChannelProcedureName
,
327 requestPayload
: BroadcastChannelRequestPayload
,
329 delete requestPayload
.hashId
;
330 delete requestPayload
.hashIds
;
332 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
333 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
334 ].includes(command
) === false && delete requestPayload
.connectorIds
;
337 private commandResponseToResponsePayload(
338 command
: BroadcastChannelProcedureName
,
339 requestPayload
: BroadcastChannelRequestPayload
,
340 commandResponse
: CommandResponse
,
341 ): BroadcastChannelResponsePayload
{
342 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
);
343 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
345 hashId
: this.chargingStation
.stationInfo
.hashId
,
346 status: responseStatus
,
350 hashId
: this.chargingStation
.stationInfo
.hashId
,
351 status: responseStatus
,
358 private commandResponseToResponseStatus(
359 command
: BroadcastChannelProcedureName
,
360 commandResponse
: CommandResponse
,
363 case BroadcastChannelProcedureName
.START_TRANSACTION
:
364 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
365 case BroadcastChannelProcedureName
.AUTHORIZE
:
369 | StartTransactionResponse
370 | StopTransactionResponse
372 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
374 return ResponseStatus
.SUCCESS
;
376 return ResponseStatus
.FAILURE
;
377 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
378 if (commandResponse
?.status === RegistrationStatusEnumType
.ACCEPTED
) {
379 return ResponseStatus
.SUCCESS
;
381 return ResponseStatus
.FAILURE
;
382 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
383 if (commandResponse
?.status === DataTransferStatus
.ACCEPTED
) {
384 return ResponseStatus
.SUCCESS
;
386 return ResponseStatus
.FAILURE
;
387 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
388 case BroadcastChannelProcedureName
.METER_VALUES
:
389 if (isEmptyObject(commandResponse
) === true) {
390 return ResponseStatus
.SUCCESS
;
392 return ResponseStatus
.FAILURE
;
393 case BroadcastChannelProcedureName
.HEARTBEAT
:
394 if ('currentTime' in commandResponse
) {
395 return ResponseStatus
.SUCCESS
;
397 return ResponseStatus
.FAILURE
;
399 return ResponseStatus
.FAILURE
;