fix: ensure per phase meterValues can't fail with custom value
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index 0086877c038bd9699328b90a0ab3f0512a1f8ce2..b28d682e9d654066143214f6ba834b017f1bcdaa 100644 (file)
@@ -194,6 +194,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               ? (phase + 1) % chargingStation.getNumberOfPhases()
               : chargingStation.getNumberOfPhases()
           }`;
+          const voltagePhaseLineToLineValueRounded = roundTo(
+            Math.sqrt(chargingStation.getNumberOfPhases()) *
+              chargingStation.stationInfo.voltageOut!,
+            2,
+          );
           const voltagePhaseLineToLineSampledValueTemplate =
             OCPP16ServiceUtils.getSampledValueTemplate(
               chargingStation,
@@ -207,11 +212,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
               voltagePhaseLineToLineSampledValueTemplate.value,
             )
               ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
-              : roundTo(
-                  Math.sqrt(chargingStation.getNumberOfPhases()) *
-                    chargingStation.stationInfo.voltageOut!,
-                  2,
-                );
+              : voltagePhaseLineToLineValueRounded;
             const fluctuationPhaseLineToLinePercent =
               voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
               Constants.DEFAULT_FLUCTUATION_PERCENT;
@@ -221,8 +222,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             );
           }
           const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-            Math.sqrt(chargingStation.getNumberOfPhases()) *
-              chargingStation.stationInfo.voltageOut!,
+            voltagePhaseLineToLineValueRounded,
             fluctuationPercent,
           );
           meterValue.sampledValue.push(
@@ -287,99 +287,115 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       const connectorMaximumPowerPerPhase = Math.round(
         connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(),
       );
-      const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue!) ?? 0;
+      const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue ?? 0);
       const connectorMinimumPowerPerPhase = Math.round(
         connectorMinimumPower / chargingStation.getNumberOfPhases(),
       );
       switch (chargingStation.stationInfo?.currentOutType) {
         case CurrentType.AC:
           if (chargingStation.getNumberOfPhases() === 3) {
-            const defaultFluctuatedPowerPerPhase =
-              powerSampledValueTemplate.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  powerSampledValueTemplate.value,
-                  connectorMaximumPower / unitDivider,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ) / chargingStation.getNumberOfPhases(),
-                powerSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
-            const phase1FluctuatedValue =
-              powerPerPhaseSampledValueTemplates.L1?.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  powerPerPhaseSampledValueTemplates.L1.value,
-                  connectorMaximumPowerPerPhase / unitDivider,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ),
-                powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
-            const phase2FluctuatedValue =
-              powerPerPhaseSampledValueTemplates.L2?.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  powerPerPhaseSampledValueTemplates.L2.value,
-                  connectorMaximumPowerPerPhase / unitDivider,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ),
-                powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
-            const phase3FluctuatedValue =
-              powerPerPhaseSampledValueTemplates.L3?.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  powerPerPhaseSampledValueTemplates.L3.value,
-                  connectorMaximumPowerPerPhase / unitDivider,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ),
-                powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
+            const defaultFluctuatedPowerPerPhase = isNotEmptyString(powerSampledValueTemplate.value)
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    powerSampledValueTemplate.value,
+                    connectorMaximumPower / unitDivider,
+                    connectorMinimumPower / unitDivider,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumPower / unitDivider,
+                    },
+                  ) / chargingStation.getNumberOfPhases(),
+                  powerSampledValueTemplate.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
+            const phase1FluctuatedValue = isNotEmptyString(
+              powerPerPhaseSampledValueTemplates.L1?.value,
+            )
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    powerPerPhaseSampledValueTemplates.L1?.value,
+                    connectorMaximumPowerPerPhase / unitDivider,
+                    connectorMinimumPowerPerPhase / unitDivider,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                    },
+                  ),
+                  powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
+            const phase2FluctuatedValue = isNotEmptyString(
+              powerPerPhaseSampledValueTemplates.L2?.value,
+            )
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    powerPerPhaseSampledValueTemplates.L2?.value,
+                    connectorMaximumPowerPerPhase / unitDivider,
+                    connectorMinimumPowerPerPhase / unitDivider,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                    },
+                  ),
+                  powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
+            const phase3FluctuatedValue = isNotEmptyString(
+              powerPerPhaseSampledValueTemplates.L3?.value,
+            )
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    powerPerPhaseSampledValueTemplates.L3?.value,
+                    connectorMaximumPowerPerPhase / unitDivider,
+                    connectorMinimumPowerPerPhase / unitDivider,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                    },
+                  ),
+                  powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
             powerMeasurandValues.L1 =
-              (phase1FluctuatedValue as number) ??
-              (defaultFluctuatedPowerPerPhase as number) ??
+              phase1FluctuatedValue ??
+              defaultFluctuatedPowerPerPhase ??
               getRandomFloatRounded(
                 connectorMaximumPowerPerPhase / unitDivider,
                 connectorMinimumPowerPerPhase / unitDivider,
               );
             powerMeasurandValues.L2 =
-              (phase2FluctuatedValue as number) ??
-              (defaultFluctuatedPowerPerPhase as number) ??
+              phase2FluctuatedValue ??
+              defaultFluctuatedPowerPerPhase ??
               getRandomFloatRounded(
                 connectorMaximumPowerPerPhase / unitDivider,
                 connectorMinimumPowerPerPhase / unitDivider,
               );
             powerMeasurandValues.L3 =
-              (phase3FluctuatedValue as number) ??
-              (defaultFluctuatedPowerPerPhase as number) ??
+              phase3FluctuatedValue ??
+              defaultFluctuatedPowerPerPhase ??
               getRandomFloatRounded(
                 connectorMaximumPowerPerPhase / unitDivider,
                 connectorMinimumPowerPerPhase / unitDivider,
               );
           } else {
-            powerMeasurandValues.L1 = powerSampledValueTemplate.value
+            powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value)
               ? getRandomFloatFluctuatedRounded(
                   OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                     powerSampledValueTemplate.value,
                     connectorMaximumPower / unitDivider,
+                    connectorMinimumPower / unitDivider,
                     {
                       limitationEnabled:
                         chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumPower / unitDivider,
                     },
                   ),
                   powerSampledValueTemplate.fluctuationPercent ??
@@ -403,9 +419,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   powerSampledValueTemplate.value,
                   connectorMaximumPower / unitDivider,
+                  connectorMinimumPower / unitDivider,
                   {
                     limitationEnabled:
                       chargingStation.stationInfo?.customValueLimitationMeterValues,
+                    fallbackValue: connectorMinimumPower / unitDivider,
                   },
                 ),
                 powerSampledValueTemplate.fluctuationPercent ??
@@ -455,7 +473,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           OCPP16ServiceUtils.buildSampledValue(
             powerPerPhaseSampledValueTemplates[
               `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
-            ]! ?? powerSampledValueTemplate,
+            ] ?? powerSampledValueTemplate,
             powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
             undefined,
             phaseValue as OCPP16MeterValuePhase,
@@ -546,73 +564,89 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
             chargingStation.stationInfo.voltageOut!,
           );
           if (chargingStation.getNumberOfPhases() === 3) {
-            const defaultFluctuatedAmperagePerPhase =
-              currentSampledValueTemplate.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  currentSampledValueTemplate.value,
-                  connectorMaximumAmperage,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ),
-                currentSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
-            const phase1FluctuatedValue =
-              currentPerPhaseSampledValueTemplates.L1?.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  currentPerPhaseSampledValueTemplates.L1.value,
-                  connectorMaximumAmperage,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ),
-                currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
-            const phase2FluctuatedValue =
-              currentPerPhaseSampledValueTemplates.L2?.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  currentPerPhaseSampledValueTemplates.L2.value,
-                  connectorMaximumAmperage,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ),
-                currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
-            const phase3FluctuatedValue =
-              currentPerPhaseSampledValueTemplates.L3?.value &&
-              getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  currentPerPhaseSampledValueTemplates.L3.value,
-                  connectorMaximumAmperage,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                  },
-                ),
-                currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              );
+            const defaultFluctuatedAmperagePerPhase = isNotEmptyString(
+              currentSampledValueTemplate.value,
+            )
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    currentSampledValueTemplate.value,
+                    connectorMaximumAmperage,
+                    connectorMinimumAmperage,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumAmperage,
+                    },
+                  ),
+                  currentSampledValueTemplate.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
+            const phase1FluctuatedValue = isNotEmptyString(
+              currentPerPhaseSampledValueTemplates.L1?.value,
+            )
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    currentPerPhaseSampledValueTemplates.L1?.value,
+                    connectorMaximumAmperage,
+                    connectorMinimumAmperage,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumAmperage,
+                    },
+                  ),
+                  currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
+            const phase2FluctuatedValue = isNotEmptyString(
+              currentPerPhaseSampledValueTemplates.L2?.value,
+            )
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    currentPerPhaseSampledValueTemplates.L2?.value,
+                    connectorMaximumAmperage,
+                    connectorMinimumAmperage,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumAmperage,
+                    },
+                  ),
+                  currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
+            const phase3FluctuatedValue = isNotEmptyString(
+              currentPerPhaseSampledValueTemplates.L3?.value,
+            )
+              ? getRandomFloatFluctuatedRounded(
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    currentPerPhaseSampledValueTemplates.L3?.value,
+                    connectorMaximumAmperage,
+                    connectorMinimumAmperage,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumAmperage,
+                    },
+                  ),
+                  currentPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
+                    Constants.DEFAULT_FLUCTUATION_PERCENT,
+                )
+              : undefined;
             currentMeasurandValues.L1 =
-              (phase1FluctuatedValue as number) ??
-              (defaultFluctuatedAmperagePerPhase as number) ??
+              phase1FluctuatedValue ??
+              defaultFluctuatedAmperagePerPhase ??
               getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
             currentMeasurandValues.L2 =
-              (phase2FluctuatedValue as number) ??
-              (defaultFluctuatedAmperagePerPhase as number) ??
+              phase2FluctuatedValue ??
+              defaultFluctuatedAmperagePerPhase ??
               getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
             currentMeasurandValues.L3 =
-              (phase3FluctuatedValue as number) ??
-              (defaultFluctuatedAmperagePerPhase as number) ??
+              phase3FluctuatedValue ??
+              defaultFluctuatedAmperagePerPhase ??
               getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
           } else {
             currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value)
@@ -620,9 +654,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                     currentSampledValueTemplate.value,
                     connectorMaximumAmperage,
+                    connectorMinimumAmperage,
                     {
                       limitationEnabled:
                         chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumAmperage,
                     },
                   ),
                   currentSampledValueTemplate.fluctuationPercent ??
@@ -648,9 +684,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
                   currentSampledValueTemplate.value,
                   connectorMaximumAmperage,
+                  connectorMinimumAmperage,
                   {
                     limitationEnabled:
                       chargingStation.stationInfo?.customValueLimitationMeterValues,
+                    fallbackValue: connectorMinimumAmperage,
                   },
                 ),
                 currentSampledValueTemplate.fluctuationPercent ??
@@ -695,7 +733,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
           OCPP16ServiceUtils.buildSampledValue(
             currentPerPhaseSampledValueTemplates[
               phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
-            ]! ?? currentSampledValueTemplate,
+            ] ?? currentSampledValueTemplate,
             currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
             undefined,
             phaseValue as OCPP16MeterValuePhase,
@@ -740,20 +778,25 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         (connectorMaximumAvailablePower * interval) / (3600 * 1000),
         2,
       );
-      const energyValueRounded = energySampledValueTemplate.value
-        ? // Cumulate the fluctuated value around the static one
-          getRandomFloatFluctuatedRounded(
+      const connectorMinimumEnergyRounded = roundTo(
+        energySampledValueTemplate.minimumValue ?? 0,
+        2,
+      );
+      const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value)
+        ? getRandomFloatFluctuatedRounded(
             OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
               energySampledValueTemplate.value,
               connectorMaximumEnergyRounded,
+              connectorMinimumEnergyRounded,
               {
                 limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
                 unitMultiplier: unitDivider,
+                fallbackValue: connectorMinimumEnergyRounded,
               },
             ),
             energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
           )
-        : getRandomFloatRounded(connectorMaximumEnergyRounded);
+        : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded);
       // Persist previous value on connector
       if (connector) {
         if (
@@ -780,12 +823,16 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         ),
       );
       const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      if (energyValueRounded > connectorMaximumEnergyRounded || debug) {
+      if (
+        energyValueRounded > connectorMaximumEnergyRounded ||
+        energyValueRounded < connectorMinimumEnergyRounded ||
+        debug
+      ) {
         logger.error(
           `${chargingStation.logPrefix()} MeterValues measurand ${
             meterValue.sampledValue[sampledValuesIndex].measurand ??
             OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
+          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
         );
       }
     }