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