]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
feat: implement TxEnded meter value accumulator per OCPP 2.0.1 spec §2.1
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Mar 2026 16:06:49 +0000 (17:06 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Mar 2026 16:06:49 +0000 (17:06 +0100)
Per E06.FR.11 and J02.FR.10, TransactionEvent(Ended) must contain meter
values sampled every TxEndedInterval from transaction start, with the
final sample marked as Transaction.End context.

- Add TxEndedInterval to OCPP20RequiredVariableName enum
- Add start/stopEndedMeterValues and getTxEndedInterval
- Accumulate periodic samples during transaction, flush in Ended event
- Harmonize Updated/Ended naming across API, variables, and log messages
- Exclude transactionEndedMeterValues from serialized configuration
- Fix stopEndedMeterValues incorrectly called in startUpdatedMeterValues
- Restore JSDoc-free ConnectorStatus fields
- Fix DEFAULT_WS_PING_INTERVAL constant reference
- Update test fixtures and assertions for serialization coverage

19 files changed:
src/charging-station/Helpers.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/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/types/ConnectorStatus.ts
src/types/ocpp/2.0/Variables.ts
src/utils/ChargingStationConfigurationUtils.ts
tests/charging-station/ChargingStation-Transactions.test.ts
tests/charging-station/helpers/StationHelpers.ts
tests/charging-station/ocpp/1.6/OCPP16Integration-Transactions.test.ts
tests/charging-station/ocpp/1.6/OCPP16ResponseService-Transactions.test.ts
tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts
tests/helpers/TestLifecycleHelpers.ts
tests/utils/ChargingStationConfigurationUtils.test.ts

index f2c1c9f5548eb7eca2769148e4c4159a17c95bc8..b2bf0cc887754c3d463ab1ef78431b0b1bc70672 100644 (file)
@@ -565,6 +565,11 @@ export const resetConnectorStatus = (connectorStatus: ConnectorStatus | undefine
   delete connectorStatus.transactionGroupIdToken
   connectorStatus.transactionEnergyActiveImportRegisterValue = 0
   delete connectorStatus.transactionBeginMeterValue
+  delete connectorStatus.transactionEndedMeterValues
+  if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+    clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+    delete connectorStatus.transactionEndedMeterValuesSetInterval
+  }
   delete connectorStatus.transactionSeqNo
   delete connectorStatus.transactionEvseSent
   delete connectorStatus.transactionIdTokenSent
index 8266f2898049e8639486f8e38da1a183ea585bd4..9b4844c6a97a768e79f63d5b56176539e595d1b1 100644 (file)
@@ -837,8 +837,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
           connectorId++
         ) {
           if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
-            OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
-            OCPP16ServiceUtils.startPeriodicMeterValues(
+            OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
+            OCPP16ServiceUtils.startUpdatedMeterValues(
               chargingStation,
               connectorId,
               secondsToMilliseconds(convertToInt(value))
index 6960de749ac51669a08336555e53e39322277c94..c6b383c83824cf869137f051e0c6bfb20b031a17 100644 (file)
@@ -453,7 +453,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
         chargingStation,
         OCPP16StandardParametersKey.MeterValueSampleInterval
       )
-      OCPP16ServiceUtils.startPeriodicMeterValues(
+      OCPP16ServiceUtils.startUpdatedMeterValues(
         chargingStation,
         connectorId,
         configuredMeterValueSampleInterval != null
@@ -542,7 +542,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
     ) {
       transactionConnectorStatus.locked = false
     }
-    OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, transactionConnectorId)
+    OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, transactionConnectorId)
     const logMsg = `${chargingStation.logPrefix()} ${moduleName}.handleResponseStopTransaction: Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${
       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       chargingStation.stationInfo?.chargingStationId
@@ -562,7 +562,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
     chargingStation: ChargingStation,
     connectorId: number
   ): Promise<void> {
-    OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+    OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
     resetConnectorStatus(connectorStatus)
     await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
index 057a8f406f595bf9143f50a73de752f078c46ee6..4b739cd0a89845f2bcf02728a1e32eb9f47ef2be 100644 (file)
@@ -589,7 +589,21 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
   }
 
-  public static startPeriodicMeterValues (
+  public static async startTransactionOnConnector (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    idTag?: string
+  ): Promise<StartTransactionResponse> {
+    return chargingStation.ocppRequestService.requestHandler<
+      Partial<StartTransactionRequest>,
+      StartTransactionResponse
+    >(chargingStation, RequestCommand.START_TRANSACTION, {
+      connectorId,
+      ...(idTag != null && { idTag }),
+    })
+  }
+
+  public static startUpdatedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number,
     interval: number
@@ -597,23 +611,23 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
     if (connectorStatus == null) {
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Connector ${connectorId.toString()} not found`
+        `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Connector ${connectorId.toString()} not found`
       )
       return
     }
     if (connectorStatus.transactionStarted !== true || connectorStatus.transactionId == null) {
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: No active transaction on connector ${connectorId.toString()}`
+        `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: No active transaction on connector ${connectorId.toString()}`
       )
       return
     }
     if (interval <= 0) {
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: MeterValueSampleInterval set to ${interval.toString()}, not sending MeterValues`
+        `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: MeterValueSampleInterval set to ${interval.toString()}, not sending MeterValues`
       )
       return
     }
-    connectorStatus.transactionMeterValuesSetInterval = setInterval(() => {
+    connectorStatus.transactionUpdatedMeterValuesSetInterval = setInterval(() => {
       const transactionId = convertToInt(connectorStatus.transactionId)
       const meterValue = buildMeterValue(chargingStation, transactionId, interval)
       chargingStation.ocppRequestService
@@ -628,38 +642,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         )
         .catch((error: unknown) => {
           logger.error(
-            `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Error while sending '${RequestCommand.METER_VALUES}':`,
+            `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Error while sending '${RequestCommand.METER_VALUES}':`,
             error
           )
         })
     }, clampToSafeTimerValue(interval))
   }
 
-  public static async startTransactionOnConnector (
-    chargingStation: ChargingStation,
-    connectorId: number,
-    idTag?: string
-  ): Promise<StartTransactionResponse> {
-    return chargingStation.ocppRequestService.requestHandler<
-      Partial<StartTransactionRequest>,
-      StartTransactionResponse
-    >(chargingStation, RequestCommand.START_TRANSACTION, {
-      connectorId,
-      ...(idTag != null && { idTag }),
-    })
-  }
-
-  public static stopPeriodicMeterValues (
-    chargingStation: ChargingStation,
-    connectorId: number
-  ): void {
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-    if (connectorStatus?.transactionMeterValuesSetInterval != null) {
-      clearInterval(connectorStatus.transactionMeterValuesSetInterval)
-      delete connectorStatus.transactionMeterValuesSetInterval
-    }
-  }
-
   public static async stopTransactionOnConnector (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -699,6 +688,17 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     })
   }
 
+  public static stopUpdatedMeterValues (
+    chargingStation: ChargingStation,
+    connectorId: number
+  ): void {
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    if (connectorStatus?.transactionUpdatedMeterValuesSetInterval != null) {
+      clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+      delete connectorStatus.transactionUpdatedMeterValuesSetInterval
+    }
+  }
+
   private static readonly composeChargingSchedule = (
     chargingSchedule: OCPP16ChargingSchedule,
     compositeInterval: Interval
index ea2ad85c00b2885610513208d0a092878187594f..c5376875c27fac4111fd357ed6a6d386c6a75966 100644 (file)
@@ -3045,7 +3045,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     connectorId: number,
     evseId?: number
   ): Promise<void> {
-    OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+    OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
     resetConnectorStatus(connectorStatus)
     await restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
index a353695d6942721b6ef1db5fa2b581744edc2982..d76b628807f6d5d9257d3bf41f27e10eb9c4e5ed 100644 (file)
@@ -415,11 +415,13 @@ export class OCPP20ResponseService extends OCPPResponseService {
               )
             })
             const txUpdatedInterval = OCPP20ServiceUtils.getTxUpdatedInterval(chargingStation)
-            OCPP20ServiceUtils.startPeriodicMeterValues(
+            OCPP20ServiceUtils.startUpdatedMeterValues(
               chargingStation,
               connectorId,
               txUpdatedInterval
             )
+            const txEndedInterval = OCPP20ServiceUtils.getTxEndedInterval(chargingStation)
+            OCPP20ServiceUtils.startEndedMeterValues(chargingStation, connectorId, txEndedInterval)
           }
           logger.info(
             `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Transaction ${requestPayload.transactionInfo.transactionId} STARTED on connector ${String(connectorId)}`
index 902021095439dabe7d27f0535bdbc055949e8194..f56c25ef3b1aa65022e83a71c9710da79981119c 100644 (file)
@@ -107,14 +107,14 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
         OCPP20ComponentName.SampledDataCtrlr,
         OCPP20RequiredVariableName.TxStartedMeasurands
       )
-      const meterValue = buildMeterValue(
+      const startedMeterValue = buildMeterValue(
         chargingStation,
         transactionId,
         0,
         measurandsKey,
         OCPP20ReadingContextEnumType.TRANSACTION_BEGIN
       ) as OCPP20MeterValue
-      return meterValue.sampledValue.length > 0 ? [meterValue] : []
+      return startedMeterValue.sampledValue.length > 0 ? [startedMeterValue] : []
     } catch (error) {
       logger.warn(
         `${chargingStation.logPrefix()} ${moduleName}.buildTransactionStartedMeterValues: ${(error as Error).message}`
@@ -128,7 +128,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     connectorId: number,
     connectorStatus: ConnectorStatus
   ): Promise<void> {
-    OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+    OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
     resetConnectorStatus(connectorStatus)
     connectorStatus.locked = false
     await sendAndSetConnectorStatus(chargingStation, {
@@ -293,6 +293,15 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  public static getTxEndedInterval (chargingStation: ChargingStation): number {
+    return OCPP20ServiceUtils.readVariableAsIntervalMs(
+      chargingStation,
+      OCPP20ComponentName.SampledDataCtrlr,
+      OCPP20RequiredVariableName.TxEndedInterval,
+      0
+    )
+  }
+
   public static getTxUpdatedInterval (chargingStation: ChargingStation): number {
     return OCPP20ServiceUtils.readVariableAsIntervalMs(
       chargingStation,
@@ -581,7 +590,46 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
-  public static startPeriodicMeterValues (
+  public static startEndedMeterValues (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    interval: number
+  ): void {
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    if (connectorStatus == null) {
+      return
+    }
+    connectorStatus.transactionEndedMeterValues = []
+    if (interval <= 0) {
+      return
+    }
+    if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+      OCPP20ServiceUtils.stopEndedMeterValues(chargingStation, connectorId)
+    }
+    connectorStatus.transactionEndedMeterValuesSetInterval = setInterval(() => {
+      const cs = chargingStation.getConnectorStatus(connectorId)
+      if (cs?.transactionStarted === true && cs.transactionId != null) {
+        const measurandsKey = buildConfigKey(
+          OCPP20ComponentName.SampledDataCtrlr,
+          OCPP20RequiredVariableName.TxEndedMeasurands
+        )
+        const meterValue = buildMeterValue(
+          chargingStation,
+          cs.transactionId,
+          interval,
+          measurandsKey
+        ) as OCPP20MeterValue
+        if (meterValue.sampledValue.length > 0) {
+          cs.transactionEndedMeterValues?.push(meterValue)
+        }
+      }
+    }, clampToSafeTimerValue(interval))
+    logger.info(
+      `${chargingStation.logPrefix()} ${moduleName}.startEndedMeterValues: TxEndedInterval started every ${formatDurationMilliSeconds(interval)}`
+    )
+  }
+
+  public static startUpdatedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number,
     interval: number
@@ -589,23 +637,23 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     const initialConnectorStatus = chargingStation.getConnectorStatus(connectorId)
     if (initialConnectorStatus == null) {
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Connector ${connectorId.toString()} not found`
+        `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Connector ${connectorId.toString()} not found`
       )
       return
     }
     if (interval <= 0) {
       logger.debug(
-        `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: TxUpdatedInterval is ${interval.toString()}, not starting periodic TransactionEvent`
+        `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: TxUpdatedInterval is ${interval.toString()}, not starting periodic TransactionEvent`
       )
       return
     }
-    if (initialConnectorStatus.transactionMeterValuesSetInterval != null) {
+    if (initialConnectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: TxUpdatedInterval already started, stopping first`
+        `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: TxUpdatedInterval already started, stopping first`
       )
-      OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+      OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
     }
-    initialConnectorStatus.transactionMeterValuesSetInterval = setInterval(() => {
+    initialConnectorStatus.transactionUpdatedMeterValuesSetInterval = setInterval(() => {
       const connectorStatus = chargingStation.getConnectorStatus(connectorId)
       if (connectorStatus?.transactionStarted === true && connectorStatus.transactionId != null) {
         if (
@@ -632,7 +680,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
               evseId
             ).catch((error: unknown) => {
               logger.error(
-                `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Error terminating deauthorized transaction:`,
+                `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Error terminating deauthorized transaction:`,
                 error
               )
             })
@@ -653,14 +701,14 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
           { meterValue: [meterValue] }
         ).catch((error: unknown) => {
           logger.error(
-            `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: Error sending periodic TransactionEvent:`,
+            `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: Error sending periodic TransactionEvent:`,
             error
           )
         })
       }
     }, clampToSafeTimerValue(interval))
     logger.info(
-      `${chargingStation.logPrefix()} ${moduleName}.startPeriodicMeterValues: TxUpdatedInterval started every ${formatDurationMilliSeconds(interval)}`
+      `${chargingStation.logPrefix()} ${moduleName}.startUpdatedMeterValues: TxUpdatedInterval started every ${formatDurationMilliSeconds(interval)}`
     )
   }
 
@@ -723,43 +771,61 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
-  public static stopPeriodicMeterValues (
+  public static stopEndedMeterValues (chargingStation: ChargingStation, connectorId: number): void {
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    if (connectorStatus?.transactionEndedMeterValuesSetInterval != null) {
+      clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+      delete connectorStatus.transactionEndedMeterValuesSetInterval
+      logger.info(
+        `${chargingStation.logPrefix()} ${moduleName}.stopEndedMeterValues: TxEndedInterval stopped`
+      )
+    }
+  }
+
+  public static stopUpdatedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number
   ): void {
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-    if (connectorStatus?.transactionMeterValuesSetInterval != null) {
-      clearInterval(connectorStatus.transactionMeterValuesSetInterval)
-      delete connectorStatus.transactionMeterValuesSetInterval
+    if (connectorStatus?.transactionUpdatedMeterValuesSetInterval != null) {
+      clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+      delete connectorStatus.transactionUpdatedMeterValuesSetInterval
       logger.info(
-        `${chargingStation.logPrefix()} ${moduleName}.stopPeriodicMeterValues: TxUpdatedInterval stopped`
+        `${chargingStation.logPrefix()} ${moduleName}.stopUpdatedMeterValues: TxUpdatedInterval stopped`
       )
     }
   }
 
   private static buildTransactionEndedMeterValues (
     chargingStation: ChargingStation,
+    connectorId: number,
     transactionId: number | string
   ): OCPP20MeterValue[] {
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    const endedMeterValues = (connectorStatus?.transactionEndedMeterValues ??
+      []) as OCPP20MeterValue[]
+
     try {
       const measurandsKey = buildConfigKey(
         OCPP20ComponentName.SampledDataCtrlr,
         OCPP20RequiredVariableName.TxEndedMeasurands
       )
-      const meterValue = buildMeterValue(
+      const finalMeterValue = buildMeterValue(
         chargingStation,
         transactionId,
         0,
         measurandsKey,
         OCPP20ReadingContextEnumType.TRANSACTION_END
       ) as OCPP20MeterValue
-      return meterValue.sampledValue.length > 0 ? [meterValue] : []
+      if (finalMeterValue.sampledValue.length > 0) {
+        return [...endedMeterValues, finalMeterValue]
+      }
     } catch (error) {
       logger.warn(
         `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEndedMeterValues: ${(error as Error).message}`
       )
-      return []
     }
+    return endedMeterValues.length > 0 ? endedMeterValues : []
   }
 
   private static readVariableAsBoolean (
@@ -863,7 +929,12 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     stoppedReason: OCPP20ReasonEnumType,
     evseId?: number
   ): Promise<OCPP20TransactionEventResponse> {
-    const endedMeterValues = this.buildTransactionEndedMeterValues(chargingStation, transactionId)
+    this.stopEndedMeterValues(chargingStation, connectorId)
+    const endedMeterValues = this.buildTransactionEndedMeterValues(
+      chargingStation,
+      connectorId,
+      transactionId
+    )
 
     const response = await this.sendTransactionEvent(
       chargingStation,
index 2d80aee456af4b1fd8ffafba6b6d49931b78ca82..6ff837a62d1a0693035e736fe8d7f1dee4a3d4eb 100644 (file)
@@ -149,20 +149,6 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     supportedAttributes: [AttributeEnumType.Actual],
     variable: 'SignReadings',
   },
-  [buildRegistryKey(OCPP20ComponentName.AlignedDataCtrlr, 'TxEndedInterval')]: {
-    component: OCPP20ComponentName.AlignedDataCtrlr,
-    dataType: DataEnumType.integer,
-    defaultValue: '900',
-    description:
-      'Size (in seconds) of the clock-aligned data interval, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
-    min: 1,
-    mutability: MutabilityEnumType.ReadWrite,
-    persistence: PersistenceEnumType.Persistent,
-    required: true,
-    supportedAttributes: [AttributeEnumType.Actual],
-    unit: OCPP20UnitEnumType.SECONDS,
-    variable: 'TxEndedInterval',
-  },
   [buildRegistryKey(
     OCPP20ComponentName.AlignedDataCtrlr,
     OCPP20RequiredVariableName.AlignedDataInterval
@@ -224,6 +210,23 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     supportedAttributes: [AttributeEnumType.Actual],
     variable: OCPP20RequiredVariableName.Measurands,
   },
+  [buildRegistryKey(
+    OCPP20ComponentName.AlignedDataCtrlr,
+    OCPP20RequiredVariableName.TxEndedInterval
+  )]: {
+    component: OCPP20ComponentName.AlignedDataCtrlr,
+    dataType: DataEnumType.integer,
+    defaultValue: '900',
+    description:
+      'Size (in seconds) of the clock-aligned data interval, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
+    min: 1,
+    mutability: MutabilityEnumType.ReadWrite,
+    persistence: PersistenceEnumType.Persistent,
+    required: true,
+    supportedAttributes: [AttributeEnumType.Actual],
+    unit: OCPP20UnitEnumType.SECONDS,
+    variable: OCPP20RequiredVariableName.TxEndedInterval,
+  },
   [buildRegistryKey(
     OCPP20ComponentName.AlignedDataCtrlr,
     OCPP20RequiredVariableName.TxEndedMeasurands
@@ -1689,20 +1692,6 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     supportedAttributes: [AttributeEnumType.Actual],
     variable: 'SignReadings',
   },
-  [buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr, 'TxEndedInterval')]: {
-    component: OCPP20ComponentName.SampledDataCtrlr,
-    dataType: DataEnumType.integer,
-    defaultValue: '60',
-    description:
-      'Interval between sampling of metering data, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
-    min: 1,
-    mutability: MutabilityEnumType.ReadWrite,
-    persistence: PersistenceEnumType.Persistent,
-    required: true,
-    supportedAttributes: [AttributeEnumType.Actual],
-    unit: OCPP20UnitEnumType.SECONDS,
-    variable: 'TxEndedInterval',
-  },
   [buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr, OCPP20MeasurandEnumType.CURRENT_IMPORT)]:
     {
       component: OCPP20ComponentName.SampledDataCtrlr,
@@ -1768,6 +1757,23 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     supportedAttributes: [AttributeEnumType.Actual],
     variable: OCPP20RequiredVariableName.Enabled,
   },
+  [buildRegistryKey(
+    OCPP20ComponentName.SampledDataCtrlr,
+    OCPP20RequiredVariableName.TxEndedInterval
+  )]: {
+    component: OCPP20ComponentName.SampledDataCtrlr,
+    dataType: DataEnumType.integer,
+    defaultValue: '60',
+    description:
+      'Interval between sampling of metering data, intended to be transmitted in the TransactionEventRequest (eventType = Ended) message.',
+    min: 1,
+    mutability: MutabilityEnumType.ReadWrite,
+    persistence: PersistenceEnumType.Persistent,
+    required: true,
+    supportedAttributes: [AttributeEnumType.Actual],
+    unit: OCPP20UnitEnumType.SECONDS,
+    variable: OCPP20RequiredVariableName.TxEndedInterval,
+  },
   [buildRegistryKey(
     OCPP20ComponentName.SampledDataCtrlr,
     OCPP20RequiredVariableName.TxEndedMeasurands
index 517159c3f191675d9f194a106022dcbf1fb6df4b..414f5e8fb7f6186921c0609c8c307bf03af5a860 100644 (file)
@@ -568,7 +568,7 @@ export const stopRunningTransactions = async (
   }
 }
 
-export const startPeriodicMeterValues = async (
+export const startUpdatedMeterValues = async (
   chargingStation: ChargingStation,
   connectorId: number,
   interval: number
@@ -576,37 +576,37 @@ export const startPeriodicMeterValues = async (
   switch (chargingStation.stationInfo?.ocppVersion) {
     case OCPPVersion.VERSION_16: {
       const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
-      OCPP16ServiceUtils.startPeriodicMeterValues(chargingStation, connectorId, interval)
+      OCPP16ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
       break
     }
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201: {
       const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      OCPP20ServiceUtils.startPeriodicMeterValues(chargingStation, connectorId, interval)
+      OCPP20ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
       break
     }
     default:
       logger.error(
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${chargingStation.logPrefix()} OCPPServiceUtils.startPeriodicMeterValues: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
+        `${chargingStation.logPrefix()} OCPPServiceUtils.startUpdatedMeterValues: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
       )
   }
 }
 
-export const stopPeriodicMeterValues = async (
+export const stopUpdatedMeterValues = async (
   chargingStation: ChargingStation,
   connectorId: number
 ): Promise<void> => {
   switch (chargingStation.stationInfo?.ocppVersion) {
     case OCPPVersion.VERSION_16: {
       const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
-      OCPP16ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+      OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
       break
     }
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201: {
       const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      OCPP20ServiceUtils.stopPeriodicMeterValues(chargingStation, connectorId)
+      OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
       break
     }
     default:
index c25c23301fc53ec3c235b277323f5b94bde85c7e..56fb49138e59aa90bf3ffce478f51c8c70a72ac9 100644 (file)
@@ -24,38 +24,21 @@ export interface ConnectorStatus {
   transactionBeginMeterValue?: MeterValue
   transactionDeauthorized?: boolean
   transactionDeauthorizedEnergyWh?: number
+  transactionEndedMeterValues?: MeterValue[]
+  transactionEndedMeterValuesSetInterval?: NodeJS.Timeout
   transactionEnergyActiveImportRegisterValue?: number // In Wh
-  /**
-   * OCPP 2.0.1 offline-first: Queue of TransactionEvents waiting to be sent
-   * Events are queued when station is offline (websocket disconnected)
-   * and replayed in order when reconnected, with seqNo preserved
-   */
   transactionEventQueue?: QueuedTransactionEvent[]
-  /**
-   * OCPP 2.0.1 E01.FR.16 compliance: Track if evse has been sent for current transaction.
-   * The evse field should only be provided in the first TransactionEventRequest
-   * that occurs after the EV has connected.
-   */
   transactionEvseSent?: boolean
   transactionGroupIdToken?: string
   transactionId?: number | string
   transactionIdTag?: string
-  /**
-   * OCPP 2.0.1 E03.FR.01 compliance: Track if idToken has been sent for current transaction.
-   * The idToken field should be provided once in the first TransactionEventRequest
-   * that occurs after the transaction has been authorized.
-   */
   transactionIdTokenSent?: boolean
-  transactionMeterValuesSetInterval?: NodeJS.Timeout
-  /**
-   * OCPP 2.0.1 E02 compliance: Transaction pending CSMS acknowledgment.
-   * Blocks duplicate RequestStartTransaction until response handler sets transactionStarted.
-   */
   transactionPending?: boolean
   transactionRemoteStarted?: boolean
   transactionSeqNo?: number
   transactionStart?: Date
   transactionStarted?: boolean
+  transactionUpdatedMeterValuesSetInterval?: NodeJS.Timeout
   type?: ConnectorEnumType
 }
 
index 72414c6b4b673ff9806004db670cf7ebc05d908a..1a8608ab2c7b4e3cf4c603eb5cc929dc15bd0182 100644 (file)
@@ -68,6 +68,7 @@ export enum OCPP20RequiredVariableName {
   StopTxOnEVSideDisconnect = 'StopTxOnEVSideDisconnect',
   StopTxOnInvalidId = 'StopTxOnInvalidId',
   TimeSource = 'TimeSource',
+  TxEndedInterval = 'TxEndedInterval',
   TxEndedMeasurands = 'TxEndedMeasurands',
   TxStartedMeasurands = 'TxStartedMeasurands',
   TxStartPoint = 'TxStartPoint',
index db0e6fb42e8450c78c4ad3156d5b7ad76c629e4a..a21e28a3a61bcf8cfeefee6153d29d82fbd51a68 100644 (file)
@@ -34,7 +34,13 @@ export const buildConnectorEntries = (chargingStation: ChargingStation): Connect
   return [...chargingStation.connectors.entries()].map(
     ([
       connectorId,
-      { transactionEventQueue, transactionMeterValuesSetInterval, ...connector },
+      {
+        transactionEndedMeterValues,
+        transactionEndedMeterValuesSetInterval,
+        transactionEventQueue,
+        transactionUpdatedMeterValuesSetInterval,
+        ...connector
+      },
     ]) => ({
       connector,
       connectorId,
@@ -48,7 +54,13 @@ export const buildConnectorsStatus = (
   return [...chargingStation.connectors.entries()].map(
     ([
       connectorId,
-      { transactionEventQueue, transactionMeterValuesSetInterval, ...connectorStatus },
+      {
+        transactionEndedMeterValues,
+        transactionEndedMeterValuesSetInterval,
+        transactionEventQueue,
+        transactionUpdatedMeterValuesSetInterval,
+        ...connectorStatus
+      },
     ]) => [connectorId, connectorStatus]
   )
 }
@@ -59,7 +71,13 @@ export const buildEvseEntries = (chargingStation: ChargingStation): EvseEntry[]
     connectors: [...evseStatus.connectors.entries()].map(
       ([
         connectorId,
-        { transactionEventQueue, transactionMeterValuesSetInterval, ...connector },
+        {
+          transactionEndedMeterValues,
+          transactionEndedMeterValuesSetInterval,
+          transactionEventQueue,
+          transactionUpdatedMeterValuesSetInterval,
+          ...connector
+        },
       ]) => ({
         connector,
         connectorId,
@@ -76,7 +94,13 @@ export const buildEvsesStatus = (
     const connectorsStatus: [number, ConnectorStatus][] = [...evseStatus.connectors.entries()].map(
       ([
         connectorId,
-        { transactionEventQueue, transactionMeterValuesSetInterval, ...connector },
+        {
+          transactionEndedMeterValues,
+          transactionEndedMeterValuesSetInterval,
+          transactionEventQueue,
+          transactionUpdatedMeterValuesSetInterval,
+          ...connector
+        },
       ]) => [connectorId, connector]
     )
     const { connectors: _, ...evseStatusRest } = evseStatus
index 51baa8c91015110b4fae3ae60a6cab38dbcbe047..826d8e4fc7ad2c6ce8cf42f3d66d11e56fd4421e 100644 (file)
@@ -538,12 +538,12 @@ await describe('ChargingStation Transaction Management', async () => {
         }
 
         // Act
-        OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 10000)
+        OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 10000)
 
         // Assert - meter values interval should be created
         if (connector1 != null) {
-          assert.notStrictEqual(connector1.transactionMeterValuesSetInterval, undefined)
-          assert.strictEqual(typeof connector1.transactionMeterValuesSetInterval, 'object')
+          assert.notStrictEqual(connector1.transactionUpdatedMeterValuesSetInterval, undefined)
+          assert.strictEqual(typeof connector1.transactionUpdatedMeterValuesSetInterval, 'object')
         }
       })
     })
@@ -558,13 +558,13 @@ await describe('ChargingStation Transaction Management', async () => {
           connector1.transactionStarted = true
           connector1.transactionId = 100
         }
-        OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 10000)
-        const firstInterval = connector1?.transactionMeterValuesSetInterval
+        OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 10000)
+        const firstInterval = connector1?.transactionUpdatedMeterValuesSetInterval
 
         // Act
-        OCPP16ServiceUtils.stopPeriodicMeterValues(station, 1)
-        OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 15000)
-        const secondInterval = connector1?.transactionMeterValuesSetInterval
+        OCPP16ServiceUtils.stopUpdatedMeterValues(station, 1)
+        OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 15000)
+        const secondInterval = connector1?.transactionUpdatedMeterValuesSetInterval
 
         // Assert - interval should be different
         assert.notStrictEqual(secondInterval, undefined)
@@ -583,13 +583,13 @@ await describe('ChargingStation Transaction Management', async () => {
           connector1.transactionStarted = true
           connector1.transactionId = 100
         }
-        OCPP16ServiceUtils.startPeriodicMeterValues(station, 1, 10000)
+        OCPP16ServiceUtils.startUpdatedMeterValues(station, 1, 10000)
 
         // Act
-        OCPP16ServiceUtils.stopPeriodicMeterValues(station, 1)
+        OCPP16ServiceUtils.stopUpdatedMeterValues(station, 1)
 
         // Assert - interval should be cleared
-        assert.strictEqual(connector1?.transactionMeterValuesSetInterval, undefined)
+        assert.strictEqual(connector1?.transactionUpdatedMeterValuesSetInterval, undefined)
       })
     })
 
@@ -608,12 +608,12 @@ await describe('ChargingStation Transaction Management', async () => {
         }
 
         // Act
-        OCPP20ServiceUtils.startPeriodicMeterValues(station, 1, 5000)
+        OCPP20ServiceUtils.startUpdatedMeterValues(station, 1, 5000)
 
         // Assert - transaction updated interval should be created
         if (connector1 != null) {
-          assert.notStrictEqual(connector1.transactionMeterValuesSetInterval, undefined)
-          assert.strictEqual(typeof connector1.transactionMeterValuesSetInterval, 'object')
+          assert.notStrictEqual(connector1.transactionUpdatedMeterValuesSetInterval, undefined)
+          assert.strictEqual(typeof connector1.transactionUpdatedMeterValuesSetInterval, 'object')
         }
       })
     })
@@ -631,13 +631,13 @@ await describe('ChargingStation Transaction Management', async () => {
           connector1.transactionStarted = true
           connector1.transactionId = 100
         }
-        OCPP20ServiceUtils.startPeriodicMeterValues(station, 1, 5000)
+        OCPP20ServiceUtils.startUpdatedMeterValues(station, 1, 5000)
 
         // Act
-        OCPP20ServiceUtils.stopPeriodicMeterValues(station, 1)
+        OCPP20ServiceUtils.stopUpdatedMeterValues(station, 1)
 
         // Assert - interval should be cleared
-        assert.strictEqual(connector1?.transactionMeterValuesSetInterval, undefined)
+        assert.strictEqual(connector1?.transactionUpdatedMeterValuesSetInterval, undefined)
       })
     })
   })
index 5150dbfa63f67887d4158cf658bae9a88b6dd751..d4fb945556ef2c27fc3340c615ba5336be87a01b 100644 (file)
@@ -218,18 +218,26 @@ export function cleanupChargingStation (station: ChargingStation): void {
 
   // Clear connector transaction state and timers
   for (const connectorStatus of station.connectors.values()) {
-    if (connectorStatus.transactionMeterValuesSetInterval != null) {
-      clearInterval(connectorStatus.transactionMeterValuesSetInterval)
-      connectorStatus.transactionMeterValuesSetInterval = undefined
+    if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
+      clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+      connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
+    }
+    if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+      clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+      connectorStatus.transactionEndedMeterValuesSetInterval = undefined
     }
   }
 
   // Clear EVSE connector transaction state and timers
   for (const evseStatus of station.evses.values()) {
     for (const connectorStatus of evseStatus.connectors.values()) {
-      if (connectorStatus.transactionMeterValuesSetInterval != null) {
-        clearInterval(connectorStatus.transactionMeterValuesSetInterval)
-        connectorStatus.transactionMeterValuesSetInterval = undefined
+      if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
+        clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+        connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
+      }
+      if (connectorStatus.transactionEndedMeterValuesSetInterval != null) {
+        clearInterval(connectorStatus.transactionEndedMeterValuesSetInterval)
+        connectorStatus.transactionEndedMeterValuesSetInterval = undefined
       }
     }
   }
@@ -974,9 +982,14 @@ function resetConnectorStatus (status: ConnectorStatus, isConnectorZero: boolean
   status.energyActiveImportRegisterValue = 0
   status.transactionEnergyActiveImportRegisterValue = 0
 
-  // Clear transaction interval
-  if (status.transactionMeterValuesSetInterval != null) {
-    clearInterval(status.transactionMeterValuesSetInterval)
-    status.transactionMeterValuesSetInterval = undefined
+  if (status.transactionUpdatedMeterValuesSetInterval != null) {
+    clearInterval(status.transactionUpdatedMeterValuesSetInterval)
+    status.transactionUpdatedMeterValuesSetInterval = undefined
+  }
+
+  status.transactionEndedMeterValues = undefined
+  if (status.transactionEndedMeterValuesSetInterval != null) {
+    clearInterval(status.transactionEndedMeterValuesSetInterval)
+    status.transactionEndedMeterValuesSetInterval = undefined
   }
 }
index b6f70485f1dd9eb9d21e539b74484b19584d1104..d100bdd90309c34df38b0723a78fdd3c8709d8a3 100644 (file)
@@ -77,14 +77,14 @@ function createIntegrationContext (): {
   // Mock meter value start/stop to avoid real timer setup
   mock.method(
     OCPP16ServiceUtils,
-    'startPeriodicMeterValues',
+    'startUpdatedMeterValues',
     (_station: unknown, _connectorId: number, _interval: number) => {
       /* noop */
     }
   )
   mock.method(
     OCPP16ServiceUtils,
-    'stopPeriodicMeterValues',
+    'stopUpdatedMeterValues',
     (_station: unknown, _connectorId: number) => {
       /* noop */
     }
index 61daa7d5c400019a7ff8aa22aa83b47c52089bcb..a59a7f8b8e4394b7b1ab163e090348c661dab0be 100644 (file)
@@ -42,10 +42,10 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
     setMockRequestHandler(station, async () => Promise.resolve({}))
 
     // Mock startMeterValues/stopMeterValues to avoid real timer setup
-    mock.method(OCPP16ServiceUtils, 'startPeriodicMeterValues', () => {
+    mock.method(OCPP16ServiceUtils, 'startUpdatedMeterValues', () => {
       /* noop */
     })
-    mock.method(OCPP16ServiceUtils, 'stopPeriodicMeterValues', () => {
+    mock.method(OCPP16ServiceUtils, 'stopUpdatedMeterValues', () => {
       /* noop */
     })
 
index 7a3500c5a4a7215a5a402d84bbfa3c8621815ea9..5ca8924fb159d9c3fa0cc122f6550a4b01ffbe70 100644 (file)
@@ -23,7 +23,7 @@ import {
   OCPP20ServiceUtils,
 } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
 import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
-import { startPeriodicMeterValues } from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
+import { startUpdatedMeterValues } from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
 import { OCPPError } from '../../../../src/exception/index.js'
 import {
   AttributeEnumType,
@@ -2000,15 +2000,15 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
       // Clean up any running timers
       for (let connectorId = 1; connectorId <= 3; connectorId++) {
         const connectorStatus = mockStation.getConnectorStatus(connectorId)
-        if (connectorStatus?.transactionMeterValuesSetInterval != null) {
-          clearInterval(connectorStatus.transactionMeterValuesSetInterval)
-          connectorStatus.transactionMeterValuesSetInterval = undefined
+        if (connectorStatus?.transactionUpdatedMeterValuesSetInterval != null) {
+          clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+          connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
         }
       }
       standardCleanup()
     })
 
-    await describe('startPeriodicMeterValues', async () => {
+    await describe('startUpdatedMeterValues', async () => {
       await it('should not start OCPP 2.0 timer for OCPP 1.6 stations via unified dispatch', async t => {
         await withMockTimers(t, ['setInterval'], async () => {
           const { station: ocpp16Station } = createMockChargingStation({
@@ -2019,10 +2019,10 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
             },
           })
 
-          await startPeriodicMeterValues(ocpp16Station, 1, 60000)
+          await startUpdatedMeterValues(ocpp16Station, 1, 60000)
 
           const connectorStatus = ocpp16Station.getConnectorStatus(1)
-          assert.strictEqual(connectorStatus?.transactionMeterValuesSetInterval, undefined)
+          assert.strictEqual(connectorStatus?.transactionUpdatedMeterValuesSetInterval, undefined)
         })
       })
 
@@ -2036,7 +2036,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
         // Zero interval should not start timer
         // This is verified by the implementation logging debug message
-        assert.strictEqual(connectorStatus.transactionMeterValuesSetInterval, undefined)
+        assert.strictEqual(connectorStatus.transactionUpdatedMeterValuesSetInterval, undefined)
       })
 
       await it('should not start timer when interval is negative', () => {
@@ -2046,7 +2046,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         assert(connectorStatus != null)
 
         // Negative interval should not start timer
-        assert.strictEqual(connectorStatus.transactionMeterValuesSetInterval, undefined)
+        assert.strictEqual(connectorStatus.transactionUpdatedMeterValuesSetInterval, undefined)
       })
 
       await it('should handle non-existent connector gracefully', () => {
index 9236282374a41ace4e28455ee866b473f078ddb2..370c91d4c57b7fc2f43f231c3da67b79460457b8 100644 (file)
@@ -113,9 +113,9 @@ export function clearConnectorTransaction (station: ChargingStation, connectorId
   connectorStatus.idTagLocalAuthorized = false
 
   // Clear any transaction interval
-  if (connectorStatus.transactionMeterValuesSetInterval != null) {
-    clearInterval(connectorStatus.transactionMeterValuesSetInterval)
-    connectorStatus.transactionMeterValuesSetInterval = undefined
+  if (connectorStatus.transactionUpdatedMeterValuesSetInterval != null) {
+    clearInterval(connectorStatus.transactionUpdatedMeterValuesSetInterval)
+    connectorStatus.transactionUpdatedMeterValuesSetInterval = undefined
   }
 }
 
index 7ba1621631626feb94078ef4905eaacf9e49988e..a7625eca9f45ca77e41aa23126ba0dc0dd6be1be 100644 (file)
@@ -70,8 +70,10 @@ await describe('ChargingStationConfigurationUtils', async () => {
           availability: AvailabilityType.Operative,
           bootStatus: 'Available',
           MeterValues: [],
+          transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+          transactionEndedMeterValuesSetInterval: interval2 as unknown as NodeJS.Timeout,
           transactionEventQueue: [],
-          transactionMeterValuesSetInterval: interval1 as unknown as NodeJS.Timeout,
+          transactionUpdatedMeterValuesSetInterval: interval1 as unknown as NodeJS.Timeout,
         } as unknown as ConnectorStatus)
 
         const station = createMockStationForConfigUtils({ connectors })
@@ -79,7 +81,9 @@ await describe('ChargingStationConfigurationUtils', async () => {
 
         assert.strictEqual(result.length, 2)
         for (const [, connector] of result) {
-          assert.ok(!('transactionMeterValuesSetInterval' in connector))
+          assert.ok(!('transactionEndedMeterValues' in connector))
+          assert.ok(!('transactionEndedMeterValuesSetInterval' in connector))
+          assert.ok(!('transactionUpdatedMeterValuesSetInterval' in connector))
           assert.ok(!('transactionEventQueue' in connector))
         }
         assert.strictEqual(result[0][0], 0)
@@ -105,8 +109,8 @@ await describe('ChargingStationConfigurationUtils', async () => {
         MeterValues: [],
         transactionEventQueue: undefined,
         transactionId: 42,
-        transactionMeterValuesSetInterval: undefined,
         transactionStarted: true,
+        transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
       const station = createMockStationForConfigUtils({ connectors })
@@ -147,7 +151,7 @@ await describe('ChargingStationConfigurationUtils', async () => {
         availability: AvailabilityType.Operative,
         MeterValues: [],
         transactionEventQueue: [],
-        transactionMeterValuesSetInterval: undefined,
+        transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
       const evses = new Map<number, EvseStatus>()
@@ -178,8 +182,10 @@ await describe('ChargingStationConfigurationUtils', async () => {
       evseConnectors.set(1, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
+        transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+        transactionEndedMeterValuesSetInterval: undefined,
         transactionEventQueue: [],
-        transactionMeterValuesSetInterval: undefined,
+        transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
       const evses = new Map<number, EvseStatus>()
@@ -201,7 +207,9 @@ await describe('ChargingStationConfigurationUtils', async () => {
       assert.strictEqual(connectorsStatus.length, 1)
       assert.strictEqual(connectorsStatus[0][0], 1)
       const connectorStatus = connectorsStatus[0][1]
-      assert.ok(!('transactionMeterValuesSetInterval' in connectorStatus))
+      assert.ok(!('transactionEndedMeterValues' in connectorStatus))
+      assert.ok(!('transactionEndedMeterValuesSetInterval' in connectorStatus))
+      assert.ok(!('transactionUpdatedMeterValuesSetInterval' in connectorStatus))
       assert.ok(!('transactionEventQueue' in connectorStatus))
     })
 
@@ -390,8 +398,10 @@ await describe('ChargingStationConfigurationUtils', async () => {
       connectors.set(1, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
+        transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+        transactionEndedMeterValuesSetInterval: undefined,
         transactionEventQueue: [],
-        transactionMeterValuesSetInterval: undefined,
+        transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
       const station = createMockStationForConfigUtils({ connectors })
@@ -401,7 +411,9 @@ await describe('ChargingStationConfigurationUtils', async () => {
       assert.strictEqual(result[0].connectorId, 0)
       assert.strictEqual(result[1].connectorId, 1)
       assert.strictEqual(result[1].connector.availability, AvailabilityType.Operative)
-      assert.ok(!('transactionMeterValuesSetInterval' in result[1].connector))
+      assert.ok(!('transactionEndedMeterValues' in result[1].connector))
+      assert.ok(!('transactionEndedMeterValuesSetInterval' in result[1].connector))
+      assert.ok(!('transactionUpdatedMeterValuesSetInterval' in result[1].connector))
       assert.ok(!('transactionEventQueue' in result[1].connector))
     })
 
@@ -443,8 +455,10 @@ await describe('ChargingStationConfigurationUtils', async () => {
       evseConnectors.set(1, {
         availability: AvailabilityType.Operative,
         MeterValues: [],
+        transactionEndedMeterValues: [{ sampledValue: [], timestamp: new Date() }],
+        transactionEndedMeterValuesSetInterval: undefined,
         transactionEventQueue: [],
-        transactionMeterValuesSetInterval: undefined,
+        transactionUpdatedMeterValuesSetInterval: undefined,
       } as unknown as ConnectorStatus)
 
       const evses = new Map<number, EvseStatus>()
@@ -467,7 +481,9 @@ await describe('ChargingStationConfigurationUtils', async () => {
       assert.strictEqual(result[1].evseId, 1)
       assert.strictEqual(result[1].connectors.length, 1)
       assert.strictEqual(result[1].connectors[0].connectorId, 1)
-      assert.ok(!('transactionMeterValuesSetInterval' in result[1].connectors[0].connector))
+      assert.ok(!('transactionEndedMeterValues' in result[1].connectors[0].connector))
+      assert.ok(!('transactionEndedMeterValuesSetInterval' in result[1].connectors[0].connector))
+      assert.ok(!('transactionUpdatedMeterValuesSetInterval' in result[1].connectors[0].connector))
       assert.ok(!('transactionEventQueue' in result[1].connectors[0].connector))
     })