Add charging profiles limit support to generated MeterValues
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 23 Mar 2022 22:45:58 +0000 (23:45 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 23 Mar 2022 22:45:58 +0000 (23:45 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts

index 06276174e9c851f86c9c5107573ac7f2e2f81e04..8240e5b0521a1ec80ab093d9646997daed6bbc11 100644 (file)
@@ -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());
     }
   }
 
index b67090af8fc6a9e7a5794cda32f6ed152c9e0645..298e50da0f0ba524ac64ba4fa16bb76280b73cac 100644 (file)
@@ -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;
index 64b49c46d4f2f165ec320d56fcfead811babf048..43b291be0680777088d9bef662cc1c16ab239999 100644 (file)
@@ -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`