]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp2): introduce measurand enum and use it
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 30 Oct 2025 12:13:37 +0000 (13:13 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 30 Oct 2025 12:13:37 +0000 (13:13 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
.serena/memories/ocpp_architecture.md [deleted file]
src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts
src/types/index.ts
src/types/ocpp/2.0/Common.ts
src/types/ocpp/MeterValues.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts
tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts

diff --git a/.serena/memories/ocpp_architecture.md b/.serena/memories/ocpp_architecture.md
deleted file mode 100644 (file)
index c40ce5f..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-# OCPP Implementation Architecture
-
-## Overview
-
-The project implements both OCPP 1.6 and OCPP 2.0.x protocols with clear separation:
-
-## Key Components
-
-### OCPP 2.0 Request Service
-
-- **Location**: `src/charging-station/ocpp/2.0/OCPP20RequestService.ts`
-- **Purpose**: Handles outgoing OCPP 2.0 requests
-- **Key Methods**:
-  - `buildRequestPayload()`: Constructs request payloads
-  - `requestHandler()`: Handles request processing
-  - Constructor sets up payload validation functions
-
-### Supported OCPP 2.0 Commands
-
-- `BOOT_NOTIFICATION`: Station startup notification
-- `HEARTBEAT`: Keep-alive messages
-- `NOTIFY_REPORT`: Configuration reports
-- `STATUS_NOTIFICATION`: Status updates
-
-### Request/Response Flow
-
-1. Request constructed via `buildRequestPayload()`
-2. Validation performed using JSON schemas
-3. Request sent via WebSocket
-4. Response handled by `OCPP20ResponseService`
-
-### Testing Patterns
-
-- Tests located in `tests/charging-station/ocpp/2.0/`
-- Use `OCPP20IncomingRequestService` for testing incoming requests
-- Use `OCPP20RequestService` for testing outgoing requests
-- Mock charging stations created with factory functions
-- Follow integration testing approach with real service instances
index 80f95db77ea56a3608785634a24dde5fc28070ba..89859da07296768e1d950c6039e09c5c1376a916 100644 (file)
@@ -8,6 +8,7 @@ import {
   MutabilityEnumType,
   OCPP20ComponentName,
   OCPP20DeviceInfoVariableName,
+  OCPP20MeasurandEnumType,
   OCPP20OptionalVariableName,
   OCPP20RequiredVariableName,
   OCPP20VendorVariableName,
@@ -592,7 +593,10 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   },
 
   // SampledDataCtrlr variables
-  [buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr as string, 'Current.Import')]: {
+  [buildRegistryKey(
+    OCPP20ComponentName.SampledDataCtrlr as string,
+    OCPP20MeasurandEnumType.CURRENT_IMPORT
+  )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.decimal,
     description: 'Instantaneous import current (A).',
@@ -601,11 +605,11 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     persistence: PersistenceEnumType.Volatile,
     supportedAttributes: [AttributeEnumType.Actual],
     unit: 'A',
-    variable: 'Current.Import',
+    variable: OCPP20MeasurandEnumType.CURRENT_IMPORT,
   },
   [buildRegistryKey(
     OCPP20ComponentName.SampledDataCtrlr as string,
-    'Energy.Active.Import.Register'
+    OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER
   )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.decimal,
@@ -615,9 +619,12 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     persistence: PersistenceEnumType.Volatile,
     supportedAttributes: [AttributeEnumType.Actual],
     unit: 'Wh',
-    variable: 'Energy.Active.Import.Register',
+    variable: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER,
   },
-  [buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr as string, 'Power.Active.Import')]: {
+  [buildRegistryKey(
+    OCPP20ComponentName.SampledDataCtrlr as string,
+    OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT
+  )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.decimal,
     description: 'Instantaneous active power import (W).',
@@ -626,9 +633,12 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     persistence: PersistenceEnumType.Volatile,
     supportedAttributes: [AttributeEnumType.Actual],
     unit: 'W',
-    variable: 'Power.Active.Import',
+    variable: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT,
   },
-  [buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr as string, 'Voltage')]: {
+  [buildRegistryKey(
+    OCPP20ComponentName.SampledDataCtrlr as string,
+    OCPP20MeasurandEnumType.VOLTAGE
+  )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.decimal,
     description: 'RMS voltage (V).',
@@ -637,7 +647,7 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     persistence: PersistenceEnumType.Volatile,
     supportedAttributes: [AttributeEnumType.Actual],
     unit: 'V',
-    variable: 'Voltage',
+    variable: OCPP20MeasurandEnumType.VOLTAGE,
   },
   [buildRegistryKey(
     OCPP20ComponentName.SampledDataCtrlr as string,
@@ -646,25 +656,23 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.MemberList,
     // Default includes cumulative energy and interval energy plus voltage for billing context
-    defaultValue: 'Energy.Active.Import.Register,Energy.Active.Import.Interval,Voltage',
+    defaultValue: `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL},${OCPP20MeasurandEnumType.VOLTAGE}`,
     description: 'Measurands sampled at transaction end.',
     enumeration: [
-      'Energy.Active.Import.Register',
-      'Energy.Active.Import.Interval',
-      'Energy.Active.Export.Register',
-      'Power.Active.Import',
-      'Power.Active.Export',
-      'Power.Reactive.Import',
-      'Power.Reactive.Export',
-      'Power.Offered',
-      'Current.Import',
-      'Current.Export',
-      'Voltage',
-      'Frequency',
-      'Temperature',
-      'SoC',
-      'RPM',
-      'Power.Factor',
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER,
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL,
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_EXPORT_REGISTER,
+      OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT,
+      OCPP20MeasurandEnumType.POWER_ACTIVE_EXPORT,
+      OCPP20MeasurandEnumType.POWER_REACTIVE_IMPORT,
+      OCPP20MeasurandEnumType.POWER_REACTIVE_EXPORT,
+      OCPP20MeasurandEnumType.POWER_OFFERED,
+      OCPP20MeasurandEnumType.CURRENT_IMPORT,
+      OCPP20MeasurandEnumType.CURRENT_EXPORT,
+      OCPP20MeasurandEnumType.VOLTAGE,
+      OCPP20MeasurandEnumType.FREQUENCY,
+      OCPP20MeasurandEnumType.STATE_OF_CHARGE,
+      OCPP20MeasurandEnumType.POWER_FACTOR,
     ],
     mutability: MutabilityEnumType.ReadWrite,
     persistence: PersistenceEnumType.Persistent,
@@ -677,25 +685,23 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.MemberList,
-    defaultValue: 'Energy.Active.Import.Register,Power.Active.Import,Voltage',
+    defaultValue: `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT},${OCPP20MeasurandEnumType.VOLTAGE}`,
     description: 'Measurands sampled at transaction start.',
     enumeration: [
-      'Energy.Active.Import.Register',
-      'Energy.Active.Import.Interval',
-      'Energy.Active.Export.Register',
-      'Power.Active.Import',
-      'Power.Active.Export',
-      'Power.Reactive.Import',
-      'Power.Reactive.Export',
-      'Power.Offered',
-      'Current.Import',
-      'Current.Export',
-      'Voltage',
-      'Frequency',
-      'Temperature',
-      'SoC',
-      'RPM',
-      'Power.Factor',
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER,
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL,
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_EXPORT_REGISTER,
+      OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT,
+      OCPP20MeasurandEnumType.POWER_ACTIVE_EXPORT,
+      OCPP20MeasurandEnumType.POWER_REACTIVE_IMPORT,
+      OCPP20MeasurandEnumType.POWER_REACTIVE_EXPORT,
+      OCPP20MeasurandEnumType.POWER_OFFERED,
+      OCPP20MeasurandEnumType.CURRENT_IMPORT,
+      OCPP20MeasurandEnumType.CURRENT_EXPORT,
+      OCPP20MeasurandEnumType.VOLTAGE,
+      OCPP20MeasurandEnumType.FREQUENCY,
+      OCPP20MeasurandEnumType.STATE_OF_CHARGE,
+      OCPP20MeasurandEnumType.POWER_FACTOR,
     ],
     mutability: MutabilityEnumType.ReadWrite,
     persistence: PersistenceEnumType.Persistent,
@@ -727,25 +733,23 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.MemberList,
-    defaultValue: 'Energy.Active.Import.Register,Current.Import,Voltage',
+    defaultValue: `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.CURRENT_IMPORT},${OCPP20MeasurandEnumType.VOLTAGE}`,
     description: 'Measurands included in periodic updates.',
     enumeration: [
-      'Energy.Active.Import.Register',
-      'Energy.Active.Import.Interval',
-      'Energy.Active.Export.Register',
-      'Power.Active.Import',
-      'Power.Active.Export',
-      'Power.Reactive.Import',
-      'Power.Reactive.Export',
-      'Power.Offered',
-      'Current.Import',
-      'Current.Export',
-      'Voltage',
-      'Frequency',
-      'Temperature',
-      'SoC',
-      'RPM',
-      'Power.Factor',
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER,
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL,
+      OCPP20MeasurandEnumType.ENERGY_ACTIVE_EXPORT_REGISTER,
+      OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT,
+      OCPP20MeasurandEnumType.POWER_ACTIVE_EXPORT,
+      OCPP20MeasurandEnumType.POWER_REACTIVE_IMPORT,
+      OCPP20MeasurandEnumType.POWER_REACTIVE_EXPORT,
+      OCPP20MeasurandEnumType.POWER_OFFERED,
+      OCPP20MeasurandEnumType.CURRENT_IMPORT,
+      OCPP20MeasurandEnumType.CURRENT_EXPORT,
+      OCPP20MeasurandEnumType.VOLTAGE,
+      OCPP20MeasurandEnumType.FREQUENCY,
+      OCPP20MeasurandEnumType.STATE_OF_CHARGE,
+      OCPP20MeasurandEnumType.POWER_FACTOR,
     ],
     mutability: MutabilityEnumType.ReadWrite,
     persistence: PersistenceEnumType.Persistent,
index a5343dfdd1aa6c573cd8cee1b893eeef06d8324c..3e29b8d4b1b9e17da023595ebc7220fdf014e81f 100644 (file)
@@ -149,6 +149,7 @@ export {
   GenericDeviceModelStatusEnumType,
   OCPP20ComponentName,
   OCPP20ConnectorStatusEnumType,
+  OCPP20MeasurandEnumType,
   ReasonCodeEnumType,
   ReportBaseEnumType,
   type ReportDataType,
index c5f370a84b4df04e9dcebe9fde564abed69e8efb..8fad49c5f4ba4ef5dd947c123899c73b81a74127 100644 (file)
@@ -196,6 +196,34 @@ export enum OCPP20ConnectorStatusEnumType {
   Unavailable = 'Unavailable',
 }
 
+export enum OCPP20MeasurandEnumType {
+  CURRENT_EXPORT = 'Current.Export',
+  CURRENT_IMPORT = 'Current.Import',
+  CURRENT_OFFERED = 'Current.Offered',
+  ENERGY_ACTIVE_EXPORT_INTERVAL = 'Energy.Active.Export.Interval',
+  ENERGY_ACTIVE_EXPORT_REGISTER = 'Energy.Active.Export.Register',
+  ENERGY_ACTIVE_IMPORT_INTERVAL = 'Energy.Active.Import.Interval',
+  ENERGY_ACTIVE_IMPORT_REGISTER = 'Energy.Active.Import.Register',
+  ENERGY_ACTIVE_NET = 'Energy.Active.Net',
+  ENERGY_APPARENT_EXPORT = 'Energy.Apparent.Export',
+  ENERGY_APPARENT_IMPORT = 'Energy.Apparent.Import',
+  ENERGY_APPARENT_NET = 'Energy.Apparent.Net',
+  ENERGY_REACTIVE_EXPORT_INTERVAL = 'Energy.Reactive.Export.Interval',
+  ENERGY_REACTIVE_EXPORT_REGISTER = 'Energy.Reactive.Export.Register',
+  ENERGY_REACTIVE_IMPORT_INTERVAL = 'Energy.Reactive.Import.Interval',
+  ENERGY_REACTIVE_IMPORT_REGISTER = 'Energy.Reactive.Import.Register',
+  ENERGY_REACTIVE_NET = 'Energy.Reactive.Net',
+  FREQUENCY = 'Frequency',
+  POWER_ACTIVE_EXPORT = 'Power.Active.Export',
+  POWER_ACTIVE_IMPORT = 'Power.Active.Import',
+  POWER_FACTOR = 'Power.Factor',
+  POWER_OFFERED = 'Power.Offered',
+  POWER_REACTIVE_EXPORT = 'Power.Reactive.Export',
+  POWER_REACTIVE_IMPORT = 'Power.Reactive.Import',
+  STATE_OF_CHARGE = 'SoC',
+  VOLTAGE = 'Voltage',
+}
+
 export enum OperationalStatusEnumType {
   Inoperative = 'Inoperative',
   Operative = 'Operative',
index 7c222e3a719e81f5394635dfb4e3fbfa103e8a67..05065ca88959a9e0621f55190c55dce3b86ab335 100644 (file)
@@ -7,6 +7,7 @@ import {
   OCPP16MeterValueUnit,
   type OCPP16SampledValue,
 } from './1.6/MeterValues.js'
+import { OCPP20MeasurandEnumType } from './2.0/Common.js'
 
 export type MeterValue = OCPP16MeterValue
 
@@ -30,9 +31,10 @@ export type MeterValueLocation = OCPP16MeterValueLocation
 
 export const MeterValueMeasurand = {
   ...OCPP16MeterValueMeasurand,
+  ...OCPP20MeasurandEnumType,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
-export type MeterValueMeasurand = OCPP16MeterValueMeasurand
+export type MeterValueMeasurand = OCPP16MeterValueMeasurand | OCPP20MeasurandEnumType
 
 export const MeterValuePhase = {
   ...OCPP16MeterValuePhase,
index 3429c5c063adfbb670a5198b6f6d0143b8a6a42e..aa91669c8c45c4e49cca703554b7f70dc726c599 100644 (file)
@@ -8,6 +8,7 @@ import {
   GetVariableStatusEnumType,
   OCPP20ComponentName,
   type OCPP20GetVariablesRequest,
+  OCPP20MeasurandEnumType,
   OCPP20OptionalVariableName,
   OCPP20RequiredVariableName,
   OCPP20VendorVariableName,
@@ -417,16 +418,18 @@ void describe('B06 - Get Variables', () => {
     const txStarted = response.getVariableResult[0]
     expect(txStarted.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
     expect(txStarted.attributeValue).toBe(
-      'Energy.Active.Import.Register,Power.Active.Import,Voltage'
+      `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT},${OCPP20MeasurandEnumType.VOLTAGE}`
     )
     const txEnded = response.getVariableResult[1]
     expect(txEnded.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
     expect(txEnded.attributeValue).toBe(
-      'Energy.Active.Import.Register,Energy.Active.Import.Interval,Voltage'
+      `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL},${OCPP20MeasurandEnumType.VOLTAGE}`
     )
     const txUpdated = response.getVariableResult[2]
     expect(txUpdated.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
-    expect(txUpdated.attributeValue).toBe('Energy.Active.Import.Register,Current.Import,Voltage')
+    expect(txUpdated.attributeValue).toBe(
+      `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.CURRENT_IMPORT},${OCPP20MeasurandEnumType.VOLTAGE}`
+    )
   })
 
   // FR: B06.FR.13
index 90be9d5801cff150afa776e9e56b8f4a79ec2ee7..f9885d14c76f5ebe2766136fd5998fdff2319928 100644 (file)
@@ -16,6 +16,7 @@ import {
   GetVariableStatusEnumType,
   OCPP20ComponentName,
   type OCPP20GetVariableDataType,
+  OCPP20MeasurandEnumType,
   OCPP20OptionalVariableName,
   OCPP20RequiredVariableName,
   type OCPP20SetVariableDataType,
@@ -1212,18 +1213,17 @@ await describe('OCPP20VariableManager test suite', async () => {
           variable: { name: OCPP20RequiredVariableName.TxStopPoint },
         },
         {
-          attributeValue: 'Energy.Active.Import.Register,Power.Active.Import,Voltage',
+          attributeValue: `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT},${OCPP20MeasurandEnumType.VOLTAGE}`,
           component: { name: OCPP20ComponentName.SampledDataCtrlr },
           variable: { name: OCPP20RequiredVariableName.TxStartedMeasurands },
         },
         {
-          attributeValue:
-            'Energy.Active.Import.Register,Current.Import,Energy.Active.Import.Interval',
+          attributeValue: `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.CURRENT_IMPORT},${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL}`,
           component: { name: OCPP20ComponentName.SampledDataCtrlr },
           variable: { name: OCPP20RequiredVariableName.TxEndedMeasurands },
         },
         {
-          attributeValue: 'Energy.Active.Import.Register,Current.Import',
+          attributeValue: `${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.CURRENT_IMPORT}`,
           component: { name: OCPP20ComponentName.SampledDataCtrlr },
           variable: { name: OCPP20RequiredVariableName.TxUpdatedMeasurands },
         },