X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fcharging-station%2Focpp%2FOCPPRequestService.ts;h=e78f71c56d1300137c12800c8f343e2a1ae9791c;hb=a4385edc2344433c20fcb92d01cbe5a330b850b4;hp=aeb09c332b7629acd8340fb874fd8d90348ecef4;hpb=a6ef1ece74c0d08e86a905571f4f6045c28131cb;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index aeb09c33..e78f71c5 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -1,12 +1,12 @@ -import Ajv, { type JSONSchemaType, type ValidateFunction } from 'ajv'; -import ajvFormats from 'ajv-formats'; +import _Ajv, { type JSONSchemaType, type ValidateFunction } from 'ajv' +import _ajvFormats from 'ajv-formats' -import { OCPPConstants } from './OCPPConstants.js'; -import type { OCPPResponseService } from './OCPPResponseService.js'; -import { OCPPServiceUtils } from './OCPPServiceUtils.js'; -import type { ChargingStation } from '../../charging-station/index.js'; -import { OCPPError } from '../../exception/index.js'; -import { PerformanceStatistics } from '../../performance/index.js'; +import { OCPPConstants } from './OCPPConstants.js' +import type { OCPPResponseService } from './OCPPResponseService.js' +import { OCPPServiceUtils } from './OCPPServiceUtils.js' +import type { ChargingStation } from '../../charging-station/index.js' +import { OCPPError } from '../../exception/index.js' +import { PerformanceStatistics } from '../../performance/index.js' import { ChargingStationEvents, type ErrorCallback, @@ -21,41 +21,44 @@ import { type RequestParams, type Response, type ResponseCallback, - type ResponseType, -} from '../../types/index.js'; + type ResponseType +} from '../../types/index.js' import { cloneObject, formatDurationMilliSeconds, handleSendMessageError, - isNullOrUndefined, - logger, -} from '../../utils/index.js'; + logger +} from '../../utils/index.js' +type Ajv = _Ajv.default +// eslint-disable-next-line @typescript-eslint/no-redeclare +const Ajv = _Ajv.default +const ajvFormats = _ajvFormats.default -const moduleName = 'OCPPRequestService'; +const moduleName = 'OCPPRequestService' const defaultRequestParams: RequestParams = { skipBufferingOnError: false, triggerMessage: false, - throwError: false, -}; + throwError: false +} export abstract class OCPPRequestService { - private static instance: OCPPRequestService | null = null; - private readonly version: OCPPVersion; - private readonly ajv: Ajv; - private readonly ocppResponseService: OCPPResponseService; - private readonly jsonValidateFunctions: Map>; - protected abstract jsonSchemas: Map>; + private static instance: OCPPRequestService | null = null + private readonly version: OCPPVersion + private readonly ajv: Ajv + private readonly ocppResponseService: OCPPResponseService + private readonly jsonValidateFunctions: Map> + protected abstract jsonSchemas: Map> - protected constructor(version: OCPPVersion, ocppResponseService: OCPPResponseService) { - this.version = version; + protected constructor (version: OCPPVersion, ocppResponseService: OCPPResponseService) { + this.version = version this.ajv = new Ajv({ keywords: ['javaType'], - multipleOfPrecision: 2, - }); - ajvFormats(this.ajv); - this.jsonValidateFunctions = new Map>(); - this.ocppResponseService = ocppResponseService; + multipleOfPrecision: 2 + }) + ajvFormats(this.ajv) + this.jsonValidateFunctions = new Map>() + this.ocppResponseService = ocppResponseService this.requestHandler = this.requestHandler.bind(this) as < // eslint-disable-next-line @typescript-eslint/no-unused-vars ReqType extends JsonType, @@ -64,71 +67,71 @@ export abstract class OCPPRequestService { chargingStation: ChargingStation, commandName: RequestCommand, commandParams?: JsonType, - params?: RequestParams, - ) => Promise; + params?: RequestParams + ) => Promise this.sendMessage = this.sendMessage.bind(this) as ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType, commandName: RequestCommand, - params?: RequestParams, - ) => Promise; + params?: RequestParams + ) => Promise this.sendResponse = this.sendResponse.bind(this) as ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType, - commandName: IncomingRequestCommand, - ) => Promise; + commandName: IncomingRequestCommand + ) => Promise this.sendError = this.sendError.bind(this) as ( chargingStation: ChargingStation, messageId: string, ocppError: OCPPError, - commandName: RequestCommand | IncomingRequestCommand, - ) => Promise; + commandName: RequestCommand | IncomingRequestCommand + ) => Promise this.internalSendMessage = this.internalSendMessage.bind(this) as ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, commandName: RequestCommand | IncomingRequestCommand, - params?: RequestParams, - ) => Promise; + params?: RequestParams + ) => Promise this.buildMessageToSend = this.buildMessageToSend.bind(this) as ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, - commandName: RequestCommand | IncomingRequestCommand, - ) => string; + commandName: RequestCommand | IncomingRequestCommand + ) => string this.validateRequestPayload = this.validateRequestPayload.bind(this) as ( chargingStation: ChargingStation, commandName: RequestCommand | IncomingRequestCommand, - payload: T, - ) => boolean; + payload: T + ) => boolean this.validateIncomingRequestResponsePayload = this.validateIncomingRequestResponsePayload.bind( - this, + this ) as ( chargingStation: ChargingStation, commandName: RequestCommand | IncomingRequestCommand, - payload: T, - ) => boolean; + payload: T + ) => boolean } public static getInstance( this: new (ocppResponseService: OCPPResponseService) => T, - ocppResponseService: OCPPResponseService, + ocppResponseService: OCPPResponseService ): T { if (OCPPRequestService.instance === null) { - OCPPRequestService.instance = new this(ocppResponseService); + OCPPRequestService.instance = new this(ocppResponseService) } - return OCPPRequestService.instance as T; + return OCPPRequestService.instance as T } - public async sendResponse( + public async sendResponse ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType, - commandName: IncomingRequestCommand, + commandName: IncomingRequestCommand ): Promise { try { // Send response message @@ -137,21 +140,21 @@ export abstract class OCPPRequestService { messageId, messagePayload, MessageType.CALL_RESULT_MESSAGE, - commandName, - ); + commandName + ) } catch (error) { handleSendMessageError(chargingStation, commandName, error as Error, { - throwError: true, - }); - return null; + throwError: true + }) + return null } } - public async sendError( + public async sendError ( chargingStation: ChargingStation, messageId: string, ocppError: OCPPError, - commandName: RequestCommand | IncomingRequestCommand, + commandName: RequestCommand | IncomingRequestCommand ): Promise { try { // Send error message @@ -160,25 +163,25 @@ export abstract class OCPPRequestService { messageId, ocppError, MessageType.CALL_ERROR_MESSAGE, - commandName, - ); + commandName + ) } catch (error) { - handleSendMessageError(chargingStation, commandName, error as Error); - return null; + handleSendMessageError(chargingStation, commandName, error as Error) + return null } } - protected async sendMessage( + protected async sendMessage ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType, commandName: RequestCommand, - params?: RequestParams, + params?: RequestParams ): Promise { params = { ...defaultRequestParams, - ...params, - }; + ...params + } try { return await this.internalSendMessage( chargingStation, @@ -186,140 +189,142 @@ export abstract class OCPPRequestService { messagePayload, MessageType.CALL_MESSAGE, commandName, - params, - ); + params + ) } catch (error) { handleSendMessageError(chargingStation, commandName, error as Error, { - throwError: params.throwError, - }); - return null; + throwError: params.throwError + }) + return null } } private validateRequestPayload( chargingStation: ChargingStation, commandName: RequestCommand | IncomingRequestCommand, - payload: T, + payload: T ): boolean { if (chargingStation.stationInfo?.ocppStrictCompliance === false) { - return true; + return true } - if (this.jsonSchemas.has(commandName as RequestCommand) === false) { + if (!this.jsonSchemas.has(commandName as RequestCommand)) { logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: No JSON schema found for command '${commandName}' PDU validation`, - ); - return true; + `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: No JSON schema found for command '${commandName}' PDU validation` + ) + return true } - const validate = this.getJsonRequestValidateFunction(commandName as RequestCommand); - payload = cloneObject(payload); - OCPPServiceUtils.convertDateToISOString(payload); + const validate = this.getJsonRequestValidateFunction(commandName as RequestCommand) + payload = cloneObject(payload) + OCPPServiceUtils.convertDateToISOString(payload) if (validate(payload)) { - return true; + return true } logger.error( `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Command '${commandName}' request PDU is invalid: %j`, - validate.errors, - ); + validate.errors + ) // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError(). throw new OCPPError( OCPPServiceUtils.ajvErrorsToErrorType(validate.errors), 'Request PDU is invalid', commandName, - JSON.stringify(validate.errors, undefined, 2), - ); + JSON.stringify(validate.errors, undefined, 2) + ) } - private getJsonRequestValidateFunction(commandName: RequestCommand) { - if (this.jsonValidateFunctions.has(commandName) === false) { + private getJsonRequestValidateFunction( + commandName: RequestCommand + ): ValidateFunction { + if (!this.jsonValidateFunctions.has(commandName)) { this.jsonValidateFunctions.set( commandName, - this.ajv.compile(this.jsonSchemas.get(commandName)!).bind(this), - ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.ajv.compile(this.jsonSchemas.get(commandName)!).bind(this) + ) } - return this.jsonValidateFunctions.get(commandName)!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.jsonValidateFunctions.get(commandName)! } private validateIncomingRequestResponsePayload( chargingStation: ChargingStation, commandName: RequestCommand | IncomingRequestCommand, - payload: T, + payload: T ): boolean { if (chargingStation.stationInfo?.ocppStrictCompliance === false) { - return true; + return true } if ( - this.ocppResponseService.jsonIncomingRequestResponseSchemas.has( - commandName as IncomingRequestCommand, - ) === false + !this.ocppResponseService.jsonIncomingRequestResponseSchemas.has( + commandName as IncomingRequestCommand + ) ) { logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: No JSON schema found for command '${commandName}' PDU validation`, - ); - return true; + `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: No JSON schema found for command '${commandName}' PDU validation` + ) + return true } const validate = this.getJsonRequestResponseValidateFunction( - commandName as IncomingRequestCommand, - ); - payload = cloneObject(payload); - OCPPServiceUtils.convertDateToISOString(payload); + commandName as IncomingRequestCommand + ) + payload = cloneObject(payload) + OCPPServiceUtils.convertDateToISOString(payload) if (validate(payload)) { - return true; + return true } logger.error( `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: Command '${commandName}' reponse PDU is invalid: %j`, - validate.errors, - ); + validate.errors + ) // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError(). throw new OCPPError( OCPPServiceUtils.ajvErrorsToErrorType(validate.errors), 'Response PDU is invalid', commandName, - JSON.stringify(validate.errors, undefined, 2), - ); + JSON.stringify(validate.errors, undefined, 2) + ) } private getJsonRequestResponseValidateFunction( - commandName: IncomingRequestCommand, - ) { - if ( - this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.has(commandName) === - false - ) { + commandName: IncomingRequestCommand + ): ValidateFunction { + if (!this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.has(commandName)) { this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.set( commandName, this.ajv + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion .compile(this.ocppResponseService.jsonIncomingRequestResponseSchemas.get(commandName)!) - .bind(this), - ); + .bind(this) + ) } - return this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.get(commandName)!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.get(commandName)! } - private async internalSendMessage( + private async internalSendMessage ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, commandName: RequestCommand | IncomingRequestCommand, - params?: RequestParams, + params?: RequestParams ): Promise { params = { ...defaultRequestParams, - ...params, - }; + ...params + } if ( - (chargingStation.inUnknownState() === true && - commandName === RequestCommand.BOOT_NOTIFICATION) || + (chargingStation.inUnknownState() && commandName === RequestCommand.BOOT_NOTIFICATION) || (chargingStation.stationInfo?.ocppStrictCompliance === false && - chargingStation.inUnknownState() === true) || - chargingStation.inAcceptedState() === true || - (chargingStation.inPendingState() === true && + chargingStation.inUnknownState()) || + chargingStation.inAcceptedState() || + (chargingStation.inPendingState() && (params.triggerMessage === true || messageType === MessageType.CALL_RESULT_MESSAGE)) ) { // eslint-disable-next-line @typescript-eslint/no-this-alias - const self = this; + const self = this // Send a message through wsConnection - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { /** * Function that will receive the request's response * @@ -330,8 +335,8 @@ export abstract class OCPPRequestService { if (chargingStation.stationInfo?.enableStatistics === true) { chargingStation.performanceStatistics?.addRequestStatistic( commandName, - MessageType.CALL_RESULT_MESSAGE, - ); + MessageType.CALL_RESULT_MESSAGE + ) } // Handle the request's response self.ocppResponseService @@ -339,17 +344,17 @@ export abstract class OCPPRequestService { chargingStation, commandName as RequestCommand, payload, - requestPayload, + requestPayload ) .then(() => { - resolve(payload); + resolve(payload) }) .catch(reject) .finally(() => { - chargingStation.requests.delete(messageId); - chargingStation.emit(ChargingStationEvents.updated); - }); - }; + chargingStation.requests.delete(messageId) + chargingStation.emit(ChargingStationEvents.updated) + }) + } /** * Function that will receive the request's error response @@ -358,28 +363,28 @@ export abstract class OCPPRequestService { * @param requestStatistic - */ const errorCallback = (ocppError: OCPPError, requestStatistic = true): void => { - if (requestStatistic === true && chargingStation.stationInfo?.enableStatistics === true) { + if (requestStatistic && chargingStation.stationInfo?.enableStatistics === true) { chargingStation.performanceStatistics?.addRequestStatistic( commandName, - MessageType.CALL_ERROR_MESSAGE, - ); + MessageType.CALL_ERROR_MESSAGE + ) } logger.error( `${chargingStation.logPrefix()} Error occurred at ${OCPPServiceUtils.getMessageTypeString( - messageType, + messageType )} command ${commandName} with PDU %j:`, messagePayload, - ocppError, - ); - chargingStation.requests.delete(messageId); - chargingStation.emit(ChargingStationEvents.updated); - reject(ocppError); - }; + ocppError + ) + chargingStation.requests.delete(messageId) + chargingStation.emit(ChargingStationEvents.updated) + reject(ocppError) + } const handleSendError = (ocppError: OCPPError): void => { if (params?.skipBufferingOnError === false) { // Buffer - chargingStation.bufferMessage(messageToSend); + chargingStation.bufferMessage(messageToSend) if (messageType === MessageType.CALL_MESSAGE) { this.cacheRequestPromise( chargingStation, @@ -387,55 +392,55 @@ export abstract class OCPPRequestService { messagePayload as JsonType, commandName, responseCallback, - errorCallback, - ); + errorCallback + ) } } else if ( params?.skipBufferingOnError === true && messageType === MessageType.CALL_MESSAGE ) { // Remove request from the cache - chargingStation.requests.delete(messageId); + chargingStation.requests.delete(messageId) } - return reject(ocppError); - }; + reject(ocppError) + } if (chargingStation.stationInfo?.enableStatistics === true) { - chargingStation.performanceStatistics?.addRequestStatistic(commandName, messageType); + chargingStation.performanceStatistics?.addRequestStatistic(commandName, messageType) } const messageToSend = this.buildMessageToSend( chargingStation, messageId, messagePayload, messageType, - commandName, - ); + commandName + ) // Check if wsConnection opened - if (chargingStation.isWebSocketConnectionOpened() === true) { - const beginId = PerformanceStatistics.beginMeasure(commandName); + if (chargingStation.isWebSocketConnectionOpened()) { + const beginId = PerformanceStatistics.beginMeasure(commandName) const sendTimeout = setTimeout(() => { - return handleSendError( + handleSendError( new OCPPError( ErrorType.GENERIC_ERROR, `Timeout ${formatDurationMilliSeconds( - OCPPConstants.OCPP_WEBSOCKET_TIMEOUT, + OCPPConstants.OCPP_WEBSOCKET_TIMEOUT )} reached for ${ params?.skipBufferingOnError === false ? '' : 'non ' }buffered message id '${messageId}' with content '${messageToSend}'`, commandName, - (messagePayload as OCPPError).details, - ), - ); - }, OCPPConstants.OCPP_WEBSOCKET_TIMEOUT); + (messagePayload as OCPPError).details + ) + ) + }, OCPPConstants.OCPP_WEBSOCKET_TIMEOUT) chargingStation.wsConnection?.send(messageToSend, (error?: Error) => { - PerformanceStatistics.endMeasure(commandName, beginId); - clearTimeout(sendTimeout); - if (isNullOrUndefined(error)) { + PerformanceStatistics.endMeasure(commandName, beginId) + clearTimeout(sendTimeout) + if (error == null) { logger.debug( `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${OCPPServiceUtils.getMessageTypeString( - messageType, - )} payload: ${messageToSend}`, - ); + messageType + )} payload: ${messageToSend}` + ) if (messageType === MessageType.CALL_MESSAGE) { this.cacheRequestPromise( chargingStation, @@ -443,77 +448,77 @@ export abstract class OCPPRequestService { messagePayload as JsonType, commandName, responseCallback, - errorCallback, - ); + errorCallback + ) } else { // Resolve response - return resolve(messagePayload); + resolve(messagePayload) } - } else if (error) { - return handleSendError( + } else if (error != null) { + handleSendError( new OCPPError( ErrorType.GENERIC_ERROR, `WebSocket errored for ${ params?.skipBufferingOnError === false ? '' : 'non ' }buffered message id '${messageId}' with content '${messageToSend}'`, commandName, - { name: error.name, message: error.message, stack: error.stack }, - ), - ); + { name: error.name, message: error.message, stack: error.stack } + ) + ) } - }); + }) } else { - return handleSendError( + handleSendError( new OCPPError( ErrorType.GENERIC_ERROR, `WebSocket closed for ${ params?.skipBufferingOnError === false ? '' : 'non ' }buffered message id '${messageId}' with content '${messageToSend}'`, commandName, - (messagePayload as OCPPError).details, - ), - ); + (messagePayload as OCPPError).details + ) + ) } - }); + }) } throw new OCPPError( ErrorType.SECURITY_ERROR, `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation?.bootNotificationResponse?.status} state on the central server`, - commandName, - ); + commandName + ) } - private buildMessageToSend( + private buildMessageToSend ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, - commandName: RequestCommand | IncomingRequestCommand, + commandName: RequestCommand | IncomingRequestCommand ): string { - let messageToSend: string; + let messageToSend: string // Type of message switch (messageType) { // Request case MessageType.CALL_MESSAGE: // Build request - this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType); + this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType) messageToSend = JSON.stringify([ messageType, messageId, commandName, - messagePayload, - ] as OutgoingRequest); - break; + messagePayload + ] as OutgoingRequest) + break // Response case MessageType.CALL_RESULT_MESSAGE: // Build response this.validateIncomingRequestResponsePayload( chargingStation, commandName, - messagePayload as JsonType, - ); - messageToSend = JSON.stringify([messageType, messageId, messagePayload] as Response); - break; + messagePayload as JsonType + ) + messageToSend = JSON.stringify([messageType, messageId, messagePayload] as Response) + break // Error Message case MessageType.CALL_ERROR_MESSAGE: // Build Error Message @@ -523,28 +528,28 @@ export abstract class OCPPRequestService { (messagePayload as OCPPError).code, (messagePayload as OCPPError).message, (messagePayload as OCPPError).details ?? { - command: (messagePayload as OCPPError).command ?? commandName, - }, - ] as ErrorResponse); - break; + command: (messagePayload as OCPPError).command ?? commandName + } + ] as ErrorResponse) + break } - return messageToSend; + return messageToSend } - private cacheRequestPromise( + private cacheRequestPromise ( chargingStation: ChargingStation, messageId: string, messagePayload: JsonType, commandName: RequestCommand | IncomingRequestCommand, responseCallback: ResponseCallback, - errorCallback: ErrorCallback, + errorCallback: ErrorCallback ): void { chargingStation.requests.set(messageId, [ responseCallback, errorCallback, commandName, - messagePayload, - ]); + messagePayload + ]) } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -553,6 +558,6 @@ export abstract class OCPPRequestService { commandName: RequestCommand, // FIXME: should be ReqType commandParams?: JsonType, - params?: RequestParams, - ): Promise; + params?: RequestParams + ): Promise }