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.sendMessageHandler
.bind(this);
39 public static getInstance
<T
extends OCPPRequestService
>(
40 this: new (chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) => T
,
41 chargingStation
: ChargingStation
,
42 ocppResponseService
: OCPPResponseService
44 if (!OCPPRequestService
.instances
.has(chargingStation
.hashId
)) {
45 OCPPRequestService
.instances
.set(
46 chargingStation
.hashId
,
47 new this(chargingStation
, ocppResponseService
)
50 return OCPPRequestService
.instances
.get(chargingStation
.hashId
) as T
;
53 public async sendResult(
55 messagePayload
: JsonType
,
56 commandName
: IncomingRequestCommand
57 ): Promise
<ResponseType
> {
59 // Send result message
60 return await this.internalSendMessage(
63 MessageType
.CALL_RESULT_MESSAGE
,
67 this.handleRequestError(commandName
, error
as Error);
71 public async sendError(
74 commandName
: IncomingRequestCommand
75 ): Promise
<ResponseType
> {
78 return await this.internalSendMessage(
81 MessageType
.CALL_ERROR_MESSAGE
,
85 this.handleRequestError(commandName
, error
as Error);
89 protected async sendMessage(
91 messagePayload
: JsonType
,
92 commandName
: RequestCommand
,
93 params
: SendParams
= {
94 skipBufferingOnError
: false,
95 triggerMessage
: false,
97 ): Promise
<ResponseType
> {
99 return await this.internalSendMessage(
102 MessageType
.CALL_MESSAGE
,
107 this.handleRequestError(commandName
, error
as Error, { throwError
: false });
111 private async internalSendMessage(
113 messagePayload
: JsonType
| OCPPError
,
114 messageType
: MessageType
,
115 commandName
?: RequestCommand
| IncomingRequestCommand
,
116 params
: SendParams
= {
117 skipBufferingOnError
: false,
118 triggerMessage
: false,
120 ): Promise
<ResponseType
> {
122 (this.chargingStation
.isInUnknownState() &&
123 commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
124 (!this.chargingStation
.getOcppStrictCompliance() &&
125 this.chargingStation
.isInUnknownState()) ||
126 this.chargingStation
.isInAcceptedState() ||
127 (this.chargingStation
.isInPendingState() && params
.triggerMessage
)
129 // eslint-disable-next-line @typescript-eslint/no-this-alias
131 // Send a message through wsConnection
132 return Utils
.promiseWithTimeout(
133 new Promise((resolve
, reject
) => {
134 const messageToSend
= this.buildMessageToSend(
142 if (this.chargingStation
.getEnableStatistics()) {
143 this.chargingStation
.performanceStatistics
.addRequestStatistic(
148 // Check if wsConnection opened
149 if (this.chargingStation
.isWebSocketConnectionOpened()) {
151 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
152 // FIXME: Handle sending error
153 this.chargingStation
.wsConnection
.send(messageToSend
);
154 PerformanceStatistics
.endMeasure(commandName
, beginId
);
155 } else if (!params
.skipBufferingOnError
) {
157 this.chargingStation
.bufferMessage(messageToSend
);
158 const ocppError
= new OCPPError(
159 ErrorType
.GENERIC_ERROR
,
160 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
162 (messagePayload
?.details
as JsonType
) ?? {}
164 if (messageType
=== MessageType
.CALL_MESSAGE
) {
165 // Reject it but keep the request in the cache
166 return reject(ocppError
);
168 return rejectCallback(ocppError
, false);
171 return rejectCallback(
173 ErrorType
.GENERIC_ERROR
,
174 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
176 (messagePayload
?.details
as JsonType
) ?? {}
182 if (messageType
!== MessageType
.CALL_MESSAGE
) {
184 return resolve(messagePayload
);
188 * Function that will receive the request's response
191 * @param requestPayload
193 async function responseCallback(
194 payload
: JsonType
| string,
195 requestPayload
: JsonType
197 if (self.chargingStation
.getEnableStatistics()) {
198 self.chargingStation
.performanceStatistics
.addRequestStatistic(
200 MessageType
.CALL_RESULT_MESSAGE
203 // Handle the request's response
205 await self.ocppResponseService
.handleResponse(
206 commandName
as RequestCommand
,
214 self.chargingStation
.requests
.delete(messageId
);
219 * Function that will receive the request's error response
222 * @param requestStatistic
224 function rejectCallback(error
: OCPPError
, requestStatistic
= true): void {
225 if (requestStatistic
&& self.chargingStation
.getEnableStatistics()) {
226 self.chargingStation
.performanceStatistics
.addRequestStatistic(
228 MessageType
.CALL_ERROR_MESSAGE
232 `${self.chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
237 self.chargingStation
.requests
.delete(messageId
);
241 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
243 ErrorType
.GENERIC_ERROR
,
244 `Timeout for message id '${messageId}'`,
246 (messagePayload
?.details
as JsonType
) ?? {}
249 messageType
=== MessageType
.CALL_MESSAGE
&&
250 this.chargingStation
.requests
.delete(messageId
);
255 ErrorType
.SECURITY_ERROR
,
256 `Cannot send command ${commandName} payload when the charging station is in ${this.chargingStation.getRegistrationStatus()} state on the central server`,
261 private buildMessageToSend(
263 messagePayload
: JsonType
| OCPPError
,
264 messageType
: MessageType
,
265 commandName
?: RequestCommand
| IncomingRequestCommand
,
266 responseCallback
?: (payload
: JsonType
| string, requestPayload
: JsonType
) => Promise
<void>,
267 rejectCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
269 let messageToSend
: string;
271 switch (messageType
) {
273 case MessageType
.CALL_MESSAGE
:
275 this.chargingStation
.requests
.set(messageId
, [
281 messageToSend
= JSON
.stringify([messageType
, messageId
, commandName
, messagePayload
]);
284 case MessageType
.CALL_RESULT_MESSAGE
:
286 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
]);
289 case MessageType
.CALL_ERROR_MESSAGE
:
290 // Build Error Message
291 messageToSend
= JSON
.stringify([
294 messagePayload
?.code
?? ErrorType
.GENERIC_ERROR
,
295 messagePayload
?.message
?? '',
296 messagePayload
?.details
?? { commandName
},
300 return messageToSend
;
303 private handleRequestError(
304 commandName
: RequestCommand
| IncomingRequestCommand
,
306 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
309 this.chargingStation
.logPrefix() + ' Request command %s error: %j',
313 if (params
?.throwError
) {
318 public abstract sendMessageHandler
<Response
extends JsonType
>(
319 commandName
: RequestCommand
,
320 commandParams
?: JsonType
,
322 ): Promise
<Response
>;