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 readonly instances
: Map
<string, OCPPRequestService
> = new Map
<
29 protected readonly chargingStation
: ChargingStation
;
30 private readonly ocppResponseService
: OCPPResponseService
;
32 protected constructor(
33 chargingStation
: ChargingStation
,
34 ocppResponseService
: OCPPResponseService
36 this.chargingStation
= chargingStation
;
37 this.ocppResponseService
= ocppResponseService
;
38 this.requestHandler
.bind(this);
39 this.sendResponse
.bind(this);
40 this.sendError
.bind(this);
43 public static getInstance
<T
extends OCPPRequestService
>(
44 this: new (chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) => T
,
45 chargingStation
: ChargingStation
,
46 ocppResponseService
: OCPPResponseService
48 if (!OCPPRequestService
.instances
.has(chargingStation
.hashId
)) {
49 OCPPRequestService
.instances
.set(
50 chargingStation
.hashId
,
51 new this(chargingStation
, ocppResponseService
)
54 return OCPPRequestService
.instances
.get(chargingStation
.hashId
) as T
;
57 public async sendResponse(
59 messagePayload
: JsonType
,
60 commandName
: IncomingRequestCommand
61 ): Promise
<ResponseType
> {
63 // Send response message
64 return await this.internalSendMessage(
67 MessageType
.CALL_RESULT_MESSAGE
,
71 this.handleRequestError(commandName
, error
as Error);
75 public async sendError(
78 commandName
: RequestCommand
| IncomingRequestCommand
79 ): Promise
<ResponseType
> {
82 return await this.internalSendMessage(
85 MessageType
.CALL_ERROR_MESSAGE
,
89 this.handleRequestError(commandName
, error
as Error);
93 protected async sendMessage(
95 messagePayload
: JsonType
,
96 commandName
: RequestCommand
,
97 params
: RequestParams
= {
98 skipBufferingOnError
: false,
99 triggerMessage
: false,
101 ): Promise
<ResponseType
> {
103 return await this.internalSendMessage(
106 MessageType
.CALL_MESSAGE
,
111 this.handleRequestError(commandName
, error
as Error, { throwError
: false });
115 private async internalSendMessage(
117 messagePayload
: JsonType
| OCPPError
,
118 messageType
: MessageType
,
119 commandName
?: RequestCommand
| IncomingRequestCommand
,
120 params
: RequestParams
= {
121 skipBufferingOnError
: false,
122 triggerMessage
: false,
124 ): Promise
<ResponseType
> {
126 (this.chargingStation
.isInUnknownState() &&
127 commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
128 (!this.chargingStation
.getOcppStrictCompliance() &&
129 this.chargingStation
.isInUnknownState()) ||
130 this.chargingStation
.isInAcceptedState() ||
131 (this.chargingStation
.isInPendingState() &&
132 (params
.triggerMessage
|| messageType
=== MessageType
.CALL_RESULT_MESSAGE
))
134 // eslint-disable-next-line @typescript-eslint/no-this-alias
136 // Send a message through wsConnection
137 return Utils
.promiseWithTimeout(
138 new Promise((resolve
, reject
) => {
139 const messageToSend
= this.buildMessageToSend(
147 if (this.chargingStation
.getEnableStatistics()) {
148 this.chargingStation
.performanceStatistics
.addRequestStatistic(
153 // Check if wsConnection opened
154 if (this.chargingStation
.isWebSocketConnectionOpened()) {
156 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
157 // FIXME: Handle sending error
158 this.chargingStation
.wsConnection
.send(messageToSend
);
159 PerformanceStatistics
.endMeasure(commandName
, beginId
);
161 `${this.chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
163 )} payload: ${messageToSend}`
165 } else if (!params
.skipBufferingOnError
) {
167 this.chargingStation
.bufferMessage(messageToSend
);
168 const ocppError
= new OCPPError(
169 ErrorType
.GENERIC_ERROR
,
170 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
172 (messagePayload
as JsonObject
)?.details
?? {}
174 if (messageType
=== MessageType
.CALL_MESSAGE
) {
175 // Reject it but keep the request in the cache
176 return reject(ocppError
);
178 return errorCallback(ocppError
, false);
181 return errorCallback(
183 ErrorType
.GENERIC_ERROR
,
184 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
186 (messagePayload
as JsonObject
)?.details
?? {}
192 if (messageType
!== MessageType
.CALL_MESSAGE
) {
194 return resolve(messagePayload
);
198 * Function that will receive the request's response
201 * @param requestPayload
203 async function responseCallback(
205 requestPayload
: JsonType
207 if (self.chargingStation
.getEnableStatistics()) {
208 self.chargingStation
.performanceStatistics
.addRequestStatistic(
210 MessageType
.CALL_RESULT_MESSAGE
213 // Handle the request's response
215 await self.ocppResponseService
.responseHandler(
216 commandName
as RequestCommand
,
224 self.chargingStation
.requests
.delete(messageId
);
229 * Function that will receive the request's error response
232 * @param requestStatistic
234 function errorCallback(error
: OCPPError
, requestStatistic
= true): void {
235 if (requestStatistic
&& self.chargingStation
.getEnableStatistics()) {
236 self.chargingStation
.performanceStatistics
.addRequestStatistic(
238 MessageType
.CALL_ERROR_MESSAGE
242 `${self.chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
247 self.chargingStation
.requests
.delete(messageId
);
251 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
253 ErrorType
.GENERIC_ERROR
,
254 `Timeout for message id '${messageId}'`,
256 (messagePayload
as JsonObject
)?.details
?? {}
259 messageType
=== MessageType
.CALL_MESSAGE
&&
260 this.chargingStation
.requests
.delete(messageId
);
265 ErrorType
.SECURITY_ERROR
,
266 `Cannot send command ${commandName} payload when the charging station is in ${this.chargingStation.getRegistrationStatus()} state on the central server`,
271 private buildMessageToSend(
273 messagePayload
: JsonType
| OCPPError
,
274 messageType
: MessageType
,
275 commandName
?: RequestCommand
| IncomingRequestCommand
,
276 responseCallback
?: (payload
: JsonType
, requestPayload
: JsonType
) => Promise
<void>,
277 errorCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
279 let messageToSend
: string;
281 switch (messageType
) {
283 case MessageType
.CALL_MESSAGE
:
285 this.chargingStation
.requests
.set(messageId
, [
289 messagePayload
as JsonType
,
291 messageToSend
= JSON
.stringify([
296 ] as OutgoingRequest
);
299 case MessageType
.CALL_RESULT_MESSAGE
:
301 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
] as Response
);
304 case MessageType
.CALL_ERROR_MESSAGE
:
305 // Build Error Message
306 messageToSend
= JSON
.stringify([
309 (messagePayload
as OCPPError
)?.code
?? ErrorType
.GENERIC_ERROR
,
310 (messagePayload
as OCPPError
)?.message
?? '',
311 (messagePayload
as OCPPError
)?.details
?? { commandName
},
315 return messageToSend
;
318 private getMessageTypeString(messageType
: MessageType
): string {
319 switch (messageType
) {
320 case MessageType
.CALL_MESSAGE
:
322 case MessageType
.CALL_RESULT_MESSAGE
:
324 case MessageType
.CALL_ERROR_MESSAGE
:
329 private handleRequestError(
330 commandName
: RequestCommand
| IncomingRequestCommand
,
332 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
335 this.chargingStation
.logPrefix() + ' Request command %s error: %j',
339 if (params
?.throwError
) {
344 // eslint-disable-next-line @typescript-eslint/no-unused-vars
345 public abstract requestHandler
<Request
extends JsonType
, Response
extends JsonType
>(
346 commandName
: RequestCommand
,
347 commandParams
?: JsonType
,
348 params
?: RequestParams
349 ): Promise
<Response
>;