]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
test(ocpp20): add GetVariables list defaults coverage and update registry enumerations
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 29 Oct 2025 22:25:09 +0000 (23:25 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 29 Oct 2025 22:25:09 +0000 (23:25 +0100)
src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts
tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts

index 7bc7aa2a1750599f55421f4b9f1808c2c219db2a..80f95db77ea56a3608785634a24dde5fc28070ba 100644 (file)
@@ -229,9 +229,16 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.ClockCtrlr as string,
     dataType: DataEnumType.SequenceList,
-    defaultValue: 'NTP,GPS,RTC',
+    defaultValue: 'NTP,GPS,RealTimeClock,Heartbeat',
     description: 'Ordered list of clock sources by preference.',
-    enumeration: ['NTP', 'GPS', 'RTC', 'Manual'],
+    enumeration: [
+      'Heartbeat',
+      'NTP',
+      'GPS',
+      'RealTimeClock',
+      'MobileNetwork',
+      'RadioTimeTransmitter',
+    ],
     mutability: MutabilityEnumType.ReadWrite,
     persistence: PersistenceEnumType.Persistent,
     supportedAttributes: [AttributeEnumType.Actual],
@@ -444,10 +451,10 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.OCPPCommCtrlr as string,
     dataType: DataEnumType.MemberList,
-    defaultValue: 'HTTP',
+    defaultValue: 'HTTPS,FTPS,SFTP',
     description: 'Supported file transfer protocols.',
-    enumeration: ['HTTP', 'HTTPS', 'FTP', 'SFTP'],
-    mutability: MutabilityEnumType.ReadWrite,
+    enumeration: ['HTTP', 'HTTPS', 'FTP', 'FTPS', 'SFTP'],
+    mutability: MutabilityEnumType.ReadOnly,
     persistence: PersistenceEnumType.Persistent,
     supportedAttributes: [AttributeEnumType.Actual],
     variable: OCPP20RequiredVariableName.FileTransferProtocols as string,
@@ -514,8 +521,9 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.OCPPCommCtrlr as string,
     dataType: DataEnumType.SequenceList,
-    defaultValue: '',
+    defaultValue: '1,2,3',
     description: 'Comma separated ordered list of network profile priorities.',
+    enumeration: ['1', '2', '3'],
     mutability: MutabilityEnumType.ReadWrite,
     persistence: PersistenceEnumType.Persistent,
     supportedAttributes: [AttributeEnumType.Actual],
@@ -583,7 +591,7 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     variable: OCPP20RequiredVariableName.UnlockOnEVSideDisconnect as string,
   },
 
-  // SampledDataCtrlr variables (simulation measurands)
+  // SampledDataCtrlr variables
   [buildRegistryKey(OCPP20ComponentName.SampledDataCtrlr as string, 'Current.Import')]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.decimal,
@@ -637,7 +645,8 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.MemberList,
-    defaultValue: 'Energy.Active.Import.Register,Current.Import',
+    // Default includes cumulative energy and interval energy plus voltage for billing context
+    defaultValue: 'Energy.Active.Import.Register,Energy.Active.Import.Interval,Voltage',
     description: 'Measurands sampled at transaction end.',
     enumeration: [
       'Energy.Active.Import.Register',
@@ -668,16 +677,25 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.MemberList,
-    defaultValue: 'Energy.Active.Import.Register,Power.Active.Import',
+    defaultValue: 'Energy.Active.Import.Register,Power.Active.Import,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',
     ],
     mutability: MutabilityEnumType.ReadWrite,
     persistence: PersistenceEnumType.Persistent,
@@ -709,16 +727,25 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.SampledDataCtrlr as string,
     dataType: DataEnumType.MemberList,
-    defaultValue: 'Energy.Active.Import.Register',
+    defaultValue: 'Energy.Active.Import.Register,Current.Import,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',
     ],
     mutability: MutabilityEnumType.ReadWrite,
     persistence: PersistenceEnumType.Persistent,
@@ -844,10 +871,8 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
   )]: {
     component: OCPP20ComponentName.TxCtrlr as string,
     dataType: DataEnumType.MemberList,
-    // Spec-aligned default (exclude EnergyTransfer & DataSigned due to measurement skew and signed data optionality)
     defaultValue: 'Authorized,EVConnected',
     description: 'Trigger conditions for starting a transaction.',
-    // Spec-aligned enumeration per errata: Authorized, EVConnected, PowerPathClosed, EnergyTransfer, ParkingBayOccupancy plus DataSigned (start only)
     enumeration: [
       'Authorized',
       'EVConnected',
@@ -865,10 +890,8 @@ export const VARIABLE_REGISTRY: Record<string, VariableMetadata> = {
     {
       component: OCPP20ComponentName.TxCtrlr as string,
       dataType: DataEnumType.MemberList,
-      // Spec-aligned default stop triggers (exclude Authorized by default to avoid id re-presentation auto-stop)
       defaultValue: 'EVConnected,PowerPathClosed',
       description: 'Trigger conditions for ending a transaction.',
-      // Spec-aligned enumeration per errata: Authorized, EVConnected, PowerPathClosed, EnergyTransfer, ParkingBayOccupancy (DataSigned excluded as invalid stop point)
       enumeration: [
         'Authorized',
         'EVConnected',
index 2e05f3b2bb87bc80379235c92b3bf20e64420a47..f69f8ce44d9925fc80dfa1568256dbe6991999a9 100644 (file)
@@ -280,7 +280,8 @@ await describe('B08 - Get Base Report', async () => {
 
     // Choose TimeSource (SequenceList) and construct an artificially long ordered list value > 10 chars
     const variableManager = OCPP20VariableManager.getInstance()
-    const longValue = 'NTP,GPS,RTC,Manual'
+    // Use members exceeding 10 chars total; exclude removed RTC/Manual.
+    const longValue = 'Heartbeat,NTP,GPS,RealTimeClock,MobileNetwork,RadioTimeTransmitter'
     // Set Actual (SequenceList). Should accept full value internally.
     const setResult: OCPP20SetVariableResultType[] = variableManager.setVariables(
       mockChargingStation,
index 3ae17add4c4297d1e6a71fdfad11d270902fec51..3429c5c063adfbb670a5198b6f6d0143b8a6a42e 100644 (file)
@@ -365,6 +365,70 @@ void describe('B06 - Get Variables', () => {
     expect(result.attributeValue).toBe('30')
   })
 
+  void it('Should retrieve list/sequence defaults for FileTransferProtocols, TimeSource, NetworkConfigurationPriority', () => {
+    const request: OCPP20GetVariablesRequest = {
+      getVariableData: [
+        {
+          component: { name: OCPP20ComponentName.OCPPCommCtrlr },
+          variable: { name: OCPP20RequiredVariableName.FileTransferProtocols },
+        },
+        {
+          component: { name: OCPP20ComponentName.ClockCtrlr },
+          variable: { name: OCPP20RequiredVariableName.TimeSource },
+        },
+        {
+          component: { name: OCPP20ComponentName.OCPPCommCtrlr },
+          variable: { name: OCPP20RequiredVariableName.NetworkConfigurationPriority },
+        },
+      ],
+    }
+    const response = incomingRequestService.handleRequestGetVariables(mockChargingStation, request)
+    expect(response.getVariableResult).toHaveLength(3)
+    const fileTransfer = response.getVariableResult[0]
+    expect(fileTransfer.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+    expect(fileTransfer.attributeValue).toBe('HTTPS,FTPS,SFTP')
+    const timeSource = response.getVariableResult[1]
+    expect(timeSource.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+    expect(timeSource.attributeValue).toBe('NTP,GPS,RealTimeClock,Heartbeat')
+    const netConfigPriority = response.getVariableResult[2]
+    expect(netConfigPriority.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+    expect(netConfigPriority.attributeValue).toBe('1,2,3')
+  })
+
+  void it('Should retrieve list defaults for TxStartedMeasurands, TxEndedMeasurands, TxUpdatedMeasurands', () => {
+    const request: OCPP20GetVariablesRequest = {
+      getVariableData: [
+        {
+          component: { name: OCPP20ComponentName.SampledDataCtrlr },
+          variable: { name: OCPP20RequiredVariableName.TxStartedMeasurands },
+        },
+        {
+          component: { name: OCPP20ComponentName.SampledDataCtrlr },
+          variable: { name: OCPP20RequiredVariableName.TxEndedMeasurands },
+        },
+        {
+          component: { name: OCPP20ComponentName.SampledDataCtrlr },
+          variable: { name: OCPP20RequiredVariableName.TxUpdatedMeasurands },
+        },
+      ],
+    }
+    const response = incomingRequestService.handleRequestGetVariables(mockChargingStation, request)
+    expect(response.getVariableResult).toHaveLength(3)
+    const txStarted = response.getVariableResult[0]
+    expect(txStarted.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+    expect(txStarted.attributeValue).toBe(
+      'Energy.Active.Import.Register,Power.Active.Import,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'
+    )
+    const txUpdated = response.getVariableResult[2]
+    expect(txUpdated.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+    expect(txUpdated.attributeValue).toBe('Energy.Active.Import.Register,Current.Import,Voltage')
+  })
+
   // FR: B06.FR.13
   void it('Should reject Target attribute for NetworkConfigurationPriority', () => {
     const request: OCPP20GetVariablesRequest = {
index 3c9d8e797907a33689934f5e5d4b328dcb2db226..034878c28c5a6f39ae87d69a91a0ecd609648f53 100644 (file)
@@ -1189,15 +1189,15 @@ await describe('OCPP20VariableManager test suite', async () => {
   await describe('List validation tests', async () => {
     const manager = OCPP20VariableManager.getInstance()
 
-    await it('Should accept valid updates to list/sequence list variables', () => {
-      const validUpdates: OCPP20SetVariableDataType[] = [
+    await it('Should accept valid updates to writable list/sequence list variables and reject read-only', () => {
+      const updateAttempts: OCPP20SetVariableDataType[] = [
         {
-          attributeValue: 'HTTP,HTTPS',
+          attributeValue: 'HTTP,HTTPS', // FileTransferProtocols now ReadOnly -> expect rejection
           component: { name: OCPP20ComponentName.OCPPCommCtrlr },
           variable: { name: OCPP20RequiredVariableName.FileTransferProtocols },
         },
         {
-          attributeValue: 'GPS,NTP,RTC', // reorder TimeSource
+          attributeValue: 'Heartbeat,NTP,GPS', // valid TimeSource reorder (RTC & Manual removed, RealTimeClock optional)
           component: { name: OCPP20ComponentName.ClockCtrlr },
           variable: { name: OCPP20RequiredVariableName.TimeSource },
         },
@@ -1228,12 +1228,50 @@ await describe('OCPP20VariableManager test suite', async () => {
           variable: { name: OCPP20RequiredVariableName.TxUpdatedMeasurands },
         },
       ]
-      const results = manager.setVariables(mockChargingStation, validUpdates)
-      for (const r of results) {
+      const results = manager.setVariables(mockChargingStation, updateAttempts)
+      // First (FileTransferProtocols) should be rejected (ReadOnly); others accepted
+      expect(results[0].attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
+      expect(results[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ReadOnly)
+      for (const r of results.slice(1)) {
         expect(r.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
       }
     })
 
+    await it('Should retrieve FileTransferProtocols default including FTPS (ReadOnly)', () => {
+      const getRes = manager.getVariables(mockChargingStation, [
+        {
+          component: { name: OCPP20ComponentName.OCPPCommCtrlr },
+          variable: { name: OCPP20RequiredVariableName.FileTransferProtocols },
+        },
+      ])[0]
+      expect(getRes.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+      expect(getRes.attributeValue).toBe('HTTPS,FTPS,SFTP')
+    })
+
+    await it('Should reject removed TimeSource members RTC and Manual', () => {
+      const res = manager.setVariables(mockChargingStation, [
+        {
+          attributeValue: 'NTP,GPS,RTC,Manual', // RTC & Manual no longer valid
+          component: { name: OCPP20ComponentName.ClockCtrlr },
+          variable: { name: OCPP20RequiredVariableName.TimeSource },
+        },
+      ])[0]
+      expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
+      expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
+      expect(res.attributeStatusInfo?.additionalInfo).toContain('Member not in enumeration')
+    })
+
+    await it('Should accept extended TimeSource including RealTimeClock and MobileNetwork', () => {
+      const res = manager.setVariables(mockChargingStation, [
+        {
+          attributeValue: 'MobileNetwork,Heartbeat,NTP,GPS,RealTimeClock',
+          component: { name: OCPP20ComponentName.ClockCtrlr },
+          variable: { name: OCPP20RequiredVariableName.TimeSource },
+        },
+      ])[0]
+      expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+    })
+
     await it('Should reject invalid list formats and members', () => {
       interface ListVar {
         component: OCPP20ComponentName