1 import { AuthorizeResponse
, StartTransactionResponse
, StopTransactionReason
, StopTransactionResponse
} from
'../../types/ocpp/Transaction';
2 import { DiagnosticsStatus
, IncomingRequestCommand
, RequestCommand
, ResponseType
, SendParams
} from
'../../types/ocpp/Requests';
4 import { BootNotificationResponse
} from
'../../types/ocpp/Responses';
5 import { ChargePointErrorCode
} from
'../../types/ocpp/ChargePointErrorCode';
6 import { ChargePointStatus
} from
'../../types/ocpp/ChargePointStatus';
7 import type ChargingStation from
'../ChargingStation';
8 import Constants from
'../../utils/Constants';
9 import { ErrorType
} from
'../../types/ocpp/ErrorType';
10 import { HandleErrorParams
} from
'../../types/Error';
11 import { JsonType
} from
'../../types/JsonType';
12 import { MessageType
} from
'../../types/ocpp/MessageType';
13 import { MeterValue
} from
'../../types/ocpp/MeterValues';
14 import OCPPError from
'../../exception/OCPPError';
15 import type OCPPResponseService from
'./OCPPResponseService';
16 import PerformanceStatistics from
'../../performance/PerformanceStatistics';
17 import Utils from
'../../utils/Utils';
18 import logger from
'../../utils/Logger';
20 export default abstract class OCPPRequestService
{
21 private static readonly instances
: Map
<string, OCPPRequestService
> = new Map
<string, OCPPRequestService
>();
22 protected readonly chargingStation
: ChargingStation
;
23 private readonly ocppResponseService
: OCPPResponseService
;
25 protected constructor(chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) {
26 this.chargingStation
= chargingStation
;
27 this.ocppResponseService
= ocppResponseService
;
30 public static getInstance
<T
extends OCPPRequestService
>(this: new (chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) => T
, chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
): T
{
31 if (!OCPPRequestService
.instances
.has(chargingStation
.id
)) {
32 OCPPRequestService
.instances
.set(chargingStation
.id
, new this(chargingStation
, ocppResponseService
));
34 return OCPPRequestService
.instances
.get(chargingStation
.id
) as T
;
37 public async sendResult(messageId
: string, messagePayload
: JsonType
, commandName
: IncomingRequestCommand
): Promise
<ResponseType
> {
39 // Send result message
40 return await this.internalSendMessage(messageId
, messagePayload
, MessageType
.CALL_RESULT_MESSAGE
, commandName
);
42 this.handleRequestError(commandName
, error
as Error);
46 public async sendError(messageId
: string, ocppError
: OCPPError
, commandName
: IncomingRequestCommand
): Promise
<ResponseType
> {
49 return await this.internalSendMessage(messageId
, ocppError
, MessageType
.CALL_ERROR_MESSAGE
, commandName
);
51 this.handleRequestError(commandName
, error
as Error);
55 protected async sendMessage(messageId
: string, messagePayload
: JsonType
, commandName
: RequestCommand
, params
: SendParams
= {
56 skipBufferingOnError
: false,
58 }): Promise
<ResponseType
> {
60 return await this.internalSendMessage(messageId
, messagePayload
, MessageType
.CALL_MESSAGE
, commandName
, params
);
62 this.handleRequestError(commandName
, error
as Error, { throwError
: false });
66 private async internalSendMessage(messageId
: string, messagePayload
: JsonType
| OCPPError
, messageType
: MessageType
, commandName
?: RequestCommand
| IncomingRequestCommand
,
67 params
: SendParams
= {
68 skipBufferingOnError
: false,
70 }): Promise
<ResponseType
> {
71 if ((this.chargingStation
.isInUnknownState() && commandName
=== RequestCommand
.BOOT_NOTIFICATION
)
72 || (!this.chargingStation
.getOcppStrictCompliance() && this.chargingStation
.isInUnknownState())
73 || this.chargingStation
.isInAcceptedState() || (this.chargingStation
.isInPendingState() && params
.triggerMessage
)) {
74 // eslint-disable-next-line @typescript-eslint/no-this-alias
76 // Send a message through wsConnection
77 return Utils
.promiseWithTimeout(new Promise((resolve
, reject
) => {
78 const messageToSend
= this.buildMessageToSend(messageId
, messagePayload
, messageType
, commandName
, responseCallback
, rejectCallback
);
79 if (this.chargingStation
.getEnableStatistics()) {
80 this.chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, messageType
);
82 // Check if wsConnection opened
83 if (this.chargingStation
.isWebSocketConnectionOpened()) {
85 const beginId
= PerformanceStatistics
.beginMeasure(commandName
);
86 // FIXME: Handle sending error
87 this.chargingStation
.wsConnection
.send(messageToSend
);
88 PerformanceStatistics
.endMeasure(commandName
, beginId
);
89 } else if (!params
.skipBufferingOnError
) {
91 this.chargingStation
.bufferMessage(messageToSend
);
92 const ocppError
= new OCPPError(ErrorType
.GENERIC_ERROR
, `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`, commandName
, messagePayload
?.details
as JsonType
?? {});
93 if (messageType
=== MessageType
.CALL_MESSAGE
) {
94 // Reject it but keep the request in the cache
95 return reject(ocppError
);
97 return rejectCallback(ocppError
, false);
100 return rejectCallback(new OCPPError(ErrorType
.GENERIC_ERROR
, `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`, commandName
, messagePayload
?.details
as JsonType
?? {}), false);
103 if (messageType
!== MessageType
.CALL_MESSAGE
) {
105 return resolve(messagePayload
);
109 * Function that will receive the request's response
112 * @param requestPayload
114 async function responseCallback(payload
: JsonType
| string, requestPayload
: JsonType
): Promise
<void> {
115 if (self.chargingStation
.getEnableStatistics()) {
116 self.chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, MessageType
.CALL_RESULT_MESSAGE
);
118 // Handle the request's response
120 await self.ocppResponseService
.handleResponse(commandName
as RequestCommand
, payload
, requestPayload
);
126 self.chargingStation
.requests
.delete(messageId
);
131 * Function that will receive the request's error response
134 * @param requestStatistic
136 function rejectCallback(error
: OCPPError
, requestStatistic
= true): void {
137 if (requestStatistic
&& self.chargingStation
.getEnableStatistics()) {
138 self.chargingStation
.performanceStatistics
.addRequestStatistic(commandName
, MessageType
.CALL_ERROR_MESSAGE
);
140 logger
.error(`${self.chargingStation.logPrefix()} Error %j occurred when calling command %s with message data %j`, error
, commandName
, messagePayload
);
141 self.chargingStation
.requests
.delete(messageId
);
144 }), Constants
.OCPP_WEBSOCKET_TIMEOUT
, new OCPPError(ErrorType
.GENERIC_ERROR
, `Timeout for message id '${messageId}'`, commandName
, messagePayload
?.details
as JsonType
?? {}), () => {
145 messageType
=== MessageType
.CALL_MESSAGE
&& this.chargingStation
.requests
.delete(messageId
);
148 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
);
151 private buildMessageToSend(messageId
: string, messagePayload
: JsonType
| OCPPError
, messageType
: MessageType
, commandName
?: RequestCommand
| IncomingRequestCommand
,
152 responseCallback
?: (payload
: JsonType
| string, requestPayload
: JsonType
) => Promise
<void>,
153 rejectCallback
?: (error
: OCPPError
, requestStatistic
?: boolean) => void): string {
154 let messageToSend
: string;
156 switch (messageType
) {
158 case MessageType
.CALL_MESSAGE
:
160 this.chargingStation
.requests
.set(messageId
, [responseCallback
, rejectCallback
, commandName
, messagePayload
]);
161 messageToSend
= JSON
.stringify([messageType
, messageId
, commandName
, messagePayload
]);
164 case MessageType
.CALL_RESULT_MESSAGE
:
166 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
]);
169 case MessageType
.CALL_ERROR_MESSAGE
:
170 // Build Error Message
171 messageToSend
= JSON
.stringify([messageType
, messageId
, messagePayload
?.code
?? ErrorType
.GENERIC_ERROR
, messagePayload
?.message
?? '', messagePayload
?.details
?? { commandName
}]);
174 return messageToSend
;
177 private handleRequestError(commandName
: RequestCommand
| IncomingRequestCommand
, error
: Error, params
: HandleErrorParams
= { throwError
: true }): void {
178 logger
.error(this.chargingStation
.logPrefix() + ' Request command %s error: %j', commandName
, error
);
179 if (params
?.throwError
) {
184 public abstract sendHeartbeat(params
?: SendParams
): Promise
<void>;
185 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
>;
186 public abstract sendStatusNotification(connectorId
: number, status: ChargePointStatus
, errorCode
?: ChargePointErrorCode
): Promise
<void>;
187 public abstract sendAuthorize(connectorId
: number, idTag
?: string): Promise
<AuthorizeResponse
>;
188 public abstract sendStartTransaction(connectorId
: number, idTag
?: string): Promise
<StartTransactionResponse
>;
189 public abstract sendStopTransaction(transactionId
: number, meterStop
: number, idTag
?: string, reason
?: StopTransactionReason
): Promise
<StopTransactionResponse
>;
190 public abstract sendMeterValues(connectorId
: number, transactionId
: number, interval
: number): Promise
<void>;
191 public abstract sendTransactionBeginMeterValues(connectorId
: number, transactionId
: number, beginMeterValue
: MeterValue
): Promise
<void>;
192 public abstract sendTransactionEndMeterValues(connectorId
: number, transactionId
: number, endMeterValue
: MeterValue
): Promise
<void>;
193 public abstract sendDiagnosticsStatusNotification(diagnosticsStatus
: DiagnosticsStatus
): Promise
<void>;