1 import { 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 { EmptyObject
} from
'../../types/EmptyObject';
8 import { HandleErrorParams
} from
'../../types/Error';
9 import { 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 { 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
;
37 this.requestHandler
.bind(this);
38 this.sendResponse
.bind(this);
39 this.sendError
.bind(this);
44 public static getInstance
<T
extends OCPPRequestService
>(
45 this: new (ocppResponseService
: OCPPResponseService
) => T
,
46 ocppResponseService
: OCPPResponseService
48 if (!OCPPRequestService
.instance
) {
49 OCPPRequestService
.instance
= new this(ocppResponseService
);
51 return OCPPRequestService
.instance
as T
;
54 public async sendResponse(
55 chargingStation
: ChargingStation
,
57 messagePayload
: JsonType
,
58 commandName
: IncomingRequestCommand
59 ): Promise
<ResponseType
> {
61 // Send response message
62 return await this.internalSendMessage(
66 MessageType
.CALL_RESULT_MESSAGE
,
70 this.handleRequestError(chargingStation
, commandName
, error
as Error);
74 public async sendError(
75 chargingStation
: ChargingStation
,
78 commandName
: RequestCommand
| IncomingRequestCommand
79 ): Promise
<ResponseType
> {
82 return await this.internalSendMessage(
86 MessageType
.CALL_ERROR_MESSAGE
,
90 this.handleRequestError(chargingStation
, commandName
, error
as Error);
94 protected async sendMessage(
95 chargingStation
: ChargingStation
,
97 messagePayload
: JsonType
,
98 commandName
: RequestCommand
,
99 params
: RequestParams
= {
100 skipBufferingOnError
: false,
101 triggerMessage
: false,
103 ): Promise
<ResponseType
> {
105 return await this.internalSendMessage(
109 MessageType
.CALL_MESSAGE
,
114 this.handleRequestError(chargingStation
, commandName
, error
as Error, { throwError
: false });
118 protected validateRequestPayload
<T
extends JsonType
>(
119 chargingStation
: ChargingStation
,
120 commandName
: RequestCommand
,
121 schema
: JSONSchemaType
<T
>,
124 if (!chargingStation
.getPayloadSchemaValidation()) {
127 const validate
= this.ajv
.compile(schema
);
128 if (validate(payload
)) {
132 `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Request PDU is invalid: %j`,
136 OCPPServiceUtils
.ajvErrorsToErrorType(validate
.errors
),
137 'Request PDU is invalid',
139 JSON
.stringify(validate
.errors
, null, 2)
143 private async internalSendMessage(
144 chargingStation
: ChargingStation
,
146 messagePayload
: JsonType
| OCPPError
,
147 messageType
: MessageType
,
148 commandName
?: RequestCommand
| IncomingRequestCommand
,
149 params
: RequestParams
= {
150 skipBufferingOnError
: false,
151 triggerMessage
: false,
153 ): Promise
<ResponseType
> {
155 (chargingStation
.isInUnknownState() && commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
156 (!chargingStation
.getOcppStrictCompliance() && chargingStation
.isInUnknownState()) ||
157 chargingStation
.isInAcceptedState() ||
158 (chargingStation
.isInPendingState() &&
159 (params
.triggerMessage
|| messageType
=== MessageType
.CALL_RESULT_MESSAGE
))
161 // eslint-disable-next-line @typescript-eslint/no-this-alias
163 // Send a message through wsConnection
164 return Utils
.promiseWithTimeout(
165 new Promise((resolve
, reject
) => {
166 const messageToSend
= this.buildMessageToSend(
175 if (chargingStation
.getEnableStatistics()) {
176 chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, messageType
);
178 // Check if wsConnection opened
179 if (chargingStation
.isWebSocketConnectionOpened()) {
181 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
182 // FIXME: Handle sending error
183 chargingStation
.wsConnection
.send(messageToSend
);
184 PerformanceStatistics
.endMeasure(commandName
, beginId
);
186 `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
188 )} payload: ${messageToSend}`
190 } else if (!params
.skipBufferingOnError
) {
192 chargingStation
.bufferMessage(messageToSend
);
193 const ocppError
= new OCPPError(
194 ErrorType
.GENERIC_ERROR
,
195 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
197 (messagePayload
as JsonObject
)?.details
?? {}
199 if (messageType
=== MessageType
.CALL_MESSAGE
) {
200 // Reject it but keep the request in the cache
201 return reject(ocppError
);
203 return errorCallback(ocppError
, false);
206 return errorCallback(
208 ErrorType
.GENERIC_ERROR
,
209 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
211 (messagePayload
as JsonObject
)?.details
?? {}
217 if (messageType
!== MessageType
.CALL_MESSAGE
) {
219 return resolve(messagePayload
);
223 * Function that will receive the request's response
226 * @param requestPayload
228 async function responseCallback(
230 requestPayload
: JsonType
232 if (chargingStation
.getEnableStatistics()) {
233 chargingStation
.performanceStatistics
.addRequestStatistic(
235 MessageType
.CALL_RESULT_MESSAGE
238 // Handle the request's response
240 await self.ocppResponseService
.responseHandler(
242 commandName
as RequestCommand
,
250 chargingStation
.requests
.delete(messageId
);
255 * Function that will receive the request's error response
258 * @param requestStatistic
260 function errorCallback(error
: OCPPError
, requestStatistic
= true): void {
261 if (requestStatistic
&& chargingStation
.getEnableStatistics()) {
262 chargingStation
.performanceStatistics
.addRequestStatistic(
264 MessageType
.CALL_ERROR_MESSAGE
268 `${chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
273 chargingStation
.requests
.delete(messageId
);
277 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
279 ErrorType
.GENERIC_ERROR
,
280 `Timeout for message id '${messageId}'`,
282 (messagePayload
as JsonObject
)?.details
?? {}
285 messageType
=== MessageType
.CALL_MESSAGE
&& chargingStation
.requests
.delete(messageId
);
290 ErrorType
.SECURITY_ERROR
,
291 `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
296 private buildMessageToSend(
297 chargingStation
: ChargingStation
,
299 messagePayload
: JsonType
| OCPPError
,
300 messageType
: MessageType
,
301 commandName
?: RequestCommand
| IncomingRequestCommand
,
302 responseCallback
?: (payload
: JsonType
, requestPayload
: JsonType
) => Promise
<void>,
303 errorCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
305 let messageToSend
: string;
307 switch (messageType
) {
309 case MessageType
.CALL_MESSAGE
:
311 chargingStation
.requests
.set(messageId
, [
315 messagePayload
as JsonType
,
317 messageToSend
= JSON
.stringify([
322 ] as OutgoingRequest
);
325 case MessageType
.CALL_RESULT_MESSAGE
:
327 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
] as Response
);
330 case MessageType
.CALL_ERROR_MESSAGE
:
331 // Build Error Message
332 messageToSend
= JSON
.stringify([
335 (messagePayload
as OCPPError
)?.code
?? ErrorType
.GENERIC_ERROR
,
336 (messagePayload
as OCPPError
)?.message
?? '',
337 (messagePayload
as OCPPError
)?.details
?? { commandName
},
341 return messageToSend
;
344 private getMessageTypeString(messageType
: MessageType
): string {
345 switch (messageType
) {
346 case MessageType
.CALL_MESSAGE
:
348 case MessageType
.CALL_RESULT_MESSAGE
:
350 case MessageType
.CALL_ERROR_MESSAGE
:
355 private handleRequestError(
356 chargingStation
: ChargingStation
,
357 commandName
: RequestCommand
| IncomingRequestCommand
,
359 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
361 logger
.error(chargingStation
.logPrefix() + ' Request command %s error: %j', commandName
, error
);
362 if (params
?.throwError
) {
367 // eslint-disable-next-line @typescript-eslint/no-unused-vars
368 public abstract requestHandler
<Request
extends JsonType
, Response
extends JsonType
>(
369 chargingStation
: ChargingStation
,
370 commandName
: RequestCommand
,
371 commandParams
?: JsonType
,
372 params
?: RequestParams
373 ): Promise
<Response
>;