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 | ||
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 | } |