1 import OCPPError from
'../../exception/OCPPError';
2 import PerformanceStatistics from
'../../performance/PerformanceStatistics';
3 import { EmptyObject
} from
'../../types/EmptyObject';
4 import { HandleErrorParams
} from
'../../types/Error';
5 import { JsonObject
, JsonType
} from
'../../types/JsonType';
6 import { ErrorType
} from
'../../types/ocpp/ErrorType';
7 import { MessageType
} from
'../../types/ocpp/MessageType';
9 IncomingRequestCommand
,
14 } from
'../../types/ocpp/Requests';
15 import { ErrorResponse
, Response
} from
'../../types/ocpp/Responses';
16 import Constants from
'../../utils/Constants';
17 import logger from
'../../utils/Logger';
18 import Utils from
'../../utils/Utils';
19 import type ChargingStation from
'../ChargingStation';
20 import type OCPPResponseService from
'./OCPPResponseService';
22 const moduleName
= 'OCPPRequestService';
24 export default abstract class OCPPRequestService
{
25 private static instance
: OCPPRequestService
| null = null;
27 private readonly ocppResponseService
: OCPPResponseService
;
29 protected constructor(ocppResponseService
: OCPPResponseService
) {
30 this.ocppResponseService
= ocppResponseService
;
31 this.requestHandler
.bind(this);
32 this.sendResponse
.bind(this);
33 this.sendError
.bind(this);
36 public static getInstance
<T
extends OCPPRequestService
>(
37 this: new (ocppResponseService
: OCPPResponseService
) => T
,
38 ocppResponseService
: OCPPResponseService
40 if (!OCPPRequestService
.instance
) {
41 OCPPRequestService
.instance
= new this(ocppResponseService
);
43 return OCPPRequestService
.instance
as T
;
46 public async sendResponse(
47 chargingStation
: ChargingStation
,
49 messagePayload
: JsonType
,
50 commandName
: IncomingRequestCommand
51 ): Promise
<ResponseType
> {
53 // Send response message
54 return await this.internalSendMessage(
58 MessageType
.CALL_RESULT_MESSAGE
,
62 this.handleRequestError(chargingStation
, commandName
, error
as Error);
66 public async sendError(
67 chargingStation
: ChargingStation
,
70 commandName
: RequestCommand
| IncomingRequestCommand
71 ): Promise
<ResponseType
> {
74 return await this.internalSendMessage(
78 MessageType
.CALL_ERROR_MESSAGE
,
82 this.handleRequestError(chargingStation
, commandName
, error
as Error);
86 protected async sendMessage(
87 chargingStation
: ChargingStation
,
89 messagePayload
: JsonType
,
90 commandName
: RequestCommand
,
91 params
: RequestParams
= {
92 skipBufferingOnError
: false,
93 triggerMessage
: false,
95 ): Promise
<ResponseType
> {
97 return await this.internalSendMessage(
101 MessageType
.CALL_MESSAGE
,
106 this.handleRequestError(chargingStation
, commandName
, error
as Error, { throwError
: false });
110 private async internalSendMessage(
111 chargingStation
: ChargingStation
,
113 messagePayload
: JsonType
| OCPPError
,
114 messageType
: MessageType
,
115 commandName
?: RequestCommand
| IncomingRequestCommand
,
116 params
: RequestParams
= {
117 skipBufferingOnError
: false,
118 triggerMessage
: false,
120 ): Promise
<ResponseType
> {
122 (chargingStation
.isInUnknownState() && commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
123 (!chargingStation
.getOcppStrictCompliance() && chargingStation
.isInUnknownState()) ||
124 chargingStation
.isInAcceptedState() ||
125 (chargingStation
.isInPendingState() &&
126 (params
.triggerMessage
|| messageType
=== MessageType
.CALL_RESULT_MESSAGE
))
128 // eslint-disable-next-line @typescript-eslint/no-this-alias
130 // Send a message through wsConnection
131 return Utils
.promiseWithTimeout(
132 new Promise((resolve
, reject
) => {
133 const messageToSend
= this.buildMessageToSend(
142 if (chargingStation
.getEnableStatistics()) {
143 chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, messageType
);
145 // Check if wsConnection opened
146 if (chargingStation
.isWebSocketConnectionOpened()) {
148 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
149 // FIXME: Handle sending error
150 chargingStation
.wsConnection
.send(messageToSend
);
151 PerformanceStatistics
.endMeasure(commandName
, beginId
);
153 `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
155 )} payload: ${messageToSend}`
157 } else if (!params
.skipBufferingOnError
) {
159 chargingStation
.bufferMessage(messageToSend
);
160 const ocppError
= new OCPPError(
161 ErrorType
.GENERIC_ERROR
,
162 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
164 (messagePayload
as JsonObject
)?.details
?? {}
166 if (messageType
=== MessageType
.CALL_MESSAGE
) {
167 // Reject it but keep the request in the cache
168 return reject(ocppError
);
170 return errorCallback(ocppError
, false);
173 return errorCallback(
175 ErrorType
.GENERIC_ERROR
,
176 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
178 (messagePayload
as JsonObject
)?.details
?? {}
184 if (messageType
!== MessageType
.CALL_MESSAGE
) {
186 return resolve(messagePayload
);
190 * Function that will receive the request's response
193 * @param requestPayload
195 async function responseCallback(
197 requestPayload
: JsonType
199 if (chargingStation
.getEnableStatistics()) {
200 chargingStation
.performanceStatistics
.addRequestStatistic(
202 MessageType
.CALL_RESULT_MESSAGE
205 // Handle the request's response
207 await self.ocppResponseService
.responseHandler(
209 commandName
as RequestCommand
,
217 chargingStation
.requests
.delete(messageId
);
222 * Function that will receive the request's error response
225 * @param requestStatistic
227 function errorCallback(error
: OCPPError
, requestStatistic
= true): void {
228 if (requestStatistic
&& chargingStation
.getEnableStatistics()) {
229 chargingStation
.performanceStatistics
.addRequestStatistic(
231 MessageType
.CALL_ERROR_MESSAGE
235 `${chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
240 chargingStation
.requests
.delete(messageId
);
244 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
246 ErrorType
.GENERIC_ERROR
,
247 `Timeout for message id '${messageId}'`,
249 (messagePayload
as JsonObject
)?.details
?? {}
252 messageType
=== MessageType
.CALL_MESSAGE
&& chargingStation
.requests
.delete(messageId
);
257 ErrorType
.SECURITY_ERROR
,
258 `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
263 private buildMessageToSend(
264 chargingStation
: ChargingStation
,
266 messagePayload
: JsonType
| OCPPError
,
267 messageType
: MessageType
,
268 commandName
?: RequestCommand
| IncomingRequestCommand
,
269 responseCallback
?: (payload
: JsonType
, requestPayload
: JsonType
) => Promise
<void>,
270 errorCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
272 let messageToSend
: string;
274 switch (messageType
) {
276 case MessageType
.CALL_MESSAGE
:
278 chargingStation
.requests
.set(messageId
, [
282 messagePayload
as JsonType
,
284 messageToSend
= JSON
.stringify([
289 ] as OutgoingRequest
);
292 case MessageType
.CALL_RESULT_MESSAGE
:
294 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
] as Response
);
297 case MessageType
.CALL_ERROR_MESSAGE
:
298 // Build Error Message
299 messageToSend
= JSON
.stringify([
302 (messagePayload
as OCPPError
)?.code
?? ErrorType
.GENERIC_ERROR
,
303 (messagePayload
as OCPPError
)?.message
?? '',
304 (messagePayload
as OCPPError
)?.details
?? { commandName
},
308 return messageToSend
;
311 private getMessageTypeString(messageType
: MessageType
): string {
312 switch (messageType
) {
313 case MessageType
.CALL_MESSAGE
:
315 case MessageType
.CALL_RESULT_MESSAGE
:
317 case MessageType
.CALL_ERROR_MESSAGE
:
322 private handleRequestError(
323 chargingStation
: ChargingStation
,
324 commandName
: RequestCommand
| IncomingRequestCommand
,
326 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
328 logger
.error(chargingStation
.logPrefix() + ' Request command %s error: %j', commandName
, error
);
329 if (params
?.throwError
) {
334 // eslint-disable-next-line @typescript-eslint/no-unused-vars
335 public abstract requestHandler
<Request
extends JsonType
, Response
extends JsonType
>(
336 chargingStation
: ChargingStation
,
337 commandName
: RequestCommand
,
338 commandParams
?: JsonType
,
339 params
?: RequestParams
340 ): Promise
<Response
>;