From 041365be4e6cfcec381c895a203815dd933afff5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 4 Dec 2023 22:35:09 +0100 Subject: [PATCH] perf: reduce OCPPUtils memory usage MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../AutomaticTransactionGenerator.ts | 4 +- src/charging-station/ChargingStation.ts | 38 +- src/charging-station/ocpp/OCPPServiceUtils.ts | 360 +++++++++--------- src/charging-station/ocpp/index.ts | 7 +- 4 files changed, 207 insertions(+), 202 deletions(-) diff --git a/src/charging-station/AutomaticTransactionGenerator.ts b/src/charging-station/AutomaticTransactionGenerator.ts index d76766cc..12cf7ee4 100644 --- a/src/charging-station/AutomaticTransactionGenerator.ts +++ b/src/charging-station/AutomaticTransactionGenerator.ts @@ -7,7 +7,7 @@ import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns'; import type { ChargingStation } from './ChargingStation'; import { checkChargingStation } from './Helpers'; import { IdTagsCache } from './IdTagsCache'; -import { OCPPServiceUtils } from './ocpp'; +import { isIdTagAuthorized } from './ocpp'; import { BaseError } from '../exception'; import { PerformanceStatistics } from '../performance'; import { @@ -431,7 +431,7 @@ export class AutomaticTransactionGenerator extends AsyncResource { )} start transaction with an idTag '${idTag}'`; if (this.getRequireAuthorize()) { ++this.connectorsStatus.get(connectorId)!.authorizeRequests!; - if (await OCPPServiceUtils.isIdTagAuthorized(this.chargingStation, connectorId, idTag)) { + if (await isIdTagAuthorized(this.chargingStation, connectorId, idTag)) { ++this.connectorsStatus.get(connectorId)!.acceptedAuthorizeRequests!; logger.info(startTransactionLogMsg); // Start transaction diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index aa251e39..5860a654 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -55,7 +55,9 @@ import { OCPP20ResponseService, type OCPPIncomingRequestService, type OCPPRequestService, - OCPPServiceUtils, + buildStatusNotificationRequest, + getMessageTypeString, + sendAndSetConnectorStatus, } from './ocpp'; import { SharedLRUCache } from './SharedLRUCache'; import { BaseError, OCPPError } from '../exception'; @@ -887,7 +889,7 @@ export class ChargingStation extends EventEmitter { await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING); } this.getConnectorStatus(reservation.connectorId)!.reservation = reservation; - await OCPPServiceUtils.sendAndSetConnectorStatus( + await sendAndSetConnectorStatus( this, reservation.connectorId, ConnectorStatusEnum.Reserved, @@ -909,7 +911,7 @@ export class ChargingStation extends EventEmitter { case ReservationTerminationReason.RESERVATION_CANCELED: case ReservationTerminationReason.REPLACE_EXISTING: case ReservationTerminationReason.EXPIRED: - await OCPPServiceUtils.sendAndSetConnectorStatus( + await sendAndSetConnectorStatus( this, reservation.connectorId, ConnectorStatusEnum.Available, @@ -1027,11 +1029,18 @@ export class ChargingStation extends EventEmitter { isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!); if (isNullOrUndefined(error)) { logger.debug( - `${this.logPrefix()} >> Buffered ${OCPPServiceUtils.getMessageTypeString( + `${this.logPrefix()} >> Buffered ${getMessageTypeString( messageType, - )} payload sent: ${message}`, + )} OCPP message sent '${JSON.stringify(message)}'`, ); this.messageBuffer.delete(message); + } else { + logger.debug( + `${this.logPrefix()} >> Buffered ${getMessageTypeString( + messageType, + )} OCPP message '${JSON.stringify(message)}' send failed:`, + error, + ); } }); } @@ -1816,7 +1825,7 @@ export class ChargingStation extends EventEmitter { } throw new OCPPError( ErrorType.PROTOCOL_ERROR, - `Cached request for message id ${messageId} ${OCPPServiceUtils.getMessageTypeString( + `Cached request for message id ${messageId} ${getMessageTypeString( messageType, )} is not an array`, undefined, @@ -2103,12 +2112,7 @@ export class ChargingStation extends EventEmitter { if (evseId > 0) { for (const [connectorId, connectorStatus] of evseStatus.connectors) { const connectorBootStatus = getBootConnectorStatus(this, connectorId, connectorStatus); - await OCPPServiceUtils.sendAndSetConnectorStatus( - this, - connectorId, - connectorBootStatus, - evseId, - ); + await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus, evseId); } } } @@ -2120,7 +2124,7 @@ export class ChargingStation extends EventEmitter { connectorId, this.getConnectorStatus(connectorId)!, ); - await OCPPServiceUtils.sendAndSetConnectorStatus(this, connectorId, connectorBootStatus); + await sendAndSetConnectorStatus(this, connectorId, connectorBootStatus); } } } @@ -2165,7 +2169,7 @@ export class ChargingStation extends EventEmitter { >( this, RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( + buildStatusNotificationRequest( this, connectorId, ConnectorStatusEnum.Unavailable, @@ -2185,11 +2189,7 @@ export class ChargingStation extends EventEmitter { >( this, RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( - this, - connectorId, - ConnectorStatusEnum.Unavailable, - ), + buildStatusNotificationRequest(this, connectorId, ConnectorStatusEnum.Unavailable), ); delete this.getConnectorStatus(connectorId)?.status; } diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index f6234463..8a0b2680 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -45,7 +45,187 @@ import { min, } from '../../utils'; +export const getMessageTypeString = (messageType: MessageType): string => { + switch (messageType) { + case MessageType.CALL_MESSAGE: + return 'request'; + case MessageType.CALL_RESULT_MESSAGE: + return 'response'; + case MessageType.CALL_ERROR_MESSAGE: + return 'error'; + default: + return 'unknown'; + } +}; + +export const buildStatusNotificationRequest = ( + chargingStation: ChargingStation, + connectorId: number, + status: ConnectorStatusEnum, + evseId?: number, +): StatusNotificationRequest => { + switch (chargingStation.stationInfo?.ocppVersion) { + case OCPPVersion.VERSION_16: + return { + connectorId, + status, + errorCode: ChargePointErrorCode.NO_ERROR, + } as OCPP16StatusNotificationRequest; + case OCPPVersion.VERSION_20: + case OCPPVersion.VERSION_201: + return { + timestamp: new Date(), + connectorStatus: status, + connectorId, + evseId, + } as OCPP20StatusNotificationRequest; + default: + throw new BaseError('Cannot build status notification payload: OCPP version not supported'); + } +}; + +export const isIdTagAuthorized = async ( + chargingStation: ChargingStation, + connectorId: number, + idTag: string, +): Promise => { + if ( + !chargingStation.getLocalAuthListEnabled() && + !chargingStation.stationInfo?.remoteAuthorization + ) { + logger.warn( + `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`, + ); + } + if ( + chargingStation.getLocalAuthListEnabled() === true && + isIdTagLocalAuthorized(chargingStation, idTag) + ) { + const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!; + connectorStatus.localAuthorizeIdTag = idTag; + connectorStatus.idTagLocalAuthorized = true; + return true; + } else if (chargingStation.stationInfo?.remoteAuthorization) { + return await isIdTagRemoteAuthorized(chargingStation, connectorId, idTag); + } + return false; +}; + +const isIdTagLocalAuthorized = (chargingStation: ChargingStation, idTag: string): boolean => { + return ( + chargingStation.hasIdTags() === true && + isNotEmptyString( + chargingStation.idTagsCache + .getIdTags(getIdTagsFile(chargingStation.stationInfo)!) + ?.find((tag) => tag === idTag), + ) + ); +}; + +const isIdTagRemoteAuthorized = async ( + chargingStation: ChargingStation, + connectorId: number, + idTag: string, +): Promise => { + chargingStation.getConnectorStatus(connectorId)!.authorizeIdTag = idTag; + return ( + ( + await chargingStation.ocppRequestService.requestHandler( + chargingStation, + RequestCommand.AUTHORIZE, + { + idTag, + }, + ) + )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED + ); +}; + +export const sendAndSetConnectorStatus = async ( + chargingStation: ChargingStation, + connectorId: number, + status: ConnectorStatusEnum, + evseId?: number, + options?: { send: boolean }, +): Promise => { + options = { send: true, ...options }; + if (options.send) { + checkConnectorStatusTransition(chargingStation, connectorId, status); + await chargingStation.ocppRequestService.requestHandler< + StatusNotificationRequest, + StatusNotificationResponse + >( + chargingStation, + RequestCommand.STATUS_NOTIFICATION, + buildStatusNotificationRequest(chargingStation, connectorId, status, evseId), + ); + } + chargingStation.getConnectorStatus(connectorId)!.status = status; + chargingStation.emit(ChargingStationEvents.connectorStatusChanged, { + connectorId, + ...chargingStation.getConnectorStatus(connectorId), + }); +}; + +const checkConnectorStatusTransition = ( + chargingStation: ChargingStation, + connectorId: number, + status: ConnectorStatusEnum, +): boolean => { + const fromStatus = chargingStation.getConnectorStatus(connectorId)!.status; + let transitionAllowed = false; + switch (chargingStation.stationInfo?.ocppVersion) { + case OCPPVersion.VERSION_16: + if ( + (connectorId === 0 && + OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex( + (transition) => transition.from === fromStatus && transition.to === status, + ) !== -1) || + (connectorId > 0 && + OCPP16Constants.ChargePointStatusConnectorTransitions.findIndex( + (transition) => transition.from === fromStatus && transition.to === status, + ) !== -1) + ) { + transitionAllowed = true; + } + break; + case OCPPVersion.VERSION_20: + case OCPPVersion.VERSION_201: + if ( + (connectorId === 0 && + OCPP20Constants.ChargingStationStatusTransitions.findIndex( + (transition) => transition.from === fromStatus && transition.to === status, + ) !== -1) || + (connectorId > 0 && + OCPP20Constants.ConnectorStatusTransitions.findIndex( + (transition) => transition.from === fromStatus && transition.to === status, + ) !== -1) + ) { + transitionAllowed = true; + } + break; + default: + throw new BaseError( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`, + ); + } + if (transitionAllowed === false) { + logger.warn( + `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo + ?.ocppVersion} connector id ${connectorId} status transition from '${ + chargingStation.getConnectorStatus(connectorId)!.status + }' to '${status}' is not allowed`, + ); + } + return transitionAllowed; +}; + export class OCPPServiceUtils { + public static getMessageTypeString = getMessageTypeString; + public static sendAndSetConnectorStatus = sendAndSetConnectorStatus; + public static isIdTagAuthorized = isIdTagAuthorized; + protected constructor() { // This is intentional } @@ -68,19 +248,6 @@ export class OCPPServiceUtils { return ErrorType.FORMAT_VIOLATION; } - public static getMessageTypeString(messageType: MessageType): string { - switch (messageType) { - case MessageType.CALL_MESSAGE: - return 'request'; - case MessageType.CALL_RESULT_MESSAGE: - return 'response'; - case MessageType.CALL_ERROR_MESSAGE: - return 'error'; - default: - return 'unknown'; - } - } - public static isRequestCommandSupported( chargingStation: ChargingStation, command: RequestCommand, @@ -169,32 +336,6 @@ export class OCPPServiceUtils { } } - public static buildStatusNotificationRequest( - chargingStation: ChargingStation, - connectorId: number, - status: ConnectorStatusEnum, - evseId?: number, - ): StatusNotificationRequest { - switch (chargingStation.stationInfo?.ocppVersion) { - case OCPPVersion.VERSION_16: - return { - connectorId, - status, - errorCode: ChargePointErrorCode.NO_ERROR, - } as OCPP16StatusNotificationRequest; - case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: - return { - timestamp: new Date(), - connectorStatus: status, - connectorId, - evseId, - } as OCPP20StatusNotificationRequest; - default: - throw new BaseError('Cannot build status notification payload: OCPP version not supported'); - } - } - public static startHeartbeatInterval(chargingStation: ChargingStation, interval: number): void { if (chargingStation.heartbeatSetInterval === undefined) { chargingStation.startHeartbeat(); @@ -203,118 +344,6 @@ export class OCPPServiceUtils { } } - public static async sendAndSetConnectorStatus( - chargingStation: ChargingStation, - connectorId: number, - status: ConnectorStatusEnum, - evseId?: number, - options?: { send: boolean }, - ) { - options = { send: true, ...options }; - if (options.send) { - OCPPServiceUtils.checkConnectorStatusTransition(chargingStation, connectorId, status); - await chargingStation.ocppRequestService.requestHandler< - StatusNotificationRequest, - StatusNotificationResponse - >( - chargingStation, - RequestCommand.STATUS_NOTIFICATION, - OCPPServiceUtils.buildStatusNotificationRequest( - chargingStation, - connectorId, - status, - evseId, - ), - ); - } - chargingStation.getConnectorStatus(connectorId)!.status = status; - chargingStation.emit(ChargingStationEvents.connectorStatusChanged, { - connectorId, - ...chargingStation.getConnectorStatus(connectorId), - }); - } - - public static async isIdTagAuthorized( - chargingStation: ChargingStation, - connectorId: number, - idTag: string, - ): Promise { - if ( - !chargingStation.getLocalAuthListEnabled() && - !chargingStation.stationInfo?.remoteAuthorization - ) { - logger.warn( - `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`, - ); - } - if ( - chargingStation.getLocalAuthListEnabled() === true && - OCPPServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag) - ) { - const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!; - connectorStatus.localAuthorizeIdTag = idTag; - connectorStatus.idTagLocalAuthorized = true; - return true; - } else if (chargingStation.stationInfo?.remoteAuthorization) { - return await OCPPServiceUtils.isIdTagRemoteAuthorized(chargingStation, connectorId, idTag); - } - return false; - } - - protected static checkConnectorStatusTransition( - chargingStation: ChargingStation, - connectorId: number, - status: ConnectorStatusEnum, - ): boolean { - const fromStatus = chargingStation.getConnectorStatus(connectorId)!.status; - let transitionAllowed = false; - switch (chargingStation.stationInfo?.ocppVersion) { - case OCPPVersion.VERSION_16: - if ( - (connectorId === 0 && - OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex( - (transition) => transition.from === fromStatus && transition.to === status, - ) !== -1) || - (connectorId > 0 && - OCPP16Constants.ChargePointStatusConnectorTransitions.findIndex( - (transition) => transition.from === fromStatus && transition.to === status, - ) !== -1) - ) { - transitionAllowed = true; - } - break; - case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: - if ( - (connectorId === 0 && - OCPP20Constants.ChargingStationStatusTransitions.findIndex( - (transition) => transition.from === fromStatus && transition.to === status, - ) !== -1) || - (connectorId > 0 && - OCPP20Constants.ConnectorStatusTransitions.findIndex( - (transition) => transition.from === fromStatus && transition.to === status, - ) !== -1) - ) { - transitionAllowed = true; - } - break; - default: - throw new BaseError( - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`, - ); - } - if (transitionAllowed === false) { - logger.warn( - `${chargingStation.logPrefix()} OCPP ${chargingStation.stationInfo - ?.ocppVersion} connector id ${connectorId} status transition from '${ - chargingStation.getConnectorStatus(connectorId)!.status - }' to '${status}' is not allowed`, - ); - } - return transitionAllowed; - } - protected static parseJsonSchemaFile( relativePath: string, ocppVersion: OCPPVersion, @@ -441,35 +470,6 @@ export class OCPPServiceUtils { return (!isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!; } - private static isIdTagLocalAuthorized(chargingStation: ChargingStation, idTag: string): boolean { - return ( - chargingStation.hasIdTags() === true && - isNotEmptyString( - chargingStation.idTagsCache - .getIdTags(getIdTagsFile(chargingStation.stationInfo)!) - ?.find((tag) => tag === idTag), - ) - ); - } - - private static async isIdTagRemoteAuthorized( - chargingStation: ChargingStation, - connectorId: number, - idTag: string, - ): Promise { - chargingStation.getConnectorStatus(connectorId)!.authorizeIdTag = idTag; - return ( - ( - await chargingStation.ocppRequestService.requestHandler< - AuthorizeRequest, - AuthorizeResponse - >(chargingStation, RequestCommand.AUTHORIZE, { - idTag, - }) - )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED - ); - } - private static logPrefix = ( ocppVersion: OCPPVersion, moduleName?: string, diff --git a/src/charging-station/ocpp/index.ts b/src/charging-station/ocpp/index.ts index 02fadb3e..2f21bd2e 100644 --- a/src/charging-station/ocpp/index.ts +++ b/src/charging-station/ocpp/index.ts @@ -8,4 +8,9 @@ export { OCPP20RequestService } from './2.0/OCPP20RequestService'; export { OCPP20ResponseService } from './2.0/OCPP20ResponseService'; export { OCPPIncomingRequestService } from './OCPPIncomingRequestService'; export { OCPPRequestService } from './OCPPRequestService'; -export { OCPPServiceUtils } from './OCPPServiceUtils'; +export { + buildStatusNotificationRequest, + getMessageTypeString, + isIdTagAuthorized, + sendAndSetConnectorStatus, +} from './OCPPServiceUtils'; -- 2.34.1