]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
fix(ocpp2): build meter values payload in broadcast channel for OCPP 2.0.x
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 22 Mar 2026 11:40:27 +0000 (12:40 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 22 Mar 2026 11:40:27 +0000 (12:40 +0100)
The OCPP 2.0 branch in handleMeterValues was passing the raw broadcast
channel payload through to requestHandler without constructing the
required evseId and meterValue fields. Build the payload using
buildMeterValue and resolve evseId from connectorId, matching the
pattern used by the OCPP 1.6 branch.

src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts
tests/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.test.ts
tests/charging-station/helpers/StationHelpers.ts

index 57a87856c6ac9fe1d42720d6b887d87988ad7eeb..f90a1c4e0ac3329242b3cf76a20c14adb53382d5 100644 (file)
@@ -68,7 +68,7 @@ import {
   logger,
 } from '../../utils/index.js'
 import { getConfigurationKey } from '../ConfigurationKeyUtils.js'
-import { buildMeterValue } from '../ocpp/index.js'
+import { buildMeterValue, OCPP20ServiceUtils } from '../ocpp/index.js'
 import { WorkerBroadcastChannel } from './WorkerBroadcastChannel.js'
 
 const moduleName = 'ChargingStationWorkerBroadcastChannel'
@@ -470,17 +470,35 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
   private async handleMeterValues (
     requestPayload?: BroadcastChannelRequestPayload
   ): Promise<MeterValuesResponse> {
+    const connectorId = requestPayload?.connectorId
     if (
       this.chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_20 ||
       this.chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_201
     ) {
+      const txUpdatedInterval = OCPP20ServiceUtils.getTxUpdatedInterval(this.chargingStation)
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const evseId = this.chargingStation.getEvseIdByConnectorId(connectorId!)
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const transactionId = this.chargingStation.getConnectorStatus(connectorId!)?.transactionId
       return await this.chargingStation.ocppRequestService.requestHandler<
         MeterValuesRequest,
         MeterValuesResponse
       >(
         this.chargingStation,
         RequestCommand.METER_VALUES,
-        requestPayload as MeterValuesRequest,
+        {
+          evseId,
+          meterValue: [
+            buildMeterValue(
+              this.chargingStation,
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              connectorId!,
+              transactionId,
+              txUpdatedInterval
+            ),
+          ],
+          ...requestPayload,
+        } as MeterValuesRequest,
         this.requestParams
       )
     }
@@ -488,7 +506,6 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
       this.chargingStation,
       StandardParametersKey.MeterValueSampleInterval
     )
-    const connectorId = requestPayload?.connectorId
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const transactionId = this.chargingStation.getConnectorStatus(connectorId!)?.transactionId
     return await this.chargingStation.ocppRequestService.requestHandler<
index c392a4ea85cb892a1e4a41827feb822362fc5ee8..437673ea1e4b4637f6a6acbcc8dba5f3d7a09110 100644 (file)
@@ -17,6 +17,7 @@ import {
   GenericStatus,
   GetCertificateStatusEnumType,
   Iso15118EVCertificateStatusEnumType,
+  MeterValueMeasurand,
   OCPP20AuthorizationStatusEnumType,
   OCPPVersion,
   ProcedureName,
@@ -739,6 +740,18 @@ await describe('ChargingStationWorkerBroadcastChannel', async () => {
     await it('should dispatch METER_VALUES for OCPP 2.0.1 via requestHandler', async () => {
       const { sentRequests, station } = createMockStationWithRequestTracking()
 
+      // Add MeterValues template to connector 1 so buildMeterValue can construct a valid payload
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus != null) {
+        connectorStatus.MeterValues = [
+          {
+            measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+            unit: 'Wh',
+            value: 0,
+          },
+        ]
+      }
+
       instance = new ChargingStationWorkerBroadcastChannel(station)
       const testable = createTestableWorkerBroadcastChannel(instance)
 
@@ -746,7 +759,7 @@ await describe('ChargingStationWorkerBroadcastChannel', async () => {
         data: [
           randomUUID(),
           BroadcastChannelProcedureName.METER_VALUES,
-          { hashIds: [station.stationInfo?.hashId] },
+          { connectorId: 1, hashIds: [station.stationInfo?.hashId] },
         ],
       })
 
@@ -754,6 +767,14 @@ await describe('ChargingStationWorkerBroadcastChannel', async () => {
 
       assert.strictEqual(sentRequests.length, 1)
       assert.strictEqual(sentRequests[0].command, RequestCommand.METER_VALUES)
+      assert.ok(
+        sentRequests[0].payload.evseId != null,
+        'OCPP 2.0.1 meter values payload should contain evseId'
+      )
+      assert.ok(
+        Array.isArray(sentRequests[0].payload.meterValue),
+        'OCPP 2.0.1 meter values payload should contain meterValue array'
+      )
     })
   })
 
index 663be5840088ff4ad41b6ba7b40754cb68e8ab23..4b25e6fce55dc1972ce33cdaa424094c7f151cbb 100644 (file)
@@ -486,6 +486,9 @@ export function createMockChargingStation (
       }
       return undefined
     },
+    getConnectorMaximumAvailablePower (_connectorId: number): number {
+      return stationInfoOverrides?.maximumPower ?? 22000
+    },
     // Methods
     getConnectorStatus (connectorId: number): ConnectorStatus | undefined {
       if (useEvses) {