]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp): extract version-specific builders and transaction operations
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 1 Apr 2026 16:08:16 +0000 (18:08 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 1 Apr 2026 16:08:16 +0000 (18:08 +0200)
Extract meter value builders into OCPP16/20MeterValueBuilders.ts leaf
modules. Extract boot notification builders into OCPP16/20RequestBuilders.ts
leaf modules. Move buildBootNotificationRequest dispatcher from Operations
to Utils. Extract OCPP 2.0 startTransactionOnConnector and
stopTransactionOnConnector into OCPP20ServiceUtils, removing inline
OCPP 2.0 enums and logic from the shared Operations module.

OCPPServiceUtils reduced from 2095 to 1648 lines.

src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts [new file with mode: 0644]
src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts [new file with mode: 0644]
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts [new file with mode: 0644]
src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts [new file with mode: 0644]
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPServiceOperations.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ocpp/index.ts
tests/charging-station/ocpp/OCPPServiceOperations.test.ts

diff --git a/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts b/src/charging-station/ocpp/1.6/OCPP16MeterValueBuilders.ts
new file mode 100644 (file)
index 0000000..c51ab48
--- /dev/null
@@ -0,0 +1,303 @@
+import type { ChargingStation } from '../../../charging-station/index.js'
+
+import { OCPPError } from '../../../exception/index.js'
+import {
+  type ConfigurationKeyType,
+  CurrentType,
+  ErrorType,
+  type MeasurandPerPhaseSampledValueTemplates,
+  type MeasurandValues,
+  type MeterValueContext,
+  type MeterValuePhase,
+  MeterValueUnit,
+  type OCPP16MeterValue,
+  type OCPP16SampledValue,
+  OCPPVersion,
+  RequestCommand,
+  type SampledValueTemplate,
+} from '../../../types/index.js'
+import { ACElectricUtils, DCElectricUtils, roundTo } from '../../../utils/index.js'
+import {
+  addLineToLineVoltageToMeterValue,
+  addMainVoltageToMeterValue,
+  addPhaseVoltageToMeterValue,
+  buildCurrentMeasurandValue,
+  buildEmptyMeterValue,
+  buildEnergyMeasurandValue,
+  buildPowerMeasurandValue,
+  buildSampledValue,
+  buildSocMeasurandValue,
+  buildVoltageMeasurandValue,
+  updateConnectorEnergyValues,
+  validateCurrentMeasurandPhaseValue,
+  validateCurrentMeasurandValue,
+  validateEnergyMeasurandValue,
+  validatePowerMeasurandValue,
+  validateSocMeasurandValue,
+} from '../OCPPServiceUtils.js'
+
+export const buildMeterValueForOCPP16 = (
+  chargingStation: ChargingStation,
+  transactionId: number | string,
+  interval: number,
+  measurandsKey?: ConfigurationKeyType,
+  context?: MeterValueContext,
+  debug = false
+): OCPP16MeterValue => {
+  const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)
+  if (connectorId == null) {
+    throw new OCPPError(
+      ErrorType.INTERNAL_ERROR,
+      `Cannot build MeterValues: no connector found for transaction ${String(transactionId)}`,
+      RequestCommand.METER_VALUES
+    )
+  }
+  const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+  const meterValue = buildEmptyMeterValue() as OCPP16MeterValue
+  const buildVersionedSampledValue = (
+    sampledValueTemplate: SampledValueTemplate,
+    value: number,
+    context?: MeterValueContext,
+    phase?: MeterValuePhase
+  ): OCPP16SampledValue => {
+    return buildSampledValueForOCPP16(sampledValueTemplate, value, context, phase)
+  }
+  // SoC measurand
+  const socMeasurand = buildSocMeasurandValue(
+    chargingStation,
+    connectorId,
+    undefined,
+    measurandsKey
+  )
+  if (socMeasurand != null) {
+    const socSampledValue = buildVersionedSampledValue(socMeasurand.template, socMeasurand.value)
+    meterValue.sampledValue.push(socSampledValue)
+    validateSocMeasurandValue(
+      chargingStation,
+      connectorId,
+      socSampledValue,
+      socMeasurand.template.minimumValue ?? 0,
+      100,
+      debug
+    )
+  }
+  // Voltage measurand
+  const voltageMeasurand = buildVoltageMeasurandValue(
+    chargingStation,
+    connectorId,
+    undefined,
+    measurandsKey
+  )
+  if (voltageMeasurand != null) {
+    addMainVoltageToMeterValue(
+      chargingStation,
+      meterValue,
+      voltageMeasurand,
+      buildVersionedSampledValue
+    )
+    for (
+      let phase = 1;
+      chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
+      phase++
+    ) {
+      addPhaseVoltageToMeterValue(
+        chargingStation,
+        connectorId,
+        meterValue,
+        voltageMeasurand,
+        phase,
+        buildVersionedSampledValue
+      )
+      addLineToLineVoltageToMeterValue(
+        chargingStation,
+        connectorId,
+        meterValue,
+        voltageMeasurand,
+        phase,
+        buildVersionedSampledValue
+      )
+    }
+  }
+  // Power.Active.Import measurand
+  const powerMeasurand = buildPowerMeasurandValue(
+    chargingStation,
+    connectorId,
+    undefined,
+    measurandsKey
+  )
+  if (powerMeasurand != null) {
+    const unitDivider = powerMeasurand.template.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
+    const connectorMaximumAvailablePower =
+      chargingStation.getConnectorMaximumAvailablePower(connectorId)
+    const connectorMaximumPower = Math.round(connectorMaximumAvailablePower)
+    const connectorMinimumPower = Math.round(powerMeasurand.template.minimumValue ?? 0)
+
+    meterValue.sampledValue.push(
+      buildVersionedSampledValue(powerMeasurand.template, powerMeasurand.values.allPhases)
+    )
+    const sampledValuesIndex = meterValue.sampledValue.length - 1
+    validatePowerMeasurandValue(
+      chargingStation,
+      connectorId,
+      connectorStatus,
+      meterValue.sampledValue[sampledValuesIndex],
+      connectorMaximumPower / unitDivider,
+      connectorMinimumPower / unitDivider,
+      debug
+    )
+    if (chargingStation.getNumberOfPhases() === 3) {
+      const connectorMaximumPowerPerPhase = Math.round(
+        connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
+      )
+      const connectorMinimumPowerPerPhase = Math.round(
+        connectorMinimumPower / chargingStation.getNumberOfPhases()
+      )
+      for (let phase = 1; phase <= chargingStation.getNumberOfPhases(); phase++) {
+        const phaseTemplate =
+          powerMeasurand.perPhaseTemplates[
+            `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates
+          ]
+        if (phaseTemplate != null) {
+          const phaseValue = `L${phase.toString()}-N` as MeterValuePhase
+          const phasePowerValue =
+            powerMeasurand.values[`L${phase.toString()}` as keyof MeasurandValues]
+          meterValue.sampledValue.push(
+            buildVersionedSampledValue(phaseTemplate, phasePowerValue, undefined, phaseValue)
+          )
+          const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1
+          validatePowerMeasurandValue(
+            chargingStation,
+            connectorId,
+            connectorStatus,
+            meterValue.sampledValue[sampledValuesPerPhaseIndex],
+            connectorMaximumPowerPerPhase / unitDivider,
+            connectorMinimumPowerPerPhase / unitDivider,
+            debug
+          )
+        }
+      }
+    }
+  }
+  // Current.Import measurand
+  const currentMeasurand = buildCurrentMeasurandValue(
+    chargingStation,
+    connectorId,
+    undefined,
+    measurandsKey
+  )
+  if (currentMeasurand != null) {
+    const connectorMaximumAvailablePower =
+      chargingStation.getConnectorMaximumAvailablePower(connectorId)
+    const connectorMaximumAmperage =
+      chargingStation.stationInfo?.currentOutType === CurrentType.AC
+        ? ACElectricUtils.amperagePerPhaseFromPower(
+          chargingStation.getNumberOfPhases(),
+          connectorMaximumAvailablePower,
+          chargingStation.getVoltageOut()
+        )
+        : DCElectricUtils.amperage(connectorMaximumAvailablePower, chargingStation.getVoltageOut())
+    const connectorMinimumAmperage = currentMeasurand.template.minimumValue ?? 0
+
+    meterValue.sampledValue.push(
+      buildVersionedSampledValue(currentMeasurand.template, currentMeasurand.values.allPhases)
+    )
+    const sampledValuesIndex = meterValue.sampledValue.length - 1
+    validateCurrentMeasurandValue(
+      chargingStation,
+      connectorId,
+      connectorStatus,
+      meterValue.sampledValue[sampledValuesIndex],
+      connectorMaximumAmperage,
+      connectorMinimumAmperage,
+      debug
+    )
+    for (
+      let phase = 1;
+      chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
+      phase++
+    ) {
+      const phaseValue = `L${phase.toString()}` as MeterValuePhase
+      meterValue.sampledValue.push(
+        buildVersionedSampledValue(
+          currentMeasurand.perPhaseTemplates[
+            phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
+          ] ?? currentMeasurand.template,
+          currentMeasurand.values[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
+          undefined,
+          phaseValue
+        )
+      )
+      const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1
+      validateCurrentMeasurandPhaseValue(
+        chargingStation,
+        connectorId,
+        connectorStatus,
+        meterValue.sampledValue[sampledValuesPerPhaseIndex],
+        connectorMaximumAmperage,
+        connectorMinimumAmperage,
+        debug
+      )
+    }
+  }
+  // Energy.Active.Import.Register measurand (default)
+  const energyMeasurand = buildEnergyMeasurandValue(
+    chargingStation,
+    connectorId,
+    interval,
+    undefined,
+    measurandsKey
+  )
+  if (energyMeasurand != null) {
+    updateConnectorEnergyValues(connectorStatus, energyMeasurand.value)
+    const unitDivider = energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+    const energySampledValue = buildVersionedSampledValue(
+      energyMeasurand.template,
+      roundTo(
+        chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider,
+        2
+      )
+    )
+    meterValue.sampledValue.push(energySampledValue)
+    const connectorMaximumAvailablePower =
+      chargingStation.getConnectorMaximumAvailablePower(connectorId)
+    const connectorMaximumEnergyRounded = roundTo(
+      (connectorMaximumAvailablePower * interval) / (3600 * 1000),
+      2
+    )
+    const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2)
+    validateEnergyMeasurandValue(
+      chargingStation,
+      connectorId,
+      energySampledValue,
+      energyMeasurand.value,
+      connectorMinimumEnergyRounded,
+      connectorMaximumEnergyRounded,
+      interval,
+      debug
+    )
+  }
+  return meterValue
+}
+
+/**
+ * Builds an OCPP 1.6 sampled value from a template and measurement data.
+ * @param sampledValueTemplate - The sampled value template to use.
+ * @param value - The measured value.
+ * @param context - The reading context.
+ * @param phase - The phase of the measurement.
+ * @returns The built OCPP 1.6 sampled value.
+ */
+export function buildSampledValueForOCPP16 (
+  sampledValueTemplate: SampledValueTemplate,
+  value: number,
+  context?: MeterValueContext,
+  phase?: MeterValuePhase
+): OCPP16SampledValue {
+  return buildSampledValue(
+    OCPPVersion.VERSION_16,
+    sampledValueTemplate,
+    value,
+    context,
+    phase
+  ) as OCPP16SampledValue
+}
diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts b/src/charging-station/ocpp/1.6/OCPP16RequestBuilders.ts
new file mode 100644 (file)
index 0000000..28cf8ee
--- /dev/null
@@ -0,0 +1,25 @@
+import type { ChargingStationInfo, OCPP16BootNotificationRequest } from '../../../types/index.js'
+
+export const buildOCPP16BootNotificationRequest = (
+  stationInfo: ChargingStationInfo
+): OCPP16BootNotificationRequest => ({
+  chargePointModel: stationInfo.chargePointModel,
+  chargePointVendor: stationInfo.chargePointVendor,
+  ...(stationInfo.chargeBoxSerialNumber != null && {
+    chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
+  }),
+  ...(stationInfo.chargePointSerialNumber != null && {
+    chargePointSerialNumber: stationInfo.chargePointSerialNumber,
+  }),
+  ...(stationInfo.firmwareVersion != null && {
+    firmwareVersion: stationInfo.firmwareVersion,
+  }),
+  ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+  ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+  ...(stationInfo.meterSerialNumber != null && {
+    meterSerialNumber: stationInfo.meterSerialNumber,
+  }),
+  ...(stationInfo.meterType != null && {
+    meterType: stationInfo.meterType,
+  }),
+})
index 9894752b037dc4bd354ab0a0633708c869c4ea4f..1d73ce05c15c8367ee50878c45f18ce320eabce7 100644 (file)
@@ -16,14 +16,12 @@ import {
 import { BaseError } from '../../../exception/index.js'
 import {
   ChargePointErrorCode,
-  type ChargingStationInfo,
   type ConfigurationKey,
   type GenericResponse,
   type MeterValuesRequest,
   type MeterValuesResponse,
   OCPP16AuthorizationStatus,
   type OCPP16AvailabilityType,
-  type OCPP16BootNotificationRequest,
   type OCPP16ChangeAvailabilityResponse,
   OCPP16ChargePointStatus,
   type OCPP16ChargingProfile,
@@ -110,37 +108,6 @@ export class OCPP16ServiceUtils {
     [OCPP16RequestCommand.STOP_TRANSACTION, 'StopTransaction'],
   ]
 
-  /**
-   * Builds an OCPP 1.6 BootNotification request payload from station info.
-   * @param stationInfo - Charging station information
-   * @returns Formatted OCPP 1.6 BootNotification request payload
-   */
-  public static buildBootNotificationRequest (
-    stationInfo: ChargingStationInfo
-  ): OCPP16BootNotificationRequest {
-    return {
-      chargePointModel: stationInfo.chargePointModel,
-      chargePointVendor: stationInfo.chargePointVendor,
-      ...(stationInfo.chargeBoxSerialNumber != null && {
-        chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
-      }),
-      ...(stationInfo.chargePointSerialNumber != null && {
-        chargePointSerialNumber: stationInfo.chargePointSerialNumber,
-      }),
-      ...(stationInfo.firmwareVersion != null && {
-        firmwareVersion: stationInfo.firmwareVersion,
-      }),
-      ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
-      ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
-      ...(stationInfo.meterSerialNumber != null && {
-        meterSerialNumber: stationInfo.meterSerialNumber,
-      }),
-      ...(stationInfo.meterType != null && {
-        meterType: stationInfo.meterType,
-      }),
-    } satisfies OCPP16BootNotificationRequest
-  }
-
   /**
    * @param commandParams - Status notification parameters
    * @returns Formatted OCPP 1.6 StatusNotification request payload
diff --git a/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts b/src/charging-station/ocpp/2.0/OCPP20MeterValueBuilders.ts
new file mode 100644 (file)
index 0000000..2bfe856
--- /dev/null
@@ -0,0 +1,212 @@
+import type { ChargingStation } from '../../../charging-station/index.js'
+
+import { OCPPError } from '../../../exception/index.js'
+import {
+  type ConfigurationKeyType,
+  ErrorType,
+  type MeterValueContext,
+  type MeterValuePhase,
+  MeterValueUnit,
+  type OCPP20MeterValue,
+  type OCPP20SampledValue,
+  OCPPVersion,
+  RequestCommand,
+  type SampledValueTemplate,
+} from '../../../types/index.js'
+import { roundTo } from '../../../utils/index.js'
+import {
+  addLineToLineVoltageToMeterValue,
+  addMainVoltageToMeterValue,
+  addPhaseVoltageToMeterValue,
+  buildCurrentMeasurandValue,
+  buildEmptyMeterValue,
+  buildEnergyMeasurandValue,
+  buildPowerMeasurandValue,
+  buildSampledValue,
+  buildSocMeasurandValue,
+  buildVoltageMeasurandValue,
+  updateConnectorEnergyValues,
+  validateEnergyMeasurandValue,
+  validateSocMeasurandValue,
+} from '../OCPPServiceUtils.js'
+
+export const buildMeterValueForOCPP20 = (
+  chargingStation: ChargingStation,
+  transactionId: number | string,
+  interval: number,
+  measurandsKey?: ConfigurationKeyType,
+  context?: MeterValueContext,
+  debug = false
+): OCPP20MeterValue => {
+  const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)
+  const evseId = chargingStation.getEvseIdByTransactionId(transactionId)
+  if (connectorId == null || evseId == null) {
+    throw new OCPPError(
+      ErrorType.INTERNAL_ERROR,
+      `Cannot build MeterValues: no connector/EVSE found for transaction ${String(transactionId)}`,
+      RequestCommand.METER_VALUES
+    )
+  }
+  const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+  const meterValue = buildEmptyMeterValue() as OCPP20MeterValue
+  const buildVersionedSampledValue = (
+    sampledValueTemplate: SampledValueTemplate,
+    value: number,
+    context?: MeterValueContext,
+    phase?: MeterValuePhase
+  ): OCPP20SampledValue => {
+    return buildSampledValueForOCPP20(sampledValueTemplate, value, context, phase)
+  }
+  // SoC measurand
+  const socMeasurand = buildSocMeasurandValue(chargingStation, connectorId, evseId, measurandsKey)
+  if (socMeasurand != null) {
+    const socSampledValue = buildVersionedSampledValue(
+      socMeasurand.template,
+      socMeasurand.value,
+      context
+    )
+    meterValue.sampledValue.push(socSampledValue)
+    validateSocMeasurandValue(
+      chargingStation,
+      connectorId,
+      socSampledValue,
+      socMeasurand.template.minimumValue ?? 0,
+      100,
+      debug
+    )
+  }
+  // Voltage measurand
+  const voltageMeasurand = buildVoltageMeasurandValue(
+    chargingStation,
+    connectorId,
+    evseId,
+    measurandsKey
+  )
+  if (voltageMeasurand != null) {
+    addMainVoltageToMeterValue(
+      chargingStation,
+      meterValue,
+      voltageMeasurand,
+      buildVersionedSampledValue,
+      context
+    )
+    for (
+      let phase = 1;
+      chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
+      phase++
+    ) {
+      addPhaseVoltageToMeterValue(
+        chargingStation,
+        connectorId,
+        meterValue,
+        voltageMeasurand,
+        phase,
+        buildVersionedSampledValue,
+        measurandsKey,
+        context
+      )
+      addLineToLineVoltageToMeterValue(
+        chargingStation,
+        connectorId,
+        meterValue,
+        voltageMeasurand,
+        phase,
+        buildVersionedSampledValue,
+        measurandsKey,
+        context
+      )
+    }
+  }
+  // Energy.Active.Import.Register measurand
+  const energyMeasurand = buildEnergyMeasurandValue(
+    chargingStation,
+    connectorId,
+    interval,
+    evseId,
+    measurandsKey
+  )
+  if (energyMeasurand != null) {
+    updateConnectorEnergyValues(connectorStatus, energyMeasurand.value)
+    const unitDivider = energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+    const energySampledValue = buildVersionedSampledValue(
+      energyMeasurand.template,
+      roundTo(
+        chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider,
+        2
+      ),
+      context
+    )
+    meterValue.sampledValue.push(energySampledValue)
+    const connectorMaximumAvailablePower =
+      chargingStation.getConnectorMaximumAvailablePower(connectorId)
+    const connectorMaximumEnergyRounded = roundTo(
+      (connectorMaximumAvailablePower * interval) / (3600 * 1000),
+      2
+    )
+    const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2)
+    validateEnergyMeasurandValue(
+      chargingStation,
+      connectorId,
+      energySampledValue,
+      energyMeasurand.value,
+      connectorMinimumEnergyRounded,
+      connectorMaximumEnergyRounded,
+      interval,
+      debug
+    )
+  }
+  // Power.Active.Import measurand
+  const powerMeasurand = buildPowerMeasurandValue(
+    chargingStation,
+    connectorId,
+    evseId,
+    measurandsKey
+  )
+  if (powerMeasurand?.values.allPhases != null) {
+    const powerSampledValue = buildVersionedSampledValue(
+      powerMeasurand.template,
+      powerMeasurand.values.allPhases,
+      context
+    )
+    meterValue.sampledValue.push(powerSampledValue)
+  }
+  // Current.Import measurand
+  const currentMeasurand = buildCurrentMeasurandValue(
+    chargingStation,
+    connectorId,
+    evseId,
+    measurandsKey
+  )
+  if (currentMeasurand?.values.allPhases != null) {
+    const currentSampledValue = buildVersionedSampledValue(
+      currentMeasurand.template,
+      currentMeasurand.values.allPhases,
+      context
+    )
+    meterValue.sampledValue.push(currentSampledValue)
+  }
+  return meterValue
+}
+
+/**
+ * Builds an OCPP 2.0 sampled value from a template and measurement data.
+ * @param sampledValueTemplate - The sampled value template to use.
+ * @param value - The measured value.
+ * @param context - The reading context.
+ * @param phase - The phase of the measurement.
+ * @returns The built OCPP 2.0 sampled value.
+ */
+export function buildSampledValueForOCPP20 (
+  sampledValueTemplate: SampledValueTemplate,
+  value: number,
+  context?: MeterValueContext,
+  phase?: MeterValuePhase
+): OCPP20SampledValue {
+  return buildSampledValue(
+    OCPPVersion.VERSION_20,
+    sampledValueTemplate,
+    value,
+    context,
+    phase
+  ) as OCPP20SampledValue
+}
diff --git a/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts
new file mode 100644 (file)
index 0000000..83ad669
--- /dev/null
@@ -0,0 +1,28 @@
+import {
+  BootReasonEnumType,
+  type ChargingStationInfo,
+  type OCPP20BootNotificationRequest,
+} from '../../../types/index.js'
+
+export const buildOCPP20BootNotificationRequest = (
+  stationInfo: ChargingStationInfo,
+  bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
+): OCPP20BootNotificationRequest => ({
+  chargingStation: {
+    model: stationInfo.chargePointModel,
+    vendorName: stationInfo.chargePointVendor,
+    ...(stationInfo.firmwareVersion != null && {
+      firmwareVersion: stationInfo.firmwareVersion,
+    }),
+    ...(stationInfo.chargeBoxSerialNumber != null && {
+      serialNumber: stationInfo.chargeBoxSerialNumber,
+    }),
+    ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
+      modem: {
+        ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+        ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+      },
+    }),
+  },
+  reason: bootReason,
+})
index 940d1c73ecf3ac4cf58c6eba917ebe6465a1571c..88fd4b7c0efc6f053c21eb48251ad7156dc283ab 100644 (file)
@@ -3,16 +3,15 @@ import { secondsToMilliseconds } from 'date-fns'
 import { type ChargingStation, resetConnectorStatus } from '../../../charging-station/index.js'
 import { OCPPError } from '../../../exception/index.js'
 import {
-  BootReasonEnumType,
-  type ChargingStationInfo,
   type ConnectorStatus,
   ConnectorStatusEnum,
   ErrorType,
-  type OCPP20BootNotificationRequest,
+  OCPP20AuthorizationStatusEnumType,
   OCPP20ChargingStateEnumType,
   OCPP20ComponentName,
   type OCPP20ConnectorStatusEnumType,
   type OCPP20EVSEType,
+  OCPP20IdTokenEnumType,
   type OCPP20IdTokenInfoType,
   type OCPP20IdTokenType,
   OCPP20IncomingRequestCommand,
@@ -32,6 +31,9 @@ import {
   OCPPVersion,
   ReasonCodeEnumType,
   RequestCommand,
+  type StartTransactionResult,
+  type StopTransactionReason,
+  type StopTransactionResult,
   type UUIDv4,
 } from '../../../types/index.js'
 import {
@@ -56,6 +58,7 @@ import { sendAndSetConnectorStatus } from '../OCPPConnectorStatusOperations.js'
 import {
   buildMeterValue,
   createPayloadConfigs,
+  mapStopReasonToOCPP20,
   PayloadValidatorOptions,
 } from '../OCPPServiceUtils.js'
 import { OCPP20VariableManager } from './OCPP20VariableManager.js'
@@ -113,37 +116,6 @@ export class OCPP20ServiceUtils {
     [OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'],
   ]
 
-  /**
-   * Builds an OCPP 2.0 BootNotification request payload from station info.
-   * @param stationInfo - Charging station information
-   * @param bootReason - Reason for the boot notification
-   * @returns Formatted OCPP 2.0 BootNotification request payload
-   */
-  public static buildBootNotificationRequest (
-    stationInfo: ChargingStationInfo,
-    bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
-  ): OCPP20BootNotificationRequest {
-    return {
-      chargingStation: {
-        model: stationInfo.chargePointModel,
-        vendorName: stationInfo.chargePointVendor,
-        ...(stationInfo.firmwareVersion != null && {
-          firmwareVersion: stationInfo.firmwareVersion,
-        }),
-        ...(stationInfo.chargeBoxSerialNumber != null && {
-          serialNumber: stationInfo.chargeBoxSerialNumber,
-        }),
-        ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
-          modem: {
-            ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
-            ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
-          },
-        }),
-      },
-      reason: bootReason,
-    } satisfies OCPP20BootNotificationRequest
-  }
-
   /**
    * @param chargingStation - Target charging station for EVSE resolution
    * @param commandParams - Status notification parameters
@@ -816,6 +788,43 @@ export class OCPP20ServiceUtils {
     )
   }
 
+  public static async startTransactionOnConnector (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    idTag?: string
+  ): Promise<StartTransactionResult> {
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    let transactionId = connectorStatus?.transactionId as string | undefined
+    if (transactionId == null) {
+      transactionId = generateUUID()
+      if (connectorStatus != null) {
+        connectorStatus.transactionId = transactionId
+      }
+      OCPP20ServiceUtils.resetTransactionSequenceNumber(chargingStation, connectorId)
+    }
+    const startedMeterValues = OCPP20ServiceUtils.buildTransactionStartedMeterValues(
+      chargingStation,
+      transactionId
+    )
+    const response = await OCPP20ServiceUtils.sendTransactionEvent(
+      chargingStation,
+      OCPP20TransactionEventEnumType.Started,
+      OCPP20TriggerReasonEnumType.Authorized,
+      connectorId,
+      transactionId,
+      {
+        idToken:
+          idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 } : undefined,
+        ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }),
+      }
+    )
+    return {
+      accepted:
+        response.idTokenInfo == null ||
+        response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
+    }
+  }
+
   /**
    * Start periodic TransactionEvent(Updated) with meter values for a connector.
    * @param chargingStation - Target charging station
@@ -986,6 +995,33 @@ export class OCPP20ServiceUtils {
     }
   }
 
+  public static async stopTransactionOnConnector (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    reason?: StopTransactionReason
+  ): Promise<StopTransactionResult> {
+    const evseId = chargingStation.getEvseIdByConnectorId(connectorId)
+    if (evseId == null) {
+      logger.warn(
+        `${chargingStation.logPrefix()} stopTransactionOnConnector: cannot resolve EVSE ID for connector ${connectorId.toString()}, skipping`
+      )
+      return { accepted: false }
+    }
+    const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason)
+    const response = await OCPP20ServiceUtils.requestStopTransaction(
+      chargingStation,
+      connectorId,
+      evseId,
+      triggerReason,
+      stoppedReason
+    )
+    return {
+      accepted:
+        response.idTokenInfo == null ||
+        response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
+    }
+  }
+
   /**
    * Stop periodic TransactionEvent(Updated) sending for a connector.
    * @param chargingStation - Target charging station
index 0607932c396bada7ef417470ca541f5479d4e66e..88495b1c6b6831ceb008e04f4f91605324dea9d5 100644 (file)
@@ -1,21 +1,15 @@
-import type { BootReasonEnumType, StopTransactionReason } from '../../types/index.js'
+import type { StopTransactionReason } from '../../types/index.js'
 
 import { type ChargingStation } from '../../charging-station/index.js'
 import { OCPPError } from '../../exception/index.js'
 import {
   AuthorizationStatus,
-  type BootNotificationRequest,
-  type ChargingStationInfo,
   ErrorType,
-  OCPP20AuthorizationStatusEnumType,
-  OCPP20IdTokenEnumType,
-  OCPP20TransactionEventEnumType,
-  OCPP20TriggerReasonEnumType,
   OCPPVersion,
   type StartTransactionResult,
   type StopTransactionResult,
 } from '../../types/index.js'
-import { generateUUID, logger, truncateId } from '../../utils/index.js'
+import { logger, truncateId } from '../../utils/index.js'
 import { OCPP16ServiceUtils } from './1.6/OCPP16ServiceUtils.js'
 import { OCPP20ServiceUtils } from './2.0/OCPP20ServiceUtils.js'
 import {
@@ -49,38 +43,8 @@ export const startTransactionOnConnector = async (
       return { accepted: response.idTagInfo.status === AuthorizationStatus.ACCEPTED }
     }
     case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-      let transactionId = connectorStatus?.transactionId as string | undefined
-      if (transactionId == null) {
-        transactionId = generateUUID()
-        if (connectorStatus != null) {
-          connectorStatus.transactionId = transactionId
-        }
-        OCPP20ServiceUtils.resetTransactionSequenceNumber(chargingStation, connectorId)
-      }
-      const startedMeterValues = OCPP20ServiceUtils.buildTransactionStartedMeterValues(
-        chargingStation,
-        transactionId
-      )
-      const response = await OCPP20ServiceUtils.sendTransactionEvent(
-        chargingStation,
-        OCPP20TransactionEventEnumType.Started,
-        OCPP20TriggerReasonEnumType.Authorized,
-        connectorId,
-        transactionId,
-        {
-          idToken:
-            idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 } : undefined,
-          ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }),
-        }
-      )
-      return {
-        accepted:
-          response.idTokenInfo == null ||
-          response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
-      }
-    }
+    case OCPPVersion.VERSION_201:
+      return OCPP20ServiceUtils.startTransactionOnConnector(chargingStation, connectorId, idTag)
     default:
       throw new OCPPError(
         ErrorType.INTERNAL_ERROR,
@@ -112,28 +76,8 @@ export const stopTransactionOnConnector = async (
       return { accepted: response.idTagInfo?.status === AuthorizationStatus.ACCEPTED }
     }
     case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const evseId = chargingStation.getEvseIdByConnectorId(connectorId)
-      if (evseId == null) {
-        logger.warn(
-          `${chargingStation.logPrefix()} stopTransactionOnConnector: cannot resolve EVSE ID for connector ${connectorId.toString()}, skipping`
-        )
-        return { accepted: false }
-      }
-      const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason)
-      const response = await OCPP20ServiceUtils.requestStopTransaction(
-        chargingStation,
-        connectorId,
-        evseId,
-        triggerReason,
-        stoppedReason
-      )
-      return {
-        accepted:
-          response.idTokenInfo == null ||
-          response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
-      }
-    }
+    case OCPPVersion.VERSION_201:
+      return OCPP20ServiceUtils.stopTransactionOnConnector(chargingStation, connectorId, reason)
     default:
       throw new OCPPError(
         ErrorType.INTERNAL_ERROR,
@@ -254,27 +198,6 @@ export const flushQueuedTransactionMessages = async (
   }
 }
 
-/**
- * Builds an OCPP BootNotification request using the appropriate version-specific handler.
- * @param stationInfo - Charging station information
- * @param bootReason - Optional boot reason (OCPP 2.0 only)
- * @returns The BootNotification request payload, or undefined if the OCPP version is unsupported
- */
-export const buildBootNotificationRequest = (
-  stationInfo: ChargingStationInfo,
-  bootReason?: BootReasonEnumType
-): BootNotificationRequest | undefined => {
-  switch (stationInfo.ocppVersion) {
-    case OCPPVersion.VERSION_16:
-      return OCPP16ServiceUtils.buildBootNotificationRequest(stationInfo)
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201:
-      return OCPP20ServiceUtils.buildBootNotificationRequest(stationInfo, bootReason)
-    default:
-      return undefined
-  }
-}
-
 export const isIdTagAuthorized = async (
   chargingStation: ChargingStation,
   connectorId: number,
index 495e8445614b88f328836ca5b7488cdb99d223d8..ba61ce421ed23de7b32eeb8b1f53257303193fb6 100644 (file)
@@ -6,11 +6,13 @@ import { readFileSync } from 'node:fs'
 import { dirname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
 
-import type { StopTransactionReason } from '../../types/index.js'
+import type { BootReasonEnumType, StopTransactionReason } from '../../types/index.js'
 
 import { type ChargingStation, getConfigurationKey } from '../../charging-station/index.js'
 import { BaseError, OCPPError } from '../../exception/index.js'
 import {
+  type BootNotificationRequest,
+  type ChargingStationInfo,
   type ConfigurationKeyType,
   type ConnectorStatus,
   CurrentType,
@@ -27,10 +29,8 @@ import {
   MeterValueMeasurand,
   MeterValuePhase,
   MeterValueUnit,
-  type OCPP16MeterValue,
   type OCPP16SampledValue,
   OCPP16StopTransactionReason,
-  type OCPP20MeterValue,
   OCPP20ReasonEnumType,
   type OCPP20SampledValue,
   OCPP20TriggerReasonEnumType,
@@ -57,6 +57,10 @@ import {
   min,
   roundTo,
 } from '../../utils/index.js'
+import { buildMeterValueForOCPP16 } from './1.6/OCPP16MeterValueBuilders.js'
+import { buildOCPP16BootNotificationRequest } from './1.6/OCPP16RequestBuilders.js'
+import { buildMeterValueForOCPP20 } from './2.0/OCPP20MeterValueBuilders.js'
+import { buildOCPP20BootNotificationRequest } from './2.0/OCPP20RequestBuilders.js'
 import { OCPPConstants } from './OCPPConstants.js'
 
 const moduleName = 'OCPPServiceUtils'
@@ -86,6 +90,21 @@ interface SingleValueMeasurandData {
   value: number
 }
 
+export const buildBootNotificationRequest = (
+  stationInfo: ChargingStationInfo,
+  bootReason?: BootReasonEnumType
+): BootNotificationRequest | undefined => {
+  switch (stationInfo.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      return buildOCPP16BootNotificationRequest(stationInfo)
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      return buildOCPP20BootNotificationRequest(stationInfo, bootReason)
+    default:
+      return undefined
+  }
+}
+
 /**
  * Maps an OCPP 1.6 or generic stop transaction reason to OCPP 2.0 stopped and trigger reasons.
  * @param reason - Stop transaction reason to map
@@ -215,7 +234,7 @@ export const convertDateToISOString = <T extends JsonType>(object: T): void => {
   }
 }
 
-const buildSocMeasurandValue = (
+export const buildSocMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   evseId?: number,
@@ -247,7 +266,7 @@ const buildSocMeasurandValue = (
   }
 }
 
-const validateSocMeasurandValue = (
+export const validateSocMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   sampledValue: SampledValue,
@@ -270,7 +289,7 @@ const validateSocMeasurandValue = (
   }
 }
 
-const buildVoltageMeasurandValue = (
+export const buildVoltageMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   evseId?: number,
@@ -303,7 +322,7 @@ const buildVoltageMeasurandValue = (
   }
 }
 
-const addMainVoltageToMeterValue = <TSampledValue extends SampledValue>(
+export const addMainVoltageToMeterValue = <TSampledValue extends SampledValue>(
   chargingStation: ChargingStation,
   meterValue: { sampledValue: TSampledValue[] },
   voltageData: { template: SampledValueTemplate; value: number },
@@ -329,7 +348,7 @@ const addMainVoltageToMeterValue = <TSampledValue extends SampledValue>(
   }
 }
 
-const addPhaseVoltageToMeterValue = <TSampledValue extends SampledValue>(
+export const addPhaseVoltageToMeterValue = <TSampledValue extends SampledValue>(
   chargingStation: ChargingStation,
   connectorId: number,
   meterValue: { sampledValue: TSampledValue[] },
@@ -382,7 +401,7 @@ const addPhaseVoltageToMeterValue = <TSampledValue extends SampledValue>(
   )
 }
 
-const addLineToLineVoltageToMeterValue = <TSampledValue extends SampledValue>(
+export const addLineToLineVoltageToMeterValue = <TSampledValue extends SampledValue>(
   chargingStation: ChargingStation,
   connectorId: number,
   meterValue: { sampledValue: TSampledValue[] },
@@ -443,7 +462,7 @@ const addLineToLineVoltageToMeterValue = <TSampledValue extends SampledValue>(
   )
 }
 
-const buildEnergyMeasurandValue = (
+export const buildEnergyMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   interval: number,
@@ -493,7 +512,7 @@ const buildEnergyMeasurandValue = (
   }
 }
 
-const updateConnectorEnergyValues = (
+export const updateConnectorEnergyValues = (
   connectorStatus: ConnectorStatus | undefined,
   energyValue: number
 ): void => {
@@ -513,7 +532,7 @@ const updateConnectorEnergyValues = (
   }
 }
 
-const validateEnergyMeasurandValue = (
+export const validateEnergyMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   sampledValue: SampledValue,
@@ -534,7 +553,7 @@ const validateEnergyMeasurandValue = (
   }
 }
 
-const buildPowerMeasurandValue = (
+export const buildPowerMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   evseId?: number,
@@ -742,7 +761,7 @@ const buildPowerMeasurandValue = (
   }
 }
 
-const validatePowerMeasurandValue = (
+export const validatePowerMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   connectorStatus: ConnectorStatus | undefined,
@@ -765,7 +784,7 @@ const validatePowerMeasurandValue = (
   }
 }
 
-const validateCurrentMeasurandValue = (
+export const validateCurrentMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   connectorStatus: ConnectorStatus | undefined,
@@ -788,7 +807,7 @@ const validateCurrentMeasurandValue = (
   }
 }
 
-const validateCurrentMeasurandPhaseValue = (
+export const validateCurrentMeasurandPhaseValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   connectorStatus: ConnectorStatus | undefined,
@@ -814,7 +833,7 @@ const validateCurrentMeasurandPhaseValue = (
   }
 }
 
-const buildCurrentMeasurandValue = (
+export const buildCurrentMeasurandValue = (
   chargingStation: ChargingStation,
   connectorId: number,
   evseId?: number,
@@ -1073,407 +1092,6 @@ export const buildMeterValue = (
   }
 }
 
-const buildMeterValueForOCPP16 = (
-  chargingStation: ChargingStation,
-  transactionId: number | string,
-  interval: number,
-  measurandsKey?: ConfigurationKeyType,
-  context?: MeterValueContext,
-  debug = false
-): OCPP16MeterValue => {
-  const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)
-  if (connectorId == null) {
-    throw new OCPPError(
-      ErrorType.INTERNAL_ERROR,
-      `Cannot build MeterValues: no connector found for transaction ${String(transactionId)}`,
-      RequestCommand.METER_VALUES
-    )
-  }
-  const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-  const meterValue = buildEmptyMeterValue() as OCPP16MeterValue
-  const buildVersionedSampledValue = (
-    sampledValueTemplate: SampledValueTemplate,
-    value: number,
-    context?: MeterValueContext,
-    phase?: MeterValuePhase
-  ): OCPP16SampledValue => {
-    return buildSampledValueForOCPP16(sampledValueTemplate, value, context, phase)
-  }
-  // SoC measurand
-  const socMeasurand = buildSocMeasurandValue(
-    chargingStation,
-    connectorId,
-    undefined,
-    measurandsKey
-  )
-  if (socMeasurand != null) {
-    const socSampledValue = buildVersionedSampledValue(socMeasurand.template, socMeasurand.value)
-    meterValue.sampledValue.push(socSampledValue)
-    validateSocMeasurandValue(
-      chargingStation,
-      connectorId,
-      socSampledValue,
-      socMeasurand.template.minimumValue ?? 0,
-      100,
-      debug
-    )
-  }
-  // Voltage measurand
-  const voltageMeasurand = buildVoltageMeasurandValue(
-    chargingStation,
-    connectorId,
-    undefined,
-    measurandsKey
-  )
-  if (voltageMeasurand != null) {
-    addMainVoltageToMeterValue(
-      chargingStation,
-      meterValue,
-      voltageMeasurand,
-      buildVersionedSampledValue
-    )
-    for (
-      let phase = 1;
-      chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-      phase++
-    ) {
-      addPhaseVoltageToMeterValue(
-        chargingStation,
-        connectorId,
-        meterValue,
-        voltageMeasurand,
-        phase,
-        buildVersionedSampledValue
-      )
-      addLineToLineVoltageToMeterValue(
-        chargingStation,
-        connectorId,
-        meterValue,
-        voltageMeasurand,
-        phase,
-        buildVersionedSampledValue
-      )
-    }
-  }
-  // Power.Active.Import measurand
-  const powerMeasurand = buildPowerMeasurandValue(
-    chargingStation,
-    connectorId,
-    undefined,
-    measurandsKey
-  )
-  if (powerMeasurand != null) {
-    const unitDivider = powerMeasurand.template.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
-    const connectorMaximumAvailablePower =
-      chargingStation.getConnectorMaximumAvailablePower(connectorId)
-    const connectorMaximumPower = Math.round(connectorMaximumAvailablePower)
-    const connectorMinimumPower = Math.round(powerMeasurand.template.minimumValue ?? 0)
-
-    meterValue.sampledValue.push(
-      buildVersionedSampledValue(powerMeasurand.template, powerMeasurand.values.allPhases)
-    )
-    const sampledValuesIndex = meterValue.sampledValue.length - 1
-    validatePowerMeasurandValue(
-      chargingStation,
-      connectorId,
-      connectorStatus,
-      meterValue.sampledValue[sampledValuesIndex],
-      connectorMaximumPower / unitDivider,
-      connectorMinimumPower / unitDivider,
-      debug
-    )
-    if (chargingStation.getNumberOfPhases() === 3) {
-      const connectorMaximumPowerPerPhase = Math.round(
-        connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
-      )
-      const connectorMinimumPowerPerPhase = Math.round(
-        connectorMinimumPower / chargingStation.getNumberOfPhases()
-      )
-      for (let phase = 1; phase <= chargingStation.getNumberOfPhases(); phase++) {
-        const phaseTemplate =
-          powerMeasurand.perPhaseTemplates[
-            `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates
-          ]
-        if (phaseTemplate != null) {
-          const phaseValue = `L${phase.toString()}-N` as MeterValuePhase
-          const phasePowerValue =
-            powerMeasurand.values[`L${phase.toString()}` as keyof MeasurandValues]
-          meterValue.sampledValue.push(
-            buildVersionedSampledValue(phaseTemplate, phasePowerValue, undefined, phaseValue)
-          )
-          const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1
-          validatePowerMeasurandValue(
-            chargingStation,
-            connectorId,
-            connectorStatus,
-            meterValue.sampledValue[sampledValuesPerPhaseIndex],
-            connectorMaximumPowerPerPhase / unitDivider,
-            connectorMinimumPowerPerPhase / unitDivider,
-            debug
-          )
-        }
-      }
-    }
-  }
-  // Current.Import measurand
-  const currentMeasurand = buildCurrentMeasurandValue(
-    chargingStation,
-    connectorId,
-    undefined,
-    measurandsKey
-  )
-  if (currentMeasurand != null) {
-    const connectorMaximumAvailablePower =
-      chargingStation.getConnectorMaximumAvailablePower(connectorId)
-    const connectorMaximumAmperage =
-      chargingStation.stationInfo?.currentOutType === CurrentType.AC
-        ? ACElectricUtils.amperagePerPhaseFromPower(
-          chargingStation.getNumberOfPhases(),
-          connectorMaximumAvailablePower,
-          chargingStation.getVoltageOut()
-        )
-        : DCElectricUtils.amperage(connectorMaximumAvailablePower, chargingStation.getVoltageOut())
-    const connectorMinimumAmperage = currentMeasurand.template.minimumValue ?? 0
-
-    meterValue.sampledValue.push(
-      buildVersionedSampledValue(currentMeasurand.template, currentMeasurand.values.allPhases)
-    )
-    const sampledValuesIndex = meterValue.sampledValue.length - 1
-    validateCurrentMeasurandValue(
-      chargingStation,
-      connectorId,
-      connectorStatus,
-      meterValue.sampledValue[sampledValuesIndex],
-      connectorMaximumAmperage,
-      connectorMinimumAmperage,
-      debug
-    )
-    for (
-      let phase = 1;
-      chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-      phase++
-    ) {
-      const phaseValue = `L${phase.toString()}` as MeterValuePhase
-      meterValue.sampledValue.push(
-        buildVersionedSampledValue(
-          currentMeasurand.perPhaseTemplates[
-            phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
-          ] ?? currentMeasurand.template,
-          currentMeasurand.values[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
-          undefined,
-          phaseValue
-        )
-      )
-      const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1
-      validateCurrentMeasurandPhaseValue(
-        chargingStation,
-        connectorId,
-        connectorStatus,
-        meterValue.sampledValue[sampledValuesPerPhaseIndex],
-        connectorMaximumAmperage,
-        connectorMinimumAmperage,
-        debug
-      )
-    }
-  }
-  // Energy.Active.Import.Register measurand (default)
-  const energyMeasurand = buildEnergyMeasurandValue(
-    chargingStation,
-    connectorId,
-    interval,
-    undefined,
-    measurandsKey
-  )
-  if (energyMeasurand != null) {
-    updateConnectorEnergyValues(connectorStatus, energyMeasurand.value)
-    const unitDivider = energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
-    const energySampledValue = buildVersionedSampledValue(
-      energyMeasurand.template,
-      roundTo(
-        chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider,
-        2
-      )
-    )
-    meterValue.sampledValue.push(energySampledValue)
-    const connectorMaximumAvailablePower =
-      chargingStation.getConnectorMaximumAvailablePower(connectorId)
-    const connectorMaximumEnergyRounded = roundTo(
-      (connectorMaximumAvailablePower * interval) / (3600 * 1000),
-      2
-    )
-    const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2)
-    validateEnergyMeasurandValue(
-      chargingStation,
-      connectorId,
-      energySampledValue,
-      energyMeasurand.value,
-      connectorMinimumEnergyRounded,
-      connectorMaximumEnergyRounded,
-      interval,
-      debug
-    )
-  }
-  return meterValue
-}
-
-const buildMeterValueForOCPP20 = (
-  chargingStation: ChargingStation,
-  transactionId: number | string,
-  interval: number,
-  measurandsKey?: ConfigurationKeyType,
-  context?: MeterValueContext,
-  debug = false
-): OCPP20MeterValue => {
-  const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)
-  const evseId = chargingStation.getEvseIdByTransactionId(transactionId)
-  if (connectorId == null || evseId == null) {
-    throw new OCPPError(
-      ErrorType.INTERNAL_ERROR,
-      `Cannot build MeterValues: no connector/EVSE found for transaction ${String(transactionId)}`,
-      RequestCommand.METER_VALUES
-    )
-  }
-  const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-  const meterValue = buildEmptyMeterValue() as OCPP20MeterValue
-  const buildVersionedSampledValue = (
-    sampledValueTemplate: SampledValueTemplate,
-    value: number,
-    context?: MeterValueContext,
-    phase?: MeterValuePhase
-  ): OCPP20SampledValue => {
-    return buildSampledValueForOCPP20(sampledValueTemplate, value, context, phase)
-  }
-  // SoC measurand
-  const socMeasurand = buildSocMeasurandValue(chargingStation, connectorId, evseId, measurandsKey)
-  if (socMeasurand != null) {
-    const socSampledValue = buildVersionedSampledValue(
-      socMeasurand.template,
-      socMeasurand.value,
-      context
-    )
-    meterValue.sampledValue.push(socSampledValue)
-    validateSocMeasurandValue(
-      chargingStation,
-      connectorId,
-      socSampledValue,
-      socMeasurand.template.minimumValue ?? 0,
-      100,
-      debug
-    )
-  }
-  // Voltage measurand
-  const voltageMeasurand = buildVoltageMeasurandValue(
-    chargingStation,
-    connectorId,
-    evseId,
-    measurandsKey
-  )
-  if (voltageMeasurand != null) {
-    addMainVoltageToMeterValue(
-      chargingStation,
-      meterValue,
-      voltageMeasurand,
-      buildVersionedSampledValue,
-      context
-    )
-    for (
-      let phase = 1;
-      chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
-      phase++
-    ) {
-      addPhaseVoltageToMeterValue(
-        chargingStation,
-        connectorId,
-        meterValue,
-        voltageMeasurand,
-        phase,
-        buildVersionedSampledValue,
-        measurandsKey,
-        context
-      )
-      addLineToLineVoltageToMeterValue(
-        chargingStation,
-        connectorId,
-        meterValue,
-        voltageMeasurand,
-        phase,
-        buildVersionedSampledValue,
-        measurandsKey,
-        context
-      )
-    }
-  }
-  // Energy.Active.Import.Register measurand
-  const energyMeasurand = buildEnergyMeasurandValue(
-    chargingStation,
-    connectorId,
-    interval,
-    evseId,
-    measurandsKey
-  )
-  if (energyMeasurand != null) {
-    updateConnectorEnergyValues(connectorStatus, energyMeasurand.value)
-    const unitDivider = energyMeasurand.template.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
-    const energySampledValue = buildVersionedSampledValue(
-      energyMeasurand.template,
-      roundTo(
-        chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider,
-        2
-      ),
-      context
-    )
-    meterValue.sampledValue.push(energySampledValue)
-    const connectorMaximumAvailablePower =
-      chargingStation.getConnectorMaximumAvailablePower(connectorId)
-    const connectorMaximumEnergyRounded = roundTo(
-      (connectorMaximumAvailablePower * interval) / (3600 * 1000),
-      2
-    )
-    const connectorMinimumEnergyRounded = roundTo(energyMeasurand.template.minimumValue ?? 0, 2)
-    validateEnergyMeasurandValue(
-      chargingStation,
-      connectorId,
-      energySampledValue,
-      energyMeasurand.value,
-      connectorMinimumEnergyRounded,
-      connectorMaximumEnergyRounded,
-      interval,
-      debug
-    )
-  }
-  // Power.Active.Import measurand
-  const powerMeasurand = buildPowerMeasurandValue(
-    chargingStation,
-    connectorId,
-    evseId,
-    measurandsKey
-  )
-  if (powerMeasurand?.values.allPhases != null) {
-    const powerSampledValue = buildVersionedSampledValue(
-      powerMeasurand.template,
-      powerMeasurand.values.allPhases,
-      context
-    )
-    meterValue.sampledValue.push(powerSampledValue)
-  }
-  // Current.Import measurand
-  const currentMeasurand = buildCurrentMeasurandValue(
-    chargingStation,
-    connectorId,
-    evseId,
-    measurandsKey
-  )
-  if (currentMeasurand?.values.allPhases != null) {
-    const currentSampledValue = buildVersionedSampledValue(
-      currentMeasurand.template,
-      currentMeasurand.values.allPhases,
-      context
-    )
-    meterValue.sampledValue.push(currentSampledValue)
-  }
-  return meterValue
-}
-
 const checkMeasurandPowerDivider = (
   chargingStation: ChargingStation,
   measurandType: MeterValueMeasurand | undefined
@@ -1688,52 +1306,6 @@ export function buildSampledValue (
   }
 }
 
-/**
- * Builds an OCPP 1.6 sampled value from a template and measurement data.
- * @param sampledValueTemplate - The sampled value template to use.
- * @param value - The measured value.
- * @param context - The reading context.
- * @param phase - The phase of the measurement.
- * @returns The built OCPP 1.6 sampled value.
- */
-function buildSampledValueForOCPP16 (
-  sampledValueTemplate: SampledValueTemplate,
-  value: number,
-  context?: MeterValueContext,
-  phase?: MeterValuePhase
-): OCPP16SampledValue {
-  return buildSampledValue(
-    OCPPVersion.VERSION_16,
-    sampledValueTemplate,
-    value,
-    context,
-    phase
-  ) as OCPP16SampledValue
-}
-
-/**
- * Builds an OCPP 2.0 sampled value from a template and measurement data.
- * @param sampledValueTemplate - The sampled value template to use.
- * @param value - The measured value.
- * @param context - The reading context.
- * @param phase - The phase of the measurement.
- * @returns The built OCPP 2.0 sampled value.
- */
-function buildSampledValueForOCPP20 (
-  sampledValueTemplate: SampledValueTemplate,
-  value: number,
-  context?: MeterValueContext,
-  phase?: MeterValuePhase
-): OCPP20SampledValue {
-  return buildSampledValue(
-    OCPPVersion.VERSION_20,
-    sampledValueTemplate,
-    value,
-    context,
-    phase
-  ) as OCPP20SampledValue
-}
-
 const getMeasurandDefaultContext = (measurandType: MeterValueMeasurand): MeterValueContext => {
   return MeterValueContext.SAMPLE_PERIODIC
 }
index d570f06efd81fd01963c7e19fb561556517f8de5..b2997cdc85147b54e2aa900e97cf47be0e651cd6 100644 (file)
@@ -17,11 +17,10 @@ export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
 export { OCPPRequestService } from './OCPPRequestService.js'
 export { createOCPPServices } from './OCPPServiceFactory.js'
 export {
-  buildBootNotificationRequest,
   flushQueuedTransactionMessages,
   isIdTagAuthorized,
   startTransactionOnConnector,
   stopRunningTransactions,
   stopTransactionOnConnector,
 } from './OCPPServiceOperations.js'
-export { buildMeterValue } from './OCPPServiceUtils.js'
+export { buildBootNotificationRequest, buildMeterValue } from './OCPPServiceUtils.js'
index f83a43bdfa70fa4c05c785a77612d06384695992..ccbd486ead8527ddd93f672c489e436057e81876 100644 (file)
@@ -13,12 +13,12 @@ import type { ChargingStationInfo } from '../../../src/types/index.js'
 import type { MockChargingStationOptions } from '../helpers/StationHelpers.js'
 
 import {
-  buildBootNotificationRequest,
   flushQueuedTransactionMessages,
   startTransactionOnConnector,
   stopRunningTransactions,
   stopTransactionOnConnector,
 } from '../../../src/charging-station/ocpp/OCPPServiceOperations.js'
+import { buildBootNotificationRequest } from '../../../src/charging-station/ocpp/OCPPServiceUtils.js'
 import {
   BootReasonEnumType,
   type OCPP20TransactionEventRequest,