Bump @types/node from 18.7.5 to 18.7.6 (#134)
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPRequestService.ts
CommitLineData
8114d10e
JB
1import OCPPError from '../../exception/OCPPError';
2import PerformanceStatistics from '../../performance/PerformanceStatistics';
3import { EmptyObject } from '../../types/EmptyObject';
4import { HandleErrorParams } from '../../types/Error';
5import { JsonObject, JsonType } from '../../types/JsonType';
6import { ErrorType } from '../../types/ocpp/ErrorType';
7import { MessageType } from '../../types/ocpp/MessageType';
e7aeea18 8import {
e7aeea18 9 IncomingRequestCommand,
b3ec7bc1 10 OutgoingRequest,
e7aeea18 11 RequestCommand,
be9b0d50 12 RequestParams,
e7aeea18 13 ResponseType,
e7aeea18 14} from '../../types/ocpp/Requests';
8114d10e 15import { ErrorResponse, Response } from '../../types/ocpp/Responses';
c0560973 16import Constants from '../../utils/Constants';
9f2e3130 17import logger from '../../utils/Logger';
8114d10e
JB
18import Utils from '../../utils/Utils';
19import type ChargingStation from '../ChargingStation';
20import type OCPPResponseService from './OCPPResponseService';
c0560973
JB
21
22export default abstract class OCPPRequestService {
08f130a0 23 private static instance: OCPPRequestService | null = null;
10068088 24
9f2e3130 25 private readonly ocppResponseService: OCPPResponseService;
c0560973 26
08f130a0 27 protected constructor(ocppResponseService: OCPPResponseService) {
c0560973 28 this.ocppResponseService = ocppResponseService;
f7f98c68 29 this.requestHandler.bind(this);
c75a6675 30 this.sendResponse.bind(this);
54ce9b7d 31 this.sendError.bind(this);
c0560973
JB
32 }
33
e7aeea18 34 public static getInstance<T extends OCPPRequestService>(
08f130a0 35 this: new (ocppResponseService: OCPPResponseService) => T,
e7aeea18
JB
36 ocppResponseService: OCPPResponseService
37 ): T {
08f130a0
JB
38 if (!OCPPRequestService.instance) {
39 OCPPRequestService.instance = new this(ocppResponseService);
9f2e3130 40 }
08f130a0 41 return OCPPRequestService.instance as T;
9f2e3130
JB
42 }
43
c75a6675 44 public async sendResponse(
08f130a0 45 chargingStation: ChargingStation,
e7aeea18 46 messageId: string,
5cc4b63b 47 messagePayload: JsonType,
e7aeea18
JB
48 commandName: IncomingRequestCommand
49 ): Promise<ResponseType> {
5e0c67e8 50 try {
c75a6675 51 // Send response message
e7aeea18 52 return await this.internalSendMessage(
08f130a0 53 chargingStation,
e7aeea18
JB
54 messageId,
55 messagePayload,
56 MessageType.CALL_RESULT_MESSAGE,
57 commandName
58 );
5e0c67e8 59 } catch (error) {
08f130a0 60 this.handleRequestError(chargingStation, commandName, error as Error);
5e0c67e8
JB
61 }
62 }
63
e7aeea18 64 public async sendError(
08f130a0 65 chargingStation: ChargingStation,
e7aeea18
JB
66 messageId: string,
67 ocppError: OCPPError,
b3ec7bc1 68 commandName: RequestCommand | IncomingRequestCommand
e7aeea18 69 ): Promise<ResponseType> {
5e0c67e8
JB
70 try {
71 // Send error message
e7aeea18 72 return await this.internalSendMessage(
08f130a0 73 chargingStation,
e7aeea18
JB
74 messageId,
75 ocppError,
76 MessageType.CALL_ERROR_MESSAGE,
77 commandName
78 );
5e0c67e8 79 } catch (error) {
08f130a0 80 this.handleRequestError(chargingStation, commandName, error as Error);
5e0c67e8
JB
81 }
82 }
83
e7aeea18 84 protected async sendMessage(
08f130a0 85 chargingStation: ChargingStation,
e7aeea18 86 messageId: string,
5cc4b63b 87 messagePayload: JsonType,
e7aeea18 88 commandName: RequestCommand,
be9b0d50 89 params: RequestParams = {
e7aeea18
JB
90 skipBufferingOnError: false,
91 triggerMessage: false,
92 }
93 ): Promise<ResponseType> {
5e0c67e8 94 try {
e7aeea18 95 return await this.internalSendMessage(
08f130a0 96 chargingStation,
e7aeea18
JB
97 messageId,
98 messagePayload,
99 MessageType.CALL_MESSAGE,
100 commandName,
101 params
102 );
5e0c67e8 103 } catch (error) {
08f130a0 104 this.handleRequestError(chargingStation, commandName, error as Error, { throwError: false });
5e0c67e8
JB
105 }
106 }
107
e7aeea18 108 private async internalSendMessage(
08f130a0 109 chargingStation: ChargingStation,
e7aeea18 110 messageId: string,
5cc4b63b 111 messagePayload: JsonType | OCPPError,
e7aeea18
JB
112 messageType: MessageType,
113 commandName?: RequestCommand | IncomingRequestCommand,
be9b0d50 114 params: RequestParams = {
e7aeea18
JB
115 skipBufferingOnError: false,
116 triggerMessage: false,
117 }
118 ): Promise<ResponseType> {
119 if (
08f130a0
JB
120 (chargingStation.isInUnknownState() && commandName === RequestCommand.BOOT_NOTIFICATION) ||
121 (!chargingStation.getOcppStrictCompliance() && chargingStation.isInUnknownState()) ||
122 chargingStation.isInAcceptedState() ||
123 (chargingStation.isInPendingState() &&
37a48c48 124 (params.triggerMessage || messageType === MessageType.CALL_RESULT_MESSAGE))
e7aeea18 125 ) {
caad9d6b
JB
126 // eslint-disable-next-line @typescript-eslint/no-this-alias
127 const self = this;
128 // Send a message through wsConnection
e7aeea18
JB
129 return Utils.promiseWithTimeout(
130 new Promise((resolve, reject) => {
131 const messageToSend = this.buildMessageToSend(
08f130a0 132 chargingStation,
e7aeea18
JB
133 messageId,
134 messagePayload,
135 messageType,
136 commandName,
137 responseCallback,
a2d1c0f1 138 errorCallback
e7aeea18 139 );
08f130a0
JB
140 if (chargingStation.getEnableStatistics()) {
141 chargingStation.performanceStatistics.addRequestStatistic(commandName, messageType);
caad9d6b 142 }
e7aeea18 143 // Check if wsConnection opened
08f130a0 144 if (chargingStation.isWebSocketConnectionOpened()) {
e7aeea18
JB
145 // Yes: Send Message
146 const beginId = PerformanceStatistics.beginMeasure(commandName);
147 // FIXME: Handle sending error
08f130a0 148 chargingStation.wsConnection.send(messageToSend);
e7aeea18 149 PerformanceStatistics.endMeasure(commandName, beginId);
6f35d2da 150 logger.debug(
08f130a0 151 `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString(
9a3b8d9f
JB
152 messageType
153 )} payload: ${messageToSend}`
6f35d2da 154 );
e7aeea18
JB
155 } else if (!params.skipBufferingOnError) {
156 // Buffer it
08f130a0 157 chargingStation.bufferMessage(messageToSend);
e7aeea18
JB
158 const ocppError = new OCPPError(
159 ErrorType.GENERIC_ERROR,
160 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
161 commandName,
5cc4b63b 162 (messagePayload as JsonObject)?.details ?? {}
e7aeea18
JB
163 );
164 if (messageType === MessageType.CALL_MESSAGE) {
165 // Reject it but keep the request in the cache
166 return reject(ocppError);
167 }
a2d1c0f1 168 return errorCallback(ocppError, false);
e7aeea18
JB
169 } else {
170 // Reject it
a2d1c0f1 171 return errorCallback(
e7aeea18
JB
172 new OCPPError(
173 ErrorType.GENERIC_ERROR,
174 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
175 commandName,
5cc4b63b 176 (messagePayload as JsonObject)?.details ?? {}
e7aeea18
JB
177 ),
178 false
179 );
caad9d6b 180 }
e7aeea18
JB
181 // Response?
182 if (messageType !== MessageType.CALL_MESSAGE) {
183 // Yes: send Ok
184 return resolve(messagePayload);
185 }
186
187 /**
188 * Function that will receive the request's response
189 *
190 * @param payload
191 * @param requestPayload
192 */
193 async function responseCallback(
5cc4b63b
JB
194 payload: JsonType,
195 requestPayload: JsonType
e7aeea18 196 ): Promise<void> {
08f130a0
JB
197 if (chargingStation.getEnableStatistics()) {
198 chargingStation.performanceStatistics.addRequestStatistic(
e7aeea18
JB
199 commandName,
200 MessageType.CALL_RESULT_MESSAGE
201 );
202 }
203 // Handle the request's response
204 try {
f7f98c68 205 await self.ocppResponseService.responseHandler(
08f130a0 206 chargingStation,
e7aeea18
JB
207 commandName as RequestCommand,
208 payload,
209 requestPayload
210 );
211 resolve(payload);
212 } catch (error) {
213 reject(error);
e7aeea18 214 } finally {
08f130a0 215 chargingStation.requests.delete(messageId);
e7aeea18 216 }
caad9d6b 217 }
caad9d6b 218
e7aeea18
JB
219 /**
220 * Function that will receive the request's error response
221 *
222 * @param error
223 * @param requestStatistic
224 */
a2d1c0f1 225 function errorCallback(error: OCPPError, requestStatistic = true): void {
08f130a0
JB
226 if (requestStatistic && chargingStation.getEnableStatistics()) {
227 chargingStation.performanceStatistics.addRequestStatistic(
e7aeea18
JB
228 commandName,
229 MessageType.CALL_ERROR_MESSAGE
230 );
231 }
232 logger.error(
08f130a0 233 `${chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
e7aeea18
JB
234 error,
235 commandName,
236 messagePayload
237 );
08f130a0 238 chargingStation.requests.delete(messageId);
e7aeea18 239 reject(error);
caad9d6b 240 }
e7aeea18
JB
241 }),
242 Constants.OCPP_WEBSOCKET_TIMEOUT,
243 new OCPPError(
244 ErrorType.GENERIC_ERROR,
245 `Timeout for message id '${messageId}'`,
246 commandName,
5cc4b63b 247 (messagePayload as JsonObject)?.details ?? {}
e7aeea18
JB
248 ),
249 () => {
08f130a0 250 messageType === MessageType.CALL_MESSAGE && chargingStation.requests.delete(messageId);
caad9d6b 251 }
e7aeea18 252 );
caad9d6b 253 }
e7aeea18
JB
254 throw new OCPPError(
255 ErrorType.SECURITY_ERROR,
08f130a0 256 `Cannot send command ${commandName} payload when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
e7aeea18
JB
257 commandName
258 );
c0560973
JB
259 }
260
e7aeea18 261 private buildMessageToSend(
08f130a0 262 chargingStation: ChargingStation,
e7aeea18 263 messageId: string,
5cc4b63b 264 messagePayload: JsonType | OCPPError,
e7aeea18
JB
265 messageType: MessageType,
266 commandName?: RequestCommand | IncomingRequestCommand,
5cc4b63b 267 responseCallback?: (payload: JsonType, requestPayload: JsonType) => Promise<void>,
a2d1c0f1 268 errorCallback?: (error: OCPPError, requestStatistic?: boolean) => void
e7aeea18 269 ): string {
e7accadb
JB
270 let messageToSend: string;
271 // Type of message
272 switch (messageType) {
273 // Request
274 case MessageType.CALL_MESSAGE:
275 // Build request
08f130a0 276 chargingStation.requests.set(messageId, [
e7aeea18 277 responseCallback,
a2d1c0f1 278 errorCallback,
e7aeea18 279 commandName,
5cc4b63b 280 messagePayload as JsonType,
e7aeea18 281 ]);
b3ec7bc1
JB
282 messageToSend = JSON.stringify([
283 messageType,
284 messageId,
285 commandName,
286 messagePayload,
287 ] as OutgoingRequest);
e7accadb
JB
288 break;
289 // Response
290 case MessageType.CALL_RESULT_MESSAGE:
291 // Build response
b3ec7bc1 292 messageToSend = JSON.stringify([messageType, messageId, messagePayload] as Response);
e7accadb
JB
293 break;
294 // Error Message
295 case MessageType.CALL_ERROR_MESSAGE:
296 // Build Error Message
e7aeea18
JB
297 messageToSend = JSON.stringify([
298 messageType,
299 messageId,
b3ec7bc1
JB
300 (messagePayload as OCPPError)?.code ?? ErrorType.GENERIC_ERROR,
301 (messagePayload as OCPPError)?.message ?? '',
302 (messagePayload as OCPPError)?.details ?? { commandName },
303 ] as ErrorResponse);
e7accadb
JB
304 break;
305 }
306 return messageToSend;
307 }
308
9a3b8d9f
JB
309 private getMessageTypeString(messageType: MessageType): string {
310 switch (messageType) {
311 case MessageType.CALL_MESSAGE:
312 return 'request';
313 case MessageType.CALL_RESULT_MESSAGE:
314 return 'response';
315 case MessageType.CALL_ERROR_MESSAGE:
316 return 'error';
317 }
318 }
319
e7aeea18 320 private handleRequestError(
08f130a0 321 chargingStation: ChargingStation,
e7aeea18
JB
322 commandName: RequestCommand | IncomingRequestCommand,
323 error: Error,
324 params: HandleErrorParams<EmptyObject> = { throwError: true }
325 ): void {
08f130a0 326 logger.error(chargingStation.logPrefix() + ' Request command %s error: %j', commandName, error);
e0a50bcd
JB
327 if (params?.throwError) {
328 throw error;
329 }
5e0c67e8
JB
330 }
331
ef6fa3fb 332 // eslint-disable-next-line @typescript-eslint/no-unused-vars
5cc4b63b 333 public abstract requestHandler<Request extends JsonType, Response extends JsonType>(
08f130a0 334 chargingStation: ChargingStation,
94a464f9 335 commandName: RequestCommand,
5cc4b63b 336 commandParams?: JsonType,
be9b0d50 337 params?: RequestParams
f22266fd 338 ): Promise<Response>;
c0560973 339}