]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor: factor out meter values generation code (#1585)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 4 Nov 2025 23:30:03 +0000 (00:30 +0100)
committerGitHub <noreply@github.com>
Tue, 4 Nov 2025 23:30:03 +0000 (00:30 +0100)
* refactor: factor out meter values generation code

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
* refactor: use helpers in both ocpp version code path

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
* refactor: align helpers namespace

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
* refactor: consolidate type definitions

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
* refactor: cleanup meter values payload defaults handling

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
src/charging-station/ocpp/OCPPConstants.ts
src/charging-station/ocpp/OCPPServiceUtils.ts

index 6f530edf0bb3e05a2d99399d1b8931aa4002e557..a8b9b3119374560f45a02e0b611f9414fe04ec4d 100644 (file)
@@ -73,11 +73,11 @@ export class OCPPConstants {
   })
 
   static readonly OCPP_MEASURANDS_SUPPORTED = Object.freeze([
-    MeterValueMeasurand.STATE_OF_CHARGE,
-    MeterValueMeasurand.VOLTAGE,
+    MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
     MeterValueMeasurand.POWER_ACTIVE_IMPORT,
     MeterValueMeasurand.CURRENT_IMPORT,
-    MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+    MeterValueMeasurand.VOLTAGE,
+    MeterValueMeasurand.STATE_OF_CHARGE,
   ])
 
   static readonly OCPP_REQUEST_EMPTY = Constants.EMPTY_FROZEN_OBJECT
index 83288e9b2d918f02c1a635cae5fe811fa8790b02..df70b9b1e0f2f5f2a23b723a0b1dc772a62516de 100644 (file)
@@ -71,6 +71,17 @@ import { OCPP16Constants } from './1.6/OCPP16Constants.js'
 import { OCPP20Constants } from './2.0/OCPP20Constants.js'
 import { OCPPConstants } from './OCPPConstants.js'
 
+interface MultiPhaseMeasurandData {
+  perPhaseTemplates: MeasurandPerPhaseSampledValueTemplates
+  template: SampledValueTemplate
+  values: MeasurandValues
+}
+
+interface SingleValueMeasurandData {
+  template: SampledValueTemplate
+  value: number
+}
+
 export const getMessageTypeString = (messageType: MessageType | undefined): string => {
   switch (messageType) {
     case MessageType.CALL_ERROR_MESSAGE:
@@ -326,788 +337,970 @@ export const convertDateToISOString = <T extends JsonType>(object: T): void => {
   }
 }
 
-export const buildMeterValue = (
+const buildSocMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number
+): null | SingleValueMeasurandData => {
+  const socSampledValueTemplate = getSampledValueTemplate(
+    chargingStation,
+    connectorId,
+    MeterValueMeasurand.STATE_OF_CHARGE
+  )
+  if (socSampledValueTemplate == null) {
+    return null
+  }
+
+  const socMaximumValue = 100
+  const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0
+  const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
+    ? getRandomFloatFluctuatedRounded(
+      Number.parseInt(socSampledValueTemplate.value),
+      socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+    )
+    : randomInt(socMinimumValue, socMaximumValue + 1)
+
+  return {
+    template: socSampledValueTemplate,
+    value: socSampledValueTemplateValue,
+  }
+}
+
+const validateSocMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
-  transactionId: number,
-  interval: number,
-  debug = false
-): MeterValue => {
+  sampledValue: SampledValue,
+  socMinimumValue: number,
+  socMaximumValue: number,
+  debug: boolean
+): void => {
   const connector = chargingStation.getConnectorStatus(connectorId)
-  let meterValue: MeterValue
-  let connectorMaximumAvailablePower: number | undefined
-  let socSampledValueTemplate: SampledValueTemplate | undefined
-  let voltageSampledValueTemplate: SampledValueTemplate | undefined
-  let powerSampledValueTemplate: SampledValueTemplate | undefined
-  let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}
-  let currentSampledValueTemplate: SampledValueTemplate | undefined
-  let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}
-  let energySampledValueTemplate: SampledValueTemplate | undefined
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16:
-      meterValue = {
-        sampledValue: [],
-        timestamp: new Date(),
-      }
-      // SoC measurand
-      socSampledValueTemplate = getSampledValueTemplate(
+  if (
+    convertToInt(sampledValue.value) > socMaximumValue ||
+    convertToInt(sampledValue.value) < socMinimumValue ||
+    debug
+  ) {
+    logger.error(
+      `${chargingStation.logPrefix()} MeterValues measurand ${
+        sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${socMinimumValue.toString()}/${sampledValue.value.toString()}/${socMaximumValue.toString()}`
+    )
+  }
+}
+
+const buildVoltageMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number
+): null | SingleValueMeasurandData => {
+  const voltageSampledValueTemplate = getSampledValueTemplate(
+    chargingStation,
+    connectorId,
+    MeterValueMeasurand.VOLTAGE
+  )
+  if (voltageSampledValueTemplate == null) {
+    return null
+  }
+
+  const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
+    ? Number.parseInt(voltageSampledValueTemplate.value)
+    : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    chargingStation.stationInfo.voltageOut!
+  const fluctuationPercent =
+    voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+  const voltageMeasurandValue = getRandomFloatFluctuatedRounded(
+    voltageSampledValueTemplateValue,
+    fluctuationPercent
+  )
+
+  return {
+    template: voltageSampledValueTemplate,
+    value: voltageMeasurandValue,
+  }
+}
+
+const addMainVoltageToMeterValue = (
+  chargingStation: ChargingStation,
+  meterValue: MeterValue,
+  voltageData: { template: SampledValueTemplate; value: number }
+): void => {
+  if (
+    chargingStation.getNumberOfPhases() !== 3 ||
+    (chargingStation.getNumberOfPhases() === 3 &&
+      chargingStation.stationInfo.mainVoltageMeterValues === true)
+  ) {
+    meterValue.sampledValue.push(
+      buildSampledValue(
+        chargingStation.stationInfo.ocppVersion,
+        voltageData.template,
+        voltageData.value
+      )
+    )
+  }
+}
+
+const addPhaseVoltageToMeterValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  meterValue: MeterValue,
+  mainVoltageData: { template: SampledValueTemplate; value: number },
+  phase: number
+): void => {
+  const phaseLineToNeutralValue = `L${phase.toString()}-N` as MeterValuePhase
+  const voltagePhaseLineToNeutralSampledValueTemplate = getSampledValueTemplate(
+    chargingStation,
+    connectorId,
+    MeterValueMeasurand.VOLTAGE,
+    phaseLineToNeutralValue
+  )
+  let voltagePhaseLineToNeutralMeasurandValue: number | undefined
+  if (voltagePhaseLineToNeutralSampledValueTemplate != null) {
+    const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
+      voltagePhaseLineToNeutralSampledValueTemplate.value
+    )
+      ? Number.parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
+      : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.stationInfo.voltageOut!
+    const fluctuationPhaseToNeutralPercent =
+      voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
+      Constants.DEFAULT_FLUCTUATION_PERCENT
+    voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
+      voltagePhaseLineToNeutralSampledValueTemplateValue,
+      fluctuationPhaseToNeutralPercent
+    )
+  }
+  meterValue.sampledValue.push(
+    buildSampledValue(
+      chargingStation.stationInfo.ocppVersion,
+      voltagePhaseLineToNeutralSampledValueTemplate ?? mainVoltageData.template,
+      voltagePhaseLineToNeutralMeasurandValue ?? mainVoltageData.value,
+      undefined,
+      phaseLineToNeutralValue
+    )
+  )
+}
+
+const addLineToLineVoltageToMeterValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  meterValue: MeterValue,
+  mainVoltageData: { template: SampledValueTemplate; value: number },
+  phase: number
+): void => {
+  if (chargingStation.stationInfo.phaseLineToLineVoltageMeterValues === true) {
+    const phaseLineToLineValue = `L${phase.toString()}-L${
+      (phase + 1) % chargingStation.getNumberOfPhases() !== 0
+        ? ((phase + 1) % chargingStation.getNumberOfPhases()).toString()
+        : chargingStation.getNumberOfPhases().toString()
+    }` as MeterValuePhase
+    const voltagePhaseLineToLineValueRounded = roundTo(
+      Math.sqrt(chargingStation.getNumberOfPhases()) *
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        chargingStation.stationInfo.voltageOut!,
+      2
+    )
+    const voltagePhaseLineToLineSampledValueTemplate = getSampledValueTemplate(
+      chargingStation,
+      connectorId,
+      MeterValueMeasurand.VOLTAGE,
+      phaseLineToLineValue
+    )
+    let voltagePhaseLineToLineMeasurandValue: number | undefined
+    if (voltagePhaseLineToLineSampledValueTemplate != null) {
+      const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
+        voltagePhaseLineToLineSampledValueTemplate.value
+      )
+        ? Number.parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
+        : voltagePhaseLineToLineValueRounded
+      const fluctuationPhaseLineToLinePercent =
+        voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
+        Constants.DEFAULT_FLUCTUATION_PERCENT
+      voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
+        voltagePhaseLineToLineSampledValueTemplateValue,
+        fluctuationPhaseLineToLinePercent
+      )
+    }
+    meterValue.sampledValue.push(
+      buildSampledValue(
+        chargingStation.stationInfo.ocppVersion,
+        voltagePhaseLineToLineSampledValueTemplate ?? mainVoltageData.template,
+        voltagePhaseLineToLineMeasurandValue ?? voltagePhaseLineToLineValueRounded,
+        undefined,
+        phaseLineToLineValue
+      )
+    )
+  }
+}
+
+const buildEnergyMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  interval: number
+): null | SingleValueMeasurandData => {
+  const energyTemplate = getSampledValueTemplate(chargingStation, connectorId)
+  if (energyTemplate == null) {
+    return null
+  }
+
+  checkMeasurandPowerDivider(chargingStation, energyTemplate.measurand)
+  const unitDivider = energyTemplate.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+  const connectorMaximumAvailablePower =
+    chargingStation.getConnectorMaximumAvailablePower(connectorId)
+  const connectorMaximumEnergyRounded = roundTo(
+    (connectorMaximumAvailablePower * interval) / (3600 * 1000),
+    2
+  )
+  const connectorMinimumEnergyRounded = roundTo(energyTemplate.minimumValue ?? 0, 2)
+
+  const energyValueRounded = isNotEmptyString(energyTemplate.value)
+    ? getRandomFloatFluctuatedRounded(
+      getLimitFromSampledValueTemplateCustomValue(
+        energyTemplate.value,
+        connectorMaximumEnergyRounded,
+        connectorMinimumEnergyRounded,
+        {
+          fallbackValue: connectorMinimumEnergyRounded,
+          limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
+          unitMultiplier: unitDivider,
+        }
+      ),
+      energyTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+    )
+    : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded)
+
+  return {
+    template: energyTemplate,
+    value: energyValueRounded,
+  }
+}
+
+const updateConnectorEnergyValues = (
+  connector: ConnectorStatus | undefined,
+  energyValue: number
+): void => {
+  if (connector != null) {
+    if (
+      connector.energyActiveImportRegisterValue != null &&
+      connector.energyActiveImportRegisterValue >= 0 &&
+      connector.transactionEnergyActiveImportRegisterValue != null &&
+      connector.transactionEnergyActiveImportRegisterValue >= 0
+    ) {
+      connector.energyActiveImportRegisterValue += energyValue
+      connector.transactionEnergyActiveImportRegisterValue += energyValue
+    } else {
+      connector.energyActiveImportRegisterValue = 0
+      connector.transactionEnergyActiveImportRegisterValue = 0
+    }
+  }
+}
+
+const validateEnergyMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  sampledValue: SampledValue,
+  energyValue: number,
+  minValue: number,
+  maxValue: number,
+  interval: number,
+  debug: boolean
+): void => {
+  if (energyValue > maxValue || energyValue < minValue || debug) {
+    const connector = chargingStation.getConnectorStatus(connectorId)
+    logger.error(
+      `${chargingStation.logPrefix()} MeterValues measurand ${
+        sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${minValue.toString()}/${energyValue.toString()}/${maxValue.toString()}, duration: ${interval.toString()}ms`
+    )
+  }
+}
+
+const buildPowerMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number
+): MultiPhaseMeasurandData | null => {
+  const powerTemplate = getSampledValueTemplate(
+    chargingStation,
+    connectorId,
+    MeterValueMeasurand.POWER_ACTIVE_IMPORT
+  )
+  if (powerTemplate == null) {
+    return null
+  }
+
+  let perPhaseTemplates: MeasurandPerPhaseSampledValueTemplates = {}
+  if (chargingStation.getNumberOfPhases() === 3) {
+    perPhaseTemplates = {
+      L1: getSampledValueTemplate(
         chargingStation,
         connectorId,
-        MeterValueMeasurand.STATE_OF_CHARGE
-      )
-      if (socSampledValueTemplate != null) {
-        const socMaximumValue = 100
-        const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0
-        const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
+        MeterValueMeasurand.POWER_ACTIVE_IMPORT,
+        MeterValuePhase.L1_N
+      ),
+      L2: getSampledValueTemplate(
+        chargingStation,
+        connectorId,
+        MeterValueMeasurand.POWER_ACTIVE_IMPORT,
+        MeterValuePhase.L2_N
+      ),
+      L3: getSampledValueTemplate(
+        chargingStation,
+        connectorId,
+        MeterValueMeasurand.POWER_ACTIVE_IMPORT,
+        MeterValuePhase.L3_N
+      ),
+    }
+  }
+
+  checkMeasurandPowerDivider(chargingStation, powerTemplate.measurand)
+  const powerValues: MeasurandValues = {} as MeasurandValues
+  const unitDivider = powerTemplate.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
+  const connectorMaximumAvailablePower =
+    chargingStation.getConnectorMaximumAvailablePower(connectorId)
+  const connectorMaximumPower = Math.round(connectorMaximumAvailablePower)
+  const connectorMaximumPowerPerPhase = Math.round(
+    connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
+  )
+  const connectorMinimumPower = Math.round(powerTemplate.minimumValue ?? 0)
+  const connectorMinimumPowerPerPhase = Math.round(
+    connectorMinimumPower / chargingStation.getNumberOfPhases()
+  )
+
+  switch (chargingStation.stationInfo?.currentOutType) {
+    case CurrentType.AC:
+      if (chargingStation.getNumberOfPhases() === 3) {
+        const defaultFluctuatedPowerPerPhase = isNotEmptyString(powerTemplate.value)
           ? getRandomFloatFluctuatedRounded(
-            Number.parseInt(socSampledValueTemplate.value),
-            socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+            getLimitFromSampledValueTemplateCustomValue(
+              powerTemplate.value,
+              connectorMaximumPower / unitDivider,
+              connectorMinimumPower / unitDivider,
+              {
+                fallbackValue: connectorMinimumPower / unitDivider,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ) / chargingStation.getNumberOfPhases(),
+            powerTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
-          : randomInt(socMinimumValue, socMaximumValue + 1)
-        meterValue.sampledValue.push(
-          buildSampledValue(
-            chargingStation.stationInfo.ocppVersion,
-            socSampledValueTemplate,
-            socSampledValueTemplateValue
+          : undefined
+
+        const phase1Value = isNotEmptyString(perPhaseTemplates.L1?.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              perPhaseTemplates.L1.value,
+              connectorMaximumPowerPerPhase / unitDivider,
+              connectorMinimumPowerPerPhase / unitDivider,
+              {
+                fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            perPhaseTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
-        )
-        const sampledValuesIndex = meterValue.sampledValue.length - 1
-        if (
-          convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue ||
-          convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue ||
-          debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesIndex].measurand ??
-              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${socMinimumValue.toString()}/${meterValue.sampledValue[
-              sampledValuesIndex
-            ].value.toString()}/${socMaximumValue.toString()}`
+          : undefined
+
+        const phase2Value = isNotEmptyString(perPhaseTemplates.L2?.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              perPhaseTemplates.L2.value,
+              connectorMaximumPowerPerPhase / unitDivider,
+              connectorMinimumPowerPerPhase / unitDivider,
+              {
+                fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            perPhaseTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
-        }
+          : undefined
+
+        const phase3Value = isNotEmptyString(perPhaseTemplates.L3?.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              perPhaseTemplates.L3.value,
+              connectorMaximumPowerPerPhase / unitDivider,
+              connectorMinimumPowerPerPhase / unitDivider,
+              {
+                fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            perPhaseTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+          )
+          : undefined
+
+        powerValues.L1 =
+          phase1Value ??
+          defaultFluctuatedPowerPerPhase ??
+          getRandomFloatRounded(
+            connectorMaximumPowerPerPhase / unitDivider,
+            connectorMinimumPowerPerPhase / unitDivider
+          )
+        powerValues.L2 =
+          phase2Value ??
+          defaultFluctuatedPowerPerPhase ??
+          getRandomFloatRounded(
+            connectorMaximumPowerPerPhase / unitDivider,
+            connectorMinimumPowerPerPhase / unitDivider
+          )
+        powerValues.L3 =
+          phase3Value ??
+          defaultFluctuatedPowerPerPhase ??
+          getRandomFloatRounded(
+            connectorMaximumPowerPerPhase / unitDivider,
+            connectorMinimumPowerPerPhase / unitDivider
+          )
+      } else {
+        powerValues.L1 = isNotEmptyString(powerTemplate.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              powerTemplate.value,
+              connectorMaximumPower / unitDivider,
+              connectorMinimumPower / unitDivider,
+              {
+                fallbackValue: connectorMinimumPower / unitDivider,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            powerTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+          )
+          : getRandomFloatRounded(
+            connectorMaximumPower / unitDivider,
+            connectorMinimumPower / unitDivider
+          )
+        powerValues.L2 = 0
+        powerValues.L3 = 0
       }
-      // Voltage measurand
-      voltageSampledValueTemplate = getSampledValueTemplate(
+      powerValues.allPhases = roundTo(powerValues.L1 + powerValues.L2 + powerValues.L3, 2)
+      break
+    case CurrentType.DC:
+      powerValues.allPhases = isNotEmptyString(powerTemplate.value)
+        ? getRandomFloatFluctuatedRounded(
+          getLimitFromSampledValueTemplateCustomValue(
+            powerTemplate.value,
+            connectorMaximumPower / unitDivider,
+            connectorMinimumPower / unitDivider,
+            {
+              fallbackValue: connectorMinimumPower / unitDivider,
+              limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+            }
+          ),
+          powerTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+        )
+        : getRandomFloatRounded(
+          connectorMaximumPower / unitDivider,
+          connectorMinimumPower / unitDivider
+        )
+      break
+    default: {
+      const errMsg = `MeterValues measurand ${
+        powerTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
+        chargingStation.templateFile
+      }, cannot calculate ${
+        powerTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+      } measurand value`
+      logger.error(`${chargingStation.logPrefix()} ${errMsg}`)
+      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES)
+    }
+  }
+
+  return {
+    perPhaseTemplates,
+    template: powerTemplate,
+    values: powerValues,
+  }
+}
+
+const validatePowerMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  connector: ConnectorStatus | undefined,
+  sampledValue: SampledValue,
+  connectorMaximumPower: number,
+  connectorMinimumPower: number,
+  debug: boolean
+): void => {
+  if (
+    convertToFloat(sampledValue.value) > connectorMaximumPower ||
+    convertToFloat(sampledValue.value) < connectorMinimumPower ||
+    debug
+  ) {
+    logger.error(
+      `${chargingStation.logPrefix()} MeterValues measurand ${
+        sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumPower.toString()}/${sampledValue.value.toString()}/${connectorMaximumPower.toString()}`
+    )
+  }
+}
+
+const validateCurrentMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  connector: ConnectorStatus | undefined,
+  sampledValue: SampledValue,
+  connectorMaximumAmperage: number,
+  connectorMinimumAmperage: number,
+  debug: boolean
+): void => {
+  if (
+    convertToFloat(sampledValue.value) > connectorMaximumAmperage ||
+    convertToFloat(sampledValue.value) < connectorMinimumAmperage ||
+    debug
+  ) {
+    logger.error(
+      `${chargingStation.logPrefix()} MeterValues measurand ${
+        sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumAmperage.toString()}/${sampledValue.value.toString()}/${connectorMaximumAmperage.toString()}`
+    )
+  }
+}
+
+const validateCurrentMeasurandPhaseValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  connector: ConnectorStatus | undefined,
+  sampledValue: SampledValue,
+  connectorMaximumAmperage: number,
+  connectorMinimumAmperage: number,
+  debug: boolean
+): void => {
+  if (
+    convertToFloat(sampledValue.value) > connectorMaximumAmperage ||
+    convertToFloat(sampledValue.value) < connectorMinimumAmperage ||
+    debug
+  ) {
+    logger.error(
+      `${chargingStation.logPrefix()} MeterValues measurand ${
+        sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+      }: phase ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        sampledValue.phase
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }, connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumAmperage.toString()}/${sampledValue.value.toString()}/${connectorMaximumAmperage.toString()}`
+    )
+  }
+}
+
+const buildCurrentMeasurandValue = (
+  chargingStation: ChargingStation,
+  connectorId: number
+): MultiPhaseMeasurandData | null => {
+  const currentTemplate = getSampledValueTemplate(
+    chargingStation,
+    connectorId,
+    MeterValueMeasurand.CURRENT_IMPORT
+  )
+  if (currentTemplate == null) {
+    return null
+  }
+
+  let perPhaseTemplates: MeasurandPerPhaseSampledValueTemplates = {}
+  if (chargingStation.getNumberOfPhases() === 3) {
+    perPhaseTemplates = {
+      L1: getSampledValueTemplate(
+        chargingStation,
+        connectorId,
+        MeterValueMeasurand.CURRENT_IMPORT,
+        MeterValuePhase.L1
+      ),
+      L2: getSampledValueTemplate(
         chargingStation,
         connectorId,
-        MeterValueMeasurand.VOLTAGE
+        MeterValueMeasurand.CURRENT_IMPORT,
+        MeterValuePhase.L2
+      ),
+      L3: getSampledValueTemplate(
+        chargingStation,
+        connectorId,
+        MeterValueMeasurand.CURRENT_IMPORT,
+        MeterValuePhase.L3
+      ),
+    }
+  }
+
+  checkMeasurandPowerDivider(chargingStation, currentTemplate.measurand)
+  const currentValues: MeasurandValues = {} as MeasurandValues
+  const connectorMaximumAvailablePower =
+    chargingStation.getConnectorMaximumAvailablePower(connectorId)
+  const connectorMinimumAmperage = currentTemplate.minimumValue ?? 0
+  let connectorMaximumAmperage: number
+
+  switch (chargingStation.stationInfo?.currentOutType) {
+    case CurrentType.AC:
+      connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
+        chargingStation.getNumberOfPhases(),
+        connectorMaximumAvailablePower,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        chargingStation.stationInfo.voltageOut!
       )
-      if (voltageSampledValueTemplate != null) {
-        const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
-          ? Number.parseInt(voltageSampledValueTemplate.value)
-          : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          chargingStation.stationInfo.voltageOut!
-        const fluctuationPercent =
-          voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
-        const voltageMeasurandValue = getRandomFloatFluctuatedRounded(
-          voltageSampledValueTemplateValue,
-          fluctuationPercent
-        )
-        if (
-          chargingStation.getNumberOfPhases() !== 3 ||
-          (chargingStation.getNumberOfPhases() === 3 &&
-            chargingStation.stationInfo.mainVoltageMeterValues === true)
-        ) {
-          meterValue.sampledValue.push(
-            buildSampledValue(
-              chargingStation.stationInfo.ocppVersion,
-              voltageSampledValueTemplate,
-              voltageMeasurandValue
-            )
+      if (chargingStation.getNumberOfPhases() === 3) {
+        const defaultFluctuatedAmperagePerPhase = isNotEmptyString(currentTemplate.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              currentTemplate.value,
+              connectorMaximumAmperage,
+              connectorMinimumAmperage,
+              {
+                fallbackValue: connectorMinimumAmperage,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            currentTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
-        }
-        for (
-          let phase = 1;
-          chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-          phase++
-        ) {
-          const phaseLineToNeutralValue = `L${phase.toString()}-N`
-          const voltagePhaseLineToNeutralSampledValueTemplate = getSampledValueTemplate(
-            chargingStation,
-            connectorId,
-            MeterValueMeasurand.VOLTAGE,
-            phaseLineToNeutralValue as MeterValuePhase
+          : undefined
+
+        const phase1Value = isNotEmptyString(perPhaseTemplates.L1?.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              perPhaseTemplates.L1.value,
+              connectorMaximumAmperage,
+              connectorMinimumAmperage,
+              {
+                fallbackValue: connectorMinimumAmperage,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            perPhaseTemplates.L1.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
-          let voltagePhaseLineToNeutralMeasurandValue: number | undefined
-          if (voltagePhaseLineToNeutralSampledValueTemplate != null) {
-            const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
-              voltagePhaseLineToNeutralSampledValueTemplate.value
-            )
-              ? Number.parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
-              : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              chargingStation.stationInfo.voltageOut!
-            const fluctuationPhaseToNeutralPercent =
-              voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
-              Constants.DEFAULT_FLUCTUATION_PERCENT
-            voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
-              voltagePhaseLineToNeutralSampledValueTemplateValue,
-              fluctuationPhaseToNeutralPercent
-            )
-          }
-          meterValue.sampledValue.push(
-            buildSampledValue(
-              chargingStation.stationInfo.ocppVersion,
-              voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
-              voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
-              undefined,
-              phaseLineToNeutralValue as MeterValuePhase
-            )
+          : undefined
+
+        const phase2Value = isNotEmptyString(perPhaseTemplates.L2?.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              perPhaseTemplates.L2.value,
+              connectorMaximumAmperage,
+              connectorMinimumAmperage,
+              {
+                fallbackValue: connectorMinimumAmperage,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            perPhaseTemplates.L2.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
-          if (chargingStation.stationInfo.phaseLineToLineVoltageMeterValues === true) {
-            const phaseLineToLineValue = `L${phase.toString()}-L${
-              (phase + 1) % chargingStation.getNumberOfPhases() !== 0
-                ? ((phase + 1) % chargingStation.getNumberOfPhases()).toString()
-                : chargingStation.getNumberOfPhases().toString()
-            }`
-            const voltagePhaseLineToLineValueRounded = roundTo(
-              Math.sqrt(chargingStation.getNumberOfPhases()) *
-                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                chargingStation.stationInfo.voltageOut!,
-              2
-            )
-            const voltagePhaseLineToLineSampledValueTemplate = getSampledValueTemplate(
-              chargingStation,
-              connectorId,
-              MeterValueMeasurand.VOLTAGE,
-              phaseLineToLineValue as MeterValuePhase
-            )
-            let voltagePhaseLineToLineMeasurandValue: number | undefined
-            if (voltagePhaseLineToLineSampledValueTemplate != null) {
-              const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
-                voltagePhaseLineToLineSampledValueTemplate.value
-              )
-                ? Number.parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
-                : voltagePhaseLineToLineValueRounded
-              const fluctuationPhaseLineToLinePercent =
-                voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
-                Constants.DEFAULT_FLUCTUATION_PERCENT
-              voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-                voltagePhaseLineToLineSampledValueTemplateValue,
-                fluctuationPhaseLineToLinePercent
-              )
+          : undefined
+
+        const phase3Value = isNotEmptyString(perPhaseTemplates.L3?.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              perPhaseTemplates.L3.value,
+              connectorMaximumAmperage,
+              connectorMinimumAmperage,
+              {
+                fallbackValue: connectorMinimumAmperage,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            perPhaseTemplates.L3.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+          )
+          : undefined
+
+        currentValues.L1 =
+          phase1Value ??
+          defaultFluctuatedAmperagePerPhase ??
+          getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+        currentValues.L2 =
+          phase2Value ??
+          defaultFluctuatedAmperagePerPhase ??
+          getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+        currentValues.L3 =
+          phase3Value ??
+          defaultFluctuatedAmperagePerPhase ??
+          getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+      } else {
+        currentValues.L1 = isNotEmptyString(currentTemplate.value)
+          ? getRandomFloatFluctuatedRounded(
+            getLimitFromSampledValueTemplateCustomValue(
+              currentTemplate.value,
+              connectorMaximumAmperage,
+              connectorMinimumAmperage,
+              {
+                fallbackValue: connectorMinimumAmperage,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+              }
+            ),
+            currentTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+          )
+          : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+        currentValues.L2 = 0
+        currentValues.L3 = 0
+      }
+      currentValues.allPhases = roundTo(
+        (currentValues.L1 + currentValues.L2 + currentValues.L3) /
+          chargingStation.getNumberOfPhases(),
+        2
+      )
+      break
+    case CurrentType.DC:
+      connectorMaximumAmperage = DCElectricUtils.amperage(
+        connectorMaximumAvailablePower,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        chargingStation.stationInfo.voltageOut!
+      )
+      currentValues.allPhases = isNotEmptyString(currentTemplate.value)
+        ? getRandomFloatFluctuatedRounded(
+          getLimitFromSampledValueTemplateCustomValue(
+            currentTemplate.value,
+            connectorMaximumAmperage,
+            connectorMinimumAmperage,
+            {
+              fallbackValue: connectorMinimumAmperage,
+              limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
             }
-            const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-              voltagePhaseLineToLineValueRounded,
-              fluctuationPercent
-            )
-            meterValue.sampledValue.push(
-              buildSampledValue(
-                chargingStation.stationInfo.ocppVersion,
-                voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
-                voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
-                undefined,
-                phaseLineToLineValue as MeterValuePhase
-              )
-            )
-          }
-        }
+          ),
+          currentTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
+        )
+        : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+      break
+    default: {
+      const errMsg = `MeterValues measurand ${
+        currentTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
+        chargingStation.templateFile
+      }, cannot calculate ${
+        currentTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+      } measurand value`
+      logger.error(`${chargingStation.logPrefix()} ${errMsg}`)
+      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES)
+    }
+  }
+
+  return {
+    perPhaseTemplates,
+    template: currentTemplate,
+    values: currentValues,
+  }
+}
+
+export const buildMeterValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  transactionId: number,
+  interval: number,
+  debug = false
+): MeterValue => {
+  const connector = chargingStation.getConnectorStatus(connectorId)
+  let meterValue: MeterValue
+
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16: {
+      meterValue = {
+        sampledValue: [],
+        timestamp: new Date(),
       }
-      // Power.Active.Import measurand
-      powerSampledValueTemplate = getSampledValueTemplate(
-        chargingStation,
-        connectorId,
-        MeterValueMeasurand.POWER_ACTIVE_IMPORT
-      )
-      if (chargingStation.getNumberOfPhases() === 3) {
-        powerPerPhaseSampledValueTemplates = {
-          L1: getSampledValueTemplate(
-            chargingStation,
-            connectorId,
-            MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-            MeterValuePhase.L1_N
-          ),
-          L2: getSampledValueTemplate(
+      // SoC measurand
+      const socMeasurand = buildSocMeasurandValue(chargingStation, connectorId)
+      if (socMeasurand != null) {
+        const socSampledValue = buildSampledValue(
+          chargingStation.stationInfo.ocppVersion,
+          socMeasurand.template,
+          socMeasurand.value
+        )
+        meterValue.sampledValue.push(socSampledValue)
+        validateSocMeasurandValue(
+          chargingStation,
+          connectorId,
+          socSampledValue,
+          socMeasurand.template.minimumValue ?? 0,
+          100,
+          debug
+        )
+      }
+      // Voltage measurand
+      const voltageMeasurand = buildVoltageMeasurandValue(chargingStation, connectorId)
+      if (voltageMeasurand != null) {
+        addMainVoltageToMeterValue(chargingStation, meterValue, voltageMeasurand)
+        for (
+          let phase = 1;
+          chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
+          phase++
+        ) {
+          addPhaseVoltageToMeterValue(
             chargingStation,
             connectorId,
-            MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-            MeterValuePhase.L2_N
-          ),
-          L3: getSampledValueTemplate(
+            meterValue,
+            voltageMeasurand,
+            phase
+          )
+          addLineToLineVoltageToMeterValue(
             chargingStation,
             connectorId,
-            MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-            MeterValuePhase.L3_N
-          ),
+            meterValue,
+            voltageMeasurand,
+            phase
+          )
         }
       }
-      if (powerSampledValueTemplate != null) {
-        checkMeasurandPowerDivider(chargingStation, powerSampledValueTemplate.measurand)
-        const errMsg = `MeterValues measurand ${
-          powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
-          chargingStation.templateFile
-        }, cannot calculate ${
-          powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-        } measurand value`
-        const powerMeasurandValues: MeasurandValues = {} as MeasurandValues
-        const unitDivider = powerSampledValueTemplate.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
-        connectorMaximumAvailablePower =
+      // Power.Active.Import measurand
+      const powerMeasurand = buildPowerMeasurandValue(chargingStation, connectorId)
+      if (powerMeasurand != null) {
+        const unitDivider = powerMeasurand.template.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
+        const connectorMaximumAvailablePower =
           chargingStation.getConnectorMaximumAvailablePower(connectorId)
         const connectorMaximumPower = Math.round(connectorMaximumAvailablePower)
-        const connectorMaximumPowerPerPhase = Math.round(
-          connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
-        )
-        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 = isNotEmptyString(
-                powerSampledValueTemplate.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    powerSampledValueTemplate.value,
-                    connectorMaximumPower / unitDivider,
-                    connectorMinimumPower / unitDivider,
-                    {
-                      fallbackValue: connectorMinimumPower / unitDivider,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ) / chargingStation.getNumberOfPhases(),
-                  powerSampledValueTemplate.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              const phase1FluctuatedValue = isNotEmptyString(
-                powerPerPhaseSampledValueTemplates.L1?.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L1.value,
-                    connectorMaximumPowerPerPhase / unitDivider,
-                    connectorMinimumPowerPerPhase / unitDivider,
-                    {
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              const phase2FluctuatedValue = isNotEmptyString(
-                powerPerPhaseSampledValueTemplates.L2?.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L2.value,
-                    connectorMaximumPowerPerPhase / unitDivider,
-                    connectorMinimumPowerPerPhase / unitDivider,
-                    {
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              const phase3FluctuatedValue = isNotEmptyString(
-                powerPerPhaseSampledValueTemplates.L3?.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L3.value,
-                    connectorMaximumPowerPerPhase / unitDivider,
-                    connectorMinimumPowerPerPhase / unitDivider,
-                    {
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              powerMeasurandValues.L1 =
-                phase1FluctuatedValue ??
-                defaultFluctuatedPowerPerPhase ??
-                getRandomFloatRounded(
-                  connectorMaximumPowerPerPhase / unitDivider,
-                  connectorMinimumPowerPerPhase / unitDivider
-                )
-              powerMeasurandValues.L2 =
-                phase2FluctuatedValue ??
-                defaultFluctuatedPowerPerPhase ??
-                getRandomFloatRounded(
-                  connectorMaximumPowerPerPhase / unitDivider,
-                  connectorMinimumPowerPerPhase / unitDivider
-                )
-              powerMeasurandValues.L3 =
-                phase3FluctuatedValue ??
-                defaultFluctuatedPowerPerPhase ??
-                getRandomFloatRounded(
-                  connectorMaximumPowerPerPhase / unitDivider,
-                  connectorMinimumPowerPerPhase / unitDivider
-                )
-            } else {
-              powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value)
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    powerSampledValueTemplate.value,
-                    connectorMaximumPower / unitDivider,
-                    connectorMinimumPower / unitDivider,
-                    {
-                      fallbackValue: connectorMinimumPower / unitDivider,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  powerSampledValueTemplate.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : getRandomFloatRounded(
-                  connectorMaximumPower / unitDivider,
-                  connectorMinimumPower / unitDivider
-                )
-              powerMeasurandValues.L2 = 0
-              powerMeasurandValues.L3 = 0
-            }
-            powerMeasurandValues.allPhases = roundTo(
-              powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
-              2
-            )
-            break
-          case CurrentType.DC:
-            powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value)
-              ? getRandomFloatFluctuatedRounded(
-                getLimitFromSampledValueTemplateCustomValue(
-                  powerSampledValueTemplate.value,
-                  connectorMaximumPower / unitDivider,
-                  connectorMinimumPower / unitDivider,
-                  {
-                    fallbackValue: connectorMinimumPower / unitDivider,
-                    limitationEnabled:
-                        chargingStation.stationInfo.customValueLimitationMeterValues,
-                  }
-                ),
-                powerSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT
-              )
-              : getRandomFloatRounded(
-                connectorMaximumPower / unitDivider,
-                connectorMinimumPower / unitDivider
-              )
-            break
-          default:
-            logger.error(`${chargingStation.logPrefix()} ${errMsg}`)
-            throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES)
-        }
+        const connectorMinimumPower = Math.round(powerMeasurand.template.minimumValue ?? 0)
+
         meterValue.sampledValue.push(
           buildSampledValue(
             chargingStation.stationInfo.ocppVersion,
-            powerSampledValueTemplate,
-            powerMeasurandValues.allPhases
+            powerMeasurand.template,
+            powerMeasurand.values.allPhases
           )
         )
         const sampledValuesIndex = meterValue.sampledValue.length - 1
-        const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2)
-        const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2)
-        if (
-          convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
-            connectorMaximumPowerRounded ||
-          convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
-            connectorMinimumPowerRounded ||
+        validatePowerMeasurandValue(
+          chargingStation,
+          connectorId,
+          connector,
+          meterValue.sampledValue[sampledValuesIndex],
+          connectorMaximumPower / unitDivider,
+          connectorMinimumPower / unitDivider,
           debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesIndex].measurand ??
-              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumPowerRounded.toString()}/${meterValue.sampledValue[
-              sampledValuesIndex
-            ].value.toString()}/${connectorMaximumPowerRounded.toString()}`
-          )
-        }
-        for (
-          let phase = 1;
-          chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-          phase++
-        ) {
-          const phaseValue = `L${phase.toString()}-N`
-          meterValue.sampledValue.push(
-            buildSampledValue(
-              chargingStation.stationInfo.ocppVersion,
-              powerPerPhaseSampledValueTemplates[
-                `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates
-              ] ?? powerSampledValueTemplate,
-              powerMeasurandValues[
-                `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates
-              ],
-              undefined,
-              phaseValue as MeterValuePhase
-            )
-          )
-          const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1
-          const connectorMaximumPowerPerPhaseRounded = roundTo(
-            connectorMaximumPowerPerPhase / unitDivider,
-            2
+        )
+        if (chargingStation.getNumberOfPhases() === 3) {
+          const connectorMaximumPowerPerPhase = Math.round(
+            connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
           )
-          const connectorMinimumPowerPerPhaseRounded = roundTo(
-            connectorMinimumPowerPerPhase / unitDivider,
-            2
+          const connectorMinimumPowerPerPhase = Math.round(
+            connectorMinimumPower / chargingStation.getNumberOfPhases()
           )
-          if (
-            convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
-              connectorMaximumPowerPerPhaseRounded ||
-            convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
-              connectorMinimumPowerPerPhaseRounded ||
-            debug
-          ) {
-            logger.error(
-              `${chargingStation.logPrefix()} MeterValues measurand ${
-                meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
-                MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              }: phase ${
-                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-              }, connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumPowerPerPhaseRounded.toString()}/${meterValue.sampledValue[
-                sampledValuesPerPhaseIndex
-              ].value.toString()}/${connectorMaximumPowerPerPhaseRounded.toString()}`
-            )
+          for (let phase = 1; phase <= chargingStation.getNumberOfPhases(); phase++) {
+            const phaseTemplate =
+              powerMeasurand.perPhaseTemplates[
+                `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates
+              ]
+            if (phaseTemplate != null) {
+              const phaseValue = `L${phase.toString()}-N` as MeterValuePhase
+              const phasePowerValue =
+                powerMeasurand.values[`L${phase.toString()}` as keyof MeasurandValues]
+              meterValue.sampledValue.push(
+                buildSampledValue(
+                  chargingStation.stationInfo.ocppVersion,
+                  phaseTemplate,
+                  phasePowerValue,
+                  undefined,
+                  phaseValue
+                )
+              )
+              const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1
+              validatePowerMeasurandValue(
+                chargingStation,
+                connectorId,
+                connector,
+                meterValue.sampledValue[sampledValuesPerPhaseIndex],
+                connectorMaximumPowerPerPhase / unitDivider,
+                connectorMinimumPowerPerPhase / unitDivider,
+                debug
+              )
+            }
           }
         }
       }
       // Current.Import measurand
-      currentSampledValueTemplate = getSampledValueTemplate(
-        chargingStation,
-        connectorId,
-        MeterValueMeasurand.CURRENT_IMPORT
-      )
-      if (chargingStation.getNumberOfPhases() === 3) {
-        currentPerPhaseSampledValueTemplates = {
-          L1: getSampledValueTemplate(
-            chargingStation,
-            connectorId,
-            MeterValueMeasurand.CURRENT_IMPORT,
-            MeterValuePhase.L1
-          ),
-          L2: getSampledValueTemplate(
-            chargingStation,
-            connectorId,
-            MeterValueMeasurand.CURRENT_IMPORT,
-            MeterValuePhase.L2
-          ),
-          L3: getSampledValueTemplate(
-            chargingStation,
-            connectorId,
-            MeterValueMeasurand.CURRENT_IMPORT,
-            MeterValuePhase.L3
-          ),
-        }
-      }
-      if (currentSampledValueTemplate != null) {
-        checkMeasurandPowerDivider(chargingStation, currentSampledValueTemplate.measurand)
-        const errMsg = `MeterValues measurand ${
-          currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
-          chargingStation.templateFile
-        }, cannot calculate ${
-          currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-        } measurand value`
-        const currentMeasurandValues: MeasurandValues = {} as MeasurandValues
-        connectorMaximumAvailablePower == null &&
-          (connectorMaximumAvailablePower =
-            chargingStation.getConnectorMaximumAvailablePower(connectorId))
-        const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0
-        let connectorMaximumAmperage: number
-        switch (chargingStation.stationInfo.currentOutType) {
-          case CurrentType.AC:
-            connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
+      const currentMeasurand = buildCurrentMeasurandValue(chargingStation, connectorId)
+      if (currentMeasurand != null) {
+        const connectorMaximumAvailablePower =
+          chargingStation.getConnectorMaximumAvailablePower(connectorId)
+        const connectorMaximumAmperage =
+          chargingStation.stationInfo.currentOutType === CurrentType.AC
+            ? ACElectricUtils.amperagePerPhaseFromPower(
               chargingStation.getNumberOfPhases(),
               connectorMaximumAvailablePower,
               // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
               chargingStation.stationInfo.voltageOut!
             )
-            if (chargingStation.getNumberOfPhases() === 3) {
-              const defaultFluctuatedAmperagePerPhase = isNotEmptyString(
-                currentSampledValueTemplate.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    currentSampledValueTemplate.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      fallbackValue: connectorMinimumAmperage,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  currentSampledValueTemplate.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              const phase1FluctuatedValue = isNotEmptyString(
-                currentPerPhaseSampledValueTemplates.L1?.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L1.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      fallbackValue: connectorMinimumAmperage,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              const phase2FluctuatedValue = isNotEmptyString(
-                currentPerPhaseSampledValueTemplates.L2?.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L2.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      fallbackValue: connectorMinimumAmperage,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              const phase3FluctuatedValue = isNotEmptyString(
-                currentPerPhaseSampledValueTemplates.L3?.value
-              )
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L3.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      fallbackValue: connectorMinimumAmperage,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : undefined
-              currentMeasurandValues.L1 =
-                phase1FluctuatedValue ??
-                defaultFluctuatedAmperagePerPhase ??
-                getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
-              currentMeasurandValues.L2 =
-                phase2FluctuatedValue ??
-                defaultFluctuatedAmperagePerPhase ??
-                getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
-              currentMeasurandValues.L3 =
-                phase3FluctuatedValue ??
-                defaultFluctuatedAmperagePerPhase ??
-                getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
-            } else {
-              currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value)
-                ? getRandomFloatFluctuatedRounded(
-                  getLimitFromSampledValueTemplateCustomValue(
-                    currentSampledValueTemplate.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      fallbackValue: connectorMinimumAmperage,
-                      limitationEnabled:
-                          chargingStation.stationInfo.customValueLimitationMeterValues,
-                    }
-                  ),
-                  currentSampledValueTemplate.fluctuationPercent ??
-                      Constants.DEFAULT_FLUCTUATION_PERCENT
-                )
-                : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
-              currentMeasurandValues.L2 = 0
-              currentMeasurandValues.L3 = 0
-            }
-            currentMeasurandValues.allPhases = roundTo(
-              (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
-                chargingStation.getNumberOfPhases(),
-              2
-            )
-            break
-          case CurrentType.DC:
-            connectorMaximumAmperage = DCElectricUtils.amperage(
+            : DCElectricUtils.amperage(
               connectorMaximumAvailablePower,
               // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
               chargingStation.stationInfo.voltageOut!
             )
-            currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value)
-              ? getRandomFloatFluctuatedRounded(
-                getLimitFromSampledValueTemplateCustomValue(
-                  currentSampledValueTemplate.value,
-                  connectorMaximumAmperage,
-                  connectorMinimumAmperage,
-                  {
-                    fallbackValue: connectorMinimumAmperage,
-                    limitationEnabled:
-                        chargingStation.stationInfo.customValueLimitationMeterValues,
-                  }
-                ),
-                currentSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT
-              )
-              : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
-            break
-          default:
-            logger.error(`${chargingStation.logPrefix()} ${errMsg}`)
-            throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES)
-        }
+        const connectorMinimumAmperage = currentMeasurand.template.minimumValue ?? 0
+
         meterValue.sampledValue.push(
           buildSampledValue(
             chargingStation.stationInfo.ocppVersion,
-            currentSampledValueTemplate,
-            currentMeasurandValues.allPhases
+            currentMeasurand.template,
+            currentMeasurand.values.allPhases
           )
         )
         const sampledValuesIndex = meterValue.sampledValue.length - 1
-        if (
-          convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
-            connectorMaximumAmperage ||
-          convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
-            connectorMinimumAmperage ||
+        validateCurrentMeasurandValue(
+          chargingStation,
+          connectorId,
+          connector,
+          meterValue.sampledValue[sampledValuesIndex],
+          connectorMaximumAmperage,
+          connectorMinimumAmperage,
           debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesIndex].measurand ??
-              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumAmperage.toString()}/${meterValue.sampledValue[
-              sampledValuesIndex
-            ].value.toString()}/${connectorMaximumAmperage.toString()}`
-          )
-        }
+        )
         for (
           let phase = 1;
           chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
           phase++
         ) {
-          const phaseValue = `L${phase.toString()}`
+          const phaseValue = `L${phase.toString()}` as MeterValuePhase
           meterValue.sampledValue.push(
             buildSampledValue(
               chargingStation.stationInfo.ocppVersion,
-              currentPerPhaseSampledValueTemplates[
+              currentMeasurand.perPhaseTemplates[
                 phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
-              ] ?? currentSampledValueTemplate,
-              currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
+              ] ?? currentMeasurand.template,
+              currentMeasurand.values[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
               undefined,
-              phaseValue as MeterValuePhase
+              phaseValue
             )
           )
           const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1
-          if (
-            convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
-              connectorMaximumAmperage ||
-            convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
-              connectorMinimumAmperage ||
+          validateCurrentMeasurandPhaseValue(
+            chargingStation,
+            connectorId,
+            connector,
+            meterValue.sampledValue[sampledValuesPerPhaseIndex],
+            connectorMaximumAmperage,
+            connectorMinimumAmperage,
             debug
-          ) {
-            logger.error(
-              `${chargingStation.logPrefix()} MeterValues measurand ${
-                meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
-                MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              }: phase ${
-                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-              }, connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumAmperage.toString()}/${meterValue.sampledValue[
-                sampledValuesPerPhaseIndex
-              ].value.toString()}/${connectorMaximumAmperage.toString()}`
-            )
-          }
+          )
         }
       }
       // Energy.Active.Import.Register measurand (default)
-      energySampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId)
-      if (energySampledValueTemplate != null) {
-        checkMeasurandPowerDivider(chargingStation, energySampledValueTemplate.measurand)
+      const energyMeasurand = buildEnergyMeasurandValue(chargingStation, connectorId, interval)
+      if (energyMeasurand != null) {
+        updateConnectorEnergyValues(connector, energyMeasurand.value)
         const unitDivider =
-          energySampledValueTemplate.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
-        connectorMaximumAvailablePower == null &&
-          (connectorMaximumAvailablePower =
-            chargingStation.getConnectorMaximumAvailablePower(connectorId))
+          energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+        const energySampledValue = buildSampledValue(
+          chargingStation.stationInfo.ocppVersion,
+          energyMeasurand.template,
+          roundTo(
+            chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
+              unitDivider,
+            2
+          )
+        )
+        meterValue.sampledValue.push(energySampledValue)
+        const connectorMaximumAvailablePower =
+          chargingStation.getConnectorMaximumAvailablePower(connectorId)
         const connectorMaximumEnergyRounded = roundTo(
           (connectorMaximumAvailablePower * interval) / (3600 * 1000),
           2
         )
-        const connectorMinimumEnergyRounded = roundTo(
-          energySampledValueTemplate.minimumValue ?? 0,
-          2
-        )
-        const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value)
-          ? getRandomFloatFluctuatedRounded(
-            getLimitFromSampledValueTemplateCustomValue(
-              energySampledValueTemplate.value,
-              connectorMaximumEnergyRounded,
-              connectorMinimumEnergyRounded,
-              {
-                fallbackValue: connectorMinimumEnergyRounded,
-                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
-                unitMultiplier: unitDivider,
-              }
-            ),
-            energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
-          )
-          : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded)
-        if (connector != null) {
-          if (
-            connector.energyActiveImportRegisterValue != null &&
-            connector.energyActiveImportRegisterValue >= 0 &&
-            connector.transactionEnergyActiveImportRegisterValue != null &&
-            connector.transactionEnergyActiveImportRegisterValue >= 0
-          ) {
-            connector.energyActiveImportRegisterValue += energyValueRounded
-            connector.transactionEnergyActiveImportRegisterValue += energyValueRounded
-          } else {
-            connector.energyActiveImportRegisterValue = 0
-            connector.transactionEnergyActiveImportRegisterValue = 0
-          }
-        }
-        meterValue.sampledValue.push(
-          buildSampledValue(
-            chargingStation.stationInfo.ocppVersion,
-            energySampledValueTemplate,
-            roundTo(
-              chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
-                unitDivider,
-              2
-            )
-          )
-        )
-        const sampledValuesIndex = meterValue.sampledValue.length - 1
-        if (
-          energyValueRounded > connectorMaximumEnergyRounded ||
-          energyValueRounded < connectorMinimumEnergyRounded ||
+        const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2)
+        validateEnergyMeasurandValue(
+          chargingStation,
+          connectorId,
+          energySampledValue,
+          energyMeasurand.value,
+          connectorMinimumEnergyRounded,
+          connectorMaximumEnergyRounded,
+          interval,
           debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesIndex].measurand ??
-              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumEnergyRounded.toString()}/${energyValueRounded.toString()}/${connectorMaximumEnergyRounded.toString()}, duration: ${interval.toString()}ms`
-          )
-        }
+        )
       }
       return meterValue
+    }
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201: {
       const meterValue: OCPP20MeterValue = {
@@ -1115,231 +1308,101 @@ export const buildMeterValue = (
         timestamp: new Date(),
       }
       // SoC measurand
-      socSampledValueTemplate = getSampledValueTemplate(
-        chargingStation,
-        connectorId,
-        MeterValueMeasurand.STATE_OF_CHARGE
-      )
-      if (socSampledValueTemplate != null) {
-        const socMaximumValue = 100
-        const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0
-        const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
-          ? getRandomFloatFluctuatedRounded(
-            Number.parseInt(socSampledValueTemplate.value),
-            socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
-          )
-          : randomInt(socMinimumValue, socMaximumValue + 1)
-        meterValue.sampledValue.push(
-          buildSampledValue(
-            chargingStation.stationInfo.ocppVersion,
-            socSampledValueTemplate,
-            socSampledValueTemplateValue
-          )
+      const socMeasurand = buildSocMeasurandValue(chargingStation, connectorId)
+      if (socMeasurand != null) {
+        const socSampledValue = buildSampledValue(
+          chargingStation.stationInfo.ocppVersion,
+          socMeasurand.template,
+          socMeasurand.value
         )
-        const sampledValuesIndex = meterValue.sampledValue.length - 1
-        if (
-          convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue ||
-          convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue ||
+        meterValue.sampledValue.push(socSampledValue)
+        validateSocMeasurandValue(
+          chargingStation,
+          connectorId,
+          socSampledValue,
+          socMeasurand.template.minimumValue ?? 0,
+          100,
           debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesIndex].measurand ??
-              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${socMinimumValue.toString()}/${meterValue.sampledValue[
-              sampledValuesIndex
-            ].value.toString()}/${socMaximumValue.toString()}`
-          )
-        }
+        )
       }
       // Voltage measurand
-      voltageSampledValueTemplate = getSampledValueTemplate(
-        chargingStation,
-        connectorId,
-        MeterValueMeasurand.VOLTAGE
-      )
-      if (voltageSampledValueTemplate != null) {
-        const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
-          ? Number.parseInt(voltageSampledValueTemplate.value)
-          : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          chargingStation.stationInfo.voltageOut!
-        const fluctuationPercent =
-          voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
-        const voltageMeasurandValue = getRandomFloatFluctuatedRounded(
-          voltageSampledValueTemplateValue,
-          fluctuationPercent
-        )
-        if (
-          chargingStation.getNumberOfPhases() !== 3 ||
-          (chargingStation.getNumberOfPhases() === 3 &&
-            chargingStation.stationInfo.mainVoltageMeterValues === true)
-        ) {
-          meterValue.sampledValue.push(
-            buildSampledValue(
-              chargingStation.stationInfo.ocppVersion,
-              voltageSampledValueTemplate,
-              voltageMeasurandValue
-            )
-          )
-        }
+      const voltageMeasurand = buildVoltageMeasurandValue(chargingStation, connectorId)
+      if (voltageMeasurand != null) {
+        addMainVoltageToMeterValue(chargingStation, meterValue, voltageMeasurand)
         for (
           let phase = 1;
           chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
           phase++
         ) {
-          const phaseLineToNeutralValue = `L${phase.toString()}-N`
-          const voltagePhaseLineToNeutralSampledValueTemplate = getSampledValueTemplate(
+          addPhaseVoltageToMeterValue(
             chargingStation,
             connectorId,
-            MeterValueMeasurand.VOLTAGE,
-            phaseLineToNeutralValue as MeterValuePhase
+            meterValue,
+            voltageMeasurand,
+            phase
           )
-          let voltagePhaseLineToNeutralMeasurandValue: number | undefined
-          if (voltagePhaseLineToNeutralSampledValueTemplate != null) {
-            const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
-              voltagePhaseLineToNeutralSampledValueTemplate.value
-            )
-              ? Number.parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
-              : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              chargingStation.stationInfo.voltageOut!
-            const fluctuationPhaseToNeutralPercent =
-              voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
-              Constants.DEFAULT_FLUCTUATION_PERCENT
-            voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
-              voltagePhaseLineToNeutralSampledValueTemplateValue,
-              fluctuationPhaseToNeutralPercent
-            )
-          }
-          meterValue.sampledValue.push(
-            buildSampledValue(
-              chargingStation.stationInfo.ocppVersion,
-              voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
-              voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
-              undefined,
-              phaseLineToNeutralValue as MeterValuePhase
-            )
+          addLineToLineVoltageToMeterValue(
+            chargingStation,
+            connectorId,
+            meterValue,
+            voltageMeasurand,
+            phase
           )
-          if (chargingStation.stationInfo.phaseLineToLineVoltageMeterValues === true) {
-            const phaseLineToLineValue = `L${phase.toString()}-L${
-              (phase + 1) % chargingStation.getNumberOfPhases() !== 0
-                ? ((phase + 1) % chargingStation.getNumberOfPhases()).toString()
-                : chargingStation.getNumberOfPhases().toString()
-            }`
-            const voltagePhaseLineToLineValueRounded = roundTo(
-              Math.sqrt(chargingStation.getNumberOfPhases()) *
-                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                chargingStation.stationInfo.voltageOut!,
-              2
-            )
-            const voltagePhaseLineToLineSampledValueTemplate = getSampledValueTemplate(
-              chargingStation,
-              connectorId,
-              MeterValueMeasurand.VOLTAGE,
-              phaseLineToLineValue as MeterValuePhase
-            )
-            let voltagePhaseLineToLineMeasurandValue: number | undefined
-            if (voltagePhaseLineToLineSampledValueTemplate != null) {
-              const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
-                voltagePhaseLineToLineSampledValueTemplate.value
-              )
-                ? Number.parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
-                : voltagePhaseLineToLineValueRounded
-              const fluctuationPhaseLineToLinePercent =
-                voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
-                Constants.DEFAULT_FLUCTUATION_PERCENT
-              voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-                voltagePhaseLineToLineSampledValueTemplateValue,
-                fluctuationPhaseLineToLinePercent
-              )
-            }
-            const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-              voltagePhaseLineToLineValueRounded,
-              fluctuationPercent
-            )
-            meterValue.sampledValue.push(
-              buildSampledValue(
-                chargingStation.stationInfo.ocppVersion,
-                voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
-                voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
-                undefined,
-                phaseLineToLineValue as MeterValuePhase
-              )
-            )
-          }
         }
       }
       // Energy.Active.Import.Register measurand
-      energySampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId)
-      if (energySampledValueTemplate != null) {
-        checkMeasurandPowerDivider(chargingStation, energySampledValueTemplate.measurand)
+      const energyMeasurand = buildEnergyMeasurandValue(chargingStation, connectorId, interval)
+      if (energyMeasurand != null) {
+        updateConnectorEnergyValues(connector, energyMeasurand.value)
         const unitDivider =
-          energySampledValueTemplate.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
-        connectorMaximumAvailablePower == null &&
-          (connectorMaximumAvailablePower =
-            chargingStation.getConnectorMaximumAvailablePower(connectorId))
+          energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+        const energySampledValue = buildSampledValue(
+          chargingStation.stationInfo.ocppVersion,
+          energyMeasurand.template,
+          roundTo(
+            chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
+              unitDivider,
+            2
+          )
+        )
+        meterValue.sampledValue.push(energySampledValue)
+        const connectorMaximumAvailablePower =
+          chargingStation.getConnectorMaximumAvailablePower(connectorId)
         const connectorMaximumEnergyRounded = roundTo(
           (connectorMaximumAvailablePower * interval) / (3600 * 1000),
           2
         )
-        const connectorMinimumEnergyRounded = roundTo(
-          energySampledValueTemplate.minimumValue ?? 0,
-          2
+        const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2)
+        validateEnergyMeasurandValue(
+          chargingStation,
+          connectorId,
+          energySampledValue,
+          energyMeasurand.value,
+          connectorMinimumEnergyRounded,
+          connectorMaximumEnergyRounded,
+          interval,
+          debug
         )
-        const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value)
-          ? getRandomFloatFluctuatedRounded(
-            getLimitFromSampledValueTemplateCustomValue(
-              energySampledValueTemplate.value,
-              connectorMaximumEnergyRounded,
-              connectorMinimumEnergyRounded,
-              {
-                fallbackValue: connectorMinimumEnergyRounded,
-                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
-                unitMultiplier: unitDivider,
-              }
-            ),
-            energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
-          )
-          : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded)
-        if (connector != null) {
-          if (
-            connector.energyActiveImportRegisterValue != null &&
-            connector.energyActiveImportRegisterValue >= 0 &&
-            connector.transactionEnergyActiveImportRegisterValue != null &&
-            connector.transactionEnergyActiveImportRegisterValue >= 0
-          ) {
-            connector.energyActiveImportRegisterValue += energyValueRounded
-            connector.transactionEnergyActiveImportRegisterValue += energyValueRounded
-          } else {
-            connector.energyActiveImportRegisterValue = 0
-            connector.transactionEnergyActiveImportRegisterValue = 0
-          }
-        }
-        meterValue.sampledValue.push(
-          buildSampledValue(
-            chargingStation.stationInfo.ocppVersion,
-            energySampledValueTemplate,
-            roundTo(
-              chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
-                unitDivider,
-              2
-            )
-          )
+      }
+      // Power.Active.Import measurand
+      const powerMeasurand = buildPowerMeasurandValue(chargingStation, connectorId)
+      if (powerMeasurand?.values.allPhases != null) {
+        const powerSampledValue = buildSampledValue(
+          chargingStation.stationInfo.ocppVersion,
+          powerMeasurand.template,
+          powerMeasurand.values.allPhases
         )
-        const sampledValuesIndex = meterValue.sampledValue.length - 1
-        if (
-          energyValueRounded > connectorMaximumEnergyRounded ||
-          energyValueRounded < connectorMinimumEnergyRounded ||
-          debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesIndex].measurand ??
-              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumEnergyRounded.toString()}/${energyValueRounded.toString()}/${connectorMaximumEnergyRounded.toString()}, duration: ${interval.toString()}ms`
-          )
-        }
+        meterValue.sampledValue.push(powerSampledValue)
+      }
+      // Current.Import measurand
+      const currentMeasurand = buildCurrentMeasurandValue(chargingStation, connectorId)
+      if (currentMeasurand?.values.allPhases != null) {
+        const currentSampledValue = buildSampledValue(
+          chargingStation.stationInfo.ocppVersion,
+          currentMeasurand.template,
+          currentMeasurand.values.allPhases
+        )
+        meterValue.sampledValue.push(currentSampledValue)
       }
       return meterValue
     }
@@ -1563,23 +1626,23 @@ function buildSampledValue (
   context?: MeterValueContext,
   phase?: MeterValuePhase
 ): SampledValue {
-  const sampledValueContext = context ?? sampledValueTemplate.context
+  const sampledValueMeasurand = sampledValueTemplate.measurand ?? getMeasurandDefault()
+  const sampledValueUnit =
+    sampledValueTemplate.unit ?? getMeasurandDefaultUnit(sampledValueMeasurand)
+  const sampledValueContext =
+    context ?? sampledValueTemplate.context ?? getMeasurandDefaultContext(sampledValueMeasurand)
   const sampledValueLocation =
-    sampledValueTemplate.location ?? getMeasurandDefaultLocation(sampledValueTemplate.measurand)
+    sampledValueTemplate.location ?? getMeasurandDefaultLocation(sampledValueMeasurand)
   const sampledValuePhase = phase ?? sampledValueTemplate.phase
 
   switch (ocppVersion) {
     case OCPPVersion.VERSION_16:
       // OCPP 1.6 format
       return {
-        ...(sampledValueTemplate.unit != null && {
-          unit: sampledValueTemplate.unit,
-        }),
-        ...(sampledValueContext != null && { context: sampledValueContext }),
-        ...(sampledValueTemplate.measurand != null && {
-          measurand: sampledValueTemplate.measurand,
-        }),
-        ...(sampledValueLocation != null && { location: sampledValueLocation }),
+        context: sampledValueContext,
+        location: sampledValueLocation,
+        measurand: sampledValueMeasurand,
+        unit: sampledValueUnit,
         value: value.toString(), // OCPP 1.6 uses string
         ...(sampledValuePhase != null && { phase: sampledValuePhase }),
       } as OCPP16SampledValue
@@ -1587,16 +1650,12 @@ function buildSampledValue (
     case OCPPVersion.VERSION_201:
       // OCPP 2.0 format
       return {
-        ...(sampledValueContext != null && { context: sampledValueContext }),
-        ...(sampledValueTemplate.measurand != null && {
-          measurand: sampledValueTemplate.measurand,
-        }),
-        ...(sampledValueLocation != null && { location: sampledValueLocation }),
+        context: sampledValueContext,
+        location: sampledValueLocation,
+        measurand: sampledValueMeasurand,
+        ...(sampledValueUnit !== undefined && { unitOfMeasure: { unit: sampledValueUnit } }),
         value, // OCPP 2.0 uses number
         ...(sampledValuePhase != null && { phase: sampledValuePhase }),
-        ...(sampledValueTemplate.unitOfMeasure != null && {
-          unitOfMeasure: sampledValueTemplate.unitOfMeasure,
-        }),
       } as OCPP20SampledValue
     default:
       throw new OCPPError(
@@ -1608,36 +1667,123 @@ function buildSampledValue (
   }
 }
 
+const getMeasurandDefaultContext = (measurandType: MeterValueMeasurand): MeterValueContext => {
+  return MeterValueContext.SAMPLE_PERIODIC
+}
+
 const getMeasurandDefaultLocation = (
   measurandType: MeterValueMeasurand
 ): MeterValueLocation | undefined => {
   switch (measurandType) {
+    case MeterValueMeasurand.CURRENT_EXPORT:
+    case MeterValueMeasurand.CURRENT_IMPORT:
+    case MeterValueMeasurand.CURRENT_OFFERED:
+      return MeterValueLocation.OUTLET
+
+    case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_ACTIVE_NET:
+    case MeterValueMeasurand.ENERGY_APPARENT_EXPORT:
+    case MeterValueMeasurand.ENERGY_APPARENT_IMPORT:
+    case MeterValueMeasurand.ENERGY_APPARENT_NET:
+    case MeterValueMeasurand.ENERGY_REACTIVE_EXPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_REACTIVE_EXPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_REACTIVE_IMPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_REACTIVE_IMPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_REACTIVE_NET:
+      return MeterValueLocation.OUTLET
+
+    case MeterValueMeasurand.FAN_RPM:
+      return MeterValueLocation.BODY
+
+    case MeterValueMeasurand.FREQUENCY:
+      return MeterValueLocation.OUTLET
+
+    case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
+    case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
+    case MeterValueMeasurand.POWER_FACTOR:
+    case MeterValueMeasurand.POWER_OFFERED:
+    case MeterValueMeasurand.POWER_REACTIVE_EXPORT:
+    case MeterValueMeasurand.POWER_REACTIVE_IMPORT:
+      return MeterValueLocation.OUTLET
+
     case MeterValueMeasurand.STATE_OF_CHARGE:
       return MeterValueLocation.EV
+
+    case MeterValueMeasurand.TEMPERATURE:
+      return MeterValueLocation.OUTLET
+
+    case MeterValueMeasurand.VOLTAGE:
+      return MeterValueLocation.OUTLET
+
+    default:
+      return undefined
   }
 }
 
-// const getMeasurandDefaultUnit = (
-//   measurandType: MeterValueMeasurand
-// ): MeterValueUnit | undefined => {
-//   switch (measurandType) {
-//     case MeterValueMeasurand.CURRENT_EXPORT:
-//     case MeterValueMeasurand.CURRENT_IMPORT:
-//     case MeterValueMeasurand.CURRENT_OFFERED:
-//       return MeterValueUnit.AMP
-//     case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
-//     case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
-//       return MeterValueUnit.WATT_HOUR
-//     case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
-//     case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
-//     case MeterValueMeasurand.POWER_OFFERED:
-//       return MeterValueUnit.WATT
-//     case MeterValueMeasurand.STATE_OF_CHARGE:
-//       return MeterValueUnit.PERCENT
-//     case MeterValueMeasurand.VOLTAGE:
-//       return MeterValueUnit.VOLT
-//   }
-// }
+const getMeasurandDefault = (): MeterValueMeasurand => {
+  return MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+}
+
+const getMeasurandDefaultUnit = (
+  measurandType: MeterValueMeasurand
+): MeterValueUnit | undefined => {
+  switch (measurandType) {
+    case MeterValueMeasurand.CURRENT_EXPORT:
+    case MeterValueMeasurand.CURRENT_IMPORT:
+    case MeterValueMeasurand.CURRENT_OFFERED:
+      return MeterValueUnit.AMP
+
+    case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_ACTIVE_NET:
+    case MeterValueMeasurand.ENERGY_APPARENT_EXPORT:
+    case MeterValueMeasurand.ENERGY_APPARENT_IMPORT:
+    case MeterValueMeasurand.ENERGY_APPARENT_NET:
+      return MeterValueUnit.WATT_HOUR
+
+    case MeterValueMeasurand.ENERGY_REACTIVE_EXPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_REACTIVE_EXPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_REACTIVE_IMPORT_INTERVAL:
+    case MeterValueMeasurand.ENERGY_REACTIVE_IMPORT_REGISTER:
+    case MeterValueMeasurand.ENERGY_REACTIVE_NET:
+      return MeterValueUnit.VAR_HOUR
+
+    case MeterValueMeasurand.FAN_RPM:
+      return MeterValueUnit.REVOLUTIONS_PER_MINUTE
+
+    case MeterValueMeasurand.FREQUENCY:
+      return MeterValueUnit.HERTZ
+
+    case MeterValueMeasurand.POWER_ACTIVE_EXPORT:
+    case MeterValueMeasurand.POWER_ACTIVE_IMPORT:
+    case MeterValueMeasurand.POWER_OFFERED:
+      return MeterValueUnit.WATT
+
+    case MeterValueMeasurand.POWER_FACTOR:
+      return undefined
+
+    case MeterValueMeasurand.POWER_REACTIVE_EXPORT:
+    case MeterValueMeasurand.POWER_REACTIVE_IMPORT:
+      return MeterValueUnit.VAR
+
+    case MeterValueMeasurand.STATE_OF_CHARGE:
+      return MeterValueUnit.PERCENT
+
+    case MeterValueMeasurand.TEMPERATURE:
+      return MeterValueUnit.TEMP_CELSIUS
+
+    case MeterValueMeasurand.VOLTAGE:
+      return MeterValueUnit.VOLT
+
+    default:
+      return undefined
+  }
+}
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class OCPPServiceUtils {