3 IncomingRequestCommand
,
7 } from
'../../types/ocpp/Requests';
8 import { StopTransactionReason
, StopTransactionResponse
} from
'../../types/ocpp/Transaction';
10 import type ChargingStation from
'../ChargingStation';
11 import Constants from
'../../utils/Constants';
12 import { EmptyObject
} from
'../../types/EmptyObject';
13 import { ErrorType
} from
'../../types/ocpp/ErrorType';
14 import { HandleErrorParams
} from
'../../types/Error';
15 import { JsonType
} from
'../../types/JsonType';
16 import { MessageType
} from
'../../types/ocpp/MessageType';
17 import { MeterValue
} from
'../../types/ocpp/MeterValues';
18 import OCPPError from
'../../exception/OCPPError';
19 import type OCPPResponseService from
'./OCPPResponseService';
20 import PerformanceStatistics from
'../../performance/PerformanceStatistics';
21 import Utils from
'../../utils/Utils';
22 import logger from
'../../utils/Logger';
24 export default abstract class OCPPRequestService
{
25 private static readonly instances
: Map
<string, OCPPRequestService
> = new Map
<
30 protected readonly chargingStation
: ChargingStation
;
31 private readonly ocppResponseService
: OCPPResponseService
;
33 protected constructor(
34 chargingStation
: ChargingStation
,
35 ocppResponseService
: OCPPResponseService
37 this.chargingStation
= chargingStation
;
38 this.ocppResponseService
= ocppResponseService
;
39 this.sendMessageHandler
.bind(this);
42 public static getInstance
<T
extends OCPPRequestService
>(
43 this: new (chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) => T
,
44 chargingStation
: ChargingStation
,
45 ocppResponseService
: OCPPResponseService
47 if (!OCPPRequestService
.instances
.has(chargingStation
.id
)) {
48 OCPPRequestService
.instances
.set(
50 new this(chargingStation
, ocppResponseService
)
53 return OCPPRequestService
.instances
.get(chargingStation
.id
) as T
;
56 public async sendResult(
58 messagePayload
: JsonType
,
59 commandName
: IncomingRequestCommand
60 ): Promise
<ResponseType
> {
62 // Send result message
63 return await this.internalSendMessage(
66 MessageType
.CALL_RESULT_MESSAGE
,
70 this.handleRequestError(commandName
, error
as Error);
74 public async sendError(
77 commandName
: IncomingRequestCommand
78 ): Promise
<ResponseType
> {
81 return await this.internalSendMessage(
84 MessageType
.CALL_ERROR_MESSAGE
,
88 this.handleRequestError(commandName
, error
as Error);
92 protected async sendMessage(
94 messagePayload
: JsonType
,
95 commandName
: RequestCommand
,
96 params
: SendParams
= {
97 skipBufferingOnError
: false,
98 triggerMessage
: false,
100 ): Promise
<ResponseType
> {
102 return await this.internalSendMessage(
105 MessageType
.CALL_MESSAGE
,
110 this.handleRequestError(commandName
, error
as Error, { throwError
: false });
114 private async internalSendMessage(
116 messagePayload
: JsonType
| OCPPError
,
117 messageType
: MessageType
,
118 commandName
?: RequestCommand
| IncomingRequestCommand
,
119 params
: SendParams
= {
120 skipBufferingOnError
: false,
121 triggerMessage
: false,
123 ): Promise
<ResponseType
> {
125 (this.chargingStation
.isInUnknownState() &&
126 commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
127 (!this.chargingStation
.getOcppStrictCompliance() &&
128 this.chargingStation
.isInUnknownState()) ||
129 this.chargingStation
.isInAcceptedState() ||
130 (this.chargingStation
.isInPendingState() && params
.triggerMessage
)
132 // eslint-disable-next-line @typescript-eslint/no-this-alias
134 // Send a message through wsConnection
135 return Utils
.promiseWithTimeout(
136 new Promise((resolve
, reject
) => {
137 const messageToSend
= this.buildMessageToSend(
145 if (this.chargingStation
.getEnableStatistics()) {
146 this.chargingStation
.performanceStatistics
.addRequestStatistic(
151 // Check if wsConnection opened
152 if (this.chargingStation
.isWebSocketConnectionOpened()) {
154 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
155 // FIXME: Handle sending error
156 this.chargingStation
.wsConnection
.send(messageToSend
);
157 PerformanceStatistics
.endMeasure(commandName
, beginId
);
158 } else if (!params
.skipBufferingOnError
) {
160 this.chargingStation
.bufferMessage(messageToSend
);
161 const ocppError
= new OCPPError(
162 ErrorType
.GENERIC_ERROR
,
163 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
165 (messagePayload
?.details
as JsonType
) ?? {}
167 if (messageType
=== MessageType
.CALL_MESSAGE
) {
168 // Reject it but keep the request in the cache
169 return reject(ocppError
);
171 return rejectCallback(ocppError
, false);
174 return rejectCallback(
176 ErrorType
.GENERIC_ERROR
,
177 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
179 (messagePayload
?.details
as JsonType
) ?? {}
185 if (messageType
!== MessageType
.CALL_MESSAGE
) {
187 return resolve(messagePayload
);
191 * Function that will receive the request's response
194 * @param requestPayload
196 async function responseCallback(
197 payload
: JsonType
| string,
198 requestPayload
: JsonType
200 if (self.chargingStation
.getEnableStatistics()) {
201 self.chargingStation
.performanceStatistics
.addRequestStatistic(
203 MessageType
.CALL_RESULT_MESSAGE
206 // Handle the request's response
208 await self.ocppResponseService
.handleResponse(
209 commandName
as RequestCommand
,
218 self.chargingStation
.requests
.delete(messageId
);
223 * Function that will receive the request's error response
226 * @param requestStatistic
228 function rejectCallback(error
: OCPPError
, requestStatistic
= true): void {
229 if (requestStatistic
&& self.chargingStation
.getEnableStatistics()) {
230 self.chargingStation
.performanceStatistics
.addRequestStatistic(
232 MessageType
.CALL_ERROR_MESSAGE
236 `${self.chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
241 self.chargingStation
.requests
.delete(messageId
);
245 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
247 ErrorType
.GENERIC_ERROR
,
248 `Timeout for message id '${messageId}'`,
250 (messagePayload
?.details
as JsonType
) ?? {}
253 messageType
=== MessageType
.CALL_MESSAGE
&&
254 this.chargingStation
.requests
.delete(messageId
);
259 ErrorType
.SECURITY_ERROR
,
260 `Cannot send command ${commandName} payload when the charging station is in ${this.chargingStation.getRegistrationStatus()} state on the central server`,
265 private buildMessageToSend(
267 messagePayload
: JsonType
| OCPPError
,
268 messageType
: MessageType
,
269 commandName
?: RequestCommand
| IncomingRequestCommand
,
270 responseCallback
?: (payload
: JsonType
| string, requestPayload
: JsonType
) => Promise
<void>,
271 rejectCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
273 let messageToSend
: string;
275 switch (messageType
) {
277 case MessageType
.CALL_MESSAGE
:
279 this.chargingStation
.requests
.set(messageId
, [
285 messageToSend
= JSON
.stringify([messageType
, messageId
, commandName
, messagePayload
]);
288 case MessageType
.CALL_RESULT_MESSAGE
:
290 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
]);
293 case MessageType
.CALL_ERROR_MESSAGE
:
294 // Build Error Message
295 messageToSend
= JSON
.stringify([
298 messagePayload
?.code
?? ErrorType
.GENERIC_ERROR
,
299 messagePayload
?.message
?? '',
300 messagePayload
?.details
?? { commandName
},
304 return messageToSend
;
307 private handleRequestError(
308 commandName
: RequestCommand
| IncomingRequestCommand
,
310 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
313 this.chargingStation
.logPrefix() + ' Request command %s error: %j',
317 if (params
?.throwError
) {
322 public abstract sendMessageHandler(
323 commandName
: RequestCommand
,
324 commandParams
?: JsonType
,
326 ): Promise
<ResponseType
>;
328 public abstract sendMeterValues(
330 transactionId
: number,
334 public abstract sendTransactionBeginMeterValues(
336 transactionId
: number,
337 beginMeterValue
: MeterValue
340 public abstract sendTransactionEndMeterValues(
342 transactionId
: number,
343 endMeterValue
: MeterValue
346 public abstract sendDiagnosticsStatusNotification(
347 diagnosticsStatus
: DiagnosticsStatus