perf: minimize OCPPUtils exports
authorJérôme Benoit <jerome.benoit@sap.com>
Tue, 5 Dec 2023 13:25:21 +0000 (14:25 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Tue, 5 Dec 2023 13:25:21 +0000 (14:25 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
15 files changed:
package.json
pnpm-lock.yaml
src/charging-station/ChargingStation.ts
src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ocpp/index.ts
src/types/index.ts
src/types/ocpp/1.6/MeterValues.ts
src/types/ocpp/1.6/Requests.ts
src/types/ocpp/1.6/Responses.ts
src/types/ocpp/MeterValues.ts
ui/web/pnpm-lock.yaml

index 68c7be88b7e15a190aa3cc9b1897a5401fa0d611..a9833b5965820f8c93fe83e41e83341c32ddfde8 100644 (file)
     "http-status-codes": "^2.3.0",
     "just-merge": "^3.2.0",
     "logform": "^2.6.0",
-    "mnemonist": "^0.39.5",
+    "mnemonist": "^0.39.6",
     "mongodb": "^6.3.0",
     "poolifier": "^3.0.9",
     "tar": "^6.2.0",
index fcddaeeb22b1365bfa4944fa401e6216d043b5d9..ac7b4fba67a9fed56e9e1ea3e18f5f25ff4570b5 100644 (file)
@@ -51,8 +51,8 @@ dependencies:
     specifier: ^2.6.0
     version: 2.6.0
   mnemonist:
-    specifier: ^0.39.5
-    version: 0.39.5
+    specifier: ^0.39.6
+    version: 0.39.6
   mongodb:
     specifier: ^6.3.0
     version: 6.3.0
@@ -1435,7 +1435,7 @@ packages:
       '@octokit/graphql': 7.0.2
       '@octokit/request': 8.1.6
       '@octokit/request-error': 5.0.1
-      '@octokit/types': 12.3.0
+      '@octokit/types': 12.4.0
       before-after-hook: 2.2.3
       universal-user-agent: 6.0.1
     dev: true
@@ -1444,7 +1444,7 @@ packages:
     resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==}
     engines: {node: '>= 18'}
     dependencies:
-      '@octokit/types': 12.3.0
+      '@octokit/types': 12.4.0
       universal-user-agent: 6.0.1
     dev: true
 
@@ -1453,7 +1453,7 @@ packages:
     engines: {node: '>= 18'}
     dependencies:
       '@octokit/request': 8.1.6
-      '@octokit/types': 12.3.0
+      '@octokit/types': 12.4.0
       universal-user-agent: 6.0.1
     dev: true
 
@@ -1461,14 +1461,14 @@ packages:
     resolution: {integrity: sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==}
     dev: true
 
-  /@octokit/plugin-paginate-rest@9.1.4(@octokit/core@5.0.2):
-    resolution: {integrity: sha512-MvZx4WvfhBnt7PtH5XE7HORsO7bBk4er1FgRIUr1qJ89NR2I6bWjGyKsxk8z42FPQ34hFQm0Baanh4gzdZR4gQ==}
+  /@octokit/plugin-paginate-rest@9.1.5(@octokit/core@5.0.2):
+    resolution: {integrity: sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg==}
     engines: {node: '>= 18'}
     peerDependencies:
       '@octokit/core': '>=5'
     dependencies:
       '@octokit/core': 5.0.2
-      '@octokit/types': 12.3.0
+      '@octokit/types': 12.4.0
     dev: true
 
   /@octokit/plugin-request-log@4.0.0(@octokit/core@5.0.2):
@@ -1487,14 +1487,14 @@ packages:
       '@octokit/core': '>=5'
     dependencies:
       '@octokit/core': 5.0.2
-      '@octokit/types': 12.3.0
+      '@octokit/types': 12.4.0
     dev: true
 
   /@octokit/request-error@5.0.1:
     resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==}
     engines: {node: '>= 18'}
     dependencies:
-      '@octokit/types': 12.3.0
+      '@octokit/types': 12.4.0
       deprecation: 2.3.1
       once: 1.4.0
     dev: true
@@ -1505,7 +1505,7 @@ packages:
     dependencies:
       '@octokit/endpoint': 9.0.4
       '@octokit/request-error': 5.0.1
-      '@octokit/types': 12.3.0
+      '@octokit/types': 12.4.0
       universal-user-agent: 6.0.1
     dev: true
 
@@ -1514,13 +1514,13 @@ packages:
     engines: {node: '>= 18'}
     dependencies:
       '@octokit/core': 5.0.2
-      '@octokit/plugin-paginate-rest': 9.1.4(@octokit/core@5.0.2)
+      '@octokit/plugin-paginate-rest': 9.1.5(@octokit/core@5.0.2)
       '@octokit/plugin-request-log': 4.0.0(@octokit/core@5.0.2)
       '@octokit/plugin-rest-endpoint-methods': 10.2.0(@octokit/core@5.0.2)
     dev: true
 
-  /@octokit/types@12.3.0:
-    resolution: {integrity: sha512-nJ8X2HRr234q3w/FcovDlA+ttUU4m1eJAourvfUUtwAWeqL8AsyRqfnLvVnYn3NFbUnsmzQCzLNdFerPwdmcDQ==}
+  /@octokit/types@12.4.0:
+    resolution: {integrity: sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==}
     dependencies:
       '@octokit/openapi-types': 19.1.0
     dev: true
@@ -6941,8 +6941,8 @@ packages:
     resolution: {integrity: sha512-VoAYUqmPRmzKbbqRejjqceGFp3VF81Qe8XXFGU0UXLxB7Mf4GGvyGq5Qn3k4AiQgDEV6WzobqlPOd+j0+m6IrA==}
     dev: true
 
-  /mnemonist@0.39.5:
-    resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==}
+  /mnemonist@0.39.6:
+    resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==}
     dependencies:
       obliterator: 2.0.4
     dev: false
index 5860a654e47afc9486653cc9ab1f4f069375f9b6..cf574f55bd4c768db507f9a36a479164806d13a9 100644 (file)
@@ -49,13 +49,14 @@ import {
   OCPP16IncomingRequestService,
   OCPP16RequestService,
   OCPP16ResponseService,
-  OCPP16ServiceUtils,
   OCPP20IncomingRequestService,
   OCPP20RequestService,
   OCPP20ResponseService,
   type OCPPIncomingRequestService,
   type OCPPRequestService,
+  buildMeterValue,
   buildStatusNotificationRequest,
+  buildTransactionEndMeterValue,
   getMessageTypeString,
   sendAndSetConnectorStatus,
 } from './ocpp';
@@ -573,8 +574,7 @@ export class ChargingStation extends EventEmitter {
     }
     if (interval > 0) {
       this.getConnectorStatus(connectorId)!.transactionSetInterval = setInterval(() => {
-        // FIXME: Implement OCPP version agnostic helpers
-        const meterValue: MeterValue = OCPP16ServiceUtils.buildMeterValue(
+        const meterValue: MeterValue = buildMeterValue(
           this,
           connectorId,
           this.getConnectorStatus(connectorId)!.transactionId!,
@@ -850,8 +850,7 @@ export class ChargingStation extends EventEmitter {
       this.stationInfo?.ocppStrictCompliance === true &&
       this.stationInfo?.outOfOrderEndMeterValues === false
     ) {
-      // FIXME: Implement OCPP version agnostic helpers
-      const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
+      const transactionEndMeterValue = buildTransactionEndMeterValue(
         this,
         connectorId,
         this.getEnergyActiveImportRegisterByTransactionId(transactionId!),
index a8d9b045abde5b93728ab16548a3a4e91fec8511..2697397a6b7e6bcebed8fd47cd34a548bb471c6e 100644 (file)
@@ -40,7 +40,7 @@ import {
 import { Constants, convertToInt, isEmptyObject, isNullOrUndefined, logger } from '../../utils';
 import type { ChargingStation } from '../ChargingStation';
 import { getConfigurationKey } from '../ConfigurationKeyUtils';
-import { OCPP16ServiceUtils } from '../ocpp';
+import { buildMeterValue } from '../ocpp';
 
 const moduleName = 'ChargingStationWorkerBroadcastChannel';
 
@@ -188,8 +188,7 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
             RequestCommand.METER_VALUES,
             {
               meterValue: [
-                // FIXME: Implement OCPP version agnostic helpers
-                OCPP16ServiceUtils.buildMeterValue(
+                buildMeterValue(
                   this.chargingStation,
                   requestPayload!.connectorId!,
                   this.chargingStation.getConnectorStatus(requestPayload!.connectorId!)!
index 21a6e1ece5b377925ba469643740f73e2c9f1554..8faf05cf609676ebca14249de5139edd215a3055 100644 (file)
@@ -32,8 +32,6 @@ import { OCPPError } from '../../../exception';
 import {
   type ChangeConfigurationRequest,
   type ChangeConfigurationResponse,
-  type ClearChargingProfileRequest,
-  type ClearChargingProfileResponse,
   ErrorType,
   type GenericResponse,
   GenericStatus,
@@ -56,6 +54,8 @@ import {
   OCPP16ChargingProfilePurposeType,
   type OCPP16ChargingSchedule,
   type OCPP16ClearCacheRequest,
+  type OCPP16ClearChargingProfileRequest,
+  type OCPP16ClearChargingProfileResponse,
   type OCPP16DataTransferRequest,
   type OCPP16DataTransferResponse,
   OCPP16DataTransferVendorId,
@@ -260,7 +260,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       ],
       [
         OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-        OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileRequest>(
+        OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
           'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
           moduleName,
           'constructor',
@@ -798,8 +798,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
 
   private handleRequestClearChargingProfile(
     chargingStation: ChargingStation,
-    commandPayload: ClearChargingProfileRequest,
-  ): ClearChargingProfileResponse {
+    commandPayload: OCPP16ClearChargingProfileRequest,
+  ): OCPP16ClearChargingProfileResponse {
     if (
       OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
index 146778141abdbcc3c72b7860d2fde9b5d8792c8e..7c331b6c79d70c328276c8b196ca9e552591d19e 100644 (file)
@@ -14,7 +14,6 @@ import {
 import { OCPPError } from '../../../exception';
 import {
   type ChangeConfigurationResponse,
-  type ClearChargingProfileResponse,
   ErrorType,
   type GenericResponse,
   type GetConfigurationResponse,
@@ -26,6 +25,7 @@ import {
   type OCPP16BootNotificationResponse,
   type OCPP16ChangeAvailabilityResponse,
   OCPP16ChargePointStatus,
+  type OCPP16ClearChargingProfileResponse,
   type OCPP16DataTransferResponse,
   type OCPP16DiagnosticsStatusNotificationResponse,
   type OCPP16FirmwareStatusNotificationResponse,
@@ -249,7 +249,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-        OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileResponse>(
+        OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
           'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
           moduleName,
           'constructor',
index b194e08e8d5a58db9641a337bd58b7d27aa5799f..4062bc0af881d3445fdfc5207983d531dbf89b2a 100644 (file)
@@ -17,52 +17,27 @@ import {
   hasFeatureProfile,
   hasReservationExpired,
 } from '../../../charging-station';
-import { OCPPError } from '../../../exception';
 import {
-  type ClearChargingProfileRequest,
-  CurrentType,
-  ErrorType,
   type GenericResponse,
   type JsonType,
-  type MeasurandPerPhaseSampledValueTemplates,
-  type MeasurandValues,
-  MeterValueContext,
-  MeterValueLocation,
-  MeterValueUnit,
   OCPP16AuthorizationStatus,
   OCPP16AvailabilityType,
   type OCPP16ChangeAvailabilityResponse,
   OCPP16ChargePointStatus,
   type OCPP16ChargingProfile,
   type OCPP16ChargingSchedule,
+  type OCPP16ClearChargingProfileRequest,
   type OCPP16IncomingRequestCommand,
   type OCPP16MeterValue,
-  OCPP16MeterValueMeasurand,
-  OCPP16MeterValuePhase,
+  OCPP16MeterValueContext,
+  OCPP16MeterValueUnit,
   OCPP16RequestCommand,
-  type OCPP16SampledValue,
   OCPP16StandardParametersKey,
   OCPP16StopTransactionReason,
   type OCPP16SupportedFeatureProfiles,
   OCPPVersion,
-  type SampledValueTemplate,
 } from '../../../types';
-import {
-  ACElectricUtils,
-  Constants,
-  DCElectricUtils,
-  convertToFloat,
-  convertToInt,
-  getRandomFloatFluctuatedRounded,
-  getRandomFloatRounded,
-  getRandomInteger,
-  isNotEmptyArray,
-  isNotEmptyString,
-  isNullOrUndefined,
-  isUndefined,
-  logger,
-  roundTo,
-} from '../../../utils';
+import { isNotEmptyArray, isNullOrUndefined, logger, roundTo } from '../../../utils';
 import { OCPPServiceUtils } from '../OCPPServiceUtils';
 
 export class OCPP16ServiceUtils extends OCPPServiceUtils {
@@ -82,764 +57,6 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return true;
   }
 
-  public static buildMeterValue(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    transactionId: number,
-    interval: number,
-    debug = false,
-  ): OCPP16MeterValue {
-    const meterValue: OCPP16MeterValue = {
-      timestamp: new Date(),
-      sampledValue: [],
-    };
-    const connector = chargingStation.getConnectorStatus(connectorId);
-    // SoC measurand
-    const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
-      chargingStation,
-      connectorId,
-      OCPP16MeterValueMeasurand.STATE_OF_CHARGE,
-    );
-    if (socSampledValueTemplate) {
-      const socMaximumValue = 100;
-      const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
-      const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
-        ? getRandomFloatFluctuatedRounded(
-            parseInt(socSampledValueTemplate.value),
-            socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
-          )
-        : getRandomInteger(socMaximumValue, socMinimumValue);
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue),
-      );
-      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 ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
-            meterValue.sampledValue[sampledValuesIndex].value
-          }/${socMaximumValue}`,
-        );
-      }
-    }
-    // Voltage measurand
-    const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
-      chargingStation,
-      connectorId,
-      OCPP16MeterValueMeasurand.VOLTAGE,
-    );
-    if (voltageSampledValueTemplate) {
-      const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
-        ? parseInt(voltageSampledValueTemplate.value)
-        : 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)
-      ) {
-        meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue),
-        );
-      }
-      for (
-        let phase = 1;
-        chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-        phase++
-      ) {
-        const phaseLineToNeutralValue = `L${phase}-N`;
-        const voltagePhaseLineToNeutralSampledValueTemplate =
-          OCPP16ServiceUtils.getSampledValueTemplate(
-            chargingStation,
-            connectorId,
-            OCPP16MeterValueMeasurand.VOLTAGE,
-            phaseLineToNeutralValue as OCPP16MeterValuePhase,
-          );
-        let voltagePhaseLineToNeutralMeasurandValue: number | undefined;
-        if (voltagePhaseLineToNeutralSampledValueTemplate) {
-          const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
-            voltagePhaseLineToNeutralSampledValueTemplate.value,
-          )
-            ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
-            : chargingStation.stationInfo.voltageOut!;
-          const fluctuationPhaseToNeutralPercent =
-            voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
-            Constants.DEFAULT_FLUCTUATION_PERCENT;
-          voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
-            voltagePhaseLineToNeutralSampledValueTemplateValue,
-            fluctuationPhaseToNeutralPercent,
-          );
-        }
-        meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(
-            voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
-            voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
-            undefined,
-            phaseLineToNeutralValue as OCPP16MeterValuePhase,
-          ),
-        );
-        if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) {
-          const phaseLineToLineValue = `L${phase}-L${
-            (phase + 1) % chargingStation.getNumberOfPhases() !== 0
-              ? (phase + 1) % chargingStation.getNumberOfPhases()
-              : chargingStation.getNumberOfPhases()
-          }`;
-          const voltagePhaseLineToLineValueRounded = roundTo(
-            Math.sqrt(chargingStation.getNumberOfPhases()) *
-              chargingStation.stationInfo.voltageOut!,
-            2,
-          );
-          const voltagePhaseLineToLineSampledValueTemplate =
-            OCPP16ServiceUtils.getSampledValueTemplate(
-              chargingStation,
-              connectorId,
-              OCPP16MeterValueMeasurand.VOLTAGE,
-              phaseLineToLineValue as OCPP16MeterValuePhase,
-            );
-          let voltagePhaseLineToLineMeasurandValue: number | undefined;
-          if (voltagePhaseLineToLineSampledValueTemplate) {
-            const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
-              voltagePhaseLineToLineSampledValueTemplate.value,
-            )
-              ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
-              : voltagePhaseLineToLineValueRounded;
-            const fluctuationPhaseLineToLinePercent =
-              voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
-              Constants.DEFAULT_FLUCTUATION_PERCENT;
-            voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-              voltagePhaseLineToLineSampledValueTemplateValue,
-              fluctuationPhaseLineToLinePercent,
-            );
-          }
-          const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
-            voltagePhaseLineToLineValueRounded,
-            fluctuationPercent,
-          );
-          meterValue.sampledValue.push(
-            OCPP16ServiceUtils.buildSampledValue(
-              voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
-              voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
-              undefined,
-              phaseLineToLineValue as OCPP16MeterValuePhase,
-            ),
-          );
-        }
-      }
-    }
-    // Power.Active.Import measurand
-    const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
-      chargingStation,
-      connectorId,
-      OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-    );
-    let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
-    if (chargingStation.getNumberOfPhases() === 3) {
-      powerPerPhaseSampledValueTemplates = {
-        L1: OCPP16ServiceUtils.getSampledValueTemplate(
-          chargingStation,
-          connectorId,
-          OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L1_N,
-        ),
-        L2: OCPP16ServiceUtils.getSampledValueTemplate(
-          chargingStation,
-          connectorId,
-          OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L2_N,
-        ),
-        L3: OCPP16ServiceUtils.getSampledValueTemplate(
-          chargingStation,
-          connectorId,
-          OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-          OCPP16MeterValuePhase.L3_N,
-        ),
-      };
-    }
-    if (powerSampledValueTemplate) {
-      OCPP16ServiceUtils.checkMeasurandPowerDivider(
-        chargingStation,
-        powerSampledValueTemplate.measurand!,
-      );
-      const errMsg = `MeterValues measurand ${
-        powerSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
-        chargingStation.templateFile
-      }, cannot calculate ${
-        powerSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      } measurand value`;
-      const powerMeasurandValues: MeasurandValues = {} as MeasurandValues;
-      const unitDivider = powerSampledValueTemplate?.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(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    powerSampledValueTemplate.value,
-                    connectorMaximumPower / unitDivider,
-                    connectorMinimumPower / unitDivider,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPower / unitDivider,
-                    },
-                  ) / chargingStation.getNumberOfPhases(),
-                  powerSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            const phase1FluctuatedValue = isNotEmptyString(
-              powerPerPhaseSampledValueTemplates.L1?.value,
-            )
-              ? getRandomFloatFluctuatedRounded(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L1?.value,
-                    connectorMaximumPowerPerPhase / unitDivider,
-                    connectorMinimumPowerPerPhase / unitDivider,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
-                    },
-                  ),
-                  powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            const phase2FluctuatedValue = isNotEmptyString(
-              powerPerPhaseSampledValueTemplates.L2?.value,
-            )
-              ? getRandomFloatFluctuatedRounded(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L2?.value,
-                    connectorMaximumPowerPerPhase / unitDivider,
-                    connectorMinimumPowerPerPhase / unitDivider,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
-                    },
-                  ),
-                  powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            const phase3FluctuatedValue = isNotEmptyString(
-              powerPerPhaseSampledValueTemplates.L3?.value,
-            )
-              ? getRandomFloatFluctuatedRounded(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L3?.value,
-                    connectorMaximumPowerPerPhase / unitDivider,
-                    connectorMinimumPowerPerPhase / unitDivider,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
-                    },
-                  ),
-                  powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            powerMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              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(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    powerSampledValueTemplate.value,
-                    connectorMaximumPower / unitDivider,
-                    connectorMinimumPower / unitDivider,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPower / unitDivider,
-                    },
-                  ),
-                  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(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  powerSampledValueTemplate.value,
-                  connectorMaximumPower / unitDivider,
-                  connectorMinimumPower / unitDivider,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                    fallbackValue: connectorMinimumPower / unitDivider,
-                  },
-                ),
-                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, OCPP16RequestCommand.METER_VALUES);
-      }
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(
-          powerSampledValueTemplate,
-          powerMeasurandValues.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 ||
-        debug
-      ) {
-        logger.error(
-          `${chargingStation.logPrefix()} MeterValues measurand ${
-            meterValue.sampledValue[sampledValuesIndex].measurand ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
-            meterValue.sampledValue[sampledValuesIndex].value
-          }/${connectorMaximumPowerRounded}`,
-        );
-      }
-      for (
-        let phase = 1;
-        chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-        phase++
-      ) {
-        const phaseValue = `L${phase}-N`;
-        meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(
-            powerPerPhaseSampledValueTemplates[
-              `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
-            ] ?? powerSampledValueTemplate,
-            powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
-            undefined,
-            phaseValue as OCPP16MeterValuePhase,
-          ),
-        );
-        const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
-        const connectorMaximumPowerPerPhaseRounded = roundTo(
-          connectorMaximumPowerPerPhase / unitDivider,
-          2,
-        );
-        const connectorMinimumPowerPerPhaseRounded = roundTo(
-          connectorMinimumPowerPerPhase / unitDivider,
-          2,
-        );
-        if (
-          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
-            connectorMaximumPowerPerPhaseRounded ||
-          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
-            connectorMinimumPowerPerPhaseRounded ||
-          debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
-              OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: phase ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-            }/${connectorMaximumPowerPerPhaseRounded}`,
-          );
-        }
-      }
-    }
-    // Current.Import measurand
-    const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
-      chargingStation,
-      connectorId,
-      OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-    );
-    let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
-    if (chargingStation.getNumberOfPhases() === 3) {
-      currentPerPhaseSampledValueTemplates = {
-        L1: OCPP16ServiceUtils.getSampledValueTemplate(
-          chargingStation,
-          connectorId,
-          OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L1,
-        ),
-        L2: OCPP16ServiceUtils.getSampledValueTemplate(
-          chargingStation,
-          connectorId,
-          OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L2,
-        ),
-        L3: OCPP16ServiceUtils.getSampledValueTemplate(
-          chargingStation,
-          connectorId,
-          OCPP16MeterValueMeasurand.CURRENT_IMPORT,
-          OCPP16MeterValuePhase.L3,
-        ),
-      };
-    }
-    if (currentSampledValueTemplate) {
-      OCPP16ServiceUtils.checkMeasurandPowerDivider(
-        chargingStation,
-        currentSampledValueTemplate.measurand!,
-      );
-      const errMsg = `MeterValues measurand ${
-        currentSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
-        chargingStation.templateFile
-      }, cannot calculate ${
-        currentSampledValueTemplate.measurand ??
-        OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      } measurand value`;
-      const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
-      const connectorMaximumAvailablePower =
-        chargingStation.getConnectorMaximumAvailablePower(connectorId);
-      const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
-      let connectorMaximumAmperage: number;
-      switch (chargingStation.stationInfo?.currentOutType) {
-        case CurrentType.AC:
-          connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
-            chargingStation.getNumberOfPhases(),
-            connectorMaximumAvailablePower,
-            chargingStation.stationInfo.voltageOut!,
-          );
-          if (chargingStation.getNumberOfPhases() === 3) {
-            const defaultFluctuatedAmperagePerPhase = isNotEmptyString(
-              currentSampledValueTemplate.value,
-            )
-              ? getRandomFloatFluctuatedRounded(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    currentSampledValueTemplate.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage,
-                    },
-                  ),
-                  currentSampledValueTemplate.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            const phase1FluctuatedValue = isNotEmptyString(
-              currentPerPhaseSampledValueTemplates.L1?.value,
-            )
-              ? getRandomFloatFluctuatedRounded(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L1?.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage,
-                    },
-                  ),
-                  currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            const phase2FluctuatedValue = isNotEmptyString(
-              currentPerPhaseSampledValueTemplates.L2?.value,
-            )
-              ? getRandomFloatFluctuatedRounded(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L2?.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage,
-                    },
-                  ),
-                  currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            const phase3FluctuatedValue = isNotEmptyString(
-              currentPerPhaseSampledValueTemplates.L3?.value,
-            )
-              ? getRandomFloatFluctuatedRounded(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L3?.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage,
-                    },
-                  ),
-                  currentPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
-                    Constants.DEFAULT_FLUCTUATION_PERCENT,
-                )
-              : undefined;
-            currentMeasurandValues.L1 =
-              phase1FluctuatedValue ??
-              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(
-                  OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                    currentSampledValueTemplate.value,
-                    connectorMaximumAmperage,
-                    connectorMinimumAmperage,
-                    {
-                      limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage,
-                    },
-                  ),
-                  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(
-            connectorMaximumAvailablePower,
-            chargingStation.stationInfo.voltageOut!,
-          );
-          currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value)
-            ? getRandomFloatFluctuatedRounded(
-                OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-                  currentSampledValueTemplate.value,
-                  connectorMaximumAmperage,
-                  connectorMinimumAmperage,
-                  {
-                    limitationEnabled:
-                      chargingStation.stationInfo?.customValueLimitationMeterValues,
-                    fallbackValue: connectorMinimumAmperage,
-                  },
-                ),
-                currentSampledValueTemplate.fluctuationPercent ??
-                  Constants.DEFAULT_FLUCTUATION_PERCENT,
-              )
-            : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
-          break;
-        default:
-          logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
-          throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-      }
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(
-          currentSampledValueTemplate,
-          currentMeasurandValues.allPhases,
-        ),
-      );
-      const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      if (
-        convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
-          connectorMaximumAmperage ||
-        convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
-          connectorMinimumAmperage ||
-        debug
-      ) {
-        logger.error(
-          `${chargingStation.logPrefix()} MeterValues measurand ${
-            meterValue.sampledValue[sampledValuesIndex].measurand ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
-            meterValue.sampledValue[sampledValuesIndex].value
-          }/${connectorMaximumAmperage}`,
-        );
-      }
-      for (
-        let phase = 1;
-        chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-        phase++
-      ) {
-        const phaseValue = `L${phase}`;
-        meterValue.sampledValue.push(
-          OCPP16ServiceUtils.buildSampledValue(
-            currentPerPhaseSampledValueTemplates[
-              phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
-            ] ?? currentSampledValueTemplate,
-            currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
-            undefined,
-            phaseValue as OCPP16MeterValuePhase,
-          ),
-        );
-        const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
-        if (
-          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
-            connectorMaximumAmperage ||
-          convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
-            connectorMinimumAmperage ||
-          debug
-        ) {
-          logger.error(
-            `${chargingStation.logPrefix()} MeterValues measurand ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
-              OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: phase ${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-            }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
-              meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-            }/${connectorMaximumAmperage}`,
-          );
-        }
-      }
-    }
-    // Energy.Active.Import.Register measurand (default)
-    const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
-      chargingStation,
-      connectorId,
-    );
-    if (energySampledValueTemplate) {
-      OCPP16ServiceUtils.checkMeasurandPowerDivider(
-        chargingStation,
-        energySampledValueTemplate.measurand!,
-      );
-      const unitDivider =
-        energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
-      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(
-            OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
-              energySampledValueTemplate.value,
-              connectorMaximumEnergyRounded,
-              connectorMinimumEnergyRounded,
-              {
-                limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
-                fallbackValue: connectorMinimumEnergyRounded,
-                unitMultiplier: unitDivider,
-              },
-            ),
-            energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
-          )
-        : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded);
-      // Persist previous value on connector
-      if (connector) {
-        if (
-          isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
-          connector.energyActiveImportRegisterValue! >= 0 &&
-          isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
-          connector.transactionEnergyActiveImportRegisterValue! >= 0
-        ) {
-          connector.energyActiveImportRegisterValue! += energyValueRounded;
-          connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded;
-        } else {
-          connector.energyActiveImportRegisterValue = 0;
-          connector.transactionEnergyActiveImportRegisterValue = 0;
-        }
-      }
-      meterValue.sampledValue.push(
-        OCPP16ServiceUtils.buildSampledValue(
-          energySampledValueTemplate,
-          roundTo(
-            chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
-              unitDivider,
-            2,
-          ),
-        ),
-      );
-      const sampledValuesIndex = meterValue.sampledValue.length - 1;
-      if (
-        energyValueRounded > connectorMaximumEnergyRounded ||
-        energyValueRounded < connectorMinimumEnergyRounded ||
-        debug
-      ) {
-        logger.error(
-          `${chargingStation.logPrefix()} MeterValues measurand ${
-            meterValue.sampledValue[sampledValuesIndex].measurand ??
-            OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-          }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
-        );
-      }
-    }
-    return meterValue;
-  }
-
   public static buildTransactionBeginMeterValue(
     chargingStation: ChargingStation,
     connectorId: number,
@@ -854,37 +71,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       chargingStation,
       connectorId,
     );
-    const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
+    const unitDivider =
+      sampledValueTemplate?.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
     meterValue.sampledValue.push(
       OCPP16ServiceUtils.buildSampledValue(
         sampledValueTemplate!,
         roundTo((meterStart ?? 0) / unitDivider, 4),
-        MeterValueContext.TRANSACTION_BEGIN,
-      ),
-    );
-    return meterValue;
-  }
-
-  public static buildTransactionEndMeterValue(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    meterStop: number,
-  ): OCPP16MeterValue {
-    const meterValue: OCPP16MeterValue = {
-      timestamp: new Date(),
-      sampledValue: [],
-    };
-    // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
-      chargingStation,
-      connectorId,
-    );
-    const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
-    meterValue.sampledValue.push(
-      OCPP16ServiceUtils.buildSampledValue(
-        sampledValueTemplate!,
-        roundTo((meterStop ?? 0) / unitDivider, 4),
-        MeterValueContext.TRANSACTION_END,
+        OCPP16MeterValueContext.TRANSACTION_BEGIN,
       ),
     );
     return meterValue;
@@ -988,7 +181,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
 
   public static clearChargingProfiles = (
     chargingStation: ChargingStation,
-    commandPayload: ClearChargingProfileRequest,
+    commandPayload: OCPP16ClearChargingProfileRequest,
     chargingProfiles: OCPP16ChargingProfile[] | undefined,
   ): boolean => {
     const { id, chargingProfilePurpose, stackLevel } = commandPayload;
@@ -1329,79 +522,4 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       return chargingSchedule;
     }
   };
-
-  private static buildSampledValue(
-    sampledValueTemplate: SampledValueTemplate,
-    value: number,
-    context?: MeterValueContext,
-    phase?: OCPP16MeterValuePhase,
-  ): OCPP16SampledValue {
-    const sampledValueContext = context ?? sampledValueTemplate?.context;
-    const sampledValueLocation =
-      sampledValueTemplate?.location ??
-      OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
-    const sampledValuePhase = phase ?? sampledValueTemplate?.phase;
-    return {
-      ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
-        unit: sampledValueTemplate.unit,
-      }),
-      ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
-      ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
-        measurand: sampledValueTemplate.measurand,
-      }),
-      ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
-      ...(!isNullOrUndefined(value) && { value: value.toString() }),
-      ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
-    } as OCPP16SampledValue;
-  }
-
-  private static checkMeasurandPowerDivider(
-    chargingStation: ChargingStation,
-    measurandType: OCPP16MeterValueMeasurand,
-  ): void {
-    if (isUndefined(chargingStation.powerDivider)) {
-      const errMsg = `MeterValues measurand ${
-        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: powerDivider is undefined`;
-      logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
-      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-    } else if (chargingStation?.powerDivider <= 0) {
-      const errMsg = `MeterValues measurand ${
-        measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-      }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
-      logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
-      throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
-    }
-  }
-
-  private static getMeasurandDefaultLocation(
-    measurandType: OCPP16MeterValueMeasurand,
-  ): MeterValueLocation | undefined {
-    switch (measurandType) {
-      case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
-        return MeterValueLocation.EV;
-    }
-  }
-
-  // private static getMeasurandDefaultUnit(
-  //   measurandType: OCPP16MeterValueMeasurand,
-  // ): MeterValueUnit | undefined {
-  //   switch (measurandType) {
-  //     case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
-  //     case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
-  //     case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
-  //       return MeterValueUnit.AMP;
-  //     case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
-  //     case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
-  //       return MeterValueUnit.WATT_HOUR;
-  //     case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
-  //     case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
-  //     case OCPP16MeterValueMeasurand.POWER_OFFERED:
-  //       return MeterValueUnit.WATT;
-  //     case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
-  //       return MeterValueUnit.PERCENT;
-  //     case OCPP16MeterValueMeasurand.VOLTAGE:
-  //       return MeterValueUnit.VOLT;
-  //   }
-  // }
 }
index 8a0b2680632c35c6bb235ff6eb6eb93c800505a5..275494057a4500517ef4618f96a06268e527e0ac 100644 (file)
@@ -9,7 +9,7 @@ import { OCPP16Constants } from './1.6/OCPP16Constants';
 import { OCPP20Constants } from './2.0/OCPP20Constants';
 import { OCPPConstants } from './OCPPConstants';
 import { type ChargingStation, getConfigurationKey, getIdTagsFile } from '../../charging-station';
-import { BaseError } from '../../exception';
+import { BaseError, OCPPError } from '../../exception';
 import {
   AuthorizationStatus,
   type AuthorizeRequest,
@@ -18,31 +18,50 @@ import {
   ChargingStationEvents,
   type ConnectorStatus,
   type ConnectorStatusEnum,
+  CurrentType,
   ErrorType,
   FileType,
   IncomingRequestCommand,
   type JsonType,
+  type MeasurandPerPhaseSampledValueTemplates,
+  type MeasurandValues,
   MessageTrigger,
   MessageType,
+  type MeterValue,
+  MeterValueContext,
+  MeterValueLocation,
   MeterValueMeasurand,
-  type MeterValuePhase,
+  MeterValuePhase,
+  MeterValueUnit,
   type OCPP16StatusNotificationRequest,
   type OCPP20StatusNotificationRequest,
   OCPPVersion,
   RequestCommand,
+  type SampledValue,
   type SampledValueTemplate,
   StandardParametersKey,
   type StatusNotificationRequest,
   type StatusNotificationResponse,
 } from '../../types';
 import {
+  ACElectricUtils,
+  Constants,
+  DCElectricUtils,
+  convertToFloat,
+  convertToInt,
+  getRandomFloatFluctuatedRounded,
+  getRandomFloatRounded,
+  getRandomInteger,
   handleFileException,
   isNotEmptyArray,
   isNotEmptyString,
+  isNullOrUndefined,
+  isUndefined,
   logPrefix,
   logger,
   max,
   min,
+  roundTo,
 } from '../../utils';
 
 export const getMessageTypeString = (messageType: MessageType): string => {
@@ -221,10 +240,980 @@ const checkConnectorStatusTransition = (
   return transitionAllowed;
 };
 
+export const buildMeterValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  transactionId: number,
+  interval: number,
+  debug = false,
+): MeterValue => {
+  const connector = chargingStation.getConnectorStatus(connectorId);
+  let meterValue: MeterValue;
+  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 = {
+        timestamp: new Date(),
+        sampledValue: [],
+      };
+      // SoC measurand
+      socSampledValueTemplate = getSampledValueTemplate(
+        chargingStation,
+        connectorId,
+        MeterValueMeasurand.STATE_OF_CHARGE,
+      );
+      if (socSampledValueTemplate) {
+        const socMaximumValue = 100;
+        const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
+        const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
+          ? getRandomFloatFluctuatedRounded(
+              parseInt(socSampledValueTemplate.value),
+              socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
+            )
+          : getRandomInteger(socMaximumValue, socMinimumValue);
+        meterValue.sampledValue.push(
+          buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue),
+        );
+        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
+            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
+              meterValue.sampledValue[sampledValuesIndex].value
+            }/${socMaximumValue}`,
+          );
+        }
+      }
+      // Voltage measurand
+      voltageSampledValueTemplate = getSampledValueTemplate(
+        chargingStation,
+        connectorId,
+        MeterValueMeasurand.VOLTAGE,
+      );
+      if (voltageSampledValueTemplate) {
+        const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
+          ? parseInt(voltageSampledValueTemplate.value)
+          : 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)
+        ) {
+          meterValue.sampledValue.push(
+            buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue),
+          );
+        }
+        for (
+          let phase = 1;
+          chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
+          phase++
+        ) {
+          const phaseLineToNeutralValue = `L${phase}-N`;
+          const voltagePhaseLineToNeutralSampledValueTemplate = getSampledValueTemplate(
+            chargingStation,
+            connectorId,
+            MeterValueMeasurand.VOLTAGE,
+            phaseLineToNeutralValue as MeterValuePhase,
+          );
+          let voltagePhaseLineToNeutralMeasurandValue: number | undefined;
+          if (voltagePhaseLineToNeutralSampledValueTemplate) {
+            const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
+              voltagePhaseLineToNeutralSampledValueTemplate.value,
+            )
+              ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
+              : chargingStation.stationInfo.voltageOut!;
+            const fluctuationPhaseToNeutralPercent =
+              voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
+              Constants.DEFAULT_FLUCTUATION_PERCENT;
+            voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
+              voltagePhaseLineToNeutralSampledValueTemplateValue,
+              fluctuationPhaseToNeutralPercent,
+            );
+          }
+          meterValue.sampledValue.push(
+            buildSampledValue(
+              voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
+              voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
+              undefined,
+              phaseLineToNeutralValue as MeterValuePhase,
+            ),
+          );
+          if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) {
+            const phaseLineToLineValue = `L${phase}-L${
+              (phase + 1) % chargingStation.getNumberOfPhases() !== 0
+                ? (phase + 1) % chargingStation.getNumberOfPhases()
+                : chargingStation.getNumberOfPhases()
+            }`;
+            const voltagePhaseLineToLineValueRounded = roundTo(
+              Math.sqrt(chargingStation.getNumberOfPhases()) *
+                chargingStation.stationInfo.voltageOut!,
+              2,
+            );
+            const voltagePhaseLineToLineSampledValueTemplate = getSampledValueTemplate(
+              chargingStation,
+              connectorId,
+              MeterValueMeasurand.VOLTAGE,
+              phaseLineToLineValue as MeterValuePhase,
+            );
+            let voltagePhaseLineToLineMeasurandValue: number | undefined;
+            if (voltagePhaseLineToLineSampledValueTemplate) {
+              const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
+                voltagePhaseLineToLineSampledValueTemplate.value,
+              )
+                ? 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(
+                voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
+                voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
+                undefined,
+                phaseLineToLineValue as MeterValuePhase,
+              ),
+            );
+          }
+        }
+      }
+      // 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(
+            chargingStation,
+            connectorId,
+            MeterValueMeasurand.POWER_ACTIVE_IMPORT,
+            MeterValuePhase.L2_N,
+          ),
+          L3: getSampledValueTemplate(
+            chargingStation,
+            connectorId,
+            MeterValueMeasurand.POWER_ACTIVE_IMPORT,
+            MeterValuePhase.L3_N,
+          ),
+        };
+      }
+      if (powerSampledValueTemplate) {
+        checkMeasurandPowerDivider(chargingStation, powerSampledValueTemplate.measurand!);
+        const errMsg = `MeterValues measurand ${
+          powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        }: 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;
+        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,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumPower / unitDivider,
+                      },
+                    ) / chargingStation.getNumberOfPhases(),
+                    powerSampledValueTemplate.fluctuationPercent ??
+                      Constants.DEFAULT_FLUCTUATION_PERCENT,
+                  )
+                : undefined;
+              const phase1FluctuatedValue = isNotEmptyString(
+                powerPerPhaseSampledValueTemplates.L1?.value,
+              )
+                ? getRandomFloatFluctuatedRounded(
+                    getLimitFromSampledValueTemplateCustomValue(
+                      powerPerPhaseSampledValueTemplates.L1?.value,
+                      connectorMaximumPowerPerPhase / unitDivider,
+                      connectorMinimumPowerPerPhase / unitDivider,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                      },
+                    ),
+                    powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                      Constants.DEFAULT_FLUCTUATION_PERCENT,
+                  )
+                : undefined;
+              const phase2FluctuatedValue = isNotEmptyString(
+                powerPerPhaseSampledValueTemplates.L2?.value,
+              )
+                ? getRandomFloatFluctuatedRounded(
+                    getLimitFromSampledValueTemplateCustomValue(
+                      powerPerPhaseSampledValueTemplates.L2?.value,
+                      connectorMaximumPowerPerPhase / unitDivider,
+                      connectorMinimumPowerPerPhase / unitDivider,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                      },
+                    ),
+                    powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                      Constants.DEFAULT_FLUCTUATION_PERCENT,
+                  )
+                : undefined;
+              const phase3FluctuatedValue = isNotEmptyString(
+                powerPerPhaseSampledValueTemplates.L3?.value,
+              )
+                ? getRandomFloatFluctuatedRounded(
+                    getLimitFromSampledValueTemplateCustomValue(
+                      powerPerPhaseSampledValueTemplates.L3?.value,
+                      connectorMaximumPowerPerPhase / unitDivider,
+                      connectorMinimumPowerPerPhase / unitDivider,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
+                      },
+                    ),
+                    powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
+                      Constants.DEFAULT_FLUCTUATION_PERCENT,
+                  )
+                : undefined;
+              powerMeasurandValues.L1 =
+                phase1FluctuatedValue ??
+                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,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumPower / unitDivider,
+                      },
+                    ),
+                    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,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumPower / unitDivider,
+                    },
+                  ),
+                  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);
+        }
+        meterValue.sampledValue.push(
+          buildSampledValue(powerSampledValueTemplate, powerMeasurandValues.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 ||
+          debug
+        ) {
+          logger.error(
+            `${chargingStation.logPrefix()} MeterValues measurand ${
+              meterValue.sampledValue[sampledValuesIndex].measurand ??
+              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
+              meterValue.sampledValue[sampledValuesIndex].value
+            }/${connectorMaximumPowerRounded}`,
+          );
+        }
+        for (
+          let phase = 1;
+          chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
+          phase++
+        ) {
+          const phaseValue = `L${phase}-N`;
+          meterValue.sampledValue.push(
+            buildSampledValue(
+              powerPerPhaseSampledValueTemplates[
+                `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
+              ] ?? powerSampledValueTemplate,
+              powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
+              undefined,
+              phaseValue as MeterValuePhase,
+            ),
+          );
+          const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
+          const connectorMaximumPowerPerPhaseRounded = roundTo(
+            connectorMaximumPowerPerPhase / unitDivider,
+            2,
+          );
+          const connectorMinimumPowerPerPhaseRounded = roundTo(
+            connectorMinimumPowerPerPhase / unitDivider,
+            2,
+          );
+          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 ${
+                meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
+              }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
+                meterValue.sampledValue[sampledValuesPerPhaseIndex].value
+              }/${connectorMaximumPowerPerPhaseRounded}`,
+            );
+          }
+        }
+      }
+      // 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) {
+        checkMeasurandPowerDivider(chargingStation, currentSampledValueTemplate.measurand!);
+        const errMsg = `MeterValues measurand ${
+          currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        }: 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;
+        const connectorMaximumAvailablePower =
+          chargingStation.getConnectorMaximumAvailablePower(connectorId);
+        const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
+        let connectorMaximumAmperage: number;
+        switch (chargingStation.stationInfo?.currentOutType) {
+          case CurrentType.AC:
+            connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
+              chargingStation.getNumberOfPhases(),
+              connectorMaximumAvailablePower,
+              chargingStation.stationInfo.voltageOut!,
+            );
+            if (chargingStation.getNumberOfPhases() === 3) {
+              const defaultFluctuatedAmperagePerPhase = isNotEmptyString(
+                currentSampledValueTemplate.value,
+              )
+                ? getRandomFloatFluctuatedRounded(
+                    getLimitFromSampledValueTemplateCustomValue(
+                      currentSampledValueTemplate.value,
+                      connectorMaximumAmperage,
+                      connectorMinimumAmperage,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumAmperage,
+                      },
+                    ),
+                    currentSampledValueTemplate.fluctuationPercent ??
+                      Constants.DEFAULT_FLUCTUATION_PERCENT,
+                  )
+                : undefined;
+              const phase1FluctuatedValue = isNotEmptyString(
+                currentPerPhaseSampledValueTemplates.L1?.value,
+              )
+                ? getRandomFloatFluctuatedRounded(
+                    getLimitFromSampledValueTemplateCustomValue(
+                      currentPerPhaseSampledValueTemplates.L1?.value,
+                      connectorMaximumAmperage,
+                      connectorMinimumAmperage,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumAmperage,
+                      },
+                    ),
+                    currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                      Constants.DEFAULT_FLUCTUATION_PERCENT,
+                  )
+                : undefined;
+              const phase2FluctuatedValue = isNotEmptyString(
+                currentPerPhaseSampledValueTemplates.L2?.value,
+              )
+                ? getRandomFloatFluctuatedRounded(
+                    getLimitFromSampledValueTemplateCustomValue(
+                      currentPerPhaseSampledValueTemplates.L2?.value,
+                      connectorMaximumAmperage,
+                      connectorMinimumAmperage,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumAmperage,
+                      },
+                    ),
+                    currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                      Constants.DEFAULT_FLUCTUATION_PERCENT,
+                  )
+                : undefined;
+              const phase3FluctuatedValue = isNotEmptyString(
+                currentPerPhaseSampledValueTemplates.L3?.value,
+              )
+                ? getRandomFloatFluctuatedRounded(
+                    getLimitFromSampledValueTemplateCustomValue(
+                      currentPerPhaseSampledValueTemplates.L3?.value,
+                      connectorMaximumAmperage,
+                      connectorMinimumAmperage,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumAmperage,
+                      },
+                    ),
+                    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,
+                      {
+                        limitationEnabled:
+                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        fallbackValue: connectorMinimumAmperage,
+                      },
+                    ),
+                    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(
+              connectorMaximumAvailablePower,
+              chargingStation.stationInfo.voltageOut!,
+            );
+            currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value)
+              ? getRandomFloatFluctuatedRounded(
+                  getLimitFromSampledValueTemplateCustomValue(
+                    currentSampledValueTemplate.value,
+                    connectorMaximumAmperage,
+                    connectorMinimumAmperage,
+                    {
+                      limitationEnabled:
+                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                      fallbackValue: connectorMinimumAmperage,
+                    },
+                  ),
+                  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);
+        }
+        meterValue.sampledValue.push(
+          buildSampledValue(currentSampledValueTemplate, currentMeasurandValues.allPhases),
+        );
+        const sampledValuesIndex = meterValue.sampledValue.length - 1;
+        if (
+          convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
+            connectorMaximumAmperage ||
+          convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
+            connectorMinimumAmperage ||
+          debug
+        ) {
+          logger.error(
+            `${chargingStation.logPrefix()} MeterValues measurand ${
+              meterValue.sampledValue[sampledValuesIndex].measurand ??
+              MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
+              meterValue.sampledValue[sampledValuesIndex].value
+            }/${connectorMaximumAmperage}`,
+          );
+        }
+        for (
+          let phase = 1;
+          chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
+          phase++
+        ) {
+          const phaseValue = `L${phase}`;
+          meterValue.sampledValue.push(
+            buildSampledValue(
+              currentPerPhaseSampledValueTemplates[
+                phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
+              ] ?? currentSampledValueTemplate,
+              currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
+              undefined,
+              phaseValue as MeterValuePhase,
+            ),
+          );
+          const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
+          if (
+            convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
+              connectorMaximumAmperage ||
+            convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
+              connectorMinimumAmperage ||
+            debug
+          ) {
+            logger.error(
+              `${chargingStation.logPrefix()} MeterValues measurand ${
+                meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
+                MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+              }: phase ${
+                meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
+              }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
+                meterValue.sampledValue[sampledValuesPerPhaseIndex].value
+              }/${connectorMaximumAmperage}`,
+            );
+          }
+        }
+      }
+      // Energy.Active.Import.Register measurand (default)
+      energySampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId);
+      if (energySampledValueTemplate) {
+        checkMeasurandPowerDivider(chargingStation, energySampledValueTemplate.measurand!);
+        const unitDivider =
+          energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
+        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,
+                {
+                  limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
+                  fallbackValue: connectorMinimumEnergyRounded,
+                  unitMultiplier: unitDivider,
+                },
+              ),
+              energySampledValueTemplate.fluctuationPercent ??
+                Constants.DEFAULT_FLUCTUATION_PERCENT,
+            )
+          : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded);
+        // Persist previous value on connector
+        if (connector) {
+          if (
+            isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
+            connector.energyActiveImportRegisterValue! >= 0 &&
+            isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
+            connector.transactionEnergyActiveImportRegisterValue! >= 0
+          ) {
+            connector.energyActiveImportRegisterValue! += energyValueRounded;
+            connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded;
+          } else {
+            connector.energyActiveImportRegisterValue = 0;
+            connector.transactionEnergyActiveImportRegisterValue = 0;
+          }
+        }
+        meterValue.sampledValue.push(
+          buildSampledValue(
+            energySampledValueTemplate,
+            roundTo(
+              chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
+                unitDivider,
+              2,
+            ),
+          ),
+        );
+        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
+            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
+          );
+        }
+      }
+      return meterValue;
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+    default:
+      throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
+      );
+  }
+};
+
+export const buildTransactionEndMeterValue = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  meterStop: number,
+): MeterValue => {
+  let meterValue: MeterValue;
+  let sampledValueTemplate: SampledValueTemplate | undefined;
+  let unitDivider: number;
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      meterValue = {
+        timestamp: new Date(),
+        sampledValue: [],
+      };
+      // Energy.Active.Import.Register measurand (default)
+      sampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId);
+      unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
+      meterValue.sampledValue.push(
+        buildSampledValue(
+          sampledValueTemplate!,
+          roundTo((meterStop ?? 0) / unitDivider, 4),
+          MeterValueContext.TRANSACTION_END,
+        ),
+      );
+      return meterValue;
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+    default:
+      throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`,
+      );
+  }
+};
+
+const checkMeasurandPowerDivider = (
+  chargingStation: ChargingStation,
+  measurandType: MeterValueMeasurand,
+): void => {
+  if (isUndefined(chargingStation.powerDivider)) {
+    const errMsg = `MeterValues measurand ${
+      measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+    }: powerDivider is undefined`;
+    logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
+    throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES);
+  } else if (chargingStation?.powerDivider <= 0) {
+    const errMsg = `MeterValues measurand ${
+      measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+    }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
+    logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
+    throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES);
+  }
+};
+
+const getLimitFromSampledValueTemplateCustomValue = (
+  value: string | undefined,
+  maxLimit: number,
+  minLimit: number,
+  options?: { limitationEnabled?: boolean; fallbackValue?: number; unitMultiplier?: number },
+): number => {
+  options = {
+    ...{
+      limitationEnabled: false,
+      unitMultiplier: 1,
+      fallbackValue: 0,
+    },
+    ...options,
+  };
+  const parsedValue = parseInt(value ?? '');
+  if (options?.limitationEnabled) {
+    return max(
+      min((!isNaN(parsedValue) ? parsedValue : Infinity) * options.unitMultiplier!, maxLimit),
+      minLimit,
+    );
+  }
+  return (!isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!;
+};
+
+const getSampledValueTemplate = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+  phase?: MeterValuePhase,
+): SampledValueTemplate | undefined => {
+  const onPhaseStr = phase ? `on phase ${phase} ` : '';
+  if (OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(measurand) === false) {
+    logger.warn(
+      `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
+    );
+    return;
+  }
+  if (
+    measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
+    getConfigurationKey(
+      chargingStation,
+      StandardParametersKey.MeterValuesSampledData,
+    )?.value?.includes(measurand) === false
+  ) {
+    logger.debug(
+      `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
+        StandardParametersKey.MeterValuesSampledData
+      }' OCPP parameter`,
+    );
+    return;
+  }
+  const sampledValueTemplates: SampledValueTemplate[] =
+    chargingStation.getConnectorStatus(connectorId)!.MeterValues;
+  for (
+    let index = 0;
+    isNotEmptyArray(sampledValueTemplates) === true && index < sampledValueTemplates.length;
+    index++
+  ) {
+    if (
+      OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(
+        sampledValueTemplates[index]?.measurand ??
+          MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+      ) === false
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
+      );
+    } else if (
+      phase &&
+      sampledValueTemplates[index]?.phase === phase &&
+      sampledValueTemplates[index]?.measurand === measurand &&
+      getConfigurationKey(
+        chargingStation,
+        StandardParametersKey.MeterValuesSampledData,
+      )?.value?.includes(measurand) === true
+    ) {
+      return sampledValueTemplates[index];
+    } else if (
+      !phase &&
+      !sampledValueTemplates[index]?.phase &&
+      sampledValueTemplates[index]?.measurand === measurand &&
+      getConfigurationKey(
+        chargingStation,
+        StandardParametersKey.MeterValuesSampledData,
+      )?.value?.includes(measurand) === true
+    ) {
+      return sampledValueTemplates[index];
+    } else if (
+      measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
+      (!sampledValueTemplates[index]?.measurand ||
+        sampledValueTemplates[index]?.measurand === measurand)
+    ) {
+      return sampledValueTemplates[index];
+    }
+  }
+  if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
+    const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`;
+    logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
+    throw new BaseError(errorMsg);
+  }
+  logger.debug(
+    `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
+  );
+};
+
+const buildSampledValue = (
+  sampledValueTemplate: SampledValueTemplate,
+  value: number,
+  context?: MeterValueContext,
+  phase?: MeterValuePhase,
+): SampledValue => {
+  const sampledValueContext = context ?? sampledValueTemplate?.context;
+  const sampledValueLocation =
+    sampledValueTemplate?.location ?? getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
+  const sampledValuePhase = phase ?? sampledValueTemplate?.phase;
+  return {
+    ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
+      unit: sampledValueTemplate.unit,
+    }),
+    ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
+    ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
+      measurand: sampledValueTemplate.measurand,
+    }),
+    ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
+    ...(!isNullOrUndefined(value) && { value: value.toString() }),
+    ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
+  } as SampledValue;
+};
+
+const getMeasurandDefaultLocation = (
+  measurandType: MeterValueMeasurand,
+): MeterValueLocation | undefined => {
+  switch (measurandType) {
+    case MeterValueMeasurand.STATE_OF_CHARGE:
+      return MeterValueLocation.EV;
+  }
+};
+
+// 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;
+//   }
+// };
+
 export class OCPPServiceUtils {
   public static getMessageTypeString = getMessageTypeString;
   public static sendAndSetConnectorStatus = sendAndSetConnectorStatus;
   public static isIdTagAuthorized = isIdTagAuthorized;
+  public static buildTransactionEndMeterValue = buildTransactionEndMeterValue;
+  protected static getSampledValueTemplate = getSampledValueTemplate;
+  protected static buildSampledValue = buildSampledValue;
 
   protected constructor() {
     // This is intentional
@@ -365,111 +1354,6 @@ export class OCPPServiceUtils {
     }
   }
 
-  protected static getSampledValueTemplate(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
-    phase?: MeterValuePhase,
-  ): SampledValueTemplate | undefined {
-    const onPhaseStr = phase ? `on phase ${phase} ` : '';
-    if (OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(measurand) === false) {
-      logger.warn(
-        `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
-      );
-      return;
-    }
-    if (
-      measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
-      getConfigurationKey(
-        chargingStation,
-        StandardParametersKey.MeterValuesSampledData,
-      )?.value?.includes(measurand) === false
-    ) {
-      logger.debug(
-        `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
-          StandardParametersKey.MeterValuesSampledData
-        }' OCPP parameter`,
-      );
-      return;
-    }
-    const sampledValueTemplates: SampledValueTemplate[] =
-      chargingStation.getConnectorStatus(connectorId)!.MeterValues;
-    for (
-      let index = 0;
-      isNotEmptyArray(sampledValueTemplates) === true && index < sampledValueTemplates.length;
-      index++
-    ) {
-      if (
-        OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(
-          sampledValueTemplates[index]?.measurand ??
-            MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
-        ) === false
-      ) {
-        logger.warn(
-          `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
-        );
-      } else if (
-        phase &&
-        sampledValueTemplates[index]?.phase === phase &&
-        sampledValueTemplates[index]?.measurand === measurand &&
-        getConfigurationKey(
-          chargingStation,
-          StandardParametersKey.MeterValuesSampledData,
-        )?.value?.includes(measurand) === true
-      ) {
-        return sampledValueTemplates[index];
-      } else if (
-        !phase &&
-        !sampledValueTemplates[index]?.phase &&
-        sampledValueTemplates[index]?.measurand === measurand &&
-        getConfigurationKey(
-          chargingStation,
-          StandardParametersKey.MeterValuesSampledData,
-        )?.value?.includes(measurand) === true
-      ) {
-        return sampledValueTemplates[index];
-      } else if (
-        measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
-        (!sampledValueTemplates[index]?.measurand ||
-          sampledValueTemplates[index]?.measurand === measurand)
-      ) {
-        return sampledValueTemplates[index];
-      }
-    }
-    if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
-      const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`;
-      logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
-      throw new BaseError(errorMsg);
-    }
-    logger.debug(
-      `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`,
-    );
-  }
-
-  protected static getLimitFromSampledValueTemplateCustomValue(
-    value: string | undefined,
-    maxLimit: number,
-    minLimit: number,
-    options?: { limitationEnabled?: boolean; fallbackValue?: number; unitMultiplier?: number },
-  ): number {
-    options = {
-      ...{
-        limitationEnabled: false,
-        unitMultiplier: 1,
-        fallbackValue: 0,
-      },
-      ...options,
-    };
-    const parsedValue = parseInt(value ?? '');
-    if (options?.limitationEnabled) {
-      return max(
-        min((!isNaN(parsedValue) ? parsedValue : Infinity) * options.unitMultiplier!, maxLimit),
-        minLimit,
-      );
-    }
-    return (!isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!;
-  }
-
   private static logPrefix = (
     ocppVersion: OCPPVersion,
     moduleName?: string,
index 2f21bd2ecf59674ee19acd58972237c7831796fc..b5abe46446ac9c5a1d2d9a789d0568a659c163e6 100644 (file)
@@ -1,15 +1,15 @@
 export { OCPP16IncomingRequestService } from './1.6/OCPP16IncomingRequestService';
 export { OCPP16RequestService } from './1.6/OCPP16RequestService';
 export { OCPP16ResponseService } from './1.6/OCPP16ResponseService';
-// FIXME: shall not be exported
-export { OCPP16ServiceUtils } from './1.6/OCPP16ServiceUtils';
 export { OCPP20IncomingRequestService } from './2.0/OCPP20IncomingRequestService';
 export { OCPP20RequestService } from './2.0/OCPP20RequestService';
 export { OCPP20ResponseService } from './2.0/OCPP20ResponseService';
 export { OCPPIncomingRequestService } from './OCPPIncomingRequestService';
 export { OCPPRequestService } from './OCPPRequestService';
 export {
+  buildMeterValue,
   buildStatusNotificationRequest,
+  buildTransactionEndMeterValue,
   getMessageTypeString,
   isIdTagAuthorized,
   sendAndSetConnectorStatus,
index a707b46b01738145aeffbfd4107e6885cfc62fa6..cc80f527c8ed699f5b9d06442dd8074eb1738325 100644 (file)
@@ -81,7 +81,6 @@ export {
 } from './WorkerBroadcastChannel';
 export {
   type ChangeConfigurationRequest,
-  type ClearChargingProfileRequest,
   type GetConfigurationRequest,
   type GetDiagnosticsRequest,
   OCPP16AvailabilityType,
@@ -89,6 +88,7 @@ export {
   type OCPP16CancelReservationRequest,
   type OCPP16ChangeAvailabilityRequest,
   type OCPP16ClearCacheRequest,
+  type OCPP16ClearChargingProfileRequest,
   type OCPP16DataTransferRequest,
   OCPP16DataTransferVendorId,
   type OCPP16DiagnosticsStatusNotificationRequest,
@@ -111,11 +111,11 @@ export {
 } from './ocpp/1.6/Requests';
 export {
   type ChangeConfigurationResponse,
-  type ClearChargingProfileResponse,
   type GetConfigurationResponse,
   type GetDiagnosticsResponse,
   type OCPP16BootNotificationResponse,
   type OCPP16ChangeAvailabilityResponse,
+  type OCPP16ClearChargingProfileResponse,
   type OCPP16DataTransferResponse,
   OCPP16DataTransferStatus,
   type OCPP16DiagnosticsStatusNotificationResponse,
@@ -198,14 +198,22 @@ export type {
 } from './MeasurandPerPhaseSampledValueTemplates';
 export type { MeasurandValues } from './MeasurandValues';
 export { MessageType } from './ocpp/MessageType';
-export { type MeterValue, MeterValueMeasurand, MeterValuePhase } from './ocpp/MeterValues';
 export {
+  type MeterValue,
   MeterValueContext,
   MeterValueLocation,
+  MeterValueMeasurand,
+  MeterValuePhase,
   MeterValueUnit,
+  type SampledValue,
+} from './ocpp/MeterValues';
+export {
   type OCPP16MeterValue,
+  OCPP16MeterValueContext,
+  OCPP16MeterValueLocation,
   OCPP16MeterValueMeasurand,
   OCPP16MeterValuePhase,
+  OCPP16MeterValueUnit,
   type OCPP16MeterValuesRequest,
   type OCPP16MeterValuesResponse,
   type OCPP16SampledValue,
index 74698284e2c6d2a6d70fd5af2345d9fff16fe96b..6d102b8809edb0eaba896036bb243ea0636eea9e 100644 (file)
@@ -1,7 +1,7 @@
 import type { EmptyObject } from '../../EmptyObject';
 import type { JsonObject } from '../../JsonType';
 
-export enum MeterValueUnit {
+export enum OCPP16MeterValueUnit {
   WATT_HOUR = 'Wh',
   KILO_WATT_HOUR = 'kWh',
   VAR_HOUR = 'varh',
@@ -20,7 +20,7 @@ export enum MeterValueUnit {
   PERCENT = 'Percent',
 }
 
-export enum MeterValueContext {
+export enum OCPP16MeterValueContext {
   INTERRUPTION_BEGIN = 'Interruption.Begin',
   INTERRUPTION_END = 'Interruption.End',
   OTHER = 'Other',
@@ -56,7 +56,7 @@ export enum OCPP16MeterValueMeasurand {
   VOLTAGE = 'Voltage',
 }
 
-export enum MeterValueLocation {
+export enum OCPP16MeterValueLocation {
   BODY = 'Body',
   CABLE = 'Cable',
   EV = 'EV',
@@ -77,19 +77,19 @@ export enum OCPP16MeterValuePhase {
   L3_L1 = 'L3-L1',
 }
 
-enum MeterValueFormat {
+enum OCPP16MeterValueFormat {
   RAW = 'Raw',
   SIGNED_DATA = 'SignedData',
 }
 
 export interface OCPP16SampledValue extends JsonObject {
   value: string;
-  unit?: MeterValueUnit;
-  context?: MeterValueContext;
+  unit?: OCPP16MeterValueUnit;
+  context?: OCPP16MeterValueContext;
   measurand?: OCPP16MeterValueMeasurand;
   phase?: OCPP16MeterValuePhase;
-  location?: MeterValueLocation;
-  format?: MeterValueFormat;
+  location?: OCPP16MeterValueLocation;
+  format?: OCPP16MeterValueFormat;
 }
 
 export interface OCPP16MeterValue extends JsonObject {
index c6f041e58581e40c7d92ad81b43dccaeb16f48b0..b14583f08d63a8342c26438edd7c8e5a6a4a9240 100644 (file)
@@ -124,7 +124,7 @@ export interface OCPP16ChangeAvailabilityRequest extends JsonObject {
   type: OCPP16AvailabilityType;
 }
 
-export interface ClearChargingProfileRequest extends JsonObject {
+export interface OCPP16ClearChargingProfileRequest extends JsonObject {
   id?: number;
   connectorId?: number;
   chargingProfilePurpose?: OCPP16ChargingProfilePurposeType;
index c98c03a36b13210e0b448ff68f32b1e862401291..a42733e8a2244721451bf109b395f20331898bcc 100644 (file)
@@ -74,7 +74,7 @@ export enum OCPP16ClearChargingProfileStatus {
   UNKNOWN = 'Unknown',
 }
 
-export interface ClearChargingProfileResponse extends JsonObject {
+export interface OCPP16ClearChargingProfileResponse extends JsonObject {
   status: OCPP16ClearChargingProfileStatus;
 }
 
index f34a8c176fd3cf711ee158bad3f9ac2e0f950c3e..78cc9af9b02905c44e810c3e95eed1c351ecf681 100644 (file)
@@ -1,15 +1,33 @@
 import {
   type OCPP16MeterValue,
+  OCPP16MeterValueContext,
+  OCPP16MeterValueLocation,
   OCPP16MeterValueMeasurand,
   OCPP16MeterValuePhase,
+  OCPP16MeterValueUnit,
   type OCPP16SampledValue,
 } from './1.6/MeterValues';
 
+export const MeterValueUnit = {
+  ...OCPP16MeterValueUnit,
+} as const;
+export type MeterValueUnit = OCPP16MeterValueUnit;
+
+export const MeterValueContext = {
+  ...OCPP16MeterValueContext,
+} as const;
+export type MeterValueContext = OCPP16MeterValueContext;
+
 export const MeterValueMeasurand = {
   ...OCPP16MeterValueMeasurand,
 } as const;
 export type MeterValueMeasurand = OCPP16MeterValueMeasurand;
 
+export const MeterValueLocation = {
+  ...OCPP16MeterValueLocation,
+} as const;
+export type MeterValueLocation = OCPP16MeterValueLocation;
+
 export const MeterValuePhase = {
   ...OCPP16MeterValuePhase,
 } as const;
index 8facfa35e68e0f51ec08c4e6c34a5dc1215d6007..93b4bbc0157b28e2969204a9f8f3cf18a5eccda6 100644 (file)
@@ -1469,7 +1469,7 @@ packages:
     hasBin: true
     dependencies:
       caniuse-lite: 1.0.30001566
-      electron-to-chromium: 1.4.601
+      electron-to-chromium: 1.4.603
       node-releases: 2.0.14
       update-browserslist-db: 1.0.13(browserslist@4.22.2)
     dev: true
@@ -1783,8 +1783,8 @@ packages:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
     dev: false
 
-  /electron-to-chromium@1.4.601:
-    resolution: {integrity: sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==}
+  /electron-to-chromium@1.4.603:
+    resolution: {integrity: sha512-Dvo5OGjnl7AZTU632dFJtWj0uJK835eeOVQIuRcmBmsFsTNn3cL05FqOyHAfGQDIoHfLhyJ1Tya3PJ0ceMz54g==}
     dev: true
 
   /emoji-regex@8.0.0: