From ad8537a7e3abb286a639164a9c5a25d48e33744a Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 23 Mar 2022 23:45:58 +0100 Subject: [PATCH] Add charging profiles limit support to generated MeterValues 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 | 69 ++++++++++++----- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 14 +++- .../ocpp/1.6/OCPP16ServiceUtils.ts | 75 ++++++++++--------- 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 06276174..8240e5b0 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -235,11 +235,10 @@ export default class ChargingStation { : defaultVoltageOut; } - public getMaximumConfiguredPower(): number | undefined { - let maximumConfiguredPower = - (this.stationInfo['maxPower'] as number) ?? this.stationInfo.maximumPower; + public getConnectorMaximumAvailablePower(connectorId: number): number { + let amperageLimitationPowerLimit: number; if (this.getAmperageLimitation() < this.stationInfo.maximumAmperage) { - maximumConfiguredPower = + amperageLimitationPowerLimit = this.getCurrentOutType() === CurrentType.AC ? ACElectricUtils.powerTotal( this.getNumberOfPhases(), @@ -248,7 +247,19 @@ export default class ChargingStation { ) : DCElectricUtils.power(this.getVoltageOut(), this.getAmperageLimitation()); } - return maximumConfiguredPower; + const connectorChargingProfilePowerLimit = this.getChargingProfilePowerLimit(connectorId); + const connectorMaximumPower = + ((this.stationInfo['maxPower'] as number) ?? this.stationInfo.maximumPower) / + this.stationInfo.powerDivider; + const connectorAmperageLimitationPowerLimit = + amperageLimitationPowerLimit / this.stationInfo.powerDivider; + return Math.min( + isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower, + isNaN(connectorAmperageLimitationPowerLimit) + ? Infinity + : connectorAmperageLimitationPowerLimit, + isNaN(connectorChargingProfilePowerLimit) ? Infinity : connectorChargingProfilePowerLimit + ); } public getTransactionIdTag(transactionId: number): string | undefined { @@ -708,13 +719,11 @@ export default class ChargingStation { } } - public getChargingProfileLimit( - connectorId: number - ): { limit: number; unit: ChargingRateUnitType } | undefined { + public getChargingProfilePowerLimit(connectorId: number): number | undefined { const timestamp = new Date().getTime(); let matchingChargingProfile: ChargingProfile; let chargingSchedulePeriods: ChargingSchedulePeriod[] = []; - if (!Utils.isEmptyArray(this.getConnectorStatus(connectorId).chargingProfiles)) { + if (!Utils.isEmptyArray(this.getConnectorStatus(connectorId)?.chargingProfiles)) { const chargingProfiles: ChargingProfile[] = this.getConnectorStatus( connectorId ).chargingProfiles.filter( @@ -749,13 +758,37 @@ export default class ChargingStation { } } } - - return ( - !Utils.isEmptyArray(chargingSchedulePeriods) && { - limit: chargingSchedulePeriods[0].limit, - unit: matchingChargingProfile.chargingSchedule.chargingRateUnit, + let limit: number; + if (!Utils.isEmptyArray(chargingSchedulePeriods)) { + switch (this.getCurrentOutType()) { + case CurrentType.AC: + limit = + matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT + ? chargingSchedulePeriods[0].limit + : ACElectricUtils.powerTotal( + this.getNumberOfPhases(), + this.getVoltageOut(), + chargingSchedulePeriods[0].limit + ); + break; + case CurrentType.DC: + limit = + matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT + ? chargingSchedulePeriods[0].limit + : DCElectricUtils.power(this.getVoltageOut(), chargingSchedulePeriods[0].limit); } - ); + } + const connectorMaximumPower = + ((this.stationInfo['maxPower'] as number) ?? this.stationInfo.maximumPower) / + this.stationInfo.powerDivider; + if (limit > connectorMaximumPower) { + logger.error( + `${this.logPrefix()} Charging profile limit is greater than connector id ${connectorId} maximum, dump their stack: %j`, + this.getConnectorStatus(connectorId).chargingProfiles + ); + limit = connectorMaximumPower; + } + return limit; } public setChargingProfile(connectorId: number, cp: ChargingProfile): void { @@ -1641,16 +1674,16 @@ export default class ChargingStation { } private getMaximumAmperage(): number | undefined { + const maximumPower = (this.stationInfo['maxPower'] as number) ?? this.stationInfo.maximumPower; switch (this.getCurrentOutType()) { case CurrentType.AC: return ACElectricUtils.amperagePerPhaseFromPower( this.getNumberOfPhases(), - ((this.stationInfo['maxPower'] as number) ?? this.stationInfo.maximumPower) / - this.getNumberOfConnectors(), + maximumPower / this.getNumberOfConnectors(), this.getVoltageOut() ); case CurrentType.DC: - return DCElectricUtils.amperage(this.stationInfo.maximumPower, this.getVoltageOut()); + return DCElectricUtils.amperage(maximumPower, this.getVoltageOut()); } } diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index b67090af..298e50da 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -398,7 +398,9 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer commandPayload.csChargingProfiles ); logger.debug( - `${this.chargingStation.logPrefix()} Charging profile(s) set, dump their stack: %j`, + `${this.chargingStation.logPrefix()} Charging profile(s) set on connector id ${ + commandPayload.connectorId + }, dump their stack: %j`, this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles ); return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED; @@ -419,7 +421,9 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) { connectorStatus.chargingProfiles = []; logger.debug( - `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`, + `${this.chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${ + commandPayload.connectorId + }, dump their stack: %j`, connectorStatus.chargingProfiles ); return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED; @@ -458,7 +462,9 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer if (clearCurrentCP) { connectorStatus.chargingProfiles[index] = {} as OCPP16ChargingProfile; logger.debug( - `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`, + `${this.chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${ + commandPayload.connectorId + }, dump their stack: %j`, connectorStatus.chargingProfiles ); clearedCP = true; @@ -708,7 +714,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) { this.chargingStation.setChargingProfile(connectorId, cp); logger.debug( - `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`, + `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`, this.chargingStation.getConnectorStatus(connectorId).chargingProfiles ); return true; diff --git a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts index 64b49c46..43b291be 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts @@ -279,12 +279,11 @@ export class OCPP16ServiceUtils { } measurand value`; const powerMeasurandValues = {} as MeasurandValues; const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; - const maximumPower = Math.round( - chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider + const connectorMaximumPower = Math.round( + chargingStation.getConnectorMaximumAvailablePower(connectorId) ); - const maximumPowerPerPhase = Math.round( - chargingStation.getMaximumConfiguredPower() / - chargingStation.stationInfo.powerDivider / + const connectorMaximumPowerPerPhase = Math.round( + chargingStation.getConnectorMaximumAvailablePower(connectorId) / chargingStation.getNumberOfPhases() ); switch (chargingStation.getCurrentOutType()) { @@ -321,15 +320,15 @@ export class OCPP16ServiceUtils { powerMeasurandValues.L1 = phase1FluctuatedValue ?? defaultFluctuatedPowerPerPhase ?? - Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider); + Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); powerMeasurandValues.L2 = phase2FluctuatedValue ?? defaultFluctuatedPowerPerPhase ?? - Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider); + Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); powerMeasurandValues.L3 = phase3FluctuatedValue ?? defaultFluctuatedPowerPerPhase ?? - Utils.getRandomFloatRounded(maximumPowerPerPhase / unitDivider); + Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); } else { powerMeasurandValues.L1 = powerSampledValueTemplate.value ? Utils.getRandomFloatFluctuatedRounded( @@ -337,7 +336,7 @@ export class OCPP16ServiceUtils { powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumPower / unitDivider); + : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); powerMeasurandValues.L2 = 0; powerMeasurandValues.L3 = 0; } @@ -353,7 +352,7 @@ export class OCPP16ServiceUtils { powerSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumPower / unitDivider); + : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); break; default: logger.error(errMsg); @@ -366,10 +365,10 @@ export class OCPP16ServiceUtils { ) ); const sampledValuesIndex = meterValue.sampledValue.length - 1; - const maximumPowerRounded = Utils.roundTo(maximumPower / unitDivider, 2); + const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2); if ( Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > - maximumPowerRounded || + connectorMaximumPowerRounded || debug ) { logger.error( @@ -378,7 +377,7 @@ export class OCPP16ServiceUtils { OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesIndex].value - }/${maximumPowerRounded}` + }/${connectorMaximumPowerRounded}` ); } for ( @@ -397,10 +396,13 @@ export class OCPP16ServiceUtils { ) ); const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; - const maximumPowerPerPhaseRounded = Utils.roundTo(maximumPowerPerPhase / unitDivider, 2); + const connectorMaximumPowerPerPhaseRounded = Utils.roundTo( + connectorMaximumPowerPerPhase / unitDivider, + 2 + ); if ( Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > - maximumPowerPerPhaseRounded || + connectorMaximumPowerPerPhaseRounded || debug ) { logger.error( @@ -411,7 +413,7 @@ export class OCPP16ServiceUtils { meterValue.sampledValue[sampledValuesPerPhaseIndex].phase }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesPerPhaseIndex].value - }/${maximumPowerPerPhaseRounded}` + }/${connectorMaximumPowerPerPhaseRounded}` ); } } @@ -456,12 +458,12 @@ export class OCPP16ServiceUtils { OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER } measurand value`; const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; - let maximumAmperage: number; + let connectorMaximumAmperage: number; switch (chargingStation.getCurrentOutType()) { case CurrentType.AC: - maximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( + connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( chargingStation.getNumberOfPhases(), - chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider, + chargingStation.getConnectorMaximumAvailablePower(connectorId), chargingStation.getVoltageOut() ); if (chargingStation.getNumberOfPhases() === 3) { @@ -496,15 +498,15 @@ export class OCPP16ServiceUtils { currentMeasurandValues.L1 = phase1FluctuatedValue ?? defaultFluctuatedAmperagePerPhase ?? - Utils.getRandomFloatRounded(maximumAmperage); + Utils.getRandomFloatRounded(connectorMaximumAmperage); currentMeasurandValues.L2 = phase2FluctuatedValue ?? defaultFluctuatedAmperagePerPhase ?? - Utils.getRandomFloatRounded(maximumAmperage); + Utils.getRandomFloatRounded(connectorMaximumAmperage); currentMeasurandValues.L3 = phase3FluctuatedValue ?? defaultFluctuatedAmperagePerPhase ?? - Utils.getRandomFloatRounded(maximumAmperage); + Utils.getRandomFloatRounded(connectorMaximumAmperage); } else { currentMeasurandValues.L1 = currentSampledValueTemplate.value ? Utils.getRandomFloatFluctuatedRounded( @@ -512,7 +514,7 @@ export class OCPP16ServiceUtils { currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumAmperage); + : Utils.getRandomFloatRounded(connectorMaximumAmperage); currentMeasurandValues.L2 = 0; currentMeasurandValues.L3 = 0; } @@ -523,8 +525,8 @@ export class OCPP16ServiceUtils { ); break; case CurrentType.DC: - maximumAmperage = DCElectricUtils.amperage( - chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider, + connectorMaximumAmperage = DCElectricUtils.amperage( + chargingStation.getConnectorMaximumAvailablePower(connectorId), chargingStation.getVoltageOut() ); currentMeasurandValues.allPhases = currentSampledValueTemplate.value @@ -533,7 +535,7 @@ export class OCPP16ServiceUtils { currentSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumAmperage); + : Utils.getRandomFloatRounded(connectorMaximumAmperage); break; default: logger.error(errMsg); @@ -547,7 +549,8 @@ export class OCPP16ServiceUtils { ); const sampledValuesIndex = meterValue.sampledValue.length - 1; if ( - Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maximumAmperage || + Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > + connectorMaximumAmperage || debug ) { logger.error( @@ -556,7 +559,7 @@ export class OCPP16ServiceUtils { OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesIndex].value - }/${maximumAmperage}` + }/${connectorMaximumAmperage}` ); } for ( @@ -577,7 +580,7 @@ export class OCPP16ServiceUtils { const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; if ( Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > - maximumAmperage || + connectorMaximumAmperage || debug ) { logger.error( @@ -588,7 +591,7 @@ export class OCPP16ServiceUtils { meterValue.sampledValue[sampledValuesPerPhaseIndex].phase }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ meterValue.sampledValue[sampledValuesPerPhaseIndex].value - }/${maximumAmperage}` + }/${connectorMaximumAmperage}` ); } } @@ -602,10 +605,8 @@ export class OCPP16ServiceUtils { ); const unitDivider = energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; - const maximumEnergyRounded = Utils.roundTo( - ((chargingStation.getMaximumConfiguredPower() / chargingStation.stationInfo.powerDivider) * - interval) / - (3600 * 1000), + const connectorMaximumEnergyRounded = Utils.roundTo( + (chargingStation.getConnectorMaximumAvailablePower(connectorId) * interval) / (3600 * 1000), 2 ); const energyValueRounded = energySampledValueTemplate.value @@ -614,7 +615,7 @@ export class OCPP16ServiceUtils { parseInt(energySampledValueTemplate.value), energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT ) - : Utils.getRandomFloatRounded(maximumEnergyRounded); + : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded); // Persist previous value on connector if ( connector && @@ -640,14 +641,14 @@ export class OCPP16ServiceUtils { ) ); const sampledValuesIndex = meterValue.sampledValue.length - 1; - if (energyValueRounded > maximumEnergyRounded || debug) { + if (energyValueRounded > connectorMaximumEnergyRounded || debug) { logger.error( `${chargingStation.logPrefix()} MeterValues measurand ${ meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER }: connectorId ${connectorId}, transaction ${ connector.transactionId - }, value: ${energyValueRounded}/${maximumEnergyRounded}, duration: ${Utils.roundTo( + }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo( interval / (3600 * 1000), 4 )}h` -- 2.34.1