3 IncomingRequestCommand
,
7 } from
'../../types/ocpp/Requests';
9 StartTransactionResponse
,
10 StopTransactionReason
,
11 StopTransactionResponse
,
12 } from
'../../types/ocpp/Transaction';
14 import type ChargingStation from
'../ChargingStation';
15 import Constants from
'../../utils/Constants';
16 import { EmptyObject
} from
'../../types/EmptyObject';
17 import { ErrorType
} from
'../../types/ocpp/ErrorType';
18 import { HandleErrorParams
} from
'../../types/Error';
19 import { JsonType
} from
'../../types/JsonType';
20 import { MessageType
} from
'../../types/ocpp/MessageType';
21 import { MeterValue
} from
'../../types/ocpp/MeterValues';
22 import OCPPError from
'../../exception/OCPPError';
23 import type OCPPResponseService from
'./OCPPResponseService';
24 import PerformanceStatistics from
'../../performance/PerformanceStatistics';
25 import Utils from
'../../utils/Utils';
26 import logger from
'../../utils/Logger';
28 export default abstract class OCPPRequestService
{
29 private static readonly instances
: Map
<string, OCPPRequestService
> = new Map
<
34 protected readonly chargingStation
: ChargingStation
;
35 private readonly ocppResponseService
: OCPPResponseService
;
37 protected constructor(
38 chargingStation
: ChargingStation
,
39 ocppResponseService
: OCPPResponseService
41 this.chargingStation
= chargingStation
;
42 this.ocppResponseService
= ocppResponseService
;
43 this.sendMessageHandler
.bind(this);
46 public static getInstance
<T
extends OCPPRequestService
>(
47 this: new (chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) => T
,
48 chargingStation
: ChargingStation
,
49 ocppResponseService
: OCPPResponseService
51 if (!OCPPRequestService
.instances
.has(chargingStation
.id
)) {
52 OCPPRequestService
.instances
.set(
54 new this(chargingStation
, ocppResponseService
)
57 return OCPPRequestService
.instances
.get(chargingStation
.id
) as T
;
60 public async sendResult(
62 messagePayload
: JsonType
,
63 commandName
: IncomingRequestCommand
64 ): Promise
<ResponseType
> {
66 // Send result message
67 return await this.internalSendMessage(
70 MessageType
.CALL_RESULT_MESSAGE
,
74 this.handleRequestError(commandName
, error
as Error);
78 public async sendError(
81 commandName
: IncomingRequestCommand
82 ): Promise
<ResponseType
> {
85 return await this.internalSendMessage(
88 MessageType
.CALL_ERROR_MESSAGE
,
92 this.handleRequestError(commandName
, error
as Error);
96 protected async sendMessage(
98 messagePayload
: JsonType
,
99 commandName
: RequestCommand
,
100 params
: SendParams
= {
101 skipBufferingOnError
: false,
102 triggerMessage
: false,
104 ): Promise
<ResponseType
> {
106 return await this.internalSendMessage(
109 MessageType
.CALL_MESSAGE
,
114 this.handleRequestError(commandName
, error
as Error, { throwError
: false });
118 private async internalSendMessage(
120 messagePayload
: JsonType
| OCPPError
,
121 messageType
: MessageType
,
122 commandName
?: RequestCommand
| IncomingRequestCommand
,
123 params
: SendParams
= {
124 skipBufferingOnError
: false,
125 triggerMessage
: false,
127 ): Promise
<ResponseType
> {
129 (this.chargingStation
.isInUnknownState() &&
130 commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
131 (!this.chargingStation
.getOcppStrictCompliance() &&
132 this.chargingStation
.isInUnknownState()) ||
133 this.chargingStation
.isInAcceptedState() ||
134 (this.chargingStation
.isInPendingState() && params
.triggerMessage
)
136 // eslint-disable-next-line @typescript-eslint/no-this-alias
138 // Send a message through wsConnection
139 return Utils
.promiseWithTimeout(
140 new Promise((resolve
, reject
) => {
141 const messageToSend
= this.buildMessageToSend(
149 if (this.chargingStation
.getEnableStatistics()) {
150 this.chargingStation
.performanceStatistics
.addRequestStatistic(
155 // Check if wsConnection opened
156 if (this.chargingStation
.isWebSocketConnectionOpened()) {
158 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
159 // FIXME: Handle sending error
160 this.chargingStation
.wsConnection
.send(messageToSend
);
161 PerformanceStatistics
.endMeasure(commandName
, beginId
);
162 } else if (!params
.skipBufferingOnError
) {
164 this.chargingStation
.bufferMessage(messageToSend
);
165 const ocppError
= new OCPPError(
166 ErrorType
.GENERIC_ERROR
,
167 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
169 (messagePayload
?.details
as JsonType
) ?? {}
171 if (messageType
=== MessageType
.CALL_MESSAGE
) {
172 // Reject it but keep the request in the cache
173 return reject(ocppError
);
175 return rejectCallback(ocppError
, false);
178 return rejectCallback(
180 ErrorType
.GENERIC_ERROR
,
181 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
183 (messagePayload
?.details
as JsonType
) ?? {}
189 if (messageType
!== MessageType
.CALL_MESSAGE
) {
191 return resolve(messagePayload
);
195 * Function that will receive the request's response
198 * @param requestPayload
200 async function responseCallback(
201 payload
: JsonType
| string,
202 requestPayload
: JsonType
204 if (self.chargingStation
.getEnableStatistics()) {
205 self.chargingStation
.performanceStatistics
.addRequestStatistic(
207 MessageType
.CALL_RESULT_MESSAGE
210 // Handle the request's response
212 await self.ocppResponseService
.handleResponse(
213 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 handleRequestError(
312 commandName
: RequestCommand
| IncomingRequestCommand
,
314 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
317 this.chargingStation
.logPrefix() + ' Request command %s error: %j',
321 if (params
?.throwError
) {
326 public abstract sendMessageHandler(
327 commandName
: RequestCommand
,
328 commandParams
?: JsonType
,
330 ): Promise
<ResponseType
>;
332 public abstract sendStartTransaction(
335 ): Promise
<StartTransactionResponse
>;
337 public abstract sendStopTransaction(
338 transactionId
: number,
341 reason
?: StopTransactionReason
342 ): Promise
<StopTransactionResponse
>;
344 public abstract sendMeterValues(
346 transactionId
: number,
350 public abstract sendTransactionBeginMeterValues(
352 transactionId
: number,
353 beginMeterValue
: MeterValue
356 public abstract sendTransactionEndMeterValues(
358 transactionId
: number,
359 endMeterValue
: MeterValue
362 public abstract sendDiagnosticsStatusNotification(
363 diagnosticsStatus
: DiagnosticsStatus