1 import type { JSONSchemaType
} from
'ajv';
2 import Ajv from
'ajv-draft-04';
3 import ajvFormats from
'ajv-formats';
5 import OCPPError from
'../../exception/OCPPError';
6 import PerformanceStatistics from
'../../performance/PerformanceStatistics';
7 import type { EmptyObject
} from
'../../types/EmptyObject';
8 import type { HandleErrorParams
} from
'../../types/Error';
9 import type { JsonObject
, JsonType
} from
'../../types/JsonType';
10 import { ErrorType
} from
'../../types/ocpp/ErrorType';
11 import { MessageType
} from
'../../types/ocpp/MessageType';
13 IncomingRequestCommand
,
18 } from
'../../types/ocpp/Requests';
19 import type { ErrorResponse
, Response
} from
'../../types/ocpp/Responses';
20 import Constants from
'../../utils/Constants';
21 import logger from
'../../utils/Logger';
22 import Utils from
'../../utils/Utils';
23 import type ChargingStation from
'../ChargingStation';
24 import type OCPPResponseService from
'./OCPPResponseService';
25 import { OCPPServiceUtils
} from
'./OCPPServiceUtils';
27 const moduleName
= 'OCPPRequestService';
29 export default abstract class OCPPRequestService
{
30 private static instance
: OCPPRequestService
| null = null;
33 private readonly ocppResponseService
: OCPPResponseService
;
35 protected constructor(ocppResponseService
: OCPPResponseService
) {
36 this.ocppResponseService
= ocppResponseService
;
39 this.requestHandler
.bind(this);
40 this.sendResponse
.bind(this);
41 this.sendError
.bind(this);
42 this.internalSendMessage
.bind(this);
43 this.buildMessageToSend
.bind(this);
44 this.validateRequestPayload
.bind(this);
47 public static getInstance
<T
extends OCPPRequestService
>(
48 this: new (ocppResponseService
: OCPPResponseService
) => T
,
49 ocppResponseService
: OCPPResponseService
51 if (OCPPRequestService
.instance
=== null) {
52 OCPPRequestService
.instance
= new this(ocppResponseService
);
54 return OCPPRequestService
.instance
as T
;
57 public async sendResponse(
58 chargingStation
: ChargingStation
,
60 messagePayload
: JsonType
,
61 commandName
: IncomingRequestCommand
62 ): Promise
<ResponseType
> {
64 // Send response message
65 return await this.internalSendMessage(
69 MessageType
.CALL_RESULT_MESSAGE
,
73 this.handleSendMessageError(chargingStation
, commandName
, error
as Error, {
79 public async sendError(
80 chargingStation
: ChargingStation
,
83 commandName
: RequestCommand
| IncomingRequestCommand
84 ): Promise
<ResponseType
> {
87 return await this.internalSendMessage(
91 MessageType
.CALL_ERROR_MESSAGE
,
95 this.handleSendMessageError(chargingStation
, commandName
, error
as Error);
99 protected async sendMessage(
100 chargingStation
: ChargingStation
,
102 messagePayload
: JsonType
,
103 commandName
: RequestCommand
,
104 params
: RequestParams
= {
105 skipBufferingOnError
: false,
106 triggerMessage
: false,
108 ): Promise
<ResponseType
> {
110 return await this.internalSendMessage(
114 MessageType
.CALL_MESSAGE
,
119 this.handleSendMessageError(chargingStation
, commandName
, error
as Error);
123 protected validateRequestPayload
<T
extends JsonType
>(
124 chargingStation
: ChargingStation
,
125 commandName
: RequestCommand
,
126 schema
: JSONSchemaType
<T
>,
129 if (chargingStation
.getPayloadSchemaValidation() === false) {
132 const validate
= this.ajv
.compile(schema
);
133 if (validate(payload
)) {
137 `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Request PDU is invalid: %j`,
140 // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
142 OCPPServiceUtils
.ajvErrorsToErrorType(validate
.errors
),
143 'Request PDU is invalid',
145 JSON
.stringify(validate
.errors
, null, 2)
149 private async internalSendMessage(
150 chargingStation
: ChargingStation
,
152 messagePayload
: JsonType
| OCPPError
,
153 messageType
: MessageType
,
154 commandName
?: RequestCommand
| IncomingRequestCommand
,
155 params
: RequestParams
= {
156 skipBufferingOnError
: false,
157 triggerMessage
: false,
159 ): Promise
<ResponseType
> {
161 (chargingStation
.isInUnknownState() && commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
162 (!chargingStation
.getOcppStrictCompliance() && chargingStation
.isInUnknownState()) ||
163 chargingStation
.isInAcceptedState() ||
164 (chargingStation
.isInPendingState() &&
165 (params
.triggerMessage
|| messageType
=== MessageType
.CALL_RESULT_MESSAGE
))
167 // eslint-disable-next-line @typescript-eslint/no-this-alias
169 // Send a message through wsConnection
170 return Utils
.promiseWithTimeout(
171 new Promise((resolve
, reject
) => {
172 const messageToSend
= this.buildMessageToSend(
181 if (chargingStation
.getEnableStatistics() === true) {
182 chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, messageType
);
184 // Check if wsConnection opened
185 if (chargingStation
.isWebSocketConnectionOpened() === true) {
187 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
188 // FIXME: Handle sending error
189 chargingStation
.wsConnection
.send(messageToSend
);
190 PerformanceStatistics
.endMeasure(commandName
, beginId
);
192 `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
194 )} payload: ${messageToSend}`
196 } else if (params
.skipBufferingOnError
=== false) {
198 chargingStation
.bufferMessage(messageToSend
);
199 const ocppError
= new OCPPError(
200 ErrorType
.GENERIC_ERROR
,
201 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
203 (messagePayload
as JsonObject
)?.details
?? {}
205 if (messageType
=== MessageType
.CALL_MESSAGE
) {
206 // Reject it but keep the request in the cache
207 return reject(ocppError
);
209 return errorCallback(ocppError
, false);
212 return errorCallback(
214 ErrorType
.GENERIC_ERROR
,
215 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
217 (messagePayload
as JsonObject
)?.details
?? {}
223 if (messageType
!== MessageType
.CALL_MESSAGE
) {
225 return resolve(messagePayload
);
229 * Function that will receive the request's response
232 * @param requestPayload
234 async function responseCallback(
236 requestPayload
: JsonType
238 if (chargingStation
.getEnableStatistics() === true) {
239 chargingStation
.performanceStatistics
.addRequestStatistic(
241 MessageType
.CALL_RESULT_MESSAGE
244 // Handle the request's response
246 await self.ocppResponseService
.responseHandler(
248 commandName
as RequestCommand
,
256 chargingStation
.requests
.delete(messageId
);
261 * Function that will receive the request's error response
264 * @param requestStatistic
266 function errorCallback(error
: OCPPError
, requestStatistic
= true): void {
267 if (requestStatistic
=== true && chargingStation
.getEnableStatistics() === true) {
268 chargingStation
.performanceStatistics
.addRequestStatistic(
270 MessageType
.CALL_ERROR_MESSAGE
274 `${chargingStation.logPrefix()} Error occurred when calling command ${commandName} with message data ${JSON.stringify(
279 chargingStation
.requests
.delete(messageId
);
283 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
285 ErrorType
.GENERIC_ERROR
,
286 `Timeout for message id '${messageId}'`,
288 (messagePayload
as JsonObject
)?.details
?? {}
291 messageType
=== MessageType
.CALL_MESSAGE
&& chargingStation
.requests
.delete(messageId
);
296 ErrorType
.SECURITY_ERROR
,
297 `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
302 private buildMessageToSend(
303 chargingStation
: ChargingStation
,
305 messagePayload
: JsonType
| OCPPError
,
306 messageType
: MessageType
,
307 commandName
?: RequestCommand
| IncomingRequestCommand
,
308 responseCallback
?: (payload
: JsonType
, requestPayload
: JsonType
) => Promise
<void>,
309 errorCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
311 let messageToSend
: string;
313 switch (messageType
) {
315 case MessageType
.CALL_MESSAGE
:
317 chargingStation
.requests
.set(messageId
, [
321 messagePayload
as JsonType
,
323 messageToSend
= JSON
.stringify([
328 ] as OutgoingRequest
);
331 case MessageType
.CALL_RESULT_MESSAGE
:
333 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
] as Response
);
336 case MessageType
.CALL_ERROR_MESSAGE
:
337 // Build Error Message
338 messageToSend
= JSON
.stringify([
341 (messagePayload
as OCPPError
)?.code
?? ErrorType
.GENERIC_ERROR
,
342 (messagePayload
as OCPPError
)?.message
?? '',
343 (messagePayload
as OCPPError
)?.details
?? { commandName
},
347 return messageToSend
;
350 private getMessageTypeString(messageType
: MessageType
): string {
351 switch (messageType
) {
352 case MessageType
.CALL_MESSAGE
:
354 case MessageType
.CALL_RESULT_MESSAGE
:
356 case MessageType
.CALL_ERROR_MESSAGE
:
361 private handleSendMessageError(
362 chargingStation
: ChargingStation
,
363 commandName
: RequestCommand
| IncomingRequestCommand
,
365 params
: HandleErrorParams
<EmptyObject
> = { throwError
: false }
367 logger
.error(`${chargingStation.logPrefix()} Request command '${commandName}' error:`, error
);
368 if (params
?.throwError
=== true) {
373 // eslint-disable-next-line @typescript-eslint/no-unused-vars
374 public abstract requestHandler
<RequestType
extends JsonType
, ResponseType
extends JsonType
>(
375 chargingStation
: ChargingStation
,
376 commandName
: RequestCommand
,
377 commandParams
?: JsonType
,
378 params
?: RequestParams
379 ): Promise
<ResponseType
>;