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