Apply prettier formating
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPRequestService.ts
CommitLineData
e7aeea18
JB
1import {
2 AuthorizeResponse,
3 StartTransactionResponse,
4 StopTransactionReason,
5 StopTransactionResponse,
6} from '../../types/ocpp/Transaction';
7import {
8 DiagnosticsStatus,
9 IncomingRequestCommand,
10 RequestCommand,
11 ResponseType,
12 SendParams,
13} from '../../types/ocpp/Requests';
c0560973 14
efa43e52 15import { BootNotificationResponse } from '../../types/ocpp/Responses';
c0560973
JB
16import { ChargePointErrorCode } from '../../types/ocpp/ChargePointErrorCode';
17import { ChargePointStatus } from '../../types/ocpp/ChargePointStatus';
9f2e3130 18import type ChargingStation from '../ChargingStation';
c0560973 19import Constants from '../../utils/Constants';
47086c3a 20import { EmptyObject } from '../../types/EmptyObject';
c0560973 21import { ErrorType } from '../../types/ocpp/ErrorType';
e0a50bcd 22import { HandleErrorParams } from '../../types/Error';
d1888640 23import { JsonType } from '../../types/JsonType';
c0560973 24import { MessageType } from '../../types/ocpp/MessageType';
fd0c36fa 25import { MeterValue } from '../../types/ocpp/MeterValues';
e58068fd 26import OCPPError from '../../exception/OCPPError';
9f2e3130 27import type OCPPResponseService from './OCPPResponseService';
a6b3c6c3 28import PerformanceStatistics from '../../performance/PerformanceStatistics';
6d9abcc2 29import Utils from '../../utils/Utils';
9f2e3130 30import logger from '../../utils/Logger';
c0560973
JB
31
32export default abstract class OCPPRequestService {
e7aeea18
JB
33 private static readonly instances: Map<string, OCPPRequestService> = new Map<
34 string,
35 OCPPRequestService
36 >();
9f2e3130
JB
37 protected readonly chargingStation: ChargingStation;
38 private readonly ocppResponseService: OCPPResponseService;
c0560973 39
e7aeea18
JB
40 protected constructor(
41 chargingStation: ChargingStation,
42 ocppResponseService: OCPPResponseService
43 ) {
c0560973
JB
44 this.chargingStation = chargingStation;
45 this.ocppResponseService = ocppResponseService;
46 }
47
e7aeea18
JB
48 public static getInstance<T extends OCPPRequestService>(
49 this: new (chargingStation: ChargingStation, ocppResponseService: OCPPResponseService) => T,
50 chargingStation: ChargingStation,
51 ocppResponseService: OCPPResponseService
52 ): T {
9f2e3130 53 if (!OCPPRequestService.instances.has(chargingStation.id)) {
e7aeea18
JB
54 OCPPRequestService.instances.set(
55 chargingStation.id,
56 new this(chargingStation, ocppResponseService)
57 );
9f2e3130
JB
58 }
59 return OCPPRequestService.instances.get(chargingStation.id) as T;
60 }
61
e7aeea18
JB
62 public async sendResult(
63 messageId: string,
64 messagePayload: JsonType,
65 commandName: IncomingRequestCommand
66 ): Promise<ResponseType> {
5e0c67e8
JB
67 try {
68 // Send result message
e7aeea18
JB
69 return await this.internalSendMessage(
70 messageId,
71 messagePayload,
72 MessageType.CALL_RESULT_MESSAGE,
73 commandName
74 );
5e0c67e8
JB
75 } catch (error) {
76 this.handleRequestError(commandName, error as Error);
77 }
78 }
79
e7aeea18
JB
80 public async sendError(
81 messageId: string,
82 ocppError: OCPPError,
83 commandName: IncomingRequestCommand
84 ): Promise<ResponseType> {
5e0c67e8
JB
85 try {
86 // Send error message
e7aeea18
JB
87 return await this.internalSendMessage(
88 messageId,
89 ocppError,
90 MessageType.CALL_ERROR_MESSAGE,
91 commandName
92 );
5e0c67e8
JB
93 } catch (error) {
94 this.handleRequestError(commandName, error as Error);
95 }
96 }
97
e7aeea18
JB
98 protected async sendMessage(
99 messageId: string,
100 messagePayload: JsonType,
101 commandName: RequestCommand,
102 params: SendParams = {
103 skipBufferingOnError: false,
104 triggerMessage: false,
105 }
106 ): Promise<ResponseType> {
5e0c67e8 107 try {
e7aeea18
JB
108 return await this.internalSendMessage(
109 messageId,
110 messagePayload,
111 MessageType.CALL_MESSAGE,
112 commandName,
113 params
114 );
5e0c67e8 115 } catch (error) {
e0a50bcd 116 this.handleRequestError(commandName, error as Error, { throwError: false });
5e0c67e8
JB
117 }
118 }
119
e7aeea18
JB
120 private async internalSendMessage(
121 messageId: string,
122 messagePayload: JsonType | OCPPError,
123 messageType: MessageType,
124 commandName?: RequestCommand | IncomingRequestCommand,
125 params: SendParams = {
126 skipBufferingOnError: false,
127 triggerMessage: false,
128 }
129 ): Promise<ResponseType> {
130 if (
131 (this.chargingStation.isInUnknownState() &&
132 commandName === RequestCommand.BOOT_NOTIFICATION) ||
133 (!this.chargingStation.getOcppStrictCompliance() &&
134 this.chargingStation.isInUnknownState()) ||
135 this.chargingStation.isInAcceptedState() ||
136 (this.chargingStation.isInPendingState() && params.triggerMessage)
137 ) {
caad9d6b
JB
138 // eslint-disable-next-line @typescript-eslint/no-this-alias
139 const self = this;
140 // Send a message through wsConnection
e7aeea18
JB
141 return Utils.promiseWithTimeout(
142 new Promise((resolve, reject) => {
143 const messageToSend = this.buildMessageToSend(
144 messageId,
145 messagePayload,
146 messageType,
147 commandName,
148 responseCallback,
149 rejectCallback
150 );
151 if (this.chargingStation.getEnableStatistics()) {
152 this.chargingStation.performanceStatistics.addRequestStatistic(
153 commandName,
154 messageType
155 );
caad9d6b 156 }
e7aeea18
JB
157 // Check if wsConnection opened
158 if (this.chargingStation.isWebSocketConnectionOpened()) {
159 // Yes: Send Message
160 const beginId = PerformanceStatistics.beginMeasure(commandName);
161 // FIXME: Handle sending error
162 this.chargingStation.wsConnection.send(messageToSend);
163 PerformanceStatistics.endMeasure(commandName, beginId);
164 } else if (!params.skipBufferingOnError) {
165 // Buffer it
166 this.chargingStation.bufferMessage(messageToSend);
167 const ocppError = new OCPPError(
168 ErrorType.GENERIC_ERROR,
169 `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`,
170 commandName,
171 (messagePayload?.details as JsonType) ?? {}
172 );
173 if (messageType === MessageType.CALL_MESSAGE) {
174 // Reject it but keep the request in the cache
175 return reject(ocppError);
176 }
177 return rejectCallback(ocppError, false);
178 } else {
179 // Reject it
180 return rejectCallback(
181 new OCPPError(
182 ErrorType.GENERIC_ERROR,
183 `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`,
184 commandName,
185 (messagePayload?.details as JsonType) ?? {}
186 ),
187 false
188 );
caad9d6b 189 }
e7aeea18
JB
190 // Response?
191 if (messageType !== MessageType.CALL_MESSAGE) {
192 // Yes: send Ok
193 return resolve(messagePayload);
194 }
195
196 /**
197 * Function that will receive the request's response
198 *
199 * @param payload
200 * @param requestPayload
201 */
202 async function responseCallback(
203 payload: JsonType | string,
204 requestPayload: JsonType
205 ): Promise<void> {
206 if (self.chargingStation.getEnableStatistics()) {
207 self.chargingStation.performanceStatistics.addRequestStatistic(
208 commandName,
209 MessageType.CALL_RESULT_MESSAGE
210 );
211 }
212 // Handle the request's response
213 try {
214 await self.ocppResponseService.handleResponse(
215 commandName as RequestCommand,
216 payload,
217 requestPayload
218 );
219 resolve(payload);
220 } catch (error) {
221 reject(error);
222 throw error;
223 } finally {
224 self.chargingStation.requests.delete(messageId);
225 }
caad9d6b 226 }
caad9d6b 227
e7aeea18
JB
228 /**
229 * Function that will receive the request's error response
230 *
231 * @param error
232 * @param requestStatistic
233 */
234 function rejectCallback(error: OCPPError, requestStatistic = true): void {
235 if (requestStatistic && self.chargingStation.getEnableStatistics()) {
236 self.chargingStation.performanceStatistics.addRequestStatistic(
237 commandName,
238 MessageType.CALL_ERROR_MESSAGE
239 );
240 }
241 logger.error(
242 `${self.chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`,
243 error,
244 commandName,
245 messagePayload
246 );
247 self.chargingStation.requests.delete(messageId);
248 reject(error);
caad9d6b 249 }
e7aeea18
JB
250 }),
251 Constants.OCPP_WEBSOCKET_TIMEOUT,
252 new OCPPError(
253 ErrorType.GENERIC_ERROR,
254 `Timeout for message id '${messageId}'`,
255 commandName,
256 (messagePayload?.details as JsonType) ?? {}
257 ),
258 () => {
259 messageType === MessageType.CALL_MESSAGE &&
260 this.chargingStation.requests.delete(messageId);
caad9d6b 261 }
e7aeea18 262 );
caad9d6b 263 }
e7aeea18
JB
264 throw new OCPPError(
265 ErrorType.SECURITY_ERROR,
266 `Cannot send command ${commandName} payload when the charging station is in ${this.chargingStation.getRegistrationStatus()} state on the central server`,
267 commandName
268 );
c0560973
JB
269 }
270
e7aeea18
JB
271 private buildMessageToSend(
272 messageId: string,
273 messagePayload: JsonType | OCPPError,
274 messageType: MessageType,
275 commandName?: RequestCommand | IncomingRequestCommand,
276 responseCallback?: (payload: JsonType | string, requestPayload: JsonType) => Promise<void>,
277 rejectCallback?: (error: OCPPError, requestStatistic?: boolean) => void
278 ): string {
e7accadb
JB
279 let messageToSend: string;
280 // Type of message
281 switch (messageType) {
282 // Request
283 case MessageType.CALL_MESSAGE:
284 // Build request
e7aeea18
JB
285 this.chargingStation.requests.set(messageId, [
286 responseCallback,
287 rejectCallback,
288 commandName,
289 messagePayload,
290 ]);
5e0c67e8 291 messageToSend = JSON.stringify([messageType, messageId, commandName, messagePayload]);
e7accadb
JB
292 break;
293 // Response
294 case MessageType.CALL_RESULT_MESSAGE:
295 // Build response
5e0c67e8 296 messageToSend = JSON.stringify([messageType, messageId, messagePayload]);
e7accadb
JB
297 break;
298 // Error Message
299 case MessageType.CALL_ERROR_MESSAGE:
300 // Build Error Message
e7aeea18
JB
301 messageToSend = JSON.stringify([
302 messageType,
303 messageId,
304 messagePayload?.code ?? ErrorType.GENERIC_ERROR,
305 messagePayload?.message ?? '',
306 messagePayload?.details ?? { commandName },
307 ]);
e7accadb
JB
308 break;
309 }
310 return messageToSend;
311 }
312
e7aeea18
JB
313 private handleRequestError(
314 commandName: RequestCommand | IncomingRequestCommand,
315 error: Error,
316 params: HandleErrorParams<EmptyObject> = { throwError: true }
317 ): void {
318 logger.error(
319 this.chargingStation.logPrefix() + ' Request command %s error: %j',
320 commandName,
321 error
322 );
e0a50bcd
JB
323 if (params?.throwError) {
324 throw error;
325 }
5e0c67e8
JB
326 }
327
caad9d6b 328 public abstract sendHeartbeat(params?: SendParams): Promise<void>;
e7aeea18
JB
329 public abstract sendBootNotification(
330 chargePointModel: string,
331 chargePointVendor: string,
332 chargeBoxSerialNumber?: string,
333 firmwareVersion?: string,
334 chargePointSerialNumber?: string,
335 iccid?: string,
336 imsi?: string,
337 meterSerialNumber?: string,
338 meterType?: string,
339 params?: SendParams
340 ): Promise<BootNotificationResponse>;
341 public abstract sendStatusNotification(
342 connectorId: number,
343 status: ChargePointStatus,
344 errorCode?: ChargePointErrorCode
345 ): Promise<void>;
163547b1 346 public abstract sendAuthorize(connectorId: number, idTag?: string): Promise<AuthorizeResponse>;
e7aeea18
JB
347 public abstract sendStartTransaction(
348 connectorId: number,
349 idTag?: string
350 ): Promise<StartTransactionResponse>;
351 public abstract sendStopTransaction(
352 transactionId: number,
353 meterStop: number,
354 idTag?: string,
355 reason?: StopTransactionReason
356 ): Promise<StopTransactionResponse>;
357 public abstract sendMeterValues(
358 connectorId: number,
359 transactionId: number,
360 interval: number
361 ): Promise<void>;
362 public abstract sendTransactionBeginMeterValues(
363 connectorId: number,
364 transactionId: number,
365 beginMeterValue: MeterValue
366 ): Promise<void>;
367 public abstract sendTransactionEndMeterValues(
368 connectorId: number,
369 transactionId: number,
370 endMeterValue: MeterValue
371 ): Promise<void>;
372 public abstract sendDiagnosticsStatusNotification(
373 diagnosticsStatus: DiagnosticsStatus
374 ): Promise<void>;
c0560973 375}