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