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