Ensure charging station data is always JSON serializable
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
index 2ad75f1d2234725f20cf239557c322f219bbfa04..62fa8e50b19a7617654157e887a9639d37113d2c 100644 (file)
@@ -1,10 +1,15 @@
 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
 
-import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
+import OCPPError from '../../../exception/OCPPError';
 import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
-import MeasurandPerPhaseSampledValueTemplates, {
-  SampledValueTemplate,
-} from '../../../types/MeasurandPerPhaseSampledValueTemplates';
+import type MeasurandPerPhaseSampledValueTemplates from '../../../types/MeasurandPerPhaseSampledValueTemplates';
+// eslint-disable-next-line no-duplicate-imports
+import type { SampledValueTemplate } from '../../../types/MeasurandPerPhaseSampledValueTemplates';
+import type MeasurandValues from '../../../types/MeasurandValues';
+import {
+  OCPP16StandardParametersKey,
+  OCPP16SupportedFeatureProfiles,
+} from '../../../types/ocpp/1.6/Configuration';
 import {
   MeterValueContext,
   MeterValueLocation,
@@ -18,39 +23,16 @@ import {
   OCPP16IncomingRequestCommand,
   OCPP16RequestCommand,
 } from '../../../types/ocpp/1.6/Requests';
-import {
-  OCPP16StandardParametersKey,
-  OCPP16SupportedFeatureProfiles,
-} from '../../../types/ocpp/1.6/Configuration';
-
-import type ChargingStation from '../../ChargingStation';
-import Constants from '../../../utils/Constants';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
-import MeasurandValues from '../../../types/MeasurandValues';
-import OCPPError from '../../../exception/OCPPError';
-import Utils from '../../../utils/Utils';
+import Constants from '../../../utils/Constants';
+import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
 import logger from '../../../utils/Logger';
+import Utils from '../../../utils/Utils';
+import type ChargingStation from '../../ChargingStation';
+import { ChargingStationUtils } from '../../ChargingStationUtils';
+import { OCPPServiceUtils } from '../OCPPServiceUtils';
 
-export class OCPP16ServiceUtils {
-  public static checkMeasurandPowerDivider(
-    chargingStation: ChargingStation,
-    measurandType: OCPP16MeterValueMeasurand
-  ): void {
-    if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) {
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
-        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: powerDivider is undefined`;
-      logger.error(errMsg);
-      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-    } else if (chargingStation.stationInfo?.powerDivider <= 0) {
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
-        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
-      logger.error(errMsg);
-      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-    }
-  }
-
+export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static checkFeatureProfile(
     chargingStation: ChargingStation,
     featureProfile: OCPP16SupportedFeatureProfiles,
@@ -67,63 +49,6 @@ export class OCPP16ServiceUtils {
     return true;
   }
 
-  public static buildSampledValue(
-    sampledValueTemplate: SampledValueTemplate,
-    value: number,
-    context?: MeterValueContext,
-    phase?: OCPP16MeterValuePhase
-  ): OCPP16SampledValue {
-    const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
-    const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
-    const sampledValueLocation =
-      sampledValueTemplate?.location ??
-      OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null);
-    const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
-    return {
-      ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && {
-        unit: sampledValueTemplate.unit,
-      }),
-      ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
-      ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && {
-        measurand: sampledValueTemplate.measurand,
-      }),
-      ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
-      ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
-      ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
-    };
-  }
-
-  public static getMeasurandDefaultUnit(
-    measurandType: OCPP16MeterValueMeasurand
-  ): MeterValueUnit | undefined {
-    switch (measurandType) {
-      case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
-      case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
-      case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
-        return MeterValueUnit.AMP;
-      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
-      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
-        return MeterValueUnit.WATT_HOUR;
-      case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
-      case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
-      case OCPP16MeterValueMeasurand.POWER_OFFERED:
-        return MeterValueUnit.WATT;
-      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
-        return MeterValueUnit.PERCENT;
-      case OCPP16MeterValueMeasurand.VOLTAGE:
-        return MeterValueUnit.VOLT;
-    }
-  }
-
-  public static getMeasurandDefaultLocation(
-    measurandType: OCPP16MeterValueMeasurand
-  ): MeterValueLocation | undefined {
-    switch (measurandType) {
-      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
-        return MeterValueLocation.EV;
-    }
-  }
-
   public static buildMeterValue(
     chargingStation: ChargingStation,
     connectorId: number,
@@ -137,7 +62,8 @@ export class OCPP16ServiceUtils {
     };
     const connector = chargingStation.getConnectorStatus(connectorId);
     // SoC measurand
-    const socSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const socSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.STATE_OF_CHARGE
     );
@@ -164,7 +90,8 @@ export class OCPP16ServiceUtils {
       }
     }
     // Voltage measurand
-    const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const voltageSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.VOLTAGE
     );
@@ -193,7 +120,8 @@ export class OCPP16ServiceUtils {
       ) {
         const phaseLineToNeutralValue = `L${phase}-N`;
         const voltagePhaseLineToNeutralSampledValueTemplate =
-          chargingStation.getSampledValueTemplate(
+          ChargingStationUtils.getSampledValueTemplate(
+            chargingStation,
             connectorId,
             OCPP16MeterValueMeasurand.VOLTAGE,
             phaseLineToNeutralValue as OCPP16MeterValuePhase
@@ -227,7 +155,8 @@ export class OCPP16ServiceUtils {
               : chargingStation.getNumberOfPhases()
           }`;
           const voltagePhaseLineToLineSampledValueTemplate =
-            chargingStation.getSampledValueTemplate(
+            ChargingStationUtils.getSampledValueTemplate(
+              chargingStation,
               connectorId,
               OCPP16MeterValueMeasurand.VOLTAGE,
               phaseLineToLineValue as OCPP16MeterValuePhase
@@ -262,24 +191,28 @@ export class OCPP16ServiceUtils {
       }
     }
     // Power.Active.Import measurand
-    const powerSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const powerSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
     );
     let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
     if (chargingStation.getNumberOfPhases() === 3) {
       powerPerPhaseSampledValueTemplates = {
-        L1: chargingStation.getSampledValueTemplate(
+        L1: ChargingStationUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
           OCPP16MeterValuePhase.L1_N
         ),
-        L2: chargingStation.getSampledValueTemplate(
+        L2: ChargingStationUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
           OCPP16MeterValuePhase.L2_N
         ),
-        L3: chargingStation.getSampledValueTemplate(
+        L3: ChargingStationUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
           OCPP16MeterValuePhase.L3_N
@@ -291,7 +224,7 @@ export class OCPP16ServiceUtils {
         chargingStation,
         powerSampledValueTemplate.measurand
       );
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+      const errMsg = `MeterValues measurand ${
         powerSampledValueTemplate.measurand ??
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
@@ -302,12 +235,11 @@ export class OCPP16ServiceUtils {
       } measurand value`;
       const powerMeasurandValues = {} as MeasurandValues;
       const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
-      const connectorMaximumPower = Math.round(
-        chargingStation.getConnectorMaximumAvailablePower(connectorId)
-      );
+      const connectorMaximumAvailablePower =
+        chargingStation.getConnectorMaximumAvailablePower(connectorId);
+      const connectorMaximumPower = Math.round(connectorMaximumAvailablePower);
       const connectorMaximumPowerPerPhase = Math.round(
-        chargingStation.getConnectorMaximumAvailablePower(connectorId) /
-          chargingStation.getNumberOfPhases()
+        connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
       );
       switch (chargingStation.getCurrentOutType()) {
         case CurrentType.AC:
@@ -315,28 +247,44 @@ export class OCPP16ServiceUtils {
             const defaultFluctuatedPowerPerPhase =
               powerSampledValueTemplate.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  powerSampledValueTemplate.value,
+                  connectorMaximumPower / unitDivider,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ) / chargingStation.getNumberOfPhases(),
                 powerSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
             const phase1FluctuatedValue =
               powerPerPhaseSampledValueTemplates?.L1?.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerPerPhaseSampledValueTemplates.L1.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  powerPerPhaseSampledValueTemplates.L1.value,
+                  connectorMaximumPowerPerPhase / unitDivider,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
             const phase2FluctuatedValue =
               powerPerPhaseSampledValueTemplates?.L2?.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerPerPhaseSampledValueTemplates.L2.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  powerPerPhaseSampledValueTemplates.L2.value,
+                  connectorMaximumPowerPerPhase / unitDivider,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
             const phase3FluctuatedValue =
               powerPerPhaseSampledValueTemplates?.L3?.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerPerPhaseSampledValueTemplates.L3.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  powerPerPhaseSampledValueTemplates.L3.value,
+                  connectorMaximumPowerPerPhase / unitDivider,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
@@ -355,7 +303,11 @@ export class OCPP16ServiceUtils {
           } else {
             powerMeasurandValues.L1 = powerSampledValueTemplate.value
               ? Utils.getRandomFloatFluctuatedRounded(
-                  parseInt(powerSampledValueTemplate.value),
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    powerSampledValueTemplate.value,
+                    connectorMaximumPower / unitDivider,
+                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  ),
                   powerSampledValueTemplate.fluctuationPercent ??
                     Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
@@ -371,14 +323,18 @@ export class OCPP16ServiceUtils {
         case CurrentType.DC:
           powerMeasurandValues.allPhases = powerSampledValueTemplate.value
             ? Utils.getRandomFloatFluctuatedRounded(
-                parseInt(powerSampledValueTemplate.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  powerSampledValueTemplate.value,
+                  connectorMaximumPower / unitDivider,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 powerSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               )
             : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
           break;
         default:
-          logger.error(errMsg);
+          logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
           throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
       }
       meterValue.sampledValue.push(
@@ -442,24 +398,28 @@ export class OCPP16ServiceUtils {
       }
     }
     // Current.Import measurand
-    const currentSampledValueTemplate = chargingStation.getSampledValueTemplate(
+    const currentSampledValueTemplate = ChargingStationUtils.getSampledValueTemplate(
+      chargingStation,
       connectorId,
       OCPP16MeterValueMeasurand.CURRENT_IMPORT
     );
     let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
     if (chargingStation.getNumberOfPhases() === 3) {
       currentPerPhaseSampledValueTemplates = {
-        L1: chargingStation.getSampledValueTemplate(
+        L1: ChargingStationUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
           OCPP16MeterValuePhase.L1
         ),
-        L2: chargingStation.getSampledValueTemplate(
+        L2: ChargingStationUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
           OCPP16MeterValuePhase.L2
         ),
-        L3: chargingStation.getSampledValueTemplate(
+        L3: ChargingStationUtils.getSampledValueTemplate(
+          chargingStation,
           connectorId,
           OCPP16MeterValueMeasurand.CURRENT_IMPORT,
           OCPP16MeterValuePhase.L3
@@ -471,7 +431,7 @@ export class OCPP16ServiceUtils {
         chargingStation,
         currentSampledValueTemplate.measurand
       );
-      const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
+      const errMsg = `MeterValues measurand ${
         currentSampledValueTemplate.measurand ??
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
@@ -481,40 +441,58 @@ export class OCPP16ServiceUtils {
         OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       } measurand value`;
       const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
+      const connectorMaximumAvailablePower =
+        chargingStation.getConnectorMaximumAvailablePower(connectorId);
       let connectorMaximumAmperage: number;
       switch (chargingStation.getCurrentOutType()) {
         case CurrentType.AC:
           connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
             chargingStation.getNumberOfPhases(),
-            chargingStation.getConnectorMaximumAvailablePower(connectorId),
+            connectorMaximumAvailablePower,
             chargingStation.getVoltageOut()
           );
           if (chargingStation.getNumberOfPhases() === 3) {
             const defaultFluctuatedAmperagePerPhase =
               currentSampledValueTemplate.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentSampledValueTemplate.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  currentSampledValueTemplate.value,
+                  connectorMaximumAmperage,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 currentSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
             const phase1FluctuatedValue =
               currentPerPhaseSampledValueTemplates?.L1?.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentPerPhaseSampledValueTemplates.L1.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  currentPerPhaseSampledValueTemplates.L1.value,
+                  connectorMaximumAmperage,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
             const phase2FluctuatedValue =
               currentPerPhaseSampledValueTemplates?.L2?.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentPerPhaseSampledValueTemplates.L2.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  currentPerPhaseSampledValueTemplates.L2.value,
+                  connectorMaximumAmperage,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
             const phase3FluctuatedValue =
               currentPerPhaseSampledValueTemplates?.L3?.value &&
               Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentPerPhaseSampledValueTemplates.L3.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  currentPerPhaseSampledValueTemplates.L3.value,
+                  connectorMaximumAmperage,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               );
@@ -533,7 +511,11 @@ export class OCPP16ServiceUtils {
           } else {
             currentMeasurandValues.L1 = currentSampledValueTemplate.value
               ? Utils.getRandomFloatFluctuatedRounded(
-                  parseInt(currentSampledValueTemplate.value),
+                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                    currentSampledValueTemplate.value,
+                    connectorMaximumAmperage,
+                    { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                  ),
                   currentSampledValueTemplate.fluctuationPercent ??
                     Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
@@ -549,19 +531,23 @@ export class OCPP16ServiceUtils {
           break;
         case CurrentType.DC:
           connectorMaximumAmperage = DCElectricUtils.amperage(
-            chargingStation.getConnectorMaximumAvailablePower(connectorId),
+            connectorMaximumAvailablePower,
             chargingStation.getVoltageOut()
           );
           currentMeasurandValues.allPhases = currentSampledValueTemplate.value
             ? Utils.getRandomFloatFluctuatedRounded(
-                parseInt(currentSampledValueTemplate.value),
+                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+                  currentSampledValueTemplate.value,
+                  connectorMaximumAmperage,
+                  { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
+                ),
                 currentSampledValueTemplate.fluctuationPercent ??
                   Constants.DEFAULT_FLUCTUATION_PERCENT
               )
             : Utils.getRandomFloatRounded(connectorMaximumAmperage);
           break;
         default:
-          logger.error(errMsg);
+          logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
           throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
       }
       meterValue.sampledValue.push(
@@ -620,7 +606,10 @@ export class OCPP16ServiceUtils {
       }
     }
     // Energy.Active.Import.Register measurand (default)
-    const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+    const energySampledValueTemplate = ChargingStationUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    );
     if (energySampledValueTemplate) {
       OCPP16ServiceUtils.checkMeasurandPowerDivider(
         chargingStation,
@@ -628,14 +617,23 @@ export class OCPP16ServiceUtils {
       );
       const unitDivider =
         energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
+      const connectorMaximumAvailablePower =
+        chargingStation.getConnectorMaximumAvailablePower(connectorId);
       const connectorMaximumEnergyRounded = Utils.roundTo(
-        (chargingStation.getConnectorMaximumAvailablePower(connectorId) * interval) / (3600 * 1000),
+        (connectorMaximumAvailablePower * interval) / (3600 * 1000),
         2
       );
       const energyValueRounded = energySampledValueTemplate.value
         ? // Cumulate the fluctuated value around the static one
           Utils.getRandomFloatFluctuatedRounded(
-            parseInt(energySampledValueTemplate.value),
+            OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
+              energySampledValueTemplate.value,
+              connectorMaximumEnergyRounded,
+              {
+                limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(),
+                unitMultiplier: unitDivider,
+              }
+            ),
             energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
         : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded);
@@ -691,7 +689,10 @@ export class OCPP16ServiceUtils {
       sampledValue: [],
     };
     // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+    const sampledValueTemplate = ChargingStationUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    );
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
@@ -713,7 +714,10 @@ export class OCPP16ServiceUtils {
       sampledValue: [],
     };
     // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
+    const sampledValueTemplate = ChargingStationUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    );
     const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
@@ -734,4 +738,80 @@ export class OCPP16ServiceUtils {
     meterValues.push(transactionEndMeterValue);
     return meterValues;
   }
+
+  private static buildSampledValue(
+    sampledValueTemplate: SampledValueTemplate,
+    value: number,
+    context?: MeterValueContext,
+    phase?: OCPP16MeterValuePhase
+  ): OCPP16SampledValue {
+    const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
+    const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
+    const sampledValueLocation =
+      sampledValueTemplate?.location ??
+      OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null);
+    const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
+    return {
+      ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && {
+        unit: sampledValueTemplate.unit,
+      }),
+      ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
+      ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && {
+        measurand: sampledValueTemplate.measurand,
+      }),
+      ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
+      ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
+      ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
+    };
+  }
+
+  private static checkMeasurandPowerDivider(
+    chargingStation: ChargingStation,
+    measurandType: OCPP16MeterValueMeasurand
+  ): void {
+    if (Utils.isUndefined(chargingStation.powerDivider)) {
+      const errMsg = `MeterValues measurand ${
+        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+      }: powerDivider is undefined`;
+      logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
+      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
+    } else if (chargingStation?.powerDivider <= 0) {
+      const errMsg = `MeterValues measurand ${
+        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+      }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
+      logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
+      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
+    }
+  }
+
+  private static getMeasurandDefaultLocation(
+    measurandType: OCPP16MeterValueMeasurand
+  ): MeterValueLocation | undefined {
+    switch (measurandType) {
+      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
+        return MeterValueLocation.EV;
+    }
+  }
+
+  private static getMeasurandDefaultUnit(
+    measurandType: OCPP16MeterValueMeasurand
+  ): MeterValueUnit | undefined {
+    switch (measurandType) {
+      case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
+      case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
+      case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
+        return MeterValueUnit.AMP;
+      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
+      case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
+        return MeterValueUnit.WATT_HOUR;
+      case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
+      case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
+      case OCPP16MeterValueMeasurand.POWER_OFFERED:
+        return MeterValueUnit.WATT;
+      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
+        return MeterValueUnit.PERCENT;
+      case OCPP16MeterValueMeasurand.VOLTAGE:
+        return MeterValueUnit.VOLT;
+    }
+  }
 }