From: Jérôme Benoit Date: Thu, 30 Oct 2025 12:13:37 +0000 (+0100) Subject: refactor(ocpp2): introduce measurand enum and use it X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=c55bdea9c493dfe80c5acde177e45939e547e7e4;p=e-mobility-charging-stations-simulator.git refactor(ocpp2): introduce measurand enum and use it Signed-off-by: Jérôme Benoit --- diff --git a/.serena/memories/ocpp_architecture.md b/.serena/memories/ocpp_architecture.md deleted file mode 100644 index c40ce5f5..00000000 --- a/.serena/memories/ocpp_architecture.md +++ /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 diff --git a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts index 80f95db7..89859da0 100644 --- a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts +++ b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts @@ -8,6 +8,7 @@ import { MutabilityEnumType, OCPP20ComponentName, OCPP20DeviceInfoVariableName, + OCPP20MeasurandEnumType, OCPP20OptionalVariableName, OCPP20RequiredVariableName, OCPP20VendorVariableName, @@ -592,7 +593,10 @@ export const VARIABLE_REGISTRY: Record = { }, // 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { )]: { 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 = { )]: { 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, diff --git a/src/types/index.ts b/src/types/index.ts index a5343dfd..3e29b8d4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -149,6 +149,7 @@ export { GenericDeviceModelStatusEnumType, OCPP20ComponentName, OCPP20ConnectorStatusEnumType, + OCPP20MeasurandEnumType, ReasonCodeEnumType, ReportBaseEnumType, type ReportDataType, diff --git a/src/types/ocpp/2.0/Common.ts b/src/types/ocpp/2.0/Common.ts index c5f370a8..8fad49c5 100644 --- a/src/types/ocpp/2.0/Common.ts +++ b/src/types/ocpp/2.0/Common.ts @@ -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', diff --git a/src/types/ocpp/MeterValues.ts b/src/types/ocpp/MeterValues.ts index 7c222e3a..05065ca8 100644 --- a/src/types/ocpp/MeterValues.ts +++ b/src/types/ocpp/MeterValues.ts @@ -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, diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts index 3429c5c0..aa91669c 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts @@ -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 diff --git a/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts b/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts index 90be9d58..f9885d14 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts @@ -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 }, },