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