Remove string literal from log messages
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPRequestService.ts
CommitLineData
c0560973 1import { AuthorizeResponse, StartTransactionResponse, StopTransactionReason, StopTransactionResponse } from '../../types/ocpp/Transaction';
b0a67e55 2import { DiagnosticsStatus, IncomingRequestCommand, RequestCommand, ResponseType, SendParams } from '../../types/ocpp/Requests';
c0560973 3
efa43e52 4import { BootNotificationResponse } from '../../types/ocpp/Responses';
c0560973
JB
5import { ChargePointErrorCode } from '../../types/ocpp/ChargePointErrorCode';
6import { ChargePointStatus } from '../../types/ocpp/ChargePointStatus';
9f2e3130 7import type ChargingStation from '../ChargingStation';
c0560973 8import Constants from '../../utils/Constants';
47086c3a 9import { EmptyObject } from '../../types/EmptyObject';
c0560973 10import { ErrorType } from '../../types/ocpp/ErrorType';
e0a50bcd 11import { HandleErrorParams } from '../../types/Error';
d1888640 12import { JsonType } from '../../types/JsonType';
c0560973 13import { MessageType } from '../../types/ocpp/MessageType';
fd0c36fa 14import { MeterValue } from '../../types/ocpp/MeterValues';
e58068fd 15import OCPPError from '../../exception/OCPPError';
9f2e3130 16import type OCPPResponseService from './OCPPResponseService';
a6b3c6c3 17import PerformanceStatistics from '../../performance/PerformanceStatistics';
6d9abcc2 18import Utils from '../../utils/Utils';
9f2e3130 19import logger from '../../utils/Logger';
c0560973
JB
20
21export default abstract class OCPPRequestService {
9f2e3130
JB
22 private static readonly instances: Map<string, OCPPRequestService> = new Map<string, OCPPRequestService>();
23 protected readonly chargingStation: ChargingStation;
24 private readonly ocppResponseService: OCPPResponseService;
c0560973 25
9f2e3130 26 protected constructor(chargingStation: ChargingStation, ocppResponseService: OCPPResponseService) {
c0560973
JB
27 this.chargingStation = chargingStation;
28 this.ocppResponseService = ocppResponseService;
29 }
30
9f2e3130
JB
31 public static getInstance<T extends OCPPRequestService>(this: new (chargingStation: ChargingStation, ocppResponseService: OCPPResponseService) => T, chargingStation: ChargingStation, ocppResponseService: OCPPResponseService): T {
32 if (!OCPPRequestService.instances.has(chargingStation.id)) {
33 OCPPRequestService.instances.set(chargingStation.id, new this(chargingStation, ocppResponseService));
34 }
35 return OCPPRequestService.instances.get(chargingStation.id) as T;
36 }
37
b0a67e55 38 public async sendResult(messageId: string, messagePayload: JsonType, commandName: IncomingRequestCommand): Promise<ResponseType> {
5e0c67e8
JB
39 try {
40 // Send result message
41 return await this.internalSendMessage(messageId, messagePayload, MessageType.CALL_RESULT_MESSAGE, commandName);
42 } catch (error) {
43 this.handleRequestError(commandName, error as Error);
44 }
45 }
46
b0a67e55 47 public async sendError(messageId: string, ocppError: OCPPError, commandName: IncomingRequestCommand): Promise<ResponseType> {
5e0c67e8
JB
48 try {
49 // Send error message
50 return await this.internalSendMessage(messageId, ocppError, MessageType.CALL_ERROR_MESSAGE, commandName);
51 } catch (error) {
52 this.handleRequestError(commandName, error as Error);
53 }
54 }
55
56 protected async sendMessage(messageId: string, messagePayload: JsonType, commandName: RequestCommand, params: SendParams = {
57 skipBufferingOnError: false,
58 triggerMessage: false
b0a67e55 59 }): Promise<ResponseType> {
5e0c67e8
JB
60 try {
61 return await this.internalSendMessage(messageId, messagePayload, MessageType.CALL_MESSAGE, commandName, params);
62 } catch (error) {
e0a50bcd 63 this.handleRequestError(commandName, error as Error, { throwError: false });
5e0c67e8
JB
64 }
65 }
66
1d4ad051 67 private async internalSendMessage(messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, commandName?: RequestCommand | IncomingRequestCommand,
caad9d6b
JB
68 params: SendParams = {
69 skipBufferingOnError: false,
70 triggerMessage: false
b0a67e55 71 }): Promise<ResponseType> {
672fed6e
JB
72 if ((this.chargingStation.isInUnknownState() && commandName === RequestCommand.BOOT_NOTIFICATION)
73 || (!this.chargingStation.getOcppStrictCompliance() && this.chargingStation.isInUnknownState())
73c4266d 74 || this.chargingStation.isInAcceptedState() || (this.chargingStation.isInPendingState() && params.triggerMessage)) {
caad9d6b
JB
75 // eslint-disable-next-line @typescript-eslint/no-this-alias
76 const self = this;
77 // Send a message through wsConnection
78 return Utils.promiseWithTimeout(new Promise((resolve, reject) => {
5e0c67e8 79 const messageToSend = this.buildMessageToSend(messageId, messagePayload, messageType, commandName, responseCallback, rejectCallback);
caad9d6b
JB
80 if (this.chargingStation.getEnableStatistics()) {
81 this.chargingStation.performanceStatistics.addRequestStatistic(commandName, messageType);
6198eef3 82 }
caad9d6b
JB
83 // Check if wsConnection opened
84 if (this.chargingStation.isWebSocketConnectionOpened()) {
85 // Yes: Send Message
86 const beginId = PerformanceStatistics.beginMeasure(commandName);
87 // FIXME: Handle sending error
88 this.chargingStation.wsConnection.send(messageToSend);
89 PerformanceStatistics.endMeasure(commandName, beginId);
90 } else if (!params.skipBufferingOnError) {
91 // Buffer it
92 this.chargingStation.bufferMessage(messageToSend);
5e0c67e8 93 const ocppError = new OCPPError(ErrorType.GENERIC_ERROR, `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`, commandName, messagePayload?.details as JsonType ?? {});
caad9d6b
JB
94 if (messageType === MessageType.CALL_MESSAGE) {
95 // Reject it but keep the request in the cache
96 return reject(ocppError);
97 }
98 return rejectCallback(ocppError, false);
99 } else {
100 // Reject it
5e0c67e8 101 return rejectCallback(new OCPPError(ErrorType.GENERIC_ERROR, `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`, commandName, messagePayload?.details as JsonType ?? {}), false);
c0560973 102 }
caad9d6b
JB
103 // Response?
104 if (messageType !== MessageType.CALL_MESSAGE) {
105 // Yes: send Ok
5e0c67e8 106 return resolve(messagePayload);
a4bc2942 107 }
c0560973 108
caad9d6b
JB
109 /**
110 * Function that will receive the request's response
111 *
112 * @param payload
113 * @param requestPayload
114 */
d1888640 115 async function responseCallback(payload: JsonType | string, requestPayload: JsonType): Promise<void> {
caad9d6b
JB
116 if (self.chargingStation.getEnableStatistics()) {
117 self.chargingStation.performanceStatistics.addRequestStatistic(commandName, MessageType.CALL_RESULT_MESSAGE);
118 }
119 // Handle the request's response
120 try {
121 await self.ocppResponseService.handleResponse(commandName as RequestCommand, payload, requestPayload);
122 resolve(payload);
123 } catch (error) {
124 reject(error);
125 throw error;
126 } finally {
127 self.chargingStation.requests.delete(messageId);
128 }
c0560973 129 }
caad9d6b
JB
130
131 /**
132 * Function that will receive the request's error response
133 *
134 * @param error
135 * @param requestStatistic
136 */
137 function rejectCallback(error: OCPPError, requestStatistic = true): void {
138 if (requestStatistic && self.chargingStation.getEnableStatistics()) {
139 self.chargingStation.performanceStatistics.addRequestStatistic(commandName, MessageType.CALL_ERROR_MESSAGE);
140 }
5e0c67e8 141 logger.error(`${self.chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`, error, commandName, messagePayload);
caad9d6b
JB
142 self.chargingStation.requests.delete(messageId);
143 reject(error);
144 }
5e0c67e8 145 }), Constants.OCPP_WEBSOCKET_TIMEOUT, new OCPPError(ErrorType.GENERIC_ERROR, `Timeout for message id '${messageId}'`, commandName, messagePayload?.details as JsonType ?? {}), () => {
caad9d6b
JB
146 messageType === MessageType.CALL_MESSAGE && this.chargingStation.requests.delete(messageId);
147 });
caad9d6b 148 }
672fed6e 149 throw new OCPPError(ErrorType.SECURITY_ERROR, `Cannot send command ${commandName} payload when the charging station is in ${this.chargingStation.getRegistrationStatus()} state on the central server`, commandName);
c0560973
JB
150 }
151
1d4ad051
JB
152 private buildMessageToSend(messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, commandName?: RequestCommand | IncomingRequestCommand,
153 responseCallback?: (payload: JsonType | string, requestPayload: JsonType) => Promise<void>,
154 rejectCallback?: (error: OCPPError, requestStatistic?: boolean) => void): string {
e7accadb
JB
155 let messageToSend: string;
156 // Type of message
157 switch (messageType) {
158 // Request
159 case MessageType.CALL_MESSAGE:
160 // Build request
5e0c67e8
JB
161 this.chargingStation.requests.set(messageId, [responseCallback, rejectCallback, commandName, messagePayload]);
162 messageToSend = JSON.stringify([messageType, messageId, commandName, messagePayload]);
e7accadb
JB
163 break;
164 // Response
165 case MessageType.CALL_RESULT_MESSAGE:
166 // Build response
5e0c67e8 167 messageToSend = JSON.stringify([messageType, messageId, messagePayload]);
e7accadb
JB
168 break;
169 // Error Message
170 case MessageType.CALL_ERROR_MESSAGE:
171 // Build Error Message
1d4ad051 172 messageToSend = JSON.stringify([messageType, messageId, messagePayload?.code ?? ErrorType.GENERIC_ERROR, messagePayload?.message ?? '', messagePayload?.details ?? { commandName }]);
e7accadb
JB
173 break;
174 }
175 return messageToSend;
176 }
177
47086c3a 178 private handleRequestError(commandName: RequestCommand | IncomingRequestCommand, error: Error, params: HandleErrorParams<EmptyObject> = { throwError: true }): void {
5e0c67e8 179 logger.error(this.chargingStation.logPrefix() + ' Request command %s error: %j', commandName, error);
e0a50bcd
JB
180 if (params?.throwError) {
181 throw error;
182 }
5e0c67e8
JB
183 }
184
caad9d6b
JB
185 public abstract sendHeartbeat(params?: SendParams): Promise<void>;
186 public abstract sendBootNotification(chargePointModel: string, chargePointVendor: string, chargeBoxSerialNumber?: string, firmwareVersion?: string, chargePointSerialNumber?: string, iccid?: string, imsi?: string, meterSerialNumber?: string, meterType?: string, params?: SendParams): Promise<BootNotificationResponse>;
c0560973 187 public abstract sendStatusNotification(connectorId: number, status: ChargePointStatus, errorCode?: ChargePointErrorCode): Promise<void>;
163547b1 188 public abstract sendAuthorize(connectorId: number, idTag?: string): Promise<AuthorizeResponse>;
c0560973
JB
189 public abstract sendStartTransaction(connectorId: number, idTag?: string): Promise<StartTransactionResponse>;
190 public abstract sendStopTransaction(transactionId: number, meterStop: number, idTag?: string, reason?: StopTransactionReason): Promise<StopTransactionResponse>;
aef1b33a 191 public abstract sendMeterValues(connectorId: number, transactionId: number, interval: number): Promise<void>;
fd0c36fa
JB
192 public abstract sendTransactionBeginMeterValues(connectorId: number, transactionId: number, beginMeterValue: MeterValue): Promise<void>;
193 public abstract sendTransactionEndMeterValues(connectorId: number, transactionId: number, endMeterValue: MeterValue): Promise<void>;
47e22477 194 public abstract sendDiagnosticsStatusNotification(diagnosticsStatus: DiagnosticsStatus): Promise<void>;
c0560973 195}