3 ChargingStationConfigurationUtils
,
4 WorkerBroadcastChannel
,
6 import { OCPP16ServiceUtils
} from
'./ocpp';
7 import { BaseError
, type OCPPError
} from
'../exception';
10 type AuthorizeRequest
,
11 type AuthorizeResponse
,
12 type BootNotificationRequest
,
13 type BootNotificationResponse
,
14 BroadcastChannelProcedureName
,
15 type BroadcastChannelRequest
,
16 type BroadcastChannelRequestPayload
,
17 type BroadcastChannelResponsePayload
,
18 type DataTransferRequest
,
19 type DataTransferResponse
,
21 type DiagnosticsStatusNotificationRequest
,
22 type DiagnosticsStatusNotificationResponse
,
23 type FirmwareStatusNotificationRequest
,
24 type FirmwareStatusNotificationResponse
,
25 type HeartbeatRequest
,
26 type HeartbeatResponse
,
28 type MeterValuesRequest
,
29 type MeterValuesResponse
,
30 RegistrationStatusEnumType
,
34 StandardParametersKey
,
35 type StartTransactionRequest
,
36 type StartTransactionResponse
,
37 type StatusNotificationRequest
,
38 type StatusNotificationResponse
,
39 type StopTransactionRequest
,
40 type StopTransactionResponse
,
42 import { Constants
, Utils
, logger
} from
'../utils';
44 const moduleName
= 'ChargingStationWorkerBroadcastChannel';
46 type CommandResponse
=
47 | StartTransactionResponse
48 | StopTransactionResponse
50 | BootNotificationResponse
51 | StatusNotificationResponse
54 | DataTransferResponse
55 | DiagnosticsStatusNotificationResponse
56 | FirmwareStatusNotificationResponse
;
58 type CommandHandler
= (
59 requestPayload
?: BroadcastChannelRequestPayload
60 ) => Promise
<CommandResponse
| void> | void;
62 export class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
63 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>;
64 private readonly chargingStation
: ChargingStation
;
66 constructor(chargingStation
: ChargingStation
) {
68 const requestParams
: RequestParams
= {
71 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
72 [BroadcastChannelProcedureName
.START_CHARGING_STATION
, () => this.chargingStation
.start()],
74 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
75 async () => this.chargingStation
.stop(),
78 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
79 () => this.chargingStation
.openWSConnection(),
82 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
83 () => this.chargingStation
.closeWSConnection(),
86 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
87 (requestPayload
?: BroadcastChannelRequestPayload
) =>
88 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
?.connectorIds
),
91 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
92 (requestPayload
?: BroadcastChannelRequestPayload
) =>
93 this.chargingStation
.stopAutomaticTransactionGenerator(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
=
182 ChargingStationConfigurationUtils
.getConfigurationKey(
184 StandardParametersKey
.MeterValueSampleInterval
186 return this.chargingStation
.ocppRequestService
.requestHandler
<
190 this.chargingStation
,
191 RequestCommand
.METER_VALUES
,
194 // FIXME: Implement OCPP version agnostic helpers
195 OCPP16ServiceUtils
.buildMeterValue(
196 this.chargingStation
,
197 requestPayload
.connectorId
,
198 this.chargingStation
.getConnectorStatus(requestPayload
.connectorId
)
200 configuredMeterValueSampleInterval
201 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
202 : Constants
.DEFAULT_METER_VALUES_INTERVAL
212 BroadcastChannelProcedureName
.DATA_TRANSFER
,
213 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
214 this.chargingStation
.ocppRequestService
.requestHandler
<
217 >(this.chargingStation
, RequestCommand
.DATA_TRANSFER
, requestPayload
, requestParams
),
220 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
221 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
222 this.chargingStation
.ocppRequestService
.requestHandler
<
223 DiagnosticsStatusNotificationRequest
,
224 DiagnosticsStatusNotificationResponse
226 this.chargingStation
,
227 RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
,
233 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
234 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
235 this.chargingStation
.ocppRequestService
.requestHandler
<
236 FirmwareStatusNotificationRequest
,
237 FirmwareStatusNotificationResponse
239 this.chargingStation
,
240 RequestCommand
.FIRMWARE_STATUS_NOTIFICATION
,
246 this.chargingStation
= chargingStation
;
247 this.onmessage
= this.requestHandler
.bind(this) as (message
: MessageEvent
) => void;
248 this.onmessageerror
= this.messageErrorHandler
.bind(this) as (message
: MessageEvent
) => void;
251 private async requestHandler(messageEvent
: MessageEvent
): Promise
<void> {
252 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
);
253 if (validatedMessageEvent
=== false) {
256 if (this.isResponse(validatedMessageEvent
.data
) === true) {
259 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
;
261 !Utils
.isNullOrUndefined(requestPayload
?.hashIds
) &&
262 requestPayload
?.hashIds
?.includes(this.chargingStation
.stationInfo
.hashId
) === false
266 if (!Utils
.isNullOrUndefined(requestPayload
?.hashId
)) {
268 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
272 let responsePayload
: BroadcastChannelResponsePayload
;
273 let commandResponse
: CommandResponse
| void;
275 commandResponse
= await this.commandHandler(command
, requestPayload
);
277 Utils
.isNullOrUndefined(commandResponse
) ||
278 Utils
.isEmptyObject(commandResponse
as CommandResponse
)
281 hashId
: this.chargingStation
.stationInfo
.hashId
,
282 status: ResponseStatus
.SUCCESS
,
285 responsePayload
= this.commandResponseToResponsePayload(
288 commandResponse
as CommandResponse
293 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
297 hashId
: this.chargingStation
.stationInfo
.hashId
,
298 status: ResponseStatus
.FAILURE
,
301 commandResponse
: commandResponse
as CommandResponse
,
302 errorMessage
: (error
as Error).message
,
303 errorStack
: (error
as Error).stack
,
304 errorDetails
: (error
as OCPPError
).details
,
307 this.sendResponse([uuid
, responsePayload
]);
311 private messageErrorHandler(messageEvent
: MessageEvent
): void {
313 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
318 private async commandHandler(
319 command
: BroadcastChannelProcedureName
,
320 requestPayload
: BroadcastChannelRequestPayload
321 ): Promise
<CommandResponse
| void> {
322 if (this.commandHandlers
.has(command
) === true) {
323 this.cleanRequestPayload(command
, requestPayload
);
324 return this.commandHandlers
.get(command
)(requestPayload
);
326 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
329 private cleanRequestPayload(
330 command
: BroadcastChannelProcedureName
,
331 requestPayload
: BroadcastChannelRequestPayload
333 delete requestPayload
.hashId
;
334 delete requestPayload
.hashIds
;
336 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
337 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
338 ].includes(command
) === false && delete requestPayload
.connectorIds
;
341 private commandResponseToResponsePayload(
342 command
: BroadcastChannelProcedureName
,
343 requestPayload
: BroadcastChannelRequestPayload
,
344 commandResponse
: CommandResponse
345 ): BroadcastChannelResponsePayload
{
346 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
);
347 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
349 hashId
: this.chargingStation
.stationInfo
.hashId
,
350 status: responseStatus
,
354 hashId
: this.chargingStation
.stationInfo
.hashId
,
355 status: responseStatus
,
362 private commandResponseToResponseStatus(
363 command
: BroadcastChannelProcedureName
,
364 commandResponse
: CommandResponse
367 case BroadcastChannelProcedureName
.START_TRANSACTION
:
368 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
369 case BroadcastChannelProcedureName
.AUTHORIZE
:
373 | StartTransactionResponse
374 | StopTransactionResponse
376 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
378 return ResponseStatus
.SUCCESS
;
380 return ResponseStatus
.FAILURE
;
381 case BroadcastChannelProcedureName
.BOOT_NOTIFICATION
:
382 if (commandResponse
?.status === RegistrationStatusEnumType
.ACCEPTED
) {
383 return ResponseStatus
.SUCCESS
;
385 return ResponseStatus
.FAILURE
;
386 case BroadcastChannelProcedureName
.DATA_TRANSFER
:
387 if (commandResponse
?.status === DataTransferStatus
.ACCEPTED
) {
388 return ResponseStatus
.SUCCESS
;
390 return ResponseStatus
.FAILURE
;
391 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
392 case BroadcastChannelProcedureName
.METER_VALUES
:
393 if (Utils
.isEmptyObject(commandResponse
) === true) {
394 return ResponseStatus
.SUCCESS
;
396 return ResponseStatus
.FAILURE
;
397 case BroadcastChannelProcedureName
.HEARTBEAT
:
398 if ('currentTime' in commandResponse
) {
399 return ResponseStatus
.SUCCESS
;
401 return ResponseStatus
.FAILURE
;
403 return ResponseStatus
.FAILURE
;