From 357991053f9d8910cdfaebde426eca58f813c05f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 10 Jun 2024 15:41:44 +0200 Subject: [PATCH] feat: handle CHARGE_POINT_MAX_PROFILE charging profiles at power limitation MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/ChargingStation.ts | 24 ++-- src/charging-station/Helpers.ts | 134 ++++++++++++------ .../ocpp/1.6/OCPP16IncomingRequestService.ts | 1 + 3 files changed, 108 insertions(+), 51 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 7dbb0d3e..e217991a 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -120,8 +120,9 @@ import { createSerialNumber, getAmperageLimitationUnitDivider, getBootConnectorStatus, - getChargingStationConnectorChargingProfilesPowerLimit, + getChargingStationChargingProfilesLimit, getChargingStationId, + getConnectorChargingProfilesLimit, getDefaultVoltageOut, getHashId, getIdTagsFile, @@ -391,14 +392,14 @@ export class ChargingStation extends EventEmitter { } public getConnectorMaximumAvailablePower (connectorId: number): number { - let connectorAmperageLimitationPowerLimit: number | undefined + let connectorAmperageLimitationLimit: number | undefined const amperageLimitation = this.getAmperageLimitation() if ( amperageLimitation != null && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion amperageLimitation < this.stationInfo!.maximumAmperage! ) { - connectorAmperageLimitationPowerLimit = + connectorAmperageLimitationLimit = (this.stationInfo?.currentOutType === CurrentType.AC ? ACElectricUtils.powerTotal( this.getNumberOfPhases(), @@ -414,20 +415,25 @@ export class ChargingStation extends EventEmitter { } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const connectorMaximumPower = this.stationInfo!.maximumPower! / this.powerDivider! - const connectorChargingProfilesPowerLimit = - getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId) + const chargingStationChargingProfilesLimit = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + getChargingStationChargingProfilesLimit(this)! / this.powerDivider! + const connectorChargingProfilesLimit = getConnectorChargingProfilesLimit(this, connectorId) return min( isNaN(connectorMaximumPower) ? Number.POSITIVE_INFINITY : connectorMaximumPower, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - isNaN(connectorAmperageLimitationPowerLimit!) + isNaN(connectorAmperageLimitationLimit!) ? Number.POSITIVE_INFINITY : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - connectorAmperageLimitationPowerLimit!, + connectorAmperageLimitationLimit!, + isNaN(chargingStationChargingProfilesLimit) + ? Number.POSITIVE_INFINITY + : chargingStationChargingProfilesLimit, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - isNaN(connectorChargingProfilesPowerLimit!) + isNaN(connectorChargingProfilesLimit!) ? Number.POSITIVE_INFINITY : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - connectorChargingProfilesPowerLimit! + connectorChargingProfilesLimit! ) } diff --git a/src/charging-station/Helpers.ts b/src/charging-station/Helpers.ts index 816db88b..cc0422e1 100644 --- a/src/charging-station/Helpers.ts +++ b/src/charging-station/Helpers.ts @@ -628,8 +628,46 @@ export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInf return unitDivider } +const getChargingStationChargingProfiles = ( + chargingStation: ChargingStation +): ChargingProfile[] => { + return (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? []) + .filter( + chargingProfile => + chargingProfile.chargingProfilePurpose === + ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE + ) + .sort((a, b) => b.stackLevel - a.stackLevel) +} + +export const getChargingStationChargingProfilesLimit = ( + chargingStation: ChargingStation +): number | undefined => { + const chargingProfiles = getChargingStationChargingProfiles(chargingStation) + if (isNotEmptyArray(chargingProfiles)) { + const chargingProfilesLimit = getChargingProfilesLimit(chargingStation, 0, chargingProfiles) + if (chargingProfilesLimit != null) { + const limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit) + const chargingStationMaximumPower = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo!.maximumPower! + if (limit > chargingStationMaximumPower) { + logger.error( + `${chargingStation.logPrefix()} ${moduleName}.getChargingStationChargingProfilesLimit: Charging profile id ${ + chargingProfilesLimit.chargingProfile.chargingProfileId + } limit ${limit} is greater than charging station maximum ${chargingStationMaximumPower}: %j`, + chargingProfilesLimit + ) + return chargingStationMaximumPower + } + return limit + } + } +} + /** - * Gets the connector charging profiles relevant for power limitation shallow cloned and sorted by priorities + * Gets the connector charging profiles relevant for power limitation shallow cloned + * and sorted by priorities * * @param chargingStation - Charging station * @param connectorId - Connector id @@ -639,7 +677,6 @@ export const getConnectorChargingProfiles = ( chargingStation: ChargingStation, connectorId: number ): ChargingProfile[] => { - // FIXME: handle charging profile purpose CHARGE_POINT_MAX_PROFILE return (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? []) .slice() .sort((a, b) => { @@ -666,47 +703,27 @@ export const getConnectorChargingProfiles = ( ) } -export const getChargingStationConnectorChargingProfilesPowerLimit = ( +export const getConnectorChargingProfilesLimit = ( chargingStation: ChargingStation, connectorId: number ): number | undefined => { const chargingProfiles = getConnectorChargingProfiles(chargingStation, connectorId) if (isNotEmptyArray(chargingProfiles)) { - const chargingProfilesLimit = getLimitFromChargingProfiles( + const chargingProfilesLimit = getChargingProfilesLimit( chargingStation, connectorId, - chargingProfiles, - chargingStation.logPrefix() + chargingProfiles ) if (chargingProfilesLimit != null) { - let { limit, chargingProfile } = chargingProfilesLimit - switch (chargingStation.stationInfo?.currentOutType) { - case CurrentType.AC: - limit = - chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT - ? limit - : ACElectricUtils.powerTotal( - chargingStation.getNumberOfPhases(), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - chargingStation.stationInfo.voltageOut!, - limit - ) - break - case CurrentType.DC: - limit = - chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT - ? limit - : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit) - } + let limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit) const connectorMaximumPower = // eslint-disable-next-line @typescript-eslint/no-non-null-assertion chargingStation.stationInfo!.maximumPower! / chargingStation.powerDivider! if (limit > connectorMaximumPower) { logger.error( - `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${ - chargingProfile.chargingProfileId - } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`, + `${chargingStation.logPrefix()} ${moduleName}.getConnectorChargingProfilesLimit: Charging profile id ${ + chargingProfilesLimit.chargingProfile.chargingProfileId + } limit ${limit} is greater than connector ${connectorId} maximum ${connectorMaximumPower}: %j`, chargingProfilesLimit ) limit = connectorMaximumPower @@ -716,6 +733,33 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = ( } } +const buildChargingProfilesLimit = ( + chargingStation: ChargingStation, + chargingProfilesLimit: ChargingProfilesLimit +): number => { + let { limit, chargingProfile } = chargingProfilesLimit + switch (chargingStation.stationInfo?.currentOutType) { + case CurrentType.AC: + limit = + chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT + ? limit + : ACElectricUtils.powerTotal( + chargingStation.getNumberOfPhases(), + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chargingStation.stationInfo.voltageOut!, + limit + ) + break + case CurrentType.DC: + limit = + chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT + ? limit + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit) + } + return limit +} + export const getDefaultVoltageOut = ( currentType: CurrentType, logPrefix: string, @@ -868,21 +912,20 @@ interface ChargingProfilesLimit { } /** - * Charging profiles shall already be sorted by connector id descending then stack level descending + * Get the charging profiles limit for a connector + * Charging profiles shall already be sorted by priorities * * @param chargingStation - * @param connectorId - * @param chargingProfiles - - * @param logPrefix - * @returns ChargingProfilesLimit */ -const getLimitFromChargingProfiles = ( +const getChargingProfilesLimit = ( chargingStation: ChargingStation, connectorId: number, - chargingProfiles: ChargingProfile[], - logPrefix: string + chargingProfiles: ChargingProfile[] ): ChargingProfilesLimit | undefined => { - const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profiles limit found: %j` + const debugLogMsg = `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profiles limit found: %j` const currentDate = new Date() const connectorStatus = chargingStation.getConnectorStatus(connectorId) let previousActiveChargingProfile: ChargingProfile | undefined @@ -890,29 +933,36 @@ const getLimitFromChargingProfiles = ( const chargingSchedule = chargingProfile.chargingSchedule if (chargingSchedule.startSchedule == null) { logger.debug( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined. Trying to set it to the connector current transaction start date` + `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined. Trying to set it to the connector current transaction start date` ) // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction chargingSchedule.startSchedule = connectorStatus?.transactionStart } if (!isDate(chargingSchedule.startSchedule)) { logger.warn( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance` + `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance` ) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion chargingSchedule.startSchedule = convertToDate(chargingSchedule.startSchedule)! } if (chargingSchedule.duration == null) { logger.debug( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined and will be set to the maximum time allowed` + `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined and will be set to the maximum time allowed` ) // OCPP specifies that if duration is not defined, it should be infinite chargingSchedule.duration = differenceInSeconds(maxTime, chargingSchedule.startSchedule) } - if (!prepareChargingProfileKind(connectorStatus, chargingProfile, currentDate, logPrefix)) { + if ( + !prepareChargingProfileKind( + connectorStatus, + chargingProfile, + currentDate, + chargingStation.logPrefix() + ) + ) { continue } - if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) { + if (!canProceedChargingProfile(chargingProfile, currentDate, chargingStation.logPrefix())) { continue } // Check if the charging profile is active @@ -934,14 +984,14 @@ const getLimitFromChargingProfiles = ( ) ) { logger.warn( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period` + `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period` ) chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn) } // Check if the first schedule period startPeriod property is equal to 0 if (chargingSchedule.chargingSchedulePeriod[0].startPeriod !== 0) { logger.error( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0` + `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0` ) continue } diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index ec55ce97..4b6a7c13 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -979,6 +979,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { start: currentDate, end: addSeconds(currentDate, duration) } + // FIXME: add and handle charging station charging profiles const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles( chargingStation, connectorId -- 2.34.1