1 import BaseError from
'../exception/BaseError';
2 import type OCPPError from
'../exception/OCPPError';
3 import { StandardParametersKey
} from
'../types/ocpp/Configuration';
8 type StatusNotificationRequest
,
9 } from
'../types/ocpp/Requests';
13 StatusNotificationResponse
,
14 } from
'../types/ocpp/Responses';
19 StartTransactionRequest
,
20 StartTransactionResponse
,
21 StopTransactionRequest
,
22 StopTransactionResponse
,
23 } from
'../types/ocpp/Transaction';
25 BroadcastChannelProcedureName
,
26 BroadcastChannelRequest
,
27 BroadcastChannelRequestPayload
,
28 BroadcastChannelResponsePayload
,
30 } from
'../types/WorkerBroadcastChannel';
31 import { ResponseStatus
} from
'../ui/web/src/types/UIProtocol';
32 import Constants from
'../utils/Constants';
33 import logger from
'../utils/Logger';
34 import Utils from
'../utils/Utils';
35 import type ChargingStation from
'./ChargingStation';
36 import { ChargingStationConfigurationUtils
} from
'./ChargingStationConfigurationUtils';
37 import { OCPP16ServiceUtils
} from
'./ocpp/1.6/OCPP16ServiceUtils';
38 import WorkerBroadcastChannel from
'./WorkerBroadcastChannel';
40 const moduleName
= 'ChargingStationWorkerBroadcastChannel';
42 type CommandResponse
=
43 | StartTransactionResponse
44 | StopTransactionResponse
46 | StatusNotificationResponse
48 | MeterValuesResponse
;
50 type CommandHandler
= (
51 requestPayload
?: BroadcastChannelRequestPayload
52 ) => Promise
<CommandResponse
| void> | void;
54 export default class ChargingStationWorkerBroadcastChannel
extends WorkerBroadcastChannel
{
55 private readonly commandHandlers
: Map
<BroadcastChannelProcedureName
, CommandHandler
>;
56 private readonly chargingStation
: ChargingStation
;
58 constructor(chargingStation
: ChargingStation
) {
60 this.commandHandlers
= new Map
<BroadcastChannelProcedureName
, CommandHandler
>([
61 [BroadcastChannelProcedureName
.START_CHARGING_STATION
, () => this.chargingStation
.start()],
63 BroadcastChannelProcedureName
.STOP_CHARGING_STATION
,
64 async () => this.chargingStation
.stop(),
67 BroadcastChannelProcedureName
.OPEN_CONNECTION
,
68 () => this.chargingStation
.openWSConnection(),
71 BroadcastChannelProcedureName
.CLOSE_CONNECTION
,
72 () => this.chargingStation
.closeWSConnection(),
75 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
76 (requestPayload
?: BroadcastChannelRequestPayload
) =>
77 this.chargingStation
.startAutomaticTransactionGenerator(requestPayload
.connectorIds
),
80 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
81 (requestPayload
?: BroadcastChannelRequestPayload
) =>
82 this.chargingStation
.stopAutomaticTransactionGenerator(requestPayload
.connectorIds
),
85 BroadcastChannelProcedureName
.START_TRANSACTION
,
86 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
87 this.chargingStation
.ocppRequestService
.requestHandler
<
88 StartTransactionRequest
,
89 StartTransactionResponse
90 >(this.chargingStation
, RequestCommand
.START_TRANSACTION
, requestPayload
),
93 BroadcastChannelProcedureName
.STOP_TRANSACTION
,
94 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
95 this.chargingStation
.ocppRequestService
.requestHandler
<
96 StopTransactionRequest
,
97 StartTransactionResponse
98 >(this.chargingStation
, RequestCommand
.STOP_TRANSACTION
, {
101 requestPayload
.meterStop
??
102 this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(
103 requestPayload
.transactionId
,
109 BroadcastChannelProcedureName
.AUTHORIZE
,
110 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
111 this.chargingStation
.ocppRequestService
.requestHandler
<
114 >(this.chargingStation
, RequestCommand
.AUTHORIZE
, requestPayload
),
117 BroadcastChannelProcedureName
.STATUS_NOTIFICATION
,
118 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
119 this.chargingStation
.ocppRequestService
.requestHandler
<
120 StatusNotificationRequest
,
121 StatusNotificationResponse
122 >(this.chargingStation
, RequestCommand
.STATUS_NOTIFICATION
, requestPayload
),
125 BroadcastChannelProcedureName
.HEARTBEAT
,
126 async (requestPayload
?: BroadcastChannelRequestPayload
) =>
127 this.chargingStation
.ocppRequestService
.requestHandler
<
130 >(this.chargingStation
, RequestCommand
.HEARTBEAT
, requestPayload
),
133 BroadcastChannelProcedureName
.METER_VALUES
,
134 async (requestPayload
?: BroadcastChannelRequestPayload
) => {
135 const configuredMeterValueSampleInterval
=
136 ChargingStationConfigurationUtils
.getConfigurationKey(
138 StandardParametersKey
.MeterValueSampleInterval
140 return this.chargingStation
.ocppRequestService
.requestHandler
<
143 >(this.chargingStation
, RequestCommand
.METER_VALUES
, {
145 meterValue
: requestPayload
.meterValue
?? [
146 OCPP16ServiceUtils
.buildMeterValue(
147 this.chargingStation
,
148 requestPayload
.connectorId
,
149 this.chargingStation
.getConnectorStatus(requestPayload
.connectorId
)?.transactionId
,
150 configuredMeterValueSampleInterval
151 ? Utils
.convertToInt(configuredMeterValueSampleInterval
.value
) * 1000
152 : Constants
.DEFAULT_METER_VALUES_INTERVAL
159 this.chargingStation
= chargingStation
;
160 this.onmessage
= this.requestHandler
.bind(this) as (message
: MessageEvent
) => void;
161 this.onmessageerror
= this.messageErrorHandler
.bind(this) as (message
: MessageEvent
) => void;
164 private async requestHandler(messageEvent
: MessageEvent
): Promise
<void> {
165 const validatedMessageEvent
= this.validateMessageEvent(messageEvent
);
166 if (validatedMessageEvent
=== false) {
169 if (this.isResponse(validatedMessageEvent
.data
) === true) {
172 const [uuid
, command
, requestPayload
] = validatedMessageEvent
.data
as BroadcastChannelRequest
;
174 requestPayload
?.hashIds
!== undefined &&
175 requestPayload
?.hashIds
?.includes(this.chargingStation
.stationInfo
.hashId
) === false
179 if (requestPayload
?.hashId
!== undefined) {
181 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
185 let responsePayload
: BroadcastChannelResponsePayload
;
186 let commandResponse
: CommandResponse
| void;
188 commandResponse
= await this.commandHandler(command
, requestPayload
);
189 if (commandResponse
=== undefined || commandResponse
=== null) {
191 hashId
: this.chargingStation
.stationInfo
.hashId
,
192 status: ResponseStatus
.SUCCESS
,
195 responsePayload
= this.commandResponseToResponsePayload(
198 commandResponse
as CommandResponse
203 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
207 hashId
: this.chargingStation
.stationInfo
.hashId
,
208 status: ResponseStatus
.FAILURE
,
211 commandResponse
: commandResponse
as CommandResponse
,
212 errorMessage
: (error
as Error).message
,
213 errorStack
: (error
as Error).stack
,
214 errorDetails
: (error
as OCPPError
).details
,
217 this.sendResponse([uuid
, responsePayload
]);
221 private messageErrorHandler(messageEvent
: MessageEvent
): void {
223 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
228 private async commandHandler(
229 command
: BroadcastChannelProcedureName
,
230 requestPayload
: BroadcastChannelRequestPayload
231 ): Promise
<CommandResponse
| void> {
232 if (this.commandHandlers
.has(command
) === true) {
233 this.cleanRequestPayload(command
, requestPayload
);
234 return this.commandHandlers
.get(command
)(requestPayload
);
236 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
239 private cleanRequestPayload(
240 command
: BroadcastChannelProcedureName
,
241 requestPayload
: BroadcastChannelRequestPayload
243 delete requestPayload
.hashId
;
244 delete requestPayload
.hashIds
;
246 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
247 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
248 ].includes(command
) === false && delete requestPayload
.connectorIds
;
251 private commandResponseToResponsePayload(
252 command
: BroadcastChannelProcedureName
,
253 requestPayload
: BroadcastChannelRequestPayload
,
254 commandResponse
: CommandResponse
255 ): BroadcastChannelResponsePayload
{
256 const responseStatus
= this.commandResponseToResponseStatus(command
, commandResponse
);
257 if (responseStatus
=== ResponseStatus
.SUCCESS
) {
259 hashId
: this.chargingStation
.stationInfo
.hashId
,
260 status: responseStatus
,
264 hashId
: this.chargingStation
.stationInfo
.hashId
,
265 status: responseStatus
,
272 private commandResponseToResponseStatus(
273 command
: BroadcastChannelProcedureName
,
274 commandResponse
: CommandResponse
277 case BroadcastChannelProcedureName
.START_TRANSACTION
:
278 case BroadcastChannelProcedureName
.STOP_TRANSACTION
:
279 case BroadcastChannelProcedureName
.AUTHORIZE
:
283 | StartTransactionResponse
284 | StopTransactionResponse
286 )?.idTagInfo
?.status === AuthorizationStatus
.ACCEPTED
288 return ResponseStatus
.SUCCESS
;
290 return ResponseStatus
.FAILURE
;
291 case BroadcastChannelProcedureName
.STATUS_NOTIFICATION
:
292 case BroadcastChannelProcedureName
.METER_VALUES
:
293 if (Utils
.isEmptyObject(commandResponse
) === true) {
294 return ResponseStatus
.SUCCESS
;
296 return ResponseStatus
.FAILURE
;
297 case BroadcastChannelProcedureName
.HEARTBEAT
:
298 if ('currentTime' in commandResponse
) {
299 return ResponseStatus
.SUCCESS
;
301 return ResponseStatus
.FAILURE
;
303 return ResponseStatus
.FAILURE
;