1 import { ErrorResponse
, Response
} from
'../../types/ocpp/Responses';
3 IncomingRequestCommand
,
8 } from
'../../types/ocpp/Requests';
9 import { JsonObject
, JsonType
} from
'../../types/JsonType';
11 import type ChargingStation from
'../ChargingStation';
12 import Constants from
'../../utils/Constants';
13 import { EmptyObject
} from
'../../types/EmptyObject';
14 import { ErrorType
} from
'../../types/ocpp/ErrorType';
15 import { HandleErrorParams
} from
'../../types/Error';
16 import { MessageType
} from
'../../types/ocpp/MessageType';
17 import OCPPError from
'../../exception/OCPPError';
18 import type OCPPResponseService from
'./OCPPResponseService';
19 import PerformanceStatistics from
'../../performance/PerformanceStatistics';
20 import Utils from
'../../utils/Utils';
21 import logger from
'../../utils/Logger';
23 export default abstract class OCPPRequestService
{
24 private static instance
: OCPPRequestService
| null = null;
26 private readonly ocppResponseService
: OCPPResponseService
;
28 protected constructor(ocppResponseService
: OCPPResponseService
) {
29 this.ocppResponseService
= ocppResponseService
;
30 this.requestHandler
.bind(this);
31 this.sendResponse
.bind(this);
32 this.sendError
.bind(this);
35 public static getInstance
<T
extends OCPPRequestService
>(
36 this: new (ocppResponseService
: OCPPResponseService
) => T
,
37 ocppResponseService
: OCPPResponseService
39 if (!OCPPRequestService
.instance
) {
40 OCPPRequestService
.instance
= new this(ocppResponseService
);
42 return OCPPRequestService
.instance
as T
;
45 public async sendResponse(
46 chargingStation
: ChargingStation
,
48 messagePayload
: JsonType
,
49 commandName
: IncomingRequestCommand
50 ): Promise
<ResponseType
> {
52 // Send response message
53 return await this.internalSendMessage(
57 MessageType
.CALL_RESULT_MESSAGE
,
61 this.handleRequestError(chargingStation
, commandName
, error
as Error);
65 public async sendError(
66 chargingStation
: ChargingStation
,
69 commandName
: RequestCommand
| IncomingRequestCommand
70 ): Promise
<ResponseType
> {
73 return await this.internalSendMessage(
77 MessageType
.CALL_ERROR_MESSAGE
,
81 this.handleRequestError(chargingStation
, commandName
, error
as Error);
85 protected async sendMessage(
86 chargingStation
: ChargingStation
,
88 messagePayload
: JsonType
,
89 commandName
: RequestCommand
,
90 params
: RequestParams
= {
91 skipBufferingOnError
: false,
92 triggerMessage
: false,
94 ): Promise
<ResponseType
> {
96 return await this.internalSendMessage(
100 MessageType
.CALL_MESSAGE
,
105 this.handleRequestError(chargingStation
, commandName
, error
as Error, { throwError
: false });
109 private async internalSendMessage(
110 chargingStation
: ChargingStation
,
112 messagePayload
: JsonType
| OCPPError
,
113 messageType
: MessageType
,
114 commandName
?: RequestCommand
| IncomingRequestCommand
,
115 params
: RequestParams
= {
116 skipBufferingOnError
: false,
117 triggerMessage
: false,
119 ): Promise
<ResponseType
> {
121 (chargingStation
.isInUnknownState() && commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
122 (!chargingStation
.getOcppStrictCompliance() && chargingStation
.isInUnknownState()) ||
123 chargingStation
.isInAcceptedState() ||
124 (chargingStation
.isInPendingState() &&
125 (params
.triggerMessage
|| messageType
=== MessageType
.CALL_RESULT_MESSAGE
))
127 // eslint-disable-next-line @typescript-eslint/no-this-alias
129 // Send a message through wsConnection
130 return Utils
.promiseWithTimeout(
131 new Promise((resolve
, reject
) => {
132 const messageToSend
= this.buildMessageToSend(
141 if (chargingStation
.getEnableStatistics()) {
142 chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, messageType
);
144 // Check if wsConnection opened
145 if (chargingStation
.isWebSocketConnectionOpened()) {
147 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
148 // FIXME: Handle sending error
149 chargingStation
.wsConnection
.send(messageToSend
);
150 PerformanceStatistics
.endMeasure(commandName
, beginId
);
152 `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
154 )} payload: ${messageToSend}`
156 } else if (!params
.skipBufferingOnError
) {
158 chargingStation
.bufferMessage(messageToSend
);
159 const ocppError
= new OCPPError(
160 ErrorType
.GENERIC_ERROR
,
161 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
163 (messagePayload
as JsonObject
)?.details
?? {}
165 if (messageType
=== MessageType
.CALL_MESSAGE
) {
166 // Reject it but keep the request in the cache
167 return reject(ocppError
);
169 return errorCallback(ocppError
, false);
172 return errorCallback(
174 ErrorType
.GENERIC_ERROR
,
175 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
177 (messagePayload
as JsonObject
)?.details
?? {}
183 if (messageType
!== MessageType
.CALL_MESSAGE
) {
185 return resolve(messagePayload
);
189 * Function that will receive the request's response
192 * @param requestPayload
194 async function responseCallback(
196 requestPayload
: JsonType
198 if (chargingStation
.getEnableStatistics()) {
199 chargingStation
.performanceStatistics
.addRequestStatistic(
201 MessageType
.CALL_RESULT_MESSAGE
204 // Handle the request's response
206 await self.ocppResponseService
.responseHandler(
208 commandName
as RequestCommand
,
216 chargingStation
.requests
.delete(messageId
);
221 * Function that will receive the request's error response
224 * @param requestStatistic
226 function errorCallback(error
: OCPPError
, requestStatistic
= true): void {
227 if (requestStatistic
&& chargingStation
.getEnableStatistics()) {
228 chargingStation
.performanceStatistics
.addRequestStatistic(
230 MessageType
.CALL_ERROR_MESSAGE
234 `${chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
239 chargingStation
.requests
.delete(messageId
);
243 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
245 ErrorType
.GENERIC_ERROR
,
246 `Timeout for message id '${messageId}'`,
248 (messagePayload
as JsonObject
)?.details
?? {}
251 messageType
=== MessageType
.CALL_MESSAGE
&& chargingStation
.requests
.delete(messageId
);
256 ErrorType
.SECURITY_ERROR
,
257 `Cannot send command ${commandName} payload when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
262 private buildMessageToSend(
263 chargingStation
: ChargingStation
,
265 messagePayload
: JsonType
| OCPPError
,
266 messageType
: MessageType
,
267 commandName
?: RequestCommand
| IncomingRequestCommand
,
268 responseCallback
?: (payload
: JsonType
, requestPayload
: JsonType
) => Promise
<void>,
269 errorCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
271 let messageToSend
: string;
273 switch (messageType
) {
275 case MessageType
.CALL_MESSAGE
:
277 chargingStation
.requests
.set(messageId
, [
281 messagePayload
as JsonType
,
283 messageToSend
= JSON
.stringify([
288 ] as OutgoingRequest
);
291 case MessageType
.CALL_RESULT_MESSAGE
:
293 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
] as Response
);
296 case MessageType
.CALL_ERROR_MESSAGE
:
297 // Build Error Message
298 messageToSend
= JSON
.stringify([
301 (messagePayload
as OCPPError
)?.code
?? ErrorType
.GENERIC_ERROR
,
302 (messagePayload
as OCPPError
)?.message
?? '',
303 (messagePayload
as OCPPError
)?.details
?? { commandName
},
307 return messageToSend
;
310 private getMessageTypeString(messageType
: MessageType
): string {
311 switch (messageType
) {
312 case MessageType
.CALL_MESSAGE
:
314 case MessageType
.CALL_RESULT_MESSAGE
:
316 case MessageType
.CALL_ERROR_MESSAGE
:
321 private handleRequestError(
322 chargingStation
: ChargingStation
,
323 commandName
: RequestCommand
| IncomingRequestCommand
,
325 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
327 logger
.error(chargingStation
.logPrefix() + ' Request command %s error: %j', commandName
, error
);
328 if (params
?.throwError
) {
333 // eslint-disable-next-line @typescript-eslint/no-unused-vars
334 public abstract requestHandler
<Request
extends JsonType
, Response
extends JsonType
>(
335 chargingStation
: ChargingStation
,
336 commandName
: RequestCommand
,
337 commandParams
?: JsonType
,
338 params
?: RequestParams
339 ): Promise
<Response
>;