2 IncomingRequestCommand
,
6 } from
'../../types/ocpp/Requests';
8 import type ChargingStation from
'../ChargingStation';
9 import Constants from
'../../utils/Constants';
10 import { EmptyObject
} from
'../../types/EmptyObject';
11 import { ErrorType
} from
'../../types/ocpp/ErrorType';
12 import { HandleErrorParams
} from
'../../types/Error';
13 import { JsonType
} from
'../../types/JsonType';
14 import { MessageType
} from
'../../types/ocpp/MessageType';
15 import OCPPError from
'../../exception/OCPPError';
16 import type OCPPResponseService from
'./OCPPResponseService';
17 import PerformanceStatistics from
'../../performance/PerformanceStatistics';
18 import Utils from
'../../utils/Utils';
19 import logger from
'../../utils/Logger';
21 export default abstract class OCPPRequestService
{
22 private static readonly instances
: Map
<string, OCPPRequestService
> = new Map
<
27 protected readonly chargingStation
: ChargingStation
;
28 private readonly ocppResponseService
: OCPPResponseService
;
30 protected constructor(
31 chargingStation
: ChargingStation
,
32 ocppResponseService
: OCPPResponseService
34 this.chargingStation
= chargingStation
;
35 this.ocppResponseService
= ocppResponseService
;
36 this.requestHandler
.bind(this);
37 this.sendResult
.bind(this);
38 this.sendError
.bind(this);
41 public static getInstance
<T
extends OCPPRequestService
>(
42 this: new (chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) => T
,
43 chargingStation
: ChargingStation
,
44 ocppResponseService
: OCPPResponseService
46 if (!OCPPRequestService
.instances
.has(chargingStation
.hashId
)) {
47 OCPPRequestService
.instances
.set(
48 chargingStation
.hashId
,
49 new this(chargingStation
, ocppResponseService
)
52 return OCPPRequestService
.instances
.get(chargingStation
.hashId
) as T
;
55 public async sendResult(
57 messagePayload
: JsonType
,
58 commandName
: IncomingRequestCommand
59 ): Promise
<ResponseType
> {
61 // Send result message
62 return await this.internalSendMessage(
65 MessageType
.CALL_RESULT_MESSAGE
,
69 this.handleRequestError(commandName
, error
as Error);
73 public async sendError(
76 commandName
: IncomingRequestCommand
77 ): Promise
<ResponseType
> {
80 return await this.internalSendMessage(
83 MessageType
.CALL_ERROR_MESSAGE
,
87 this.handleRequestError(commandName
, error
as Error);
91 protected async sendMessage(
93 messagePayload
: JsonType
,
94 commandName
: RequestCommand
,
95 params
: RequestParams
= {
96 skipBufferingOnError
: false,
97 triggerMessage
: false,
99 ): Promise
<ResponseType
> {
101 return await this.internalSendMessage(
104 MessageType
.CALL_MESSAGE
,
109 this.handleRequestError(commandName
, error
as Error, { throwError
: false });
113 private async internalSendMessage(
115 messagePayload
: JsonType
| OCPPError
,
116 messageType
: MessageType
,
117 commandName
?: RequestCommand
| IncomingRequestCommand
,
118 params
: RequestParams
= {
119 skipBufferingOnError
: false,
120 triggerMessage
: false,
122 ): Promise
<ResponseType
> {
124 (this.chargingStation
.isInUnknownState() &&
125 commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
126 (!this.chargingStation
.getOcppStrictCompliance() &&
127 this.chargingStation
.isInUnknownState()) ||
128 this.chargingStation
.isInAcceptedState() ||
129 (this.chargingStation
.isInPendingState() &&
130 (params
.triggerMessage
|| messageType
=== MessageType
.CALL_RESULT_MESSAGE
))
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
);
159 `${this.chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
161 )} payload: ${messageToSend}`
163 } else if (!params
.skipBufferingOnError
) {
165 this.chargingStation
.bufferMessage(messageToSend
);
166 const ocppError
= new OCPPError(
167 ErrorType
.GENERIC_ERROR
,
168 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
170 (messagePayload
?.details
as JsonType
) ?? {}
172 if (messageType
=== MessageType
.CALL_MESSAGE
) {
173 // Reject it but keep the request in the cache
174 return reject(ocppError
);
176 return rejectCallback(ocppError
, false);
179 return rejectCallback(
181 ErrorType
.GENERIC_ERROR
,
182 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
184 (messagePayload
?.details
as JsonType
) ?? {}
190 if (messageType
!== MessageType
.CALL_MESSAGE
) {
192 return resolve(messagePayload
);
196 * Function that will receive the request's response
199 * @param requestPayload
201 async function responseCallback(
202 payload
: JsonType
| string,
203 requestPayload
: JsonType
205 if (self.chargingStation
.getEnableStatistics()) {
206 self.chargingStation
.performanceStatistics
.addRequestStatistic(
208 MessageType
.CALL_RESULT_MESSAGE
211 // Handle the request's response
213 await self.ocppResponseService
.responseHandler(
214 commandName
as RequestCommand
,
222 self.chargingStation
.requests
.delete(messageId
);
227 * Function that will receive the request's error response
230 * @param requestStatistic
232 function rejectCallback(error
: OCPPError
, requestStatistic
= true): void {
233 if (requestStatistic
&& self.chargingStation
.getEnableStatistics()) {
234 self.chargingStation
.performanceStatistics
.addRequestStatistic(
236 MessageType
.CALL_ERROR_MESSAGE
240 `${self.chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
245 self.chargingStation
.requests
.delete(messageId
);
249 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
251 ErrorType
.GENERIC_ERROR
,
252 `Timeout for message id '${messageId}'`,
254 (messagePayload
?.details
as JsonType
) ?? {}
257 messageType
=== MessageType
.CALL_MESSAGE
&&
258 this.chargingStation
.requests
.delete(messageId
);
263 ErrorType
.SECURITY_ERROR
,
264 `Cannot send command ${commandName} payload when the charging station is in ${this.chargingStation.getRegistrationStatus()} state on the central server`,
269 private buildMessageToSend(
271 messagePayload
: JsonType
| OCPPError
,
272 messageType
: MessageType
,
273 commandName
?: RequestCommand
| IncomingRequestCommand
,
274 responseCallback
?: (payload
: JsonType
| string, requestPayload
: JsonType
) => Promise
<void>,
275 rejectCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
277 let messageToSend
: string;
279 switch (messageType
) {
281 case MessageType
.CALL_MESSAGE
:
283 this.chargingStation
.requests
.set(messageId
, [
289 messageToSend
= JSON
.stringify([messageType
, messageId
, commandName
, messagePayload
]);
292 case MessageType
.CALL_RESULT_MESSAGE
:
294 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
]);
297 case MessageType
.CALL_ERROR_MESSAGE
:
298 // Build Error Message
299 messageToSend
= JSON
.stringify([
302 messagePayload
?.code
?? ErrorType
.GENERIC_ERROR
,
303 messagePayload
?.message
?? '',
304 messagePayload
?.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 commandName
: RequestCommand
| IncomingRequestCommand
,
325 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
328 this.chargingStation
.logPrefix() + ' Request command %s error: %j',
332 if (params
?.throwError
) {
337 // eslint-disable-next-line @typescript-eslint/no-unused-vars
338 public abstract requestHandler
<Request
extends JsonType
, Response
extends JsonType
>(
339 commandName
: RequestCommand
,
340 commandParams
?: JsonType
,
341 params
?: RequestParams
342 ): Promise
<Response
>;