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';
26 const moduleName
= 'OCPPRequestService';
28 export default abstract class OCPPRequestService
{
29 private static instance
: OCPPRequestService
| null = null;
32 private readonly ocppResponseService
: OCPPResponseService
;
34 protected constructor(ocppResponseService
: OCPPResponseService
) {
35 this.ocppResponseService
= ocppResponseService
;
36 this.requestHandler
.bind(this);
37 this.sendResponse
.bind(this);
38 this.sendError
.bind(this);
43 public static getInstance
<T
extends OCPPRequestService
>(
44 this: new (ocppResponseService
: OCPPResponseService
) => T
,
45 ocppResponseService
: OCPPResponseService
47 if (!OCPPRequestService
.instance
) {
48 OCPPRequestService
.instance
= new this(ocppResponseService
);
50 return OCPPRequestService
.instance
as T
;
53 public async sendResponse(
54 chargingStation
: ChargingStation
,
56 messagePayload
: JsonType
,
57 commandName
: IncomingRequestCommand
58 ): Promise
<ResponseType
> {
60 // Send response message
61 return await this.internalSendMessage(
65 MessageType
.CALL_RESULT_MESSAGE
,
69 this.handleRequestError(chargingStation
, commandName
, error
as Error);
73 public async sendError(
74 chargingStation
: ChargingStation
,
77 commandName
: RequestCommand
| IncomingRequestCommand
78 ): Promise
<ResponseType
> {
81 return await this.internalSendMessage(
85 MessageType
.CALL_ERROR_MESSAGE
,
89 this.handleRequestError(chargingStation
, commandName
, error
as Error);
93 protected async sendMessage(
94 chargingStation
: ChargingStation
,
96 messagePayload
: JsonType
,
97 commandName
: RequestCommand
,
98 params
: RequestParams
= {
99 skipBufferingOnError
: false,
100 triggerMessage
: false,
102 ): Promise
<ResponseType
> {
104 return await this.internalSendMessage(
108 MessageType
.CALL_MESSAGE
,
113 this.handleRequestError(chargingStation
, commandName
, error
as Error, { throwError
: false });
117 protected validateRequestPayload
<T
extends JsonType
>(
118 chargingStation
: ChargingStation
,
119 commandName
: RequestCommand
,
120 schema
: JSONSchemaType
<T
>,
123 if (!chargingStation
.getPayloadSchemaValidation()) {
126 const validate
= this.ajv
.compile(schema
);
127 if (validate(payload
)) {
131 `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Request PDU is invalid: %j`,
135 ErrorType
.FORMATION_VIOLATION
,
136 'Request PDU is invalid',
138 JSON
.stringify(validate
.errors
, null, 2)
142 private async internalSendMessage(
143 chargingStation
: ChargingStation
,
145 messagePayload
: JsonType
| OCPPError
,
146 messageType
: MessageType
,
147 commandName
?: RequestCommand
| IncomingRequestCommand
,
148 params
: RequestParams
= {
149 skipBufferingOnError
: false,
150 triggerMessage
: false,
152 ): Promise
<ResponseType
> {
154 (chargingStation
.isInUnknownState() && commandName
=== RequestCommand
.BOOT_NOTIFICATION
) ||
155 (!chargingStation
.getOcppStrictCompliance() && chargingStation
.isInUnknownState()) ||
156 chargingStation
.isInAcceptedState() ||
157 (chargingStation
.isInPendingState() &&
158 (params
.triggerMessage
|| messageType
=== MessageType
.CALL_RESULT_MESSAGE
))
160 // eslint-disable-next-line @typescript-eslint/no-this-alias
162 // Send a message through wsConnection
163 return Utils
.promiseWithTimeout(
164 new Promise((resolve
, reject
) => {
165 const messageToSend
= this.buildMessageToSend(
174 if (chargingStation
.getEnableStatistics()) {
175 chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, messageType
);
177 // Check if wsConnection opened
178 if (chargingStation
.isWebSocketConnectionOpened()) {
180 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
181 // FIXME: Handle sending error
182 chargingStation
.wsConnection
.send(messageToSend
);
183 PerformanceStatistics
.endMeasure(commandName
, beginId
);
185 `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
187 )} payload: ${messageToSend}`
189 } else if (!params
.skipBufferingOnError
) {
191 chargingStation
.bufferMessage(messageToSend
);
192 const ocppError
= new OCPPError(
193 ErrorType
.GENERIC_ERROR
,
194 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
196 (messagePayload
as JsonObject
)?.details
?? {}
198 if (messageType
=== MessageType
.CALL_MESSAGE
) {
199 // Reject it but keep the request in the cache
200 return reject(ocppError
);
202 return errorCallback(ocppError
, false);
205 return errorCallback(
207 ErrorType
.GENERIC_ERROR
,
208 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
210 (messagePayload
as JsonObject
)?.details
?? {}
216 if (messageType
!== MessageType
.CALL_MESSAGE
) {
218 return resolve(messagePayload
);
222 * Function that will receive the request's response
225 * @param requestPayload
227 async function responseCallback(
229 requestPayload
: JsonType
231 if (chargingStation
.getEnableStatistics()) {
232 chargingStation
.performanceStatistics
.addRequestStatistic(
234 MessageType
.CALL_RESULT_MESSAGE
237 // Handle the request's response
239 await self.ocppResponseService
.responseHandler(
241 commandName
as RequestCommand
,
249 chargingStation
.requests
.delete(messageId
);
254 * Function that will receive the request's error response
257 * @param requestStatistic
259 function errorCallback(error
: OCPPError
, requestStatistic
= true): void {
260 if (requestStatistic
&& chargingStation
.getEnableStatistics()) {
261 chargingStation
.performanceStatistics
.addRequestStatistic(
263 MessageType
.CALL_ERROR_MESSAGE
267 `${chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
272 chargingStation
.requests
.delete(messageId
);
276 Constants
.OCPP_WEBSOCKET_TIMEOUT
,
278 ErrorType
.GENERIC_ERROR
,
279 `Timeout for message id '${messageId}'`,
281 (messagePayload
as JsonObject
)?.details
?? {}
284 messageType
=== MessageType
.CALL_MESSAGE
&& chargingStation
.requests
.delete(messageId
);
289 ErrorType
.SECURITY_ERROR
,
290 `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
295 private buildMessageToSend(
296 chargingStation
: ChargingStation
,
298 messagePayload
: JsonType
| OCPPError
,
299 messageType
: MessageType
,
300 commandName
?: RequestCommand
| IncomingRequestCommand
,
301 responseCallback
?: (payload
: JsonType
, requestPayload
: JsonType
) => Promise
<void>,
302 errorCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void
304 let messageToSend
: string;
306 switch (messageType
) {
308 case MessageType
.CALL_MESSAGE
:
310 chargingStation
.requests
.set(messageId
, [
314 messagePayload
as JsonType
,
316 messageToSend
= JSON
.stringify([
321 ] as OutgoingRequest
);
324 case MessageType
.CALL_RESULT_MESSAGE
:
326 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
] as Response
);
329 case MessageType
.CALL_ERROR_MESSAGE
:
330 // Build Error Message
331 messageToSend
= JSON
.stringify([
334 (messagePayload
as OCPPError
)?.code
?? ErrorType
.GENERIC_ERROR
,
335 (messagePayload
as OCPPError
)?.message
?? '',
336 (messagePayload
as OCPPError
)?.details
?? { commandName
},
340 return messageToSend
;
343 private getMessageTypeString(messageType
: MessageType
): string {
344 switch (messageType
) {
345 case MessageType
.CALL_MESSAGE
:
347 case MessageType
.CALL_RESULT_MESSAGE
:
349 case MessageType
.CALL_ERROR_MESSAGE
:
354 private handleRequestError(
355 chargingStation
: ChargingStation
,
356 commandName
: RequestCommand
| IncomingRequestCommand
,
358 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
360 logger
.error(chargingStation
.logPrefix() + ' Request command %s error: %j', commandName
, error
);
361 if (params
?.throwError
) {
366 // eslint-disable-next-line @typescript-eslint/no-unused-vars
367 public abstract requestHandler
<Request
extends JsonType
, Response
extends JsonType
>(
368 chargingStation
: ChargingStation
,
369 commandName
: RequestCommand
,
370 commandParams
?: JsonType
,
371 params
?: RequestParams
372 ): Promise
<Response
>;