)]: {
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],
)]: {
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,
)]: {
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],
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,
)]: {
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',
)]: {
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,
)]: {
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,
)]: {
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',
{
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',
// 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,
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 = {
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 },
},
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