Commit | Line | Data |
---|---|---|
c0560973 | 1 | import { AuthorizeResponse, StartTransactionResponse, StopTransactionReason, StopTransactionResponse } from '../../types/ocpp/Transaction'; |
b0a67e55 | 2 | import { DiagnosticsStatus, IncomingRequestCommand, RequestCommand, ResponseType, SendParams } from '../../types/ocpp/Requests'; |
c0560973 | 3 | |
efa43e52 | 4 | import { BootNotificationResponse } from '../../types/ocpp/Responses'; |
c0560973 JB |
5 | import { ChargePointErrorCode } from '../../types/ocpp/ChargePointErrorCode'; |
6 | import { ChargePointStatus } from '../../types/ocpp/ChargePointStatus'; | |
9f2e3130 | 7 | import type ChargingStation from '../ChargingStation'; |
c0560973 | 8 | import Constants from '../../utils/Constants'; |
47086c3a | 9 | import { EmptyObject } from '../../types/EmptyObject'; |
c0560973 | 10 | import { ErrorType } from '../../types/ocpp/ErrorType'; |
e0a50bcd | 11 | import { HandleErrorParams } from '../../types/Error'; |
d1888640 | 12 | import { JsonType } from '../../types/JsonType'; |
c0560973 | 13 | import { MessageType } from '../../types/ocpp/MessageType'; |
fd0c36fa | 14 | import { MeterValue } from '../../types/ocpp/MeterValues'; |
e58068fd | 15 | import OCPPError from '../../exception/OCPPError'; |
9f2e3130 | 16 | import type OCPPResponseService from './OCPPResponseService'; |
a6b3c6c3 | 17 | import PerformanceStatistics from '../../performance/PerformanceStatistics'; |
6d9abcc2 | 18 | import Utils from '../../utils/Utils'; |
9f2e3130 | 19 | import logger from '../../utils/Logger'; |
c0560973 JB |
20 | |
21 | export 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 | } |