diff --git a/README.md b/README.md index 53fea743..62f2718e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # eebus-go -[![Build Status](https://github.com/enbility/eebus-go/actions/workflows/default.yml/badge.svg?branch=main)](https://github.com/enbility/eebus-go/actions/workflows/default.yml/badge.svg?branch=main) +[![Build Status](https://github.com/enbility/eebus-go/actions/workflows/default.yml/badge.svg?branch=dev)](https://github.com/enbility/eebus-go/actions/workflows/default.yml/badge.svg?branch=dev) [![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4)](https://godoc.org/github.com/enbility/eebus-go) -[![Coverage Status](https://coveralls.io/repos/github/enbility/eebus-go/badge.svg?branch=main)](https://coveralls.io/github/enbility/eebus-go?branch=main) +[![Coverage Status](https://coveralls.io/repos/github/enbility/eebus-go/badge.svg?branch=dev)](https://coveralls.io/github/enbility/eebus-go?branch=dev) [![Go report](https://goreportcard.com/badge/github.com/enbility/eebus-go)](https://goreportcard.com/report/github.com/enbility/eebus-go) This library provides a complete foundation for implementing [EEBUS](https://eebus.org) use cases. The use cases define various functional scenarios for different device categories, e.g. energy management systems, charging stations, heat pumps, and more. diff --git a/features/deviceclassification.go b/features/deviceclassification.go index 5adb1bf6..c2f5f9c6 100644 --- a/features/deviceclassification.go +++ b/features/deviceclassification.go @@ -5,21 +5,6 @@ import ( "github.com/enbility/eebus-go/spine/model" ) -type ManufacturerType struct { - BrandName string - VendorName string - VendorCode string - DeviceName string - DeviceCode string - SerialNumber string - SoftwareRevision string - HardwareRevision string - PowerSource string - ManufacturerNodeIdentification string - ManufacturerLabel string - ManufacturerDescription string -} - type DeviceClassification struct { *FeatureImpl } @@ -38,22 +23,12 @@ func NewDeviceClassification(localRole, remoteRole model.RoleType, spineLocalDev } // request DeviceClassificationManufacturerData from a remote device entity -func (d *DeviceClassification) RequestManufacturerDetailsForEntity() (*model.MsgCounterType, error) { - // request DeviceClassificationManufacturer from a remote entity - msgCounter, err := d.requestData(model.FunctionTypeDeviceClassificationManufacturerData, nil, nil) - if err != nil { - return nil, err - } - - return msgCounter, nil +func (d *DeviceClassification) RequestManufacturerDetails() (*model.MsgCounterType, error) { + return d.requestData(model.FunctionTypeDeviceClassificationManufacturerData, nil, nil) } // get the current manufacturer details for a remote device entity -func (d *DeviceClassification) GetManufacturerDetails() (*ManufacturerType, error) { - if d.featureRemote == nil { - return nil, ErrDataNotAvailable - } - +func (d *DeviceClassification) GetManufacturerDetails() (*model.DeviceClassificationManufacturerDataType, error) { rData := d.featureRemote.Data(model.FunctionTypeDeviceClassificationManufacturerData) if rData == nil { return nil, ErrDataNotAvailable @@ -64,44 +39,5 @@ func (d *DeviceClassification) GetManufacturerDetails() (*ManufacturerType, erro return nil, ErrDataNotAvailable } - details := &ManufacturerType{} - - if data.BrandName != nil { - details.BrandName = string(*data.BrandName) - } - if data.VendorName != nil { - details.VendorName = string(*data.VendorName) - } - if data.VendorCode != nil { - details.VendorCode = string(*data.VendorCode) - } - if data.DeviceName != nil { - details.DeviceName = string(*data.DeviceName) - } - if data.DeviceCode != nil { - details.DeviceCode = string(*data.DeviceCode) - } - if data.SerialNumber != nil { - details.SerialNumber = string(*data.SerialNumber) - } - if data.SoftwareRevision != nil { - details.SoftwareRevision = string(*data.SoftwareRevision) - } - if data.HardwareRevision != nil { - details.HardwareRevision = string(*data.HardwareRevision) - } - if data.PowerSource != nil { - details.PowerSource = string(*data.PowerSource) - } - if data.ManufacturerNodeIdentification != nil { - details.ManufacturerNodeIdentification = string(*data.ManufacturerNodeIdentification) - } - if data.ManufacturerLabel != nil { - details.ManufacturerLabel = string(*data.ManufacturerLabel) - } - if data.ManufacturerDescription != nil { - details.ManufacturerDescription = string(*data.ManufacturerDescription) - } - - return details, nil + return data, nil } diff --git a/features/deviceclassification_test.go b/features/deviceclassification_test.go index f028247a..48a5c63a 100644 --- a/features/deviceclassification_test.go +++ b/features/deviceclassification_test.go @@ -50,8 +50,8 @@ func (s *DeviceClassificationSuite) BeforeTest(suiteName, testName string) { assert.NotNil(s.T(), s.deviceClassification) } -func (s *DeviceClassificationSuite) Test_RequestManufacturerDetailsForEntity() { - counter, err := s.deviceClassification.RequestManufacturerDetailsForEntity() +func (s *DeviceClassificationSuite) Test_RequestManufacturerDetails() { + counter, err := s.deviceClassification.RequestManufacturerDetails() assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } diff --git a/features/deviceconfiguration.go b/features/deviceconfiguration.go index 5662ee59..dc63a6ac 100644 --- a/features/deviceconfiguration.go +++ b/features/deviceconfiguration.go @@ -1,25 +1,10 @@ package features import ( - "time" - "github.com/enbility/eebus-go/spine" "github.com/enbility/eebus-go/spine/model" ) -type DeviceConfigurationType struct { - Key string - ValueBool bool - ValueDate time.Time - ValueDatetime time.Time - ValueDuration time.Duration - ValueString string - ValueTime time.Time - ValueFloat float64 - Type model.DeviceConfigurationKeyValueTypeType - Unit string -} - type DeviceConfiguration struct { *FeatureImpl } @@ -38,92 +23,61 @@ func NewDeviceConfiguration(localRole, remoteRole model.RoleType, spineLocalDevi } // request DeviceConfiguration data from a remote entity -func (d *DeviceConfiguration) Request() error { - // request DeviceConfigurationKeyValueDescriptionListData from a remote entity - if _, err := d.requestData(model.FunctionTypeDeviceConfigurationKeyValueDescriptionListData, nil, nil); err != nil { - return err - } - - return nil +func (d *DeviceConfiguration) RequestDescriptions() error { + _, err := d.requestData(model.FunctionTypeDeviceConfigurationKeyValueDescriptionListData, nil, nil) + return err } // request DeviceConfigurationKeyValueListDataType from a remote entity -func (d *DeviceConfiguration) RequestKeyValueList() (*model.MsgCounterType, error) { - // request FunctionTypeDeviceConfigurationKeyValueListData from a remote entity - msgCounter, err := d.requestData(model.FunctionTypeDeviceConfigurationKeyValueListData, nil, nil) - if err != nil { - return nil, err - } - - return msgCounter, nil +func (d *DeviceConfiguration) RequestKeyValues() (*model.MsgCounterType, error) { + return d.requestData(model.FunctionTypeDeviceConfigurationKeyValueListData, nil, nil) } -// returns if a provided scopetype in the device configuration descriptions is available or not -// returns an error if no description data is available yet -func (d *DeviceConfiguration) GetDescriptionKeyNameSupport(keyName model.DeviceConfigurationKeyNameType) (bool, error) { - if d.featureRemote == nil { - return false, ErrDataNotAvailable - } - +// return current descriptions for Device Configuration +func (d *DeviceConfiguration) GetDescriptions() ([]model.DeviceConfigurationKeyValueDescriptionDataType, error) { rData := d.featureRemote.Data(model.FunctionTypeDeviceConfigurationKeyValueDescriptionListData) if rData == nil { - return false, ErrDataNotAvailable + return nil, ErrDataNotAvailable } data := rData.(*model.DeviceConfigurationKeyValueDescriptionListDataType) if data == nil { - return false, ErrDataNotAvailable - } - - for _, item := range data.DeviceConfigurationKeyValueDescriptionData { - if item.KeyId == nil || item.KeyName == nil { - continue - } - if *item.KeyName == keyName { - return true, nil - } + return nil, ErrDataNotAvailable } - return false, ErrDataNotAvailable + return data.DeviceConfigurationKeyValueDescriptionData, nil } -// return current SoC for measurements -func (d *DeviceConfiguration) GetEVCommunicationStandard() (*string, error) { - if d.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - descRef, err := d.deviceConfigurationKeyValueDescriptionListData() +// returns the description of a provided key name +func (d *DeviceConfiguration) GetDescriptionForKeyId(keyId model.DeviceConfigurationKeyIdType) (*model.DeviceConfigurationKeyValueDescriptionDataType, error) { + descriptions, err := d.GetDescriptions() if err != nil { - return nil, ErrMetadataNotAvailable - } - - rData := d.featureRemote.Data(model.FunctionTypeDeviceConfigurationKeyValueListData) - if rData == nil { - return nil, ErrDataNotAvailable + return nil, err } - data := rData.(*model.DeviceConfigurationKeyValueListDataType) - if data == nil { - return nil, ErrDataNotAvailable + for _, item := range descriptions { + if item.KeyId != nil && *item.KeyId == keyId { + return &item, nil + } } - for _, item := range data.DeviceConfigurationKeyValueData { - if item.KeyId == nil || item.Value == nil { - continue - } + return nil, ErrDataNotAvailable +} - desc, exists := descRef[*item.KeyId] - if !exists { - continue - } +// returns the description of a provided key name +// returns an error if the key name was not found +func (d *DeviceConfiguration) GetDescriptionForKeyName(keyName model.DeviceConfigurationKeyNameType) (*model.DeviceConfigurationKeyValueDescriptionDataType, error) { + descriptions, err := d.GetDescriptions() + if err != nil { + return nil, err + } - if desc.KeyName == nil { + for _, item := range descriptions { + if item.KeyId == nil || item.KeyName == nil { continue } - - if *desc.KeyName == model.DeviceConfigurationKeyNameTypeCommunicationsStandard { - return (*string)(item.Value.String), nil + if *item.KeyName == keyName { + return &item, nil } } @@ -131,28 +85,7 @@ func (d *DeviceConfiguration) GetEVCommunicationStandard() (*string, error) { } // return current values for Device Configuration -func (d *DeviceConfiguration) GetValues() ([]DeviceConfigurationType, error) { - if d.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - rDescData := d.featureRemote.Data(model.FunctionTypeDeviceConfigurationKeyValueDescriptionListData) - if rDescData == nil { - return nil, ErrMetadataNotAvailable - } - descData := rDescData.(*model.DeviceConfigurationKeyValueDescriptionListDataType) - if descData == nil { - return nil, ErrDataNotAvailable - } - - ref := make(map[model.DeviceConfigurationKeyIdType]model.DeviceConfigurationKeyValueDescriptionDataType) - for _, item := range descData.DeviceConfigurationKeyValueDescriptionData { - if item.KeyName == nil || item.KeyId == nil { - continue - } - ref[*item.KeyId] = item - } - +func (d *DeviceConfiguration) GetKeyValues() ([]model.DeviceConfigurationKeyValueDataType, error) { rData := d.featureRemote.Data(model.FunctionTypeDeviceConfigurationKeyValueListData) if rData == nil { return nil, ErrDataNotAvailable @@ -163,98 +96,51 @@ func (d *DeviceConfiguration) GetValues() ([]DeviceConfigurationType, error) { return nil, ErrDataNotAvailable } - var resultSet []DeviceConfigurationType + return data.DeviceConfigurationKeyValueData, nil +} + +// return a pointer value for a given key and value type +func (d *DeviceConfiguration) GetKeyValueForKeyName(keyname model.DeviceConfigurationKeyNameType, valueType model.DeviceConfigurationKeyValueTypeType) (any, error) { + values, err := d.GetKeyValues() + if err != nil { + return nil, err + } - for _, item := range data.DeviceConfigurationKeyValueData { - if item.KeyId == nil { + for _, item := range values { + if item.KeyId == nil || item.Value == nil { continue } - desc, exists := ref[*item.KeyId] - if !exists || desc.KeyName == nil { + + desc, err := d.GetDescriptionForKeyId(*item.KeyId) + if err != nil { continue } - result := DeviceConfigurationType{ - Key: string(*desc.KeyName), - } - if desc.ValueType == nil { + if desc.KeyName == nil { continue } - result.Type = *desc.ValueType - switch *desc.ValueType { - case model.DeviceConfigurationKeyValueTypeTypeBoolean: - if item.Value.Boolean != nil { - result.ValueBool = bool(*item.Value.Boolean) - } - case model.DeviceConfigurationKeyValueTypeTypeDate: - if item.Value.Date != nil { - if value, err := item.Value.Date.GetTime(); err == nil { - result.ValueDate = value - } - } - case model.DeviceConfigurationKeyValueTypeTypeDateTime: - if item.Value.DateTime != nil { - if value, err := item.Value.DateTime.GetTime(); err == nil { - result.ValueDatetime = value - } - } - case model.DeviceConfigurationKeyValueTypeTypeDuration: - if item.Value.Duration != nil { - if value, err := item.Value.Duration.GetTimeDuration(); err == nil { - result.ValueDuration = value - } - } - case model.DeviceConfigurationKeyValueTypeTypeString: - if item.Value.String != nil { - result.ValueString = string(*item.Value.String) - } - case model.DeviceConfigurationKeyValueTypeTypeTime: - if item.Value.Time != nil { - if value, err := item.Value.Time.GetTime(); err != nil { - result.ValueTime = value - } - } - case model.DeviceConfigurationKeyValueTypeTypeScaledNumber: - if item.Value.ScaledNumber != nil { - result.ValueFloat = item.Value.ScaledNumber.GetValue() + + if *desc.KeyName == keyname { + switch valueType { + case model.DeviceConfigurationKeyValueTypeTypeBoolean: + return item.Value.Boolean, nil + case model.DeviceConfigurationKeyValueTypeTypeDate: + return item.Value.Date, nil + case model.DeviceConfigurationKeyValueTypeTypeDateTime: + return item.Value.DateTime, nil + case model.DeviceConfigurationKeyValueTypeTypeDuration: + return item.Value.Duration, nil + case model.DeviceConfigurationKeyValueTypeTypeString: + return item.Value.String, nil + case model.DeviceConfigurationKeyValueTypeTypeTime: + return item.Value.Time, nil + case model.DeviceConfigurationKeyValueTypeTypeScaledNumber: + return item.Value.ScaledNumber, nil + default: + return nil, ErrDataNotAvailable } } - if desc.Unit != nil { - result.Unit = string(*desc.Unit) - } - - resultSet = append(resultSet, result) - } - - return resultSet, nil -} - -// helper - -type deviceConfigurationKeyValueDescriptionMap map[model.DeviceConfigurationKeyIdType]model.DeviceConfigurationKeyValueDescriptionDataType - -// return a map of DeviceConfigurationKeyValueDescriptionListDataType with keyId as key -func (d *DeviceConfiguration) deviceConfigurationKeyValueDescriptionListData() (deviceConfigurationKeyValueDescriptionMap, error) { - if d.featureRemote == nil { - return nil, ErrDataNotAvailable } - rData := d.featureRemote.Data(model.FunctionTypeDeviceConfigurationKeyValueDescriptionListData) - if rData == nil { - return nil, ErrMetadataNotAvailable - } - - data := rData.(*model.DeviceConfigurationKeyValueDescriptionListDataType) - if data == nil { - return nil, ErrMetadataNotAvailable - } - - ref := make(deviceConfigurationKeyValueDescriptionMap) - for _, item := range data.DeviceConfigurationKeyValueDescriptionData { - if item.KeyId == nil { - continue - } - ref[*item.KeyId] = item - } - return ref, nil + return nil, ErrDataNotAvailable } diff --git a/features/deviceconfiguration_test.go b/features/deviceconfiguration_test.go index ef545389..22b40e7f 100644 --- a/features/deviceconfiguration_test.go +++ b/features/deviceconfiguration_test.go @@ -51,64 +51,87 @@ func (s *DeviceConfigurationSuite) BeforeTest(suiteName, testName string) { assert.NotNil(s.T(), s.deviceConfiguration) } -func (s *DeviceConfigurationSuite) Test_Request() { - err := s.deviceConfiguration.Request() +func (s *DeviceConfigurationSuite) Test_RequestDescriptions() { + err := s.deviceConfiguration.RequestDescriptions() assert.Nil(s.T(), err) } func (s *DeviceConfigurationSuite) Test_RequestKeyValueList() { - counter, err := s.deviceConfiguration.RequestKeyValueList() + counter, err := s.deviceConfiguration.RequestKeyValues() assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } -func (s *DeviceConfigurationSuite) Test_GetDescriptionKeyNameSupport() { - exists, err := s.deviceConfiguration.GetDescriptionKeyNameSupport(model.DeviceConfigurationKeyNameTypeCommunicationsStandard) +func (s *DeviceConfigurationSuite) Test_GetDescriptionForKeyId() { + keyId := model.DeviceConfigurationKeyIdType(0) + desc, err := s.deviceConfiguration.GetDescriptionForKeyId(keyId) assert.NotNil(s.T(), err) - assert.Equal(s.T(), false, exists) + assert.Nil(s.T(), desc) s.addDescription() - exists, err = s.deviceConfiguration.GetDescriptionKeyNameSupport(model.DeviceConfigurationKeyNameTypeCommunicationsStandard) + desc, err = s.deviceConfiguration.GetDescriptionForKeyId(keyId) assert.Nil(s.T(), err) - assert.Equal(s.T(), true, exists) + assert.NotNil(s.T(), desc) } -func (s *DeviceConfigurationSuite) Test_GetEVCommunicationStandard() { - value, err := s.deviceConfiguration.GetEVCommunicationStandard() +func (s *DeviceConfigurationSuite) Test_GetDescriptionForKeyName() { + desc, err := s.deviceConfiguration.GetDescriptionForKeyName(model.DeviceConfigurationKeyNameTypeCommunicationsStandard) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), desc) + + s.addDescription() + + desc, err = s.deviceConfiguration.GetDescriptionForKeyName(model.DeviceConfigurationKeyNameTypeCommunicationsStandard) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), desc) +} + +func (s *DeviceConfigurationSuite) Test_GetValueForKey() { + key := model.DeviceConfigurationKeyNameTypeCommunicationsStandard + valueType := model.DeviceConfigurationKeyValueTypeTypeString + + value, err := s.deviceConfiguration.GetKeyValueForKeyName(key, valueType) assert.NotNil(s.T(), err) assert.Nil(s.T(), value) s.addDescription() - value, err = s.deviceConfiguration.GetEVCommunicationStandard() + value, err = s.deviceConfiguration.GetKeyValueForKeyName(key, valueType) assert.NotNil(s.T(), err) assert.Nil(s.T(), value) s.addData() - value, err = s.deviceConfiguration.GetEVCommunicationStandard() + value, err = s.deviceConfiguration.GetKeyValueForKeyName(key, valueType) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), value) + + value, err = s.deviceConfiguration.GetKeyValueForKeyName(model.DeviceConfigurationKeyNameTypeAsymmetricChargingSupported, model.DeviceConfigurationKeyValueTypeTypeBoolean) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), value) + + value, err = s.deviceConfiguration.GetKeyValueForKeyName(model.DeviceConfigurationKeyNameTypePvCurtailmentLimitFactor, model.DeviceConfigurationKeyValueTypeTypeScaledNumber) assert.Nil(s.T(), err) assert.NotNil(s.T(), value) } func (s *DeviceConfigurationSuite) Test_GetValues() { - data, err := s.deviceConfiguration.GetValues() + data, err := s.deviceConfiguration.GetKeyValues() assert.NotNil(s.T(), err) assert.Nil(s.T(), data) s.addDescription() - data, err = s.deviceConfiguration.GetValues() + data, err = s.deviceConfiguration.GetKeyValues() assert.NotNil(s.T(), err) assert.Nil(s.T(), data) s.addData() - data, err = s.deviceConfiguration.GetValues() + data, err = s.deviceConfiguration.GetKeyValues() assert.Nil(s.T(), err) assert.NotNil(s.T(), data) - } // helper @@ -131,6 +154,7 @@ func (s *DeviceConfigurationSuite) addDescription() { KeyId: util.Ptr(model.DeviceConfigurationKeyIdType(2)), KeyName: util.Ptr(model.DeviceConfigurationKeyNameTypePvCurtailmentLimitFactor), ValueType: util.Ptr(model.DeviceConfigurationKeyValueTypeTypeScaledNumber), + Unit: util.Ptr(model.UnitOfMeasurementTypepct), }, }, } diff --git a/features/devicediagnosis.go b/features/devicediagnosis.go index 9a3af5c6..701cca58 100644 --- a/features/devicediagnosis.go +++ b/features/devicediagnosis.go @@ -5,11 +5,6 @@ import ( "github.com/enbility/eebus-go/spine/model" ) -type DeviceDiagnosisType struct { - OperatingState model.DeviceDiagnosisOperatingStateType - PowerSupplyCondition model.PowerSupplyConditionType -} - type DeviceDiagnosis struct { *FeatureImpl } @@ -28,22 +23,12 @@ func NewDeviceDiagnosis(localRole, remoteRole model.RoleType, spineLocalDevice * } // request DeviceDiagnosisStateData from a remote entity -func (d *DeviceDiagnosis) RequestStateForEntity() (*model.MsgCounterType, error) { - // request FunctionTypeDeviceDiagnosisStateData from a remote entity - msgCounter, err := d.requestData(model.FunctionTypeDeviceDiagnosisStateData, nil, nil) - if err != nil { - return nil, err - } - - return msgCounter, nil +func (d *DeviceDiagnosis) RequestState() (*model.MsgCounterType, error) { + return d.requestData(model.FunctionTypeDeviceDiagnosisStateData, nil, nil) } // get the current diagnosis state for an device entity -func (d *DeviceDiagnosis) GetState() (*DeviceDiagnosisType, error) { - if d.featureRemote == nil { - return nil, ErrDataNotAvailable - } - +func (d *DeviceDiagnosis) GetState() (*model.DeviceDiagnosisStateDataType, error) { rData := d.featureRemote.Data(model.FunctionTypeDeviceDiagnosisStateData) if rData == nil { return nil, ErrDataNotAvailable @@ -54,15 +39,7 @@ func (d *DeviceDiagnosis) GetState() (*DeviceDiagnosisType, error) { return nil, ErrDataNotAvailable } - details := &DeviceDiagnosisType{} - if data.OperatingState != nil { - details.OperatingState = *data.OperatingState - } - if data.PowerSupplyCondition != nil { - details.PowerSupplyCondition = *data.PowerSupplyCondition - } - - return details, nil + return data, nil } func (d *DeviceDiagnosis) SendState(operatingState *model.DeviceDiagnosisStateDataType) { diff --git a/features/devicediagnosis_test.go b/features/devicediagnosis_test.go index 295af4f2..44047f2c 100644 --- a/features/devicediagnosis_test.go +++ b/features/devicediagnosis_test.go @@ -50,8 +50,8 @@ func (s *DeviceDiagnosisSuite) BeforeTest(suiteName, testName string) { assert.NotNil(s.T(), s.deviceDiagnosis) } -func (s *DeviceDiagnosisSuite) Test_RequestStateForEntity() { - counter, err := s.deviceDiagnosis.RequestStateForEntity() +func (s *DeviceDiagnosisSuite) Test_RequestState() { + counter, err := s.deviceDiagnosis.RequestState() assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } diff --git a/features/electricalconnection.go b/features/electricalconnection.go index 89bfb78a..ad6fdf37 100644 --- a/features/electricalconnection.go +++ b/features/electricalconnection.go @@ -5,24 +5,6 @@ import ( "github.com/enbility/eebus-go/spine/model" ) -// Details about the electrical connection -type ElectricalDescriptionType struct { - ConnectionID uint - PowerSupplyType model.ElectricalConnectionVoltageTypeType - AcConnectedPhases uint - PositiveEnergyDirection model.EnergyDirectionType -} - -// Details about the limits of an electrical connection -type ElectricalLimitType struct { - ConnectionID uint - Min float64 - Max float64 - Default float64 - Phase model.ElectricalConnectionPhaseNameType - Scope model.ScopeTypeType -} - type ElectricalConnection struct { *FeatureImpl } @@ -41,297 +23,253 @@ func NewElectricalConnection(localRole, remoteRole model.RoleType, spineLocalDev } // request ElectricalConnectionDescriptionListDataType from a remote entity -func (e *ElectricalConnection) RequestDescription() error { - if _, err := e.requestData(model.FunctionTypeElectricalConnectionDescriptionListData, nil, nil); err != nil { - return err - } +func (e *ElectricalConnection) RequestDescriptions() error { + _, err := e.requestData(model.FunctionTypeElectricalConnectionDescriptionListData, nil, nil) - return nil + return err } // request FunctionTypeElectricalConnectionParameterDescriptionListData from a remote entity -func (e *ElectricalConnection) RequestParameterDescription() error { - if _, err := e.requestData(model.FunctionTypeElectricalConnectionParameterDescriptionListData, nil, nil); err != nil { - return err - } +func (e *ElectricalConnection) RequestParameterDescriptions() error { + _, err := e.requestData(model.FunctionTypeElectricalConnectionParameterDescriptionListData, nil, nil) - return nil + return err } // request FunctionTypeElectricalConnectionPermittedValueSetListData from a remote entity -func (e *ElectricalConnection) RequestPermittedValueSet() (*model.MsgCounterType, error) { - msgCounter, err := e.requestData(model.FunctionTypeElectricalConnectionPermittedValueSetListData, nil, nil) - if err != nil { - return nil, err +func (e *ElectricalConnection) RequestPermittedValueSets() (*model.MsgCounterType, error) { + return e.requestData(model.FunctionTypeElectricalConnectionPermittedValueSetListData, nil, nil) +} + +// return list of description for Electrical Connection +func (e *ElectricalConnection) GetDescriptions() ([]model.ElectricalConnectionDescriptionDataType, error) { + rData := e.featureRemote.Data(model.FunctionTypeElectricalConnectionDescriptionListData) + if rData == nil { + return nil, ErrMetadataNotAvailable + } + data := rData.(*model.ElectricalConnectionDescriptionListDataType) + if data == nil { + return nil, ErrMetadataNotAvailable } - return msgCounter, nil + return data.ElectricalConnectionDescriptionData, nil } -type electricalParamDescriptionMapMeasurementId map[model.MeasurementIdType]model.ElectricalConnectionParameterDescriptionDataType -type electricatlParamDescriptionMapParamId map[model.ElectricalConnectionParameterIdType]model.ElectricalConnectionParameterDescriptionDataType - -// return a map of ElectricalConnectionParameterDescriptionListDataType with measurementId as key and -// ElectricalConnectionParameterDescriptionListDataType with parameterId as key -func (e *ElectricalConnection) GetParamDescriptionListData() (electricalParamDescriptionMapMeasurementId, electricatlParamDescriptionMapParamId, error) { - if e.featureRemote == nil { - return nil, nil, ErrDataNotAvailable +// return current electrical description for a given measurementId +func (e *ElectricalConnection) GetDescriptionForMeasurementId(measurementId model.MeasurementIdType) (*model.ElectricalConnectionDescriptionDataType, error) { + param, err := e.GetParameterDescriptionForMeasurementId(measurementId) + if err != nil { + return nil, err } - rData := e.featureRemote.Data(model.FunctionTypeElectricalConnectionParameterDescriptionListData) - if rData == nil { - return nil, nil, ErrDataNotAvailable - } - data := rData.(*model.ElectricalConnectionParameterDescriptionListDataType) - if data == nil { - return nil, nil, ErrDataNotAvailable + descriptions, err := e.GetDescriptions() + if err != nil { + return nil, err } - refMeasurement := make(electricalParamDescriptionMapMeasurementId) - refElectrical := make(electricatlParamDescriptionMapParamId) - for _, item := range data.ElectricalConnectionParameterDescriptionData { - if item.MeasurementId == nil || item.ElectricalConnectionId == nil { + for _, item := range descriptions { + if item.ElectricalConnectionId == nil || + param.ElectricalConnectionId == nil || + *item.ElectricalConnectionId != *param.ElectricalConnectionId { continue } - refMeasurement[*item.MeasurementId] = item - refElectrical[*item.ParameterId] = item + + return &item, nil } - return refMeasurement, refElectrical, nil + return nil, ErrMetadataNotAvailable } -// return current values for Electrical Description -func (e *ElectricalConnection) GetDescription() ([]ElectricalDescriptionType, error) { - if e.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - rData := e.featureRemote.Data(model.FunctionTypeElectricalConnectionDescriptionListData) +// return parameter descriptions for all Electrical Connections +func (e *ElectricalConnection) GetParameterDescriptions() ([]model.ElectricalConnectionParameterDescriptionDataType, error) { + rData := e.featureRemote.Data(model.FunctionTypeElectricalConnectionParameterDescriptionListData) if rData == nil { - return nil, ErrMetadataNotAvailable + return nil, ErrDataNotAvailable } - data := rData.(*model.ElectricalConnectionDescriptionListDataType) + data := rData.(*model.ElectricalConnectionParameterDescriptionListDataType) if data == nil { - return nil, ErrMetadataNotAvailable + return nil, ErrDataNotAvailable } - var resultSet []ElectricalDescriptionType - - for _, item := range data.ElectricalConnectionDescriptionData { - if item.ElectricalConnectionId == nil { - continue - } + return data.ElectricalConnectionParameterDescriptionData, nil +} - result := ElectricalDescriptionType{} +// return parameter description for a specific parameterId +func (e *ElectricalConnection) GetParameterDescriptionForParameterId(parameterId model.ElectricalConnectionParameterIdType) (*model.ElectricalConnectionParameterDescriptionDataType, error) { + desc, err := e.GetParameterDescriptions() + if err != nil { + return nil, err + } - if item.PowerSupplyType != nil { - result.PowerSupplyType = *item.PowerSupplyType - } - if item.AcConnectedPhases != nil { - result.AcConnectedPhases = *item.AcConnectedPhases - } - if item.PositiveEnergyDirection != nil { - result.PositiveEnergyDirection = *item.PositiveEnergyDirection + for _, element := range desc { + if element.ParameterId == nil || *element.ParameterId != parameterId { + continue } - resultSet = append(resultSet, result) + return &element, nil } - return resultSet, nil + return nil, ErrDataNotAvailable } -// return number of phases the device is connected with -func (e *ElectricalConnection) GetConnectedPhases() (uint, error) { - if e.featureRemote == nil { - return 0, ErrDataNotAvailable - } - - rData := e.featureRemote.Data(model.FunctionTypeElectricalConnectionDescriptionListData) - if rData == nil { - return 0, ErrDataNotAvailable - } - - data := rData.(*model.ElectricalConnectionDescriptionListDataType) - if data == nil { - return 0, ErrDataNotAvailable +// return parameter description for a specific measurementId +func (e *ElectricalConnection) GetParameterDescriptionForMeasurementId(measurementId model.MeasurementIdType) (*model.ElectricalConnectionParameterDescriptionDataType, error) { + desc, err := e.GetParameterDescriptions() + if err != nil { + return nil, err } - for _, item := range data.ElectricalConnectionDescriptionData { - if item.ElectricalConnectionId == nil { + for _, element := range desc { + if element.MeasurementId == nil || *element.MeasurementId != measurementId { continue } - if item.AcConnectedPhases != nil { - return *item.AcConnectedPhases, nil - } + return &element, nil } - // default to 3 if the value is not available - return 3, nil + return nil, ErrDataNotAvailable } -// return current current limit values -// -// returns a map with the phase ("a", "b", "c") as a key for -// minimum, maximum, default/pause values -func (e *ElectricalConnection) GetCurrentsLimits() (map[string]float64, map[string]float64, map[string]float64, error) { - if e.featureRemote == nil { - return nil, nil, nil, ErrDataNotAvailable +// return parameter description for a specific measurementId +func (e *ElectricalConnection) GetParameterDescriptionForMeasuredPhase(phase model.ElectricalConnectionPhaseNameType) (*model.ElectricalConnectionParameterDescriptionDataType, error) { + desc, err := e.GetParameterDescriptions() + if err != nil { + return nil, err } - _, paramRef, err := e.GetParamDescriptionListData() - if err != nil { - return nil, nil, nil, ErrMetadataNotAvailable + for _, element := range desc { + if element.AcMeasuredPhases == nil || *element.AcMeasuredPhases != phase { + continue + } + + return &element, nil } + return nil, ErrDataNotAvailable +} + +// return permitted values for all Electrical Connections +func (e *ElectricalConnection) GetPermittedValueSets() ([]model.ElectricalConnectionPermittedValueSetDataType, error) { rData := e.featureRemote.Data(model.FunctionTypeElectricalConnectionPermittedValueSetListData) if rData == nil { - return nil, nil, nil, ErrDataNotAvailable + return nil, ErrDataNotAvailable } data := rData.(*model.ElectricalConnectionPermittedValueSetListDataType) if data == nil { - return nil, nil, nil, ErrDataNotAvailable + return nil, ErrDataNotAvailable } - resultSetMin := make(map[string]float64) - resultSetMax := make(map[string]float64) - resultSetDefault := make(map[string]float64) - for _, item := range data.ElectricalConnectionPermittedValueSetData { - if item.ElectricalConnectionId == nil || item.PermittedValueSet == nil { - continue - } + return data.ElectricalConnectionPermittedValueSetData, nil +} - param, exists := paramRef[*item.ParameterId] - if !exists { - continue - } +// return permitted valueset for a provided measuremnetId +func (e *ElectricalConnection) GetPermittedValueSetForParameterId(parameterId model.ElectricalConnectionParameterIdType) (*model.ElectricalConnectionPermittedValueSetDataType, error) { + values, err := e.GetPermittedValueSets() + if err != nil { + return nil, err + } - if param.AcMeasuredPhases == nil { + for _, element := range values { + if element.ParameterId == nil || *element.ParameterId != parameterId { continue } - for _, set := range item.PermittedValueSet { - if set.Value != nil && len(set.Value) > 0 { - resultSetDefault[string(*param.AcMeasuredPhases)] = set.Value[0].GetValue() - } - if set.Range != nil { - for _, rangeItem := range set.Range { - if rangeItem.Min != nil { - resultSetMin[string(*param.AcMeasuredPhases)] = rangeItem.Min.GetValue() - } - if rangeItem.Max != nil { - resultSetMax[string(*param.AcMeasuredPhases)] = rangeItem.Max.GetValue() - } - } - } - } - } - - if len(resultSetMin) == 0 && len(resultSetMax) == 0 && len(resultSetMax) == 0 { - return nil, nil, nil, ErrDataNotAvailable + return &element, nil } - return resultSetMin, resultSetMax, resultSetDefault, nil + return nil, ErrDataNotAvailable } -// return current values for Electrical Limits -// -// EV only: Min power data is only provided via IEC61851 or using VAS in ISO15118-2. -func (e *ElectricalConnection) GetEVLimitValues() ([]ElectricalLimitType, error) { - if e.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - rData := e.featureRemote.Data(model.FunctionTypeElectricalConnectionParameterDescriptionListData) - if rData == nil { - return nil, ErrMetadataNotAvailable +// return permitted valueset for a provided measuremnetId +func (e *ElectricalConnection) GetPermittedValueSetForMeasurementId(measurementId model.MeasurementIdType) (*model.ElectricalConnectionPermittedValueSetDataType, error) { + param, err := e.GetParameterDescriptionForMeasurementId(measurementId) + if err != nil { + return nil, err } - paramDescriptionData := rData.(*model.ElectricalConnectionParameterDescriptionListDataType) - if paramDescriptionData == nil { - return nil, ErrMetadataNotAvailable + values, err := e.GetPermittedValueSets() + if err != nil { + return nil, err } - paramRef := make(map[model.ElectricalConnectionParameterIdType]model.ElectricalConnectionParameterDescriptionDataType) - for _, item := range paramDescriptionData.ElectricalConnectionParameterDescriptionData { - if item.ParameterId == nil { + for _, element := range values { + if element.ParameterId == nil || *element.ParameterId != *param.ParameterId { continue } - paramRef[*item.ParameterId] = item - } - rData2 := e.featureRemote.Data(model.FunctionTypeElectricalConnectionPermittedValueSetListData) - if rData2 == nil { - return nil, ErrDataNotAvailable + return &element, nil } - data := rData2.(*model.ElectricalConnectionPermittedValueSetListDataType) - if data == nil { - return nil, ErrDataNotAvailable + return nil, ErrDataNotAvailable +} + +// returns minimum, maximum, default/pause limit values +func (e *ElectricalConnection) GetLimitsForParameterId(parameterId model.ElectricalConnectionParameterIdType) (float64, float64, float64, error) { + data, err := e.GetPermittedValueSetForParameterId(parameterId) + if err != nil || data.ElectricalConnectionId == nil || data.PermittedValueSet == nil { + return 0, 0, 0, err } - var resultSet []ElectricalLimitType + var resultMin, resultMax, resultDefault float64 - for _, item := range data.ElectricalConnectionPermittedValueSetData { - if item.ParameterId == nil || item.ElectricalConnectionId == nil { - continue + for _, set := range data.PermittedValueSet { + if set.Value != nil && len(set.Value) > 0 { + resultDefault = set.Value[0].GetValue() } - param, exists := paramRef[*item.ParameterId] - if !exists { - continue + if set.Range != nil { + for _, rangeItem := range set.Range { + if rangeItem.Min != nil { + resultMin = rangeItem.Min.GetValue() + } + if rangeItem.Max != nil { + resultMax = rangeItem.Max.GetValue() + } + } } + } - if len(item.PermittedValueSet) == 0 { - continue - } + return resultMin, resultMax, resultDefault, nil +} + +// Adjust a value to be within the permitted value range +func (e *ElectricalConnection) AdjustValueToBeWithinPermittedValuesForParameter(value float64, parameterId model.ElectricalConnectionParameterIdType) float64 { + permittedValues, err := e.GetPermittedValueSetForParameterId(parameterId) + if err != nil { + return value + } + + data := permittedValues.PermittedValueSet - var value, minValue, maxValue float64 - hasValue := false - hasRange := false + var defaultValue, minValue, maxValue float64 + var hasDefaultValue, hasRange bool - for _, element := range item.PermittedValueSet { - // is a value set - if element.Value != nil && len(element.Value) > 0 { - value = element.Value[0].GetValue() - hasValue = true + for _, element := range data { + // is a value set + if element.Value != nil && len(element.Value) > 0 { + defaultValue = element.Value[0].GetValue() + hasDefaultValue = true + } + // is a range set + if element.Range != nil && len(element.Range) > 0 { + if element.Range[0].Min != nil { + minValue = element.Range[0].Min.GetValue() } - // is a range set - if element.Range != nil && len(element.Range) > 0 { - if element.Range[0].Min != nil { - minValue = element.Range[0].Min.GetValue() - } - if element.Range[0].Max != nil { - maxValue = element.Range[0].Max.GetValue() - } - hasRange = true + if element.Range[0].Max != nil { + maxValue = element.Range[0].Max.GetValue() } + hasRange = true } + } - switch { - // AC Total Power Limits - case param.ScopeType != nil && *param.ScopeType == model.ScopeTypeTypeACPowerTotal && hasRange: - result := ElectricalLimitType{ - ConnectionID: uint(*item.ElectricalConnectionId), - Min: minValue, - Max: maxValue, - Scope: model.ScopeTypeTypeACPowerTotal, - } - resultSet = append(resultSet, result) - - case param.AcMeasuredPhases != nil && hasRange && hasValue: - // AC Phase Current Limits - result := ElectricalLimitType{ - ConnectionID: uint(*item.ElectricalConnectionId), - Min: minValue, - Max: maxValue, - Default: value, - Phase: *param.AcMeasuredPhases, - Scope: model.ScopeTypeTypeACCurrent, - } - resultSet = append(resultSet, result) + if hasRange { + if hasDefaultValue && value < minValue { + value = defaultValue + } + if value > maxValue { + value = maxValue } } - return resultSet, nil + return value } diff --git a/features/electricalconnection_test.go b/features/electricalconnection_test.go index d94dd9e3..5bbd2280 100644 --- a/features/electricalconnection_test.go +++ b/features/electricalconnection_test.go @@ -52,157 +52,337 @@ func (s *ElectricalConnectionSuite) BeforeTest(suiteName, testName string) { assert.NotNil(s.T(), s.electricalConnection) } -func (s *ElectricalConnectionSuite) Test_RequestDescription() { - err := s.electricalConnection.RequestDescription() +func (s *ElectricalConnectionSuite) Test_RequestDescriptions() { + err := s.electricalConnection.RequestDescriptions() assert.Nil(s.T(), err) } -func (s *ElectricalConnectionSuite) Test_RequestParameterDescription() { - err := s.electricalConnection.RequestParameterDescription() +func (s *ElectricalConnectionSuite) Test_RequestParameterDescriptions() { + err := s.electricalConnection.RequestParameterDescriptions() assert.Nil(s.T(), err) } -func (s *ElectricalConnectionSuite) Test_RequestPermittedValueSet() { - counter, err := s.electricalConnection.RequestPermittedValueSet() +func (s *ElectricalConnectionSuite) Test_RequestPermittedValueSets() { + counter, err := s.electricalConnection.RequestPermittedValueSets() assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } -func (s *ElectricalConnectionSuite) Test_GetParamDescriptionListData() { - mapMeasurementId, mapParamId, err := s.electricalConnection.GetParamDescriptionListData() +func (s *ElectricalConnectionSuite) Test_GetDescriptions() { + data, err := s.electricalConnection.GetDescriptions() assert.NotNil(s.T(), err) - assert.Nil(s.T(), mapMeasurementId) - assert.Nil(s.T(), mapParamId) + assert.Nil(s.T(), data) - s.addParamDescription() + s.addDescription() - mapMeasurementId, mapParamId, err = s.electricalConnection.GetParamDescriptionListData() + data, err = s.electricalConnection.GetDescriptions() assert.Nil(s.T(), err) - assert.NotNil(s.T(), mapMeasurementId) - assert.NotNil(s.T(), mapParamId) + assert.NotNil(s.T(), data) } -func (s *ElectricalConnectionSuite) Test_GetDescription() { - data, err := s.electricalConnection.GetDescription() +func (s *ElectricalConnectionSuite) Test_GetDescriptionForMeasurementId() { + measurementId := model.MeasurementIdType(1) + data, err := s.electricalConnection.GetDescriptionForMeasurementId(measurementId) assert.NotNil(s.T(), err) assert.Nil(s.T(), data) s.addDescription() - data, err = s.electricalConnection.GetDescription() + data, err = s.electricalConnection.GetDescriptionForMeasurementId(measurementId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addParamDescriptionCurrents() + + data, err = s.electricalConnection.GetDescriptionForMeasurementId(measurementId) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) +} + +func (s *ElectricalConnectionSuite) Test_GetParameterDescriptions() { + data, err := s.electricalConnection.GetParameterDescriptions() + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addParamDescriptionCurrents() + + data, err = s.electricalConnection.GetParameterDescriptions() assert.Nil(s.T(), err) assert.NotNil(s.T(), data) } -func (s *ElectricalConnectionSuite) Test_GetConnectedPhases() { - data, err := s.electricalConnection.GetConnectedPhases() +func (s *ElectricalConnectionSuite) Test_GetParameterDescriptionForParameterId() { + parametertId := model.ElectricalConnectionParameterIdType(1) + data, err := s.electricalConnection.GetParameterDescriptionForParameterId(parametertId) assert.NotNil(s.T(), err) - assert.Equal(s.T(), uint(0), data) + assert.Nil(s.T(), data) s.addDescription() - data, err = s.electricalConnection.GetConnectedPhases() + data, err = s.electricalConnection.GetParameterDescriptionForParameterId(parametertId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addParamDescriptionCurrents() + + data, err = s.electricalConnection.GetParameterDescriptionForParameterId(parametertId) assert.Nil(s.T(), err) - assert.NotEqual(s.T(), uint(0), data) + assert.NotNil(s.T(), data) + + parametertId = model.ElectricalConnectionParameterIdType(10) + data, err = s.electricalConnection.GetParameterDescriptionForParameterId(parametertId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) } -func (s *ElectricalConnectionSuite) Test_GetCurrentsLimits() { - data1, data2, data3, err := s.electricalConnection.GetCurrentsLimits() +func (s *ElectricalConnectionSuite) Test_GetParameterDescriptionForMeasurementId() { + measurementId := model.MeasurementIdType(1) + data, err := s.electricalConnection.GetParameterDescriptionForMeasurementId(measurementId) assert.NotNil(s.T(), err) - assert.Nil(s.T(), data1) - assert.Nil(s.T(), data2) - assert.Nil(s.T(), data3) + assert.Nil(s.T(), data) - s.addParamDescription() + s.addDescription() - data1, data2, data3, err = s.electricalConnection.GetCurrentsLimits() + data, err = s.electricalConnection.GetParameterDescriptionForMeasurementId(measurementId) assert.NotNil(s.T(), err) - assert.Nil(s.T(), data1) - assert.Nil(s.T(), data2) - assert.Nil(s.T(), data3) + assert.Nil(s.T(), data) - s.addPermittedValueSet() + s.addParamDescriptionCurrents() - data1, data2, data3, err = s.electricalConnection.GetCurrentsLimits() + data, err = s.electricalConnection.GetParameterDescriptionForMeasurementId(measurementId) assert.Nil(s.T(), err) - assert.NotNil(s.T(), data1) - assert.NotNil(s.T(), data2) - assert.NotNil(s.T(), data3) + assert.NotNil(s.T(), data) + + measurementId = model.MeasurementIdType(10) + data, err = s.electricalConnection.GetParameterDescriptionForMeasurementId(measurementId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) } -func (s *ElectricalConnectionSuite) Test_GetEVLimitValues() { - data, err := s.electricalConnection.GetEVLimitValues() +func (s *ElectricalConnectionSuite) Test_GetParameterDescriptionForMeasuredPhase() { + phase := model.ElectricalConnectionPhaseNameTypeA + data, err := s.electricalConnection.GetParameterDescriptionForMeasuredPhase(phase) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addDescription() + + data, err = s.electricalConnection.GetParameterDescriptionForMeasuredPhase(phase) assert.NotNil(s.T(), err) assert.Nil(s.T(), data) - s.addParamDescription() + s.addParamDescriptionCurrents() + + data, err = s.electricalConnection.GetParameterDescriptionForMeasuredPhase(phase) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) + + phase = model.ElectricalConnectionPhaseNameTypeBc + data, err = s.electricalConnection.GetParameterDescriptionForMeasuredPhase(phase) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) +} - data, err = s.electricalConnection.GetEVLimitValues() +func (s *ElectricalConnectionSuite) Test_GetPermittedValueSets() { + data, err := s.electricalConnection.GetPermittedValueSets() assert.NotNil(s.T(), err) assert.Nil(s.T(), data) s.addPermittedValueSet() - data, err = s.electricalConnection.GetEVLimitValues() + data, err = s.electricalConnection.GetPermittedValueSets() assert.Nil(s.T(), err) assert.NotNil(s.T(), data) - s.addParamDescription2() + s.addParamDescriptionPower() - data, err = s.electricalConnection.GetEVLimitValues() + data, err = s.electricalConnection.GetPermittedValueSets() assert.Nil(s.T(), err) assert.NotNil(s.T(), data) } +func (s *ElectricalConnectionSuite) Test_GetPermittedValueSetForParameterId() { + parametertId := model.ElectricalConnectionParameterIdType(1) + data, err := s.electricalConnection.GetPermittedValueSetForParameterId(parametertId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addPermittedValueSet() + + data, err = s.electricalConnection.GetPermittedValueSetForParameterId(parametertId) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) + + parametertId = model.ElectricalConnectionParameterIdType(10) + data, err = s.electricalConnection.GetPermittedValueSetForParameterId(parametertId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) +} + +func (s *ElectricalConnectionSuite) Test_GetPermittedValueSetForMeasurementId() { + measurementId := model.MeasurementIdType(1) + data, err := s.electricalConnection.GetPermittedValueSetForMeasurementId(measurementId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addPermittedValueSet() + + data, err = s.electricalConnection.GetPermittedValueSetForMeasurementId(measurementId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addParamDescriptionCurrents() + + data, err = s.electricalConnection.GetPermittedValueSetForMeasurementId(measurementId) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) + + measurementId = model.MeasurementIdType(10) + data, err = s.electricalConnection.GetPermittedValueSetForMeasurementId(measurementId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) +} + +func (s *ElectricalConnectionSuite) Test_GetLimitsForParameterId() { + parameterId := model.ElectricalConnectionParameterIdType(1) + minV, maxV, defaultV, err := s.electricalConnection.GetLimitsForParameterId(parameterId) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), minV, 0.0) + assert.Equal(s.T(), maxV, 0.0) + assert.Equal(s.T(), defaultV, 0.0) + + s.addPermittedValueSet() + s.addParamDescriptionCurrents() + + minV, maxV, defaultV, err = s.electricalConnection.GetLimitsForParameterId(parameterId) + assert.Nil(s.T(), err) + assert.Equal(s.T(), minV, 2.0) + assert.Equal(s.T(), maxV, 16.0) + assert.Equal(s.T(), defaultV, 0.1) +} + +func (s *ElectricalConnectionSuite) Test_AdjustValueToBeWithinPermittedValuesForParameter() { + parameterId := model.ElectricalConnectionParameterIdType(1) + s.addPermittedValueSet() + s.addParamDescriptionCurrents() + + value := s.electricalConnection.AdjustValueToBeWithinPermittedValuesForParameter(20, parameterId) + assert.Equal(s.T(), value, 16.0) + value = s.electricalConnection.AdjustValueToBeWithinPermittedValuesForParameter(2, parameterId) + assert.Equal(s.T(), value, 2.0) + value = s.electricalConnection.AdjustValueToBeWithinPermittedValuesForParameter(1, parameterId) + assert.Equal(s.T(), value, 0.1) +} + // helper -func (s *ElectricalConnectionSuite) addParamDescription() { +func (s *ElectricalConnectionSuite) addDescription() { rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(1))) - fData := &model.ElectricalConnectionParameterDescriptionListDataType{ - ElectricalConnectionParameterDescriptionData: []model.ElectricalConnectionParameterDescriptionDataType{ + fData := &model.ElectricalConnectionDescriptionListDataType{ + ElectricalConnectionDescriptionData: []model.ElectricalConnectionDescriptionDataType{ { - ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), - ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(0)), - MeasurementId: util.Ptr(model.MeasurementIdType(0)), - VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), - AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeAbc), - ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + PowerSupplyType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcConnectedPhases: util.Ptr(uint(3)), + PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume), }, }, } - rF.UpdateData(model.FunctionTypeElectricalConnectionParameterDescriptionListData, fData, nil, nil) + rF.UpdateData(model.FunctionTypeElectricalConnectionDescriptionListData, fData, nil, nil) } -func (s *ElectricalConnectionSuite) addParamDescription2() { +func (s *ElectricalConnectionSuite) addParamDescriptionCurrents() { rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(1))) fData := &model.ElectricalConnectionParameterDescriptionListDataType{ ElectricalConnectionParameterDescriptionData: []model.ElectricalConnectionParameterDescriptionDataType{ { - ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), - ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(0)), - MeasurementId: util.Ptr(model.MeasurementIdType(0)), - VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), - AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeAbc), - ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(1)), + MeasurementId: util.Ptr(model.MeasurementIdType(1)), + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeA), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(2)), + MeasurementId: util.Ptr(model.MeasurementIdType(4)), + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeA), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(3)), + MeasurementId: util.Ptr(model.MeasurementIdType(2)), + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeB), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(4)), + MeasurementId: util.Ptr(model.MeasurementIdType(5)), + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeB), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(5)), + MeasurementId: util.Ptr(model.MeasurementIdType(3)), + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeC), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(6)), + MeasurementId: util.Ptr(model.MeasurementIdType(6)), + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeC), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(7)), + MeasurementId: util.Ptr(model.MeasurementIdType(7)), + VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeAbc), + AcMeasuredInReferenceTo: util.Ptr(model.ElectricalConnectionPhaseNameTypeNeutral), + AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal), + AcMeasurementVariant: util.Ptr(model.ElectricalConnectionMeasurandVariantTypeRms), }, }, } rF.UpdateData(model.FunctionTypeElectricalConnectionParameterDescriptionListData, fData, nil, nil) } -func (s *ElectricalConnectionSuite) addDescription() { +func (s *ElectricalConnectionSuite) addParamDescriptionPower() { rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(1))) - fData := &model.ElectricalConnectionDescriptionListDataType{ - ElectricalConnectionDescriptionData: []model.ElectricalConnectionDescriptionDataType{ + fData := &model.ElectricalConnectionParameterDescriptionListDataType{ + ElectricalConnectionParameterDescriptionData: []model.ElectricalConnectionParameterDescriptionDataType{ { - ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), - PowerSupplyType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), - AcConnectedPhases: util.Ptr(uint(3)), - PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume), + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(8)), + AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeAbc), + ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal), }, }, } - rF.UpdateData(model.FunctionTypeElectricalConnectionDescriptionListData, fData, nil, nil) + rF.UpdateData(model.FunctionTypeElectricalConnectionParameterDescriptionListData, fData, nil, nil) } func (s *ElectricalConnectionSuite) addPermittedValueSet() { @@ -211,11 +391,11 @@ func (s *ElectricalConnectionSuite) addPermittedValueSet() { ElectricalConnectionPermittedValueSetData: []model.ElectricalConnectionPermittedValueSetDataType{ { ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), - ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(1)), PermittedValueSet: []model.ScaledNumberSetType{ { Value: []model.ScaledNumberType{ - *model.NewScaledNumberType(10), + *model.NewScaledNumberType(0.1), }, Range: []model.ScaledNumberRangeType{ { @@ -226,6 +406,57 @@ func (s *ElectricalConnectionSuite) addPermittedValueSet() { }, }, }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(3)), + PermittedValueSet: []model.ScaledNumberSetType{ + { + Value: []model.ScaledNumberType{ + *model.NewScaledNumberType(0.1), + }, + Range: []model.ScaledNumberRangeType{ + { + Min: model.NewScaledNumberType(2), + Max: model.NewScaledNumberType(16), + }, + }, + }, + }, + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(5)), + PermittedValueSet: []model.ScaledNumberSetType{ + { + Value: []model.ScaledNumberType{ + *model.NewScaledNumberType(0.1), + }, + Range: []model.ScaledNumberRangeType{ + { + Min: model.NewScaledNumberType(2), + Max: model.NewScaledNumberType(16), + }, + }, + }, + }, + }, + { + ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), + ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(8)), + PermittedValueSet: []model.ScaledNumberSetType{ + { + Value: []model.ScaledNumberType{ + *model.NewScaledNumberType(0.1), + }, + Range: []model.ScaledNumberRangeType{ + { + Min: model.NewScaledNumberType(400), + Max: model.NewScaledNumberType(113664), + }, + }, + }, + }, + }, }, } rF.UpdateData(model.FunctionTypeElectricalConnectionPermittedValueSetListData, fData, nil, nil) diff --git a/features/feature.go b/features/feature.go index 08c261b8..b47e54f2 100644 --- a/features/feature.go +++ b/features/feature.go @@ -3,7 +3,6 @@ package features import ( "errors" - "github.com/enbility/eebus-go/logging" "github.com/enbility/eebus-go/spine" "github.com/enbility/eebus-go/spine/model" ) @@ -83,7 +82,6 @@ func (f *FeatureImpl) requestData(function model.FunctionType, selectors any, el msgCounter, fErr := f.featureLocal.RequestData(function, selectors, elements, f.featureRemote) if fErr != nil { - logging.Log.Debug(fErr.String()) return nil, errors.New(fErr.String()) } diff --git a/features/helper.go b/features/helper.go deleted file mode 100644 index 73c3dc77..00000000 --- a/features/helper.go +++ /dev/null @@ -1,38 +0,0 @@ -package features - -import ( - "github.com/enbility/eebus-go/service" - "github.com/enbility/eebus-go/spine" - "github.com/enbility/eebus-go/spine/model" -) - -// check if the given usecase, actor is supported by the remote device -func IsUsecaseSupported(usecase model.UseCaseNameType, actor model.UseCaseActorType, remoteDevice *spine.DeviceRemoteImpl) bool { - uci := remoteDevice.UseCaseManager().UseCaseInformation() - for _, element := range uci { - if *element.Actor != actor { - continue - } - for _, uc := range element.UseCaseSupport { - if *uc.UseCaseName == usecase { - return true - } - } - } - - return false -} - -// return the remote entity of a given type and device ski -func EntityOfTypeForSki(service *service.EEBUSService, entityType model.EntityTypeType, ski string) (*spine.EntityRemoteImpl, error) { - rDevice := service.RemoteDeviceForSki(ski) - - entities := rDevice.Entities() - for _, entity := range entities { - if entity.EntityType() == entityType { - return entity, nil - } - } - - return nil, ErrEntityNotFound -} diff --git a/features/identification.go b/features/identification.go index 88f94770..0225febf 100644 --- a/features/identification.go +++ b/features/identification.go @@ -5,11 +5,6 @@ import ( "github.com/enbility/eebus-go/spine/model" ) -type IdentificationType struct { - Identifier string - Type model.IdentificationTypeType -} - type Identification struct { *FeatureImpl } @@ -28,21 +23,12 @@ func NewIdentification(localRole, remoteRole model.RoleType, spineLocalDevice *s } // request FunctionTypeIdentificationListData from a remote entity -func (i *Identification) Request() (*model.MsgCounterType, error) { - msgCounter, err := i.requestData(model.FunctionTypeIdentificationListData, nil, nil) - if err != nil { - return nil, err - } - - return msgCounter, nil +func (i *Identification) RequestValues() (*model.MsgCounterType, error) { + return i.requestData(model.FunctionTypeIdentificationListData, nil, nil) } // return current values for Identification -func (i *Identification) GetValues() ([]IdentificationType, error) { - if i.featureRemote == nil { - return nil, ErrDataNotAvailable - } - +func (i *Identification) GetValues() ([]model.IdentificationDataType, error) { rData := i.featureRemote.Data(model.FunctionTypeIdentificationListData) if rData == nil { return nil, ErrDataNotAvailable @@ -53,22 +39,5 @@ func (i *Identification) GetValues() ([]IdentificationType, error) { return nil, ErrDataNotAvailable } - var resultSet []IdentificationType - - for _, item := range data.IdentificationData { - if item.IdentificationValue == nil { - continue - } - - result := IdentificationType{ - Identifier: string(*item.IdentificationValue), - } - if item.IdentificationType != nil { - result.Type = *item.IdentificationType - } - - resultSet = append(resultSet, result) - } - - return resultSet, nil + return data.IdentificationData, nil } diff --git a/features/identification_test.go b/features/identification_test.go index 7be53e07..37f7942a 100644 --- a/features/identification_test.go +++ b/features/identification_test.go @@ -50,8 +50,8 @@ func (s *IdentificationSuite) BeforeTest(suiteName, testName string) { assert.NotNil(s.T(), s.identification) } -func (s *IdentificationSuite) Test_Request() { - counter, err := s.identification.Request() +func (s *IdentificationSuite) Test_RequestValues() { + counter, err := s.identification.RequestValues() assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } diff --git a/features/incentivetable.go b/features/incentivetable.go index aee75896..a39713ca 100644 --- a/features/incentivetable.go +++ b/features/incentivetable.go @@ -23,31 +23,116 @@ func NewIncentiveTable(localRole, remoteRole model.RoleType, spineLocalDevice *s } // request FunctionTypeIncentiveTableDescriptionData from a remote entity -func (i *IncentiveTable) RequestDescription() error { +func (i *IncentiveTable) RequestDescriptions() error { _, err := i.requestData(model.FunctionTypeIncentiveTableDescriptionData, nil, nil) - if err != nil { - return err - } - - return nil + return err } // request FunctionTypeIncentiveTableConstraintsData from a remote entity func (i *IncentiveTable) RequestConstraints() error { _, err := i.requestData(model.FunctionTypeIncentiveTableConstraintsData, nil, nil) - if err != nil { - return err + return err +} + +// request FunctionTypeIncentiveTableData from a remote entity +func (i *IncentiveTable) RequestValues() (*model.MsgCounterType, error) { + return i.requestData(model.FunctionTypeIncentiveTableData, nil, nil) +} + +// write incentivetable descriptions +// returns an error if this failed +func (i *IncentiveTable) WriteValues(data []model.IncentiveTableType) (*model.MsgCounterType, error) { + if len(data) == 0 { + return nil, ErrMissingData + } + + cmd := model.CmdType{ + IncentiveTableData: &model.IncentiveTableDataType{ + IncentiveTable: data, + }, } - return nil + return i.featureRemote.Sender().Write(i.featureLocal.Address(), i.featureRemote.Address(), cmd) } -// request FunctionTypeIncentiveTableData from a remote entity -func (i *IncentiveTable) RequestValues() error { - _, err := i.requestData(model.FunctionTypeIncentiveTableData, nil, nil) +// return current values for Time Series +func (i *IncentiveTable) GetValues() ([]model.IncentiveTableType, error) { + rData := i.featureRemote.Data(model.FunctionTypeIncentiveTableData) + if rData == nil { + return nil, ErrDataNotAvailable + } + + data := rData.(*model.IncentiveTableDataType) + if data == nil { + return nil, ErrDataNotAvailable + } + + return data.IncentiveTable, nil +} + +// write incentivetable descriptions +// returns an error if this failed +func (i *IncentiveTable) WriteDescriptions(data []model.IncentiveTableDescriptionType) (*model.MsgCounterType, error) { + if len(data) == 0 { + return nil, ErrMissingData + } + + cmd := model.CmdType{ + IncentiveTableDescriptionData: &model.IncentiveTableDescriptionDataType{ + IncentiveTableDescription: data, + }, + } + + return i.featureRemote.Sender().Write(i.featureLocal.Address(), i.featureRemote.Address(), cmd) +} + +// return list of descriptions +func (i *IncentiveTable) GetDescriptions() ([]model.IncentiveTableDescriptionType, error) { + rData := i.featureRemote.Data(model.FunctionTypeIncentiveTableDescriptionData) + if rData == nil { + return nil, ErrDataNotAvailable + } + + data := rData.(*model.IncentiveTableDescriptionDataType) + if data == nil { + return nil, ErrDataNotAvailable + } + + return data.IncentiveTableDescription, nil +} + +// return list of descriptions +func (i *IncentiveTable) GetDescriptionsForScope(scope model.ScopeTypeType) ([]model.IncentiveTableDescriptionType, error) { + data, err := i.GetDescriptions() if err != nil { - return err + return nil, err + } + + var result []model.IncentiveTableDescriptionType + for _, item := range data { + if item.TariffDescription != nil && item.TariffDescription.ScopeType != nil && *item.TariffDescription.ScopeType == scope { + result = append(result, item) + } + } + + if len(result) == 0 { + return nil, ErrDataNotAvailable + } + + return result, nil +} + +// return list of constraints +func (i *IncentiveTable) GetConstraints() ([]model.IncentiveTableConstraintsType, error) { + rData := i.featureRemote.Data(model.FunctionTypeIncentiveTableConstraintsData) + if rData == nil { + return nil, ErrDataNotAvailable + } + + data := rData.(*model.IncentiveTableConstraintsDataType) + if data == nil { + return nil, ErrDataNotAvailable } - return nil + return data.IncentiveTableConstraints, nil } diff --git a/features/incentivetable_test.go b/features/incentivetable_test.go index 173f0f82..2f5d03b5 100644 --- a/features/incentivetable_test.go +++ b/features/incentivetable_test.go @@ -5,6 +5,7 @@ import ( "github.com/enbility/eebus-go/spine" "github.com/enbility/eebus-go/spine/model" + "github.com/enbility/eebus-go/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -51,8 +52,8 @@ func (s *IncentiveTableSuite) BeforeTest(suiteName, testName string) { assert.NotNil(s.T(), s.incentiveTable) } -func (s *IncentiveTableSuite) Test_RequestDescription() { - err := s.incentiveTable.RequestDescription() +func (s *IncentiveTableSuite) Test_RequestDescriptions() { + err := s.incentiveTable.RequestDescriptions() assert.Nil(s.T(), err) } @@ -62,6 +63,235 @@ func (s *IncentiveTableSuite) Test_RequestConstraints() { } func (s *IncentiveTableSuite) Test_RequestValues() { - err := s.incentiveTable.RequestValues() + counter, err := s.incentiveTable.RequestValues() assert.Nil(s.T(), err) + assert.NotNil(s.T(), counter) +} + +func (s *IncentiveTableSuite) Test_WriteValues() { + counter, err := s.incentiveTable.WriteValues(nil) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), counter) + + data := []model.IncentiveTableType{} + counter, err = s.incentiveTable.WriteValues(data) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), counter) + + data = []model.IncentiveTableType{ + { + Tariff: &model.TariffDataType{ + TariffId: util.Ptr(model.TariffIdType(0)), + }, + IncentiveSlot: []model.IncentiveTableIncentiveSlotType{ + { + TimeInterval: &model.TimeTableDataType{ + StartTime: &model.AbsoluteOrRecurringTimeType{ + Relative: model.NewDurationType(0), + }, + }, + Tier: []model.IncentiveTableTierType{ + { + Tier: &model.TierDataType{ + TierId: util.Ptr(model.TierIdType(0)), + }, + Boundary: []model.TierBoundaryDataType{ + { + BoundaryId: util.Ptr(model.TierBoundaryIdType(0)), + LowerBoundaryValue: model.NewScaledNumberType(0), + }, + }, + Incentive: []model.IncentiveDataType{ + { + IncentiveId: util.Ptr(model.IncentiveIdType(1)), + Value: model.NewScaledNumberType(100), + }, + }, + }, + }, + }, + }, + }, + } + + counter, err = s.incentiveTable.WriteValues(data) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), counter) +} + +func (s *IncentiveTableSuite) Test_GetValues() { + data, err := s.incentiveTable.GetValues() + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0, len(data)) + + s.addData() + + data, err = s.incentiveTable.GetValues() + assert.Nil(s.T(), err) + assert.NotEqual(s.T(), nil, data) +} + +func (s *IncentiveTableSuite) Test_WriteDescriptions() { + counter, err := s.incentiveTable.WriteDescriptions(nil) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), counter) + + data := []model.IncentiveTableDescriptionType{} + counter, err = s.incentiveTable.WriteDescriptions(data) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), counter) + + data = []model.IncentiveTableDescriptionType{ + { + TariffDescription: &model.TariffDescriptionDataType{ + TariffId: util.Ptr(model.TariffIdType(0)), + }, + Tier: []model.IncentiveTableDescriptionTierType{ + { + TierDescription: &model.TierDescriptionDataType{ + TierId: util.Ptr(model.TierIdType(0)), + TierType: util.Ptr(model.TierTypeTypeFixedCost), + }, + BoundaryDescription: []model.TierBoundaryDescriptionDataType{ + { + BoundaryId: util.Ptr(model.TierBoundaryIdType(0)), + BoundaryType: util.Ptr(model.TierBoundaryTypeTypePowerBoundary), + BoundaryUnit: util.Ptr(model.UnitOfMeasurementTypeW), + }, + }, + IncentiveDescription: []model.IncentiveDescriptionDataType{ + { + IncentiveId: util.Ptr(model.IncentiveIdType(0)), + IncentiveType: util.Ptr(model.IncentiveTypeTypeAbsoluteCost), + Currency: util.Ptr(model.CurrencyTypeEur), + }, + }, + }, + }, + }, + } + + counter, err = s.incentiveTable.WriteDescriptions(data) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), counter) +} + +func (s *IncentiveTableSuite) Test_GetDescriptions() { + data, err := s.incentiveTable.GetDescriptions() + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0, len(data)) + + s.addDescription() + + data, err = s.incentiveTable.GetDescriptions() + assert.Nil(s.T(), err) + assert.NotEqual(s.T(), nil, data) +} + +func (s *IncentiveTableSuite) Test_GetDescriptionsForScope() { + scope := model.ScopeTypeTypeSimpleIncentiveTable + data, err := s.incentiveTable.GetDescriptionsForScope(scope) + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0, len(data)) + + s.addDescription() + + data, err = s.incentiveTable.GetDescriptionsForScope(scope) + assert.Nil(s.T(), err) + assert.NotEqual(s.T(), nil, data) +} + +func (s *IncentiveTableSuite) Test_GetConstraints() { + data, err := s.incentiveTable.GetConstraints() + assert.NotNil(s.T(), err) + assert.Equal(s.T(), 0, len(data)) + + s.addConstraints() + + data, err = s.incentiveTable.GetConstraints() + assert.Nil(s.T(), err) + assert.NotEqual(s.T(), nil, data) +} + +// helpers + +func (s *IncentiveTableSuite) addData() { + rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(1))) + + fData := &model.IncentiveTableDataType{ + IncentiveTable: []model.IncentiveTableType{ + { + Tariff: &model.TariffDataType{ + TariffId: util.Ptr(model.TariffIdType(0)), + }, + IncentiveSlot: []model.IncentiveTableIncentiveSlotType{ + { + TimeInterval: &model.TimeTableDataType{}, + }, + }, + }, + }, + } + rF.UpdateData(model.FunctionTypeIncentiveTableData, fData, nil, nil) +} + +func (s *IncentiveTableSuite) addDescription() { + rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(1))) + fData := &model.IncentiveTableDescriptionDataType{ + IncentiveTableDescription: []model.IncentiveTableDescriptionType{ + { + TariffDescription: &model.TariffDescriptionDataType{ + TariffId: util.Ptr(model.TariffIdType(0)), + TariffWriteable: util.Ptr(true), + UpdateRequired: util.Ptr(true), + ScopeType: util.Ptr(model.ScopeTypeTypeSimpleIncentiveTable), + }, + Tier: []model.IncentiveTableDescriptionTierType{ + { + TierDescription: &model.TierDescriptionDataType{ + TierId: util.Ptr(model.TierIdType(0)), + TierType: util.Ptr(model.TierTypeTypeDynamicCost), + }, + BoundaryDescription: []model.TierBoundaryDescriptionDataType{ + { + BoundaryId: util.Ptr(model.TierBoundaryIdType(0)), + BoundaryType: util.Ptr(model.TierBoundaryTypeTypePowerBoundary), + BoundaryUnit: util.Ptr(model.UnitOfMeasurementTypeW), + }, + }, + IncentiveDescription: []model.IncentiveDescriptionDataType{ + { + IncentiveId: util.Ptr(model.IncentiveIdType(0)), + IncentiveType: util.Ptr(model.IncentiveTypeTypeAbsoluteCost), + Currency: util.Ptr(model.CurrencyTypeEur), + }, + }, + }, + }, + }, + }, + } + rF.UpdateData(model.FunctionTypeIncentiveTableDescriptionData, fData, nil, nil) +} + +func (s *IncentiveTableSuite) addConstraints() { + rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(1))) + fData := &model.IncentiveTableConstraintsDataType{ + IncentiveTableConstraints: []model.IncentiveTableConstraintsType{ + { + Tariff: &model.TariffDataType{ + TariffId: util.Ptr(model.TariffIdType(0)), + }, + TariffConstraints: &model.TariffOverallConstraintsDataType{ + MaxTiersPerTariff: util.Ptr(model.TierCountType(3)), + MaxBoundariesPerTier: util.Ptr(model.TierBoundaryCountType(1)), + MaxIncentivesPerTier: util.Ptr(model.IncentiveCountType(3)), + }, + IncentiveSlotConstraints: &model.TimeTableConstraintsDataType{ + SlotCountMax: util.Ptr(model.TimeSlotCountType(24)), + }, + }, + }, + } + rF.UpdateData(model.FunctionTypeIncentiveTableConstraintsData, fData, nil, nil) } diff --git a/features/loadcontrol.go b/features/loadcontrol.go index 3777d275..feca212f 100644 --- a/features/loadcontrol.go +++ b/features/loadcontrol.go @@ -5,17 +5,6 @@ import ( "github.com/enbility/eebus-go/spine/model" ) -type LoadControlLimitType struct { - LimitId uint - MeasurementId uint - Category model.LoadControlCategoryType - Unit model.UnitOfMeasurementType - Scope model.ScopeTypeType - IsChangeable bool - IsActive bool - Value float64 -} - type LoadControl struct { *FeatureImpl } @@ -34,42 +23,25 @@ func NewLoadControl(localRole, remoteRole model.RoleType, spineLocalDevice *spin } // request FunctionTypeLoadControlLimitDescriptionListData from a remote device -func (l *LoadControl) RequestLimitDescription() error { - if _, err := l.requestData(model.FunctionTypeLoadControlLimitDescriptionListData, nil, nil); err != nil { - return err - } - - return nil +func (l *LoadControl) RequestLimitDescriptions() error { + _, err := l.requestData(model.FunctionTypeLoadControlLimitDescriptionListData, nil, nil) + return err } // request FunctionTypeLoadControlLimitConstraintsListData from a remote device func (l *LoadControl) RequestLimitConstraints() error { - if _, err := l.requestData(model.FunctionTypeLoadControlLimitConstraintsListData, nil, nil); err != nil { - return err - } - - return nil + _, err := l.requestData(model.FunctionTypeLoadControlLimitConstraintsListData, nil, nil) + return err } // request FunctionTypeLoadControlLimitListData from a remote device -func (l *LoadControl) RequestLimits() (*model.MsgCounterType, error) { - msgCounter, err := l.requestData(model.FunctionTypeLoadControlLimitListData, nil, nil) - if err != nil { - return nil, err - } - - return msgCounter, nil +func (l *LoadControl) RequestLimitValues() (*model.MsgCounterType, error) { + return l.requestData(model.FunctionTypeLoadControlLimitListData, nil, nil) } -type loadControlLimitDescriptionMap map[model.LoadControlLimitIdType]model.LoadControlLimitDescriptionDataType - -// returns the load control descriptions +// returns the load control limit descriptions // returns an error if no description data is available yet -func (l *LoadControl) GetLimitDescription() (loadControlLimitDescriptionMap, error) { - if l.featureRemote == nil { - return nil, ErrDataNotAvailable - } - +func (l *LoadControl) GetLimitDescriptions() ([]model.LoadControlLimitDescriptionDataType, error) { rData := l.featureRemote.Data(model.FunctionTypeLoadControlLimitDescriptionListData) if rData == nil { return nil, ErrMetadataNotAvailable @@ -80,47 +52,58 @@ func (l *LoadControl) GetLimitDescription() (loadControlLimitDescriptionMap, err return nil, ErrDataNotAvailable } - ref := make(loadControlLimitDescriptionMap) - for _, item := range data.LoadControlLimitDescriptionData { - if item.LimitId == nil { - continue - } - ref[*item.LimitId] = item + return data.LoadControlLimitDescriptionData, nil +} + +// returns the load control limit descriptions of a provided category +// returns an error if no description data for the category is available +func (l *LoadControl) GetLimitDescriptionsForCategory(category model.LoadControlCategoryType) ([]model.LoadControlLimitDescriptionDataType, error) { + data, err := l.GetLimitDescriptions() + if err != nil { + return nil, err } - return ref, nil -} + var result []model.LoadControlLimitDescriptionDataType -// returns if a provided category in the load control limit descriptions is available or not -// returns an error if no description data is available yet -func (l *LoadControl) GetLimitDescriptionCategorySupport(category model.LoadControlCategoryType) (bool, error) { - if l.featureRemote == nil { - return false, ErrDataNotAvailable + for _, item := range data { + if item.LimitId != nil && item.LimitCategory != nil && *item.LimitCategory == category { + result = append(result, item) + } + } + + if len(result) == 0 { + return nil, ErrDataNotAvailable } - data, err := l.GetLimitDescription() + return result, nil +} + +// returns the load control limit descriptions for a provided measurementId +// returns an error if no description data for the measurementId is available +func (l *LoadControl) GetLimitDescriptionsForMeasurementId(measurementId model.MeasurementIdType) ([]model.LoadControlLimitDescriptionDataType, error) { + data, err := l.GetLimitDescriptions() if err != nil { - return false, err + return nil, err } + var result []model.LoadControlLimitDescriptionDataType + for _, item := range data { - if item.LimitId == nil || item.LimitCategory == nil { - continue - } - if *item.LimitCategory == category { - return true, nil + if item.LimitId != nil && item.MeasurementId != nil && *item.MeasurementId == measurementId { + result = append(result, item) } } - return false, ErrDataNotAvailable + if len(result) == 0 { + return nil, ErrDataNotAvailable + } + + return result, nil } // write load control limits // returns an error if this failed func (l *LoadControl) WriteLimitValues(data []model.LoadControlLimitDataType) (*model.MsgCounterType, error) { - if l.featureRemote == nil { - return nil, ErrDataNotAvailable - } if len(data) == 0 { return nil, ErrMissingData } @@ -134,83 +117,33 @@ func (l *LoadControl) WriteLimitValues(data []model.LoadControlLimitDataType) (* return l.featureRemote.Sender().Write(l.featureLocal.Address(), l.featureRemote.Address(), cmd) } -func (l *LoadControl) GetLimitValues() ([]LoadControlLimitType, error) { - if l.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - rData := l.featureRemote.Data(model.FunctionTypeLoadControlLimitDescriptionListData) +// return limit data +func (l *LoadControl) GetLimitValues() ([]model.LoadControlLimitDataType, error) { + rData := l.featureRemote.Data(model.FunctionTypeLoadControlLimitListData) if rData == nil { - return nil, ErrMetadataNotAvailable - } - - descriptionData := rData.(*model.LoadControlLimitDescriptionListDataType) - if descriptionData == nil { - return nil, ErrMetadataNotAvailable - } - - descRef := make(map[model.LoadControlLimitIdType]model.LoadControlLimitDescriptionDataType) - for _, item := range descriptionData.LoadControlLimitDescriptionData { - if item.MeasurementId == nil { - continue - } - descRef[*item.LimitId] = item - } - - rData2 := l.featureRemote.Data(model.FunctionTypeLoadControlLimitListData) - if rData2 == nil { return nil, ErrDataNotAvailable } - data := rData2.(*model.LoadControlLimitListDataType) + + data := rData.(*model.LoadControlLimitListDataType) if data == nil { return nil, ErrDataNotAvailable } - var resultSet []LoadControlLimitType - for _, item := range data.LoadControlLimitData { - if item.LimitId == nil { - continue - } - - desc, exists := descRef[*item.LimitId] - if !exists { - continue - } - - result := LoadControlLimitType{ - LimitId: uint(*item.LimitId), - } + return data.LoadControlLimitData, nil +} - if desc.MeasurementId != nil { - result.MeasurementId = uint(*desc.MeasurementId) - } - if desc.LimitCategory != nil { - result.Category = *desc.LimitCategory - } - if desc.ScopeType != nil { - result.Scope = *desc.ScopeType - } - if desc.Unit != nil { - result.Unit = *desc.Unit - } +// return limit values +func (l *LoadControl) GetLimitValueForLimitId(limitId model.LoadControlLimitIdType) (*model.LoadControlLimitDataType, error) { + data, err := l.GetLimitValues() + if err != nil { + return nil, err + } - // EEBus_UC_TS_OverloadProtectionByEvChargingCurrentCurtailment V1.01b 3.2.1.2.2.2 - // If omitted or set to "true", the timePeriod, value and isLimitActive element SHALL be writeable by a client. - result.IsChangeable = true - if item.IsLimitChangeable != nil { - result.IsChangeable = *item.IsLimitChangeable - } - // If set to "true" or omitted, the timePeriod and value element SHALL be applied, at least if timePeriod or value are set. - result.IsActive = true - if item.IsLimitActive != nil { - result.IsActive = *item.IsLimitActive - } - if item.Value != nil { - result.Value = item.Value.GetValue() + for _, item := range data { + if item.LimitId != nil && *item.LimitId == limitId { + return &item, nil } - - resultSet = append(resultSet, result) } - return resultSet, nil + return nil, ErrDataNotAvailable } diff --git a/features/loadcontrol_test.go b/features/loadcontrol_test.go index 58c5a880..75200e5a 100644 --- a/features/loadcontrol_test.go +++ b/features/loadcontrol_test.go @@ -53,7 +53,7 @@ func (s *LoadControlSuite) BeforeTest(suiteName, testName string) { } func (s *LoadControlSuite) Test_RequestLimitDescription() { - err := s.loadControl.RequestLimitDescription() + err := s.loadControl.RequestLimitDescriptions() assert.Nil(s.T(), err) } @@ -63,37 +63,55 @@ func (s *LoadControlSuite) Test_RequestLimitConstraints() { } func (s *LoadControlSuite) Test_RequestLimits() { - counter, err := s.loadControl.RequestLimits() + counter, err := s.loadControl.RequestLimitValues() assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } -func (s *LoadControlSuite) Test_GetLimitDescription() { - data, err := s.loadControl.GetLimitDescription() +func (s *LoadControlSuite) Test_GetLimitDescriptions() { + data, err := s.loadControl.GetLimitDescriptions() assert.NotNil(s.T(), err) assert.Nil(s.T(), data) s.addDescription() - data, err = s.loadControl.GetLimitDescription() + data, err = s.loadControl.GetLimitDescriptions() assert.Nil(s.T(), err) assert.NotNil(s.T(), data) } -func (s *LoadControlSuite) Test_GetLimitDescriptionCategorySupport() { - exists, err := s.loadControl.GetLimitDescriptionCategorySupport(model.LoadControlCategoryTypeObligation) +func (s *LoadControlSuite) Test_GetLimitDescriptionsForCategory() { + data, err := s.loadControl.GetLimitDescriptionsForCategory(model.LoadControlCategoryTypeObligation) assert.NotNil(s.T(), err) - assert.Equal(s.T(), false, exists) + assert.Nil(s.T(), data) s.addDescription() - exists, err = s.loadControl.GetLimitDescriptionCategorySupport(model.LoadControlCategoryTypeOptimization) + data, err = s.loadControl.GetLimitDescriptionsForCategory(model.LoadControlCategoryTypeOptimization) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + data, err = s.loadControl.GetLimitDescriptionsForCategory(model.LoadControlCategoryTypeObligation) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) +} + +func (s *LoadControlSuite) Test_GetLimitDescriptionsForMeasurementId() { + measurementId := model.MeasurementIdType(0) + data, err := s.loadControl.GetLimitDescriptionsForMeasurementId(measurementId) assert.NotNil(s.T(), err) - assert.Equal(s.T(), false, exists) + assert.Nil(s.T(), data) + + s.addDescription() - exists, err = s.loadControl.GetLimitDescriptionCategorySupport(model.LoadControlCategoryTypeObligation) + data, err = s.loadControl.GetLimitDescriptionsForMeasurementId(measurementId) assert.Nil(s.T(), err) - assert.Equal(s.T(), true, exists) + assert.NotNil(s.T(), data) + + measurementId = model.MeasurementIdType(10) + data, err = s.loadControl.GetLimitDescriptionsForMeasurementId(measurementId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) } func (s *LoadControlSuite) Test_WriteLimitValues() { @@ -117,7 +135,7 @@ func (s *LoadControlSuite) Test_WriteLimitValues() { assert.NotNil(s.T(), counter) } -func (s *LoadControlSuite) Test_GetLimitValues() { +func (s *LoadControlSuite) Test_GetLimitData() { data, err := s.loadControl.GetLimitValues() assert.NotNil(s.T(), err) assert.Nil(s.T(), data) @@ -135,6 +153,30 @@ func (s *LoadControlSuite) Test_GetLimitValues() { assert.NotNil(s.T(), data) } +func (s *LoadControlSuite) Test_GetLimitDataForLimitId() { + limitId := model.LoadControlLimitIdType(0) + data, err := s.loadControl.GetLimitValueForLimitId(limitId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addDescription() + + data, err = s.loadControl.GetLimitValueForLimitId(limitId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addData() + + data, err = s.loadControl.GetLimitValueForLimitId(limitId) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) + + limitId = model.LoadControlLimitIdType(10) + data, err = s.loadControl.GetLimitValueForLimitId(limitId) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) +} + // helper func (s *LoadControlSuite) addDescription() { diff --git a/features/measurement.go b/features/measurement.go index 3d5d69a0..8dacef96 100644 --- a/features/measurement.go +++ b/features/measurement.go @@ -1,23 +1,10 @@ package features import ( - "time" - "github.com/enbility/eebus-go/spine" "github.com/enbility/eebus-go/spine/model" ) -type MeasurementType struct { - MeasurementId uint - Value float64 - ValueMin float64 - ValueMax float64 - ValueStep float64 - Unit model.UnitOfMeasurementType - Scope model.ScopeTypeType - Timestamp time.Time -} - type Measurement struct { *FeatureImpl } @@ -36,244 +23,127 @@ func NewMeasurement(localRole, remoteRole model.RoleType, spineLocalDevice *spin } // request FunctionTypeMeasurementDescriptionListData from a remote device -func (m *Measurement) RequestDescription() error { - if _, err := m.requestData(model.FunctionTypeMeasurementDescriptionListData, nil, nil); err != nil { - return err - } +func (m *Measurement) RequestDescriptions() error { + _, err := m.requestData(model.FunctionTypeMeasurementDescriptionListData, nil, nil) - return nil + return err } // request FunctionTypeMeasurementConstraintsListData from a remote entity func (m *Measurement) RequestConstraints() error { - if _, err := m.requestData(model.FunctionTypeMeasurementConstraintsListData, nil, nil); err != nil { - return err - } - - return nil + _, err := m.requestData(model.FunctionTypeMeasurementConstraintsListData, nil, nil) + return err } // request FunctionTypeMeasurementListData from a remote entity -func (m *Measurement) Request() (*model.MsgCounterType, error) { - msgCounter, err := m.requestData(model.FunctionTypeMeasurementListData, nil, nil) - if err != nil { - return nil, err - } - - return msgCounter, nil +func (m *Measurement) RequestValues() (*model.MsgCounterType, error) { + return m.requestData(model.FunctionTypeMeasurementListData, nil, nil) } -// return current value of a defined scope -func (m *Measurement) GetValueForScope(scope model.ScopeTypeType, electricalConnection *ElectricalConnection) (float64, error) { - if m.featureRemote == nil { - return 0, ErrDataNotAvailable - } - - descRef, err := m.GetDescription() - if err != nil { - return 0, ErrMetadataNotAvailable - } - - rData := m.featureRemote.Data(model.FunctionTypeMeasurementListData) +// return list of descriptions +func (m *Measurement) GetDescriptions() ([]model.MeasurementDescriptionDataType, error) { + rData := m.featureRemote.Data(model.FunctionTypeMeasurementDescriptionListData) if rData == nil { - return 0, ErrDataNotAvailable + return nil, ErrMetadataNotAvailable } - - data := rData.(*model.MeasurementListDataType) + data := rData.(*model.MeasurementDescriptionListDataType) if data == nil { - return 0, ErrDataNotAvailable - } - - var result float64 - for _, item := range data.MeasurementData { - if item.MeasurementId == nil || item.Value == nil { - continue - } - - desc, exists := descRef[*item.MeasurementId] - if !exists { - continue - } - - if desc.ScopeType == nil { - continue - } - - if *desc.ScopeType == scope { - return item.Value.GetValue(), nil - } + return nil, ErrMetadataNotAvailable } - return result, nil + return data.MeasurementDescriptionData, nil } -// return current values of a defined scope per phase -// -// returns a map with the phase ("a", "b", "c") as a key -// -// If the scope is not available, it will return an empty map -func (m *Measurement) GetValuesPerPhaseForScope(scope model.ScopeTypeType, electricalConnection *ElectricalConnection) (map[string]float64, error) { - if m.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - descRef, err := m.GetDescription() +// return a list of MeasurementDescriptionDataType for a given scope +func (m *Measurement) GetDescriptionsForScope(scope model.ScopeTypeType) ([]model.MeasurementDescriptionDataType, error) { + data, err := m.GetDescriptions() if err != nil { - return nil, ErrMetadataNotAvailable - } - - paramRef, _, err := electricalConnection.GetParamDescriptionListData() - if err != nil { - return nil, ErrMetadataNotAvailable - } - - rData := m.featureRemote.Data(model.FunctionTypeMeasurementListData) - if rData == nil { - return nil, ErrDataNotAvailable - } - - data := rData.(*model.MeasurementListDataType) - if data == nil { - return nil, ErrDataNotAvailable + return nil, err } - resultSet := make(map[string]float64) - for _, item := range data.MeasurementData { - if item.MeasurementId == nil || item.Value == nil { - continue - } - - param, exists := paramRef[*item.MeasurementId] - if !exists { - continue - } - - desc, exists := descRef[*item.MeasurementId] - if !exists { - continue - } - - if desc.ScopeType == nil || param.AcMeasuredPhases == nil { - continue - } - - if *desc.ScopeType == scope { - resultSet[string(*param.AcMeasuredPhases)] = item.Value.GetValue() + var result []model.MeasurementDescriptionDataType + for _, item := range data { + if item.ScopeType != nil && *item.ScopeType == scope { + result = append(result, item) } } - return resultSet, nil -} - -type measurementDescriptionMap map[model.MeasurementIdType]model.MeasurementDescriptionDataType - -// return a map of MeasurementDescriptionListDataType with measurementId as key -// returns an error if no description data is available yet -func (m *Measurement) GetDescription() (measurementDescriptionMap, error) { - if m.featureRemote == nil { + if len(result) == 0 { return nil, ErrDataNotAvailable } - rData := m.featureRemote.Data(model.FunctionTypeMeasurementDescriptionListData) - if rData == nil { - return nil, ErrMetadataNotAvailable - } - - data := rData.(*model.MeasurementDescriptionListDataType) - if data == nil { - return nil, ErrMetadataNotAvailable - } - - ref := make(measurementDescriptionMap) - for _, item := range data.MeasurementDescriptionData { - if item.MeasurementId == nil { - continue - } - ref[*item.MeasurementId] = item - } - return ref, nil + return result, nil } -// return a map of MeasurementDescriptionListDataType with measurementId as key for a given scope -// returns an error if no description data is available yet -func (m *Measurement) GetDescriptionForScope(scope model.ScopeTypeType) (measurementDescriptionMap, error) { - if m.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - data, err := m.GetDescription() +// return current electrical description for a given measurementId +func (m *Measurement) GetDescriptionForMeasurementId(measurementId model.MeasurementIdType) (*model.MeasurementDescriptionDataType, error) { + descriptions, err := m.GetDescriptions() if err != nil { return nil, err } - ref := make(measurementDescriptionMap) - for _, item := range data { - if item.MeasurementId == nil || item.ScopeType == nil { + for _, item := range descriptions { + if item.MeasurementId == nil || + *item.MeasurementId != measurementId { continue } - if *item.ScopeType == scope { - ref[*item.MeasurementId] = item - } - } - if len(ref) == 0 { - return nil, ErrDataNotAvailable + return &item, nil } - return ref, nil + return nil, ErrMetadataNotAvailable } -// return current SoC for measurements -func (m *Measurement) GetSoC() (float64, error) { - if m.featureRemote == nil { - return 0, ErrDataNotAvailable - } - - descRef, err := m.GetDescription() - if err != nil { - return 0, ErrMetadataNotAvailable - } - +// return current values for measurements +func (m *Measurement) GetValues() ([]model.MeasurementDataType, error) { rData := m.featureRemote.Data(model.FunctionTypeMeasurementListData) if rData == nil { - return 0, ErrDataNotAvailable + return nil, ErrDataNotAvailable } data := rData.(*model.MeasurementListDataType) if data == nil { - return 0, ErrDataNotAvailable + return nil, ErrDataNotAvailable } - for _, item := range data.MeasurementData { - if item.MeasurementId == nil || item.Value == nil { - continue - } + return data.MeasurementData, nil +} + +// return current values of a defined measurementType, commodityType and scopeType +// +// if nothing is found, it will return an error +func (m *Measurement) GetValuesForTypeCommodityScope(measurement model.MeasurementTypeType, commodity model.CommodityTypeType, scope model.ScopeTypeType) ([]model.MeasurementDataType, error) { + values, err := m.GetValues() + if err != nil { + return nil, err + } - desc, exists := descRef[*item.MeasurementId] - if !exists { + var resultSet []model.MeasurementDataType + for _, item := range values { + if item.MeasurementId == nil || item.Value == nil { continue } - if desc.ScopeType == nil { + desc, err := m.GetDescriptionForMeasurementId(*item.MeasurementId) + if err != nil || + desc.MeasurementType == nil || *desc.MeasurementType != measurement || + desc.CommodityType == nil || *desc.CommodityType != commodity || + desc.ScopeType == nil || *desc.ScopeType != scope { continue } - if *desc.ScopeType == model.ScopeTypeTypeStateOfCharge { - return item.Value.GetValue(), nil - } + resultSet = append(resultSet, item) } - return 0, ErrDataNotAvailable -} - -type measurementConstraintMap map[model.MeasurementIdType]model.MeasurementConstraintsDataType - -// return a map of MeasurementDescriptionListDataType with measurementId as key -func (m *Measurement) GetConstraints() (measurementConstraintMap, error) { - if m.featureRemote == nil { + if len(resultSet) == 0 { return nil, ErrDataNotAvailable } + return resultSet, nil +} + +// return measurement constraints +func (m *Measurement) GetConstraints() ([]model.MeasurementConstraintsDataType, error) { rData := m.featureRemote.Data(model.FunctionTypeMeasurementConstraintsListData) if rData == nil { return nil, ErrMetadataNotAvailable @@ -284,87 +154,5 @@ func (m *Measurement) GetConstraints() (measurementConstraintMap, error) { return nil, ErrDataNotAvailable } - ref := make(measurementConstraintMap) - for _, item := range data.MeasurementConstraintsData { - if item.MeasurementId == nil { - continue - } - ref[*item.MeasurementId] = item - } - return ref, nil -} - -// return current values for measurements -func (m *Measurement) GetValues() ([]MeasurementType, error) { - if m.featureRemote == nil { - return nil, ErrDataNotAvailable - } - - // constraints can be optional - constraintsRef, _ := m.GetConstraints() - - descRef, err := m.GetDescription() - if err != nil { - return nil, ErrMetadataNotAvailable - } - - rData := m.featureRemote.Data(model.FunctionTypeMeasurementListData) - if rData == nil { - return nil, ErrDataNotAvailable - } - - data := rData.(*model.MeasurementListDataType) - if data == nil { - return nil, ErrDataNotAvailable - } - - var resultSet []MeasurementType - for _, item := range data.MeasurementData { - if item.MeasurementId == nil { - continue - } - - desc, exists := descRef[*item.MeasurementId] - if !exists { - continue - } - - result := MeasurementType{ - MeasurementId: uint(*item.MeasurementId), - } - - if item.Value != nil { - result.Value = item.Value.GetValue() - } - - if item.Timestamp != nil { - if value, err := item.Timestamp.GetDateTimeType().GetTime(); err == nil { - result.Timestamp = value - } - } - - if desc.ScopeType != nil { - result.Scope = *desc.ScopeType - } - if desc.Unit != nil { - result.Unit = *desc.Unit - } - - constraint, exists := constraintsRef[*item.MeasurementId] - if exists { - if constraint.ValueRangeMin != nil { - result.ValueMin = constraint.ValueRangeMin.GetValue() - } - if constraint.ValueRangeMax != nil { - result.ValueMax = constraint.ValueRangeMax.GetValue() - } - if constraint.ValueStepSize != nil { - result.ValueStep = constraint.ValueStepSize.GetValue() - } - } - - resultSet = append(resultSet, result) - } - - return resultSet, nil + return data.MeasurementConstraintsData, nil } diff --git a/features/measurement_test.go b/features/measurement_test.go index b4ffbc73..a8c1c377 100644 --- a/features/measurement_test.go +++ b/features/measurement_test.go @@ -21,9 +21,8 @@ type MeasurementSuite struct { localDevice *spine.DeviceLocalImpl remoteEntity *spine.EntityRemoteImpl - measurement *Measurement - electricalConnection *ElectricalConnection - sentMessage []byte + measurement *Measurement + sentMessage []byte } var _ spine.SpineDataConnection = (*MeasurementSuite)(nil) @@ -60,14 +59,10 @@ func (s *MeasurementSuite) BeforeTest(suiteName, testName string) { s.measurement, err = NewMeasurement(model.RoleTypeServer, model.RoleTypeClient, s.localDevice, s.remoteEntity) assert.Nil(s.T(), err) assert.NotNil(s.T(), s.measurement) - - s.electricalConnection, err = NewElectricalConnection(model.RoleTypeServer, model.RoleTypeClient, s.localDevice, s.remoteEntity) - assert.Nil(s.T(), err) - assert.NotNil(s.T(), s.electricalConnection) } -func (s *MeasurementSuite) Test_RequestLimitDescription() { - err := s.measurement.RequestDescription() +func (s *MeasurementSuite) Test_RequestDescriptions() { + err := s.measurement.RequestDescriptions() assert.Nil(s.T(), err) } @@ -76,82 +71,49 @@ func (s *MeasurementSuite) Test_RequestConstraints() { assert.Nil(s.T(), err) } -func (s *MeasurementSuite) Test_Request() { - counter, err := s.measurement.Request() +func (s *MeasurementSuite) Test_RequestValues() { + counter, err := s.measurement.RequestValues() assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } -func (s *MeasurementSuite) Test_GetValueForScope() { - data, err := s.measurement.GetValueForScope(model.ScopeTypeTypeACCurrent, s.electricalConnection) - assert.NotNil(s.T(), err) - assert.Equal(s.T(), 0.0, data) - - s.addDescription() - - data, err = s.measurement.GetValueForScope(model.ScopeTypeTypeACCurrent, s.electricalConnection) - assert.NotNil(s.T(), err) - assert.Equal(s.T(), 0.0, data) - - s.addData() - - data, err = s.measurement.GetValueForScope(model.ScopeTypeTypeACCurrent, s.electricalConnection) - assert.Nil(s.T(), err) - assert.NotEqual(s.T(), 0.0, data) -} +func (s *MeasurementSuite) Test_GetValuesForTypeCommodityScope() { + measurement := model.MeasurementTypeTypeCurrent + commodity := model.CommodityTypeTypeElectricity + scope := model.ScopeTypeTypeACCurrent -func (s *MeasurementSuite) Test_GetValuesPerPhaseForScope() { - data, err := s.measurement.GetValuesPerPhaseForScope(model.ScopeTypeTypeACCurrent, s.electricalConnection) + data, err := s.measurement.GetValuesForTypeCommodityScope(measurement, commodity, scope) assert.NotNil(s.T(), err) assert.Nil(s.T(), data) s.addDescription() - data, err = s.measurement.GetValuesPerPhaseForScope(model.ScopeTypeTypeACCurrent, s.electricalConnection) - assert.NotNil(s.T(), err) - assert.Nil(s.T(), data) - - s.addElectricalParamDescription() - - data, err = s.measurement.GetValuesPerPhaseForScope(model.ScopeTypeTypeACCurrent, s.electricalConnection) + data, err = s.measurement.GetValuesForTypeCommodityScope(measurement, commodity, scope) assert.NotNil(s.T(), err) assert.Nil(s.T(), data) s.addData() - data, err = s.measurement.GetValuesPerPhaseForScope(model.ScopeTypeTypeACCurrent, s.electricalConnection) + data, err = s.measurement.GetValuesForTypeCommodityScope(measurement, commodity, scope) assert.Nil(s.T(), err) assert.NotNil(s.T(), data) -} -func (s *MeasurementSuite) Test_GetDescriptionForScope() { - data, err := s.measurement.GetDescriptionForScope(model.ScopeTypeTypeACCurrent) + measurement = model.MeasurementTypeTypeArea + data, err = s.measurement.GetValuesForTypeCommodityScope(measurement, commodity, scope) assert.NotNil(s.T(), err) assert.Nil(s.T(), data) - - s.addDescription() - - data, err = s.measurement.GetDescriptionForScope(model.ScopeTypeTypeACCurrent) - assert.Nil(s.T(), err) - assert.NotNil(s.T(), data) } -func (s *MeasurementSuite) Test_GetSoC() { - data, err := s.measurement.GetSoC() +func (s *MeasurementSuite) Test_GetDescriptionsForScope() { + data, err := s.measurement.GetDescriptionsForScope(model.ScopeTypeTypeACCurrent) assert.NotNil(s.T(), err) - assert.Equal(s.T(), 0.0, data) + assert.Nil(s.T(), data) s.addDescription() - data, err = s.measurement.GetSoC() - assert.NotNil(s.T(), err) - assert.Equal(s.T(), 0.0, data) - - s.addData() - - data, err = s.measurement.GetSoC() + data, err = s.measurement.GetDescriptionsForScope(model.ScopeTypeTypeACCurrent) assert.Nil(s.T(), err) - assert.NotEqual(s.T(), 0, data) + assert.NotNil(s.T(), data) } func (s *MeasurementSuite) Test_GetConstraints() { @@ -193,13 +155,17 @@ func (s *MeasurementSuite) addDescription() { fData := &model.MeasurementDescriptionListDataType{ MeasurementDescriptionData: []model.MeasurementDescriptionDataType{ { - MeasurementId: util.Ptr(model.MeasurementIdType(0)), - ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), - Unit: util.Ptr(model.UnitOfMeasurementTypeA), + MeasurementId: util.Ptr(model.MeasurementIdType(0)), + MeasurementType: util.Ptr(model.MeasurementTypeTypeCurrent), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), + Unit: util.Ptr(model.UnitOfMeasurementTypeA), }, { - MeasurementId: util.Ptr(model.MeasurementIdType(1)), - ScopeType: util.Ptr(model.ScopeTypeTypeStateOfCharge), + MeasurementId: util.Ptr(model.MeasurementIdType(1)), + MeasurementType: util.Ptr(model.MeasurementTypeTypePercentage), + CommodityType: util.Ptr(model.CommodityTypeTypeElectricity), + ScopeType: util.Ptr(model.ScopeTypeTypeStateOfCharge), }, }, } @@ -227,23 +193,6 @@ func (s *MeasurementSuite) addConstraints() { rF.UpdateData(model.FunctionTypeMeasurementConstraintsListData, fData, nil, nil) } -func (s *MeasurementSuite) addElectricalParamDescription() { - rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(2))) - fData := &model.ElectricalConnectionParameterDescriptionListDataType{ - ElectricalConnectionParameterDescriptionData: []model.ElectricalConnectionParameterDescriptionDataType{ - { - ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)), - ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(0)), - MeasurementId: util.Ptr(model.MeasurementIdType(0)), - VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc), - AcMeasuredPhases: util.Ptr(model.ElectricalConnectionPhaseNameTypeAbc), - ScopeType: util.Ptr(model.ScopeTypeTypeACCurrent), - }, - }, - } - rF.UpdateData(model.FunctionTypeElectricalConnectionParameterDescriptionListData, fData, nil, nil) -} - func (s *MeasurementSuite) addData() { rF := s.remoteEntity.Feature(util.Ptr(model.AddressFeatureType(1))) diff --git a/features/timeseries.go b/features/timeseries.go index 8a1e7b09..7b3d02c2 100644 --- a/features/timeseries.go +++ b/features/timeseries.go @@ -1,52 +1,10 @@ package features import ( - "time" - "github.com/enbility/eebus-go/spine" "github.com/enbility/eebus-go/spine/model" ) -type TimeSeriesSlotType struct { - TimeSeriesSlotId uint - PeriodStartTime time.Duration - PeriodEndTime time.Duration - Duration time.Duration - Value float64 - ValueMin float64 - ValueMax float64 -} - -type TimeSeriesType struct { - TimeSeriesId uint - PeriodStartTime time.Duration - PeriodEndTime time.Duration - Slots []TimeSeriesSlotType -} - -type TimeSeriesDescriptionType struct { - TimeSeriesId uint - Type model.TimeSeriesTypeType // Description - MeasurementId uint // Description - IsWriteable bool // Description - IsUpdateRequired bool // Description - Unit model.UnitOfMeasurementType // Description -} - -type TimeSeriesConstraintsType struct { - TimeSeriesId uint - SlotCountMin uint // Constraints - SlotCountMax uint // Constraints - SlotDurationMin time.Duration // Constraints - SlotDurationMax time.Duration // Constraints - SlotDurationStep time.Duration // Constraints - EarliestStartTime time.Time // Constraints - LatestEndTime time.Time // Constraints - SlotValueMin float64 // Constraints - SlotValueMax float64 // Constraints - SlotValueStep float64 // Constraints -} - type TimeSeries struct { *FeatureImpl } @@ -65,41 +23,40 @@ func NewTimeSeries(localRole, remoteRole model.RoleType, spineLocalDevice *spine } // request FunctionTypeTimeSeriesDescriptionListData from a remote entity -func (t *TimeSeries) RequestDescription() error { +func (t *TimeSeries) RequestDescriptions() error { _, err := t.requestData(model.FunctionTypeTimeSeriesDescriptionListData, nil, nil) - if err != nil { - return err - } - - return nil + return err } // request FunctionTypeTimeSeriesConstraintsListData from a remote entity func (t *TimeSeries) RequestConstraints() error { _, err := t.requestData(model.FunctionTypeTimeSeriesConstraintsListData, nil, nil) - if err != nil { - return err - } - - return nil + return err } // request FunctionTypeTimeSeriesListData from a remote device -func (t *TimeSeries) Request() (*model.MsgCounterType, error) { - msgCounter, err := t.requestData(model.FunctionTypeTimeSeriesListData, nil, nil) - if err != nil { - return nil, err +func (t *TimeSeries) RequestValues() (*model.MsgCounterType, error) { + return t.requestData(model.FunctionTypeTimeSeriesListData, nil, nil) +} + +// write Time Series values +// returns an error if this failed +func (t *TimeSeries) WriteValues(data []model.TimeSeriesDataType) (*model.MsgCounterType, error) { + if len(data) == 0 { + return nil, ErrMissingData } - return msgCounter, nil + cmd := model.CmdType{ + TimeSeriesListData: &model.TimeSeriesListDataType{ + TimeSeriesData: data, + }, + } + + return t.featureRemote.Sender().Write(t.featureLocal.Address(), t.featureRemote.Address(), cmd) } // return current values for Time Series -func (t *TimeSeries) GetValues() ([]TimeSeriesType, error) { - if t.featureRemote == nil { - return nil, ErrDataNotAvailable - } - +func (t *TimeSeries) GetValues() ([]model.TimeSeriesDataType, error) { rData := t.featureRemote.Data(model.FunctionTypeTimeSeriesListData) if rData == nil { return nil, ErrDataNotAvailable @@ -110,78 +67,39 @@ func (t *TimeSeries) GetValues() ([]TimeSeriesType, error) { return nil, ErrDataNotAvailable } - var resultSet []TimeSeriesType + return data.TimeSeriesData, nil +} + +// return current value for a given TimeSeriesType +// there can only be one item matching the type +func (t *TimeSeries) GetValueForType(timeSeriesType model.TimeSeriesTypeType) (*model.TimeSeriesDataType, error) { + data, err := t.GetValues() + if err != nil { + return nil, err + } - for _, item := range data.TimeSeriesData { + for _, item := range data { if item.TimeSeriesId == nil { continue } - result := TimeSeriesType{ - TimeSeriesId: uint(*item.TimeSeriesId), - } - - if item.TimePeriod != nil { - if item.TimePeriod.StartTime != nil { - if value, err := item.TimePeriod.StartTime.GetTimeDuration(); err == nil { - result.PeriodStartTime = value - } - } - if item.TimePeriod.EndTime != nil { - if value, err := item.TimePeriod.EndTime.GetTimeDuration(); err == nil { - result.PeriodEndTime = value - } - } + desc, err := t.GetDescriptionForId(*item.TimeSeriesId) + if err != nil { + continue } - var slots []TimeSeriesSlotType - for _, slot := range item.TimeSeriesSlot { - element := TimeSeriesSlotType{ - TimeSeriesSlotId: uint(*slot.TimeSeriesSlotId), - } - if slot.Value != nil { - element.Value = slot.Value.GetValue() - } - if slot.MinValue != nil { - element.ValueMin = slot.MinValue.GetValue() - } - if slot.MaxValue != nil { - element.ValueMax = slot.MaxValue.GetValue() - } - if slot.TimePeriod != nil { - if slot.TimePeriod.StartTime != nil { - if value, err := slot.TimePeriod.StartTime.GetTimeDuration(); err == nil { - element.PeriodStartTime = value - } - } - if slot.TimePeriod.EndTime != nil { - if value, err := slot.TimePeriod.EndTime.GetTimeDuration(); err == nil { - element.PeriodEndTime = value - } - } - } - if slot.Duration != nil { - if value, err := slot.Duration.GetTimeDuration(); err == nil { - element.Duration = value - } - } - - slots = append(slots, element) + if desc.TimeSeriesType == nil || *desc.TimeSeriesType != timeSeriesType { + continue } - result.Slots = slots - resultSet = append(resultSet, result) + return &item, nil } - return resultSet, nil + return nil, ErrDataNotAvailable } -// return current description values for Time Series -func (t *TimeSeries) GetDescriptionValues() ([]TimeSeriesDescriptionType, error) { - if t.featureRemote == nil { - return nil, ErrDataNotAvailable - } - +// return list of descriptions +func (t *TimeSeries) GetDescriptions() ([]model.TimeSeriesDescriptionDataType, error) { rData := t.featureRemote.Data(model.FunctionTypeTimeSeriesDescriptionListData) if rData == nil { return nil, ErrDataNotAvailable @@ -192,45 +110,41 @@ func (t *TimeSeries) GetDescriptionValues() ([]TimeSeriesDescriptionType, error) return nil, ErrDataNotAvailable } - var resultSet []TimeSeriesDescriptionType - - for _, item := range data.TimeSeriesDescriptionData { - if item.TimeSeriesId == nil { - continue - } + return data.TimeSeriesDescriptionData, nil +} - result := TimeSeriesDescriptionType{ - TimeSeriesId: uint(*item.TimeSeriesId), - } +func (t *TimeSeries) GetDescriptionForId(id model.TimeSeriesIdType) (*model.TimeSeriesDescriptionDataType, error) { + data, err := t.GetDescriptions() + if err != nil { + return nil, err + } - if item.TimeSeriesType != nil { - result.Type = *item.TimeSeriesType - } - if item.MeasurementId != nil { - result.MeasurementId = uint(*item.MeasurementId) - } - if item.TimeSeriesWriteable != nil { - result.IsWriteable = *item.TimeSeriesWriteable - } - if item.UpdateRequired != nil { - result.IsUpdateRequired = *item.UpdateRequired - } - if item.Unit != nil { - result.Unit = *item.Unit + for _, item := range data { + if item.TimeSeriesId != nil && *item.TimeSeriesId == id { + return &item, nil } - - resultSet = append(resultSet, result) } - return resultSet, nil + return nil, ErrDataNotAvailable } -// return current constraint values for Time Series -func (t *TimeSeries) GetConstraintValues() ([]TimeSeriesConstraintsType, error) { - if t.featureRemote == nil { - return nil, ErrDataNotAvailable +func (t *TimeSeries) GetDescriptionForType(timeSeriesType model.TimeSeriesTypeType) (*model.TimeSeriesDescriptionDataType, error) { + data, err := t.GetDescriptions() + if err != nil { + return nil, err + } + + for _, item := range data { + if item.TimeSeriesType != nil && *item.TimeSeriesType == timeSeriesType { + return &item, nil + } } + return nil, ErrDataNotAvailable +} + +// return current constraints for Time Series +func (t *TimeSeries) GetConstraints() ([]model.TimeSeriesConstraintsDataType, error) { rData := t.featureRemote.Data(model.FunctionTypeTimeSeriesConstraintsListData) switch constraintsData := rData.(type) { case *model.TimeSeriesConstraintsListDataType: @@ -244,60 +158,5 @@ func (t *TimeSeries) GetConstraintValues() ([]TimeSeriesConstraintsType, error) return nil, ErrDataNotAvailable } - var resultSet []TimeSeriesConstraintsType - - for _, item := range data.TimeSeriesConstraintsData { - if item.TimeSeriesId == nil { - continue - } - - result := TimeSeriesConstraintsType{ - TimeSeriesId: uint(*item.TimeSeriesId), - } - - if item.SlotCountMin != nil { - result.SlotCountMin = uint(*item.SlotCountMin) - } - if item.SlotCountMax != nil { - result.SlotCountMax = uint(*item.SlotCountMax) - } - if item.SlotDurationMin != nil { - if value, err := item.SlotDurationMin.GetTimeDuration(); err == nil { - result.SlotDurationMin = value - } - } - if item.SlotDurationMax != nil { - if value, err := item.SlotDurationMax.GetTimeDuration(); err == nil { - result.SlotDurationMax = value - } - } - if item.SlotDurationStepSize != nil { - if value, err := item.SlotDurationStepSize.GetTimeDuration(); err == nil { - result.SlotDurationStep = value - } - } - if item.EarliestTimeSeriesStartTime != nil { - if value, err := item.EarliestTimeSeriesStartTime.GetTime(); err == nil { - result.EarliestStartTime = value - } - } - if item.LatestTimeSeriesEndTime != nil { - if value, err := item.LatestTimeSeriesEndTime.GetTime(); err == nil { - result.LatestEndTime = value - } - } - if item.SlotValueMin != nil { - result.SlotValueMin = item.SlotValueMin.GetValue() - } - if item.SlotValueMax != nil { - result.SlotValueMax = item.SlotValueMax.GetValue() - } - if item.SlotValueStepSize != nil { - result.SlotValueStep = item.SlotValueStepSize.GetValue() - } - - resultSet = append(resultSet, result) - } - - return resultSet, nil + return data.TimeSeriesConstraintsData, nil } diff --git a/features/timeseries_test.go b/features/timeseries_test.go index eaacf6f8..97c0a107 100644 --- a/features/timeseries_test.go +++ b/features/timeseries_test.go @@ -54,7 +54,7 @@ func (s *TimeSeriesSuite) BeforeTest(suiteName, testName string) { } func (s *TimeSeriesSuite) Test_RequestDescription() { - err := s.timeSeries.RequestDescription() + err := s.timeSeries.RequestDescriptions() assert.Nil(s.T(), err) } @@ -63,8 +63,28 @@ func (s *TimeSeriesSuite) Test_RequestConstraints() { assert.Nil(s.T(), err) } -func (s *TimeSeriesSuite) Test_Request() { - counter, err := s.timeSeries.Request() +func (s *TimeSeriesSuite) Test_RequestValues() { + counter, err := s.timeSeries.RequestValues() + assert.Nil(s.T(), err) + assert.NotNil(s.T(), counter) +} + +func (s *TimeSeriesSuite) Test_WriteValues() { + counter, err := s.timeSeries.WriteValues(nil) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), counter) + + data := []model.TimeSeriesDataType{} + counter, err = s.timeSeries.WriteValues(data) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), counter) + + data = []model.TimeSeriesDataType{ + { + TimeSeriesId: util.Ptr(model.TimeSeriesIdType(1)), + }, + } + counter, err = s.timeSeries.WriteValues(data) assert.Nil(s.T(), err) assert.NotNil(s.T(), counter) } @@ -81,26 +101,78 @@ func (s *TimeSeriesSuite) Test_GetValues() { assert.NotEqual(s.T(), nil, data) } -func (s *TimeSeriesSuite) Test_GetDescriptionValues() { - data, err := s.timeSeries.GetDescriptionValues() +func (s *TimeSeriesSuite) Test_GetValuesForId() { + data, err := s.timeSeries.GetValueForType(model.TimeSeriesTypeTypeSingleDemand) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addData() + + data, err = s.timeSeries.GetValueForType(model.TimeSeriesTypeTypeSingleDemand) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addDescription() + + data, err = s.timeSeries.GetValueForType(model.TimeSeriesTypeTypeSingleDemand) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) + + data, err = s.timeSeries.GetValueForType(model.TimeSeriesTypeTypePlan) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) +} + +func (s *TimeSeriesSuite) Test_GetDescriptions() { + data, err := s.timeSeries.GetDescriptions() assert.NotNil(s.T(), err) assert.Equal(s.T(), 0, len(data)) s.addDescription() - data, err = s.timeSeries.GetDescriptionValues() + data, err = s.timeSeries.GetDescriptions() assert.Nil(s.T(), err) assert.NotEqual(s.T(), nil, data) } -func (s *TimeSeriesSuite) Test_GetConstraintValues() { - data, err := s.timeSeries.GetConstraintValues() +func (s *TimeSeriesSuite) Test_GetDescriptionsForId() { + id := model.TimeSeriesIdType(0) + data, err := s.timeSeries.GetDescriptionForId(id) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addDescription() + + data, err = s.timeSeries.GetDescriptionForId(id) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) + + id = model.TimeSeriesIdType(1) + data, err = s.timeSeries.GetDescriptionForId(id) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) +} + +func (s *TimeSeriesSuite) Test_GetDescriptionForType() { + data, err := s.timeSeries.GetDescriptionForType(model.TimeSeriesTypeTypeSingleDemand) + assert.NotNil(s.T(), err) + assert.Nil(s.T(), data) + + s.addDescription() + + data, err = s.timeSeries.GetDescriptionForType(model.TimeSeriesTypeTypeSingleDemand) + assert.Nil(s.T(), err) + assert.NotNil(s.T(), data) +} + +func (s *TimeSeriesSuite) Test_GetConstraints() { + data, err := s.timeSeries.GetConstraints() assert.NotNil(s.T(), err) assert.Equal(s.T(), 0, len(data)) s.addConstraints() - data, err = s.timeSeries.GetConstraintValues() + data, err = s.timeSeries.GetConstraints() assert.Nil(s.T(), err) assert.NotEqual(s.T(), nil, data) } diff --git a/integration_tests/emobility_measurement_test.go b/integration_tests/emobility_measurement_test.go index 5f84545a..f64d96f1 100644 --- a/integration_tests/emobility_measurement_test.go +++ b/integration_tests/emobility_measurement_test.go @@ -75,27 +75,34 @@ func (s *EmobilityMeasurementSuite) TestGetValuesPerPhaseForScope() { msgCounter, _ = s.readHandler.HandleIncomingSpineMesssage(loadFileData(s.T(), m_measurementListData_recv_notify_file_path)) waitForAck(s.T(), msgCounter, s.writeHandler) - resultMap, err := s.measurement.GetValuesPerPhaseForScope(model.ScopeTypeTypeACCurrent, s.electricalconnection) + measurement := model.MeasurementTypeTypeCurrent + commodity := model.CommodityTypeTypeElectricity + scope := model.ScopeTypeTypeACCurrent + data, err := s.measurement.GetValuesForTypeCommodityScope(measurement, commodity, scope) // Assert assert.Nil(s.T(), err) - assert.NotNil(s.T(), resultMap) - assert.Equal(s.T(), 1, len(resultMap)) - assert.Equal(s.T(), 5.0, resultMap["a"]) + assert.NotNil(s.T(), data) + assert.Equal(s.T(), 1, len(data)) + assert.Equal(s.T(), 5.0, data[0].Value.GetValue()) - resultMap, err = s.measurement.GetValuesPerPhaseForScope(model.ScopeTypeTypeACPower, s.electricalconnection) + measurement = model.MeasurementTypeTypePower + scope = model.ScopeTypeTypeACPower + data, err = s.measurement.GetValuesForTypeCommodityScope(measurement, commodity, scope) // Assert assert.Nil(s.T(), err) - assert.NotNil(s.T(), resultMap) - assert.Equal(s.T(), 1, len(resultMap)) - assert.Equal(s.T(), 1185.0, resultMap["a"]) + assert.NotNil(s.T(), data) + assert.Equal(s.T(), 1, len(data)) + assert.Equal(s.T(), 1185.0, data[0].Value.GetValue()) - resultMap, err = s.measurement.GetValuesPerPhaseForScope(model.ScopeTypeTypeCharge, s.electricalconnection) + measurement = model.MeasurementTypeTypeEnergy + scope = model.ScopeTypeTypeCharge + data, err = s.measurement.GetValuesForTypeCommodityScope(measurement, commodity, scope) // Assert assert.Nil(s.T(), err) - assert.NotNil(s.T(), resultMap) - assert.Equal(s.T(), 1, len(resultMap)) - assert.Equal(s.T(), 1825.0, resultMap["a"]) + assert.NotNil(s.T(), data) + assert.Equal(s.T(), 1, len(data)) + assert.Equal(s.T(), 1825.0, data[0].Value.GetValue()) } diff --git a/service/mdns.go b/service/mdns.go index 3cadc334..6b4eef5d 100644 --- a/service/mdns.go +++ b/service/mdns.go @@ -313,6 +313,7 @@ func (m *mdns) resolveEntries() { if m.av != nil { // instead of limiting search on specific allowed interfaces, we allow all and filter the results if avBrowser, err = m.av.ServiceBrowserNew(avahi.InterfaceUnspec, avahi.ProtoUnspec, shipZeroConfServiceType, shipZeroConfDomain, 0); err != nil { + logging.Log.Debug("mdns: error setting up avahi browser:", err) return } } else { @@ -401,7 +402,7 @@ func (m *mdns) processAvahiService(service avahi.Service, remove bool) { } if !allow { - logging.Log.Debug("avahi - gnoring service as its interface is not in the allowed list:", service.Name) + logging.Log.Debug("avahi - ignoring service as its interface is not in the allowed list:", service.Name) return } diff --git a/ship/connection.go b/ship/connection.go index 2aa46f94..af82b42f 100644 --- a/ship/connection.go +++ b/ship/connection.go @@ -114,7 +114,10 @@ func (c *ShipConnection) CloseConnection(safe bool, reason string) { } _ = c.sendShipModel(model.MsgTypeEnd, closeMessage) - return + + if c.smeState != smeError { + return + } } c.DataHandler.CloseDataConnection() diff --git a/ship/handshake.go b/ship/handshake.go index fe444f95..1ef1fd58 100644 --- a/ship/handshake.go +++ b/ship/handshake.go @@ -77,6 +77,10 @@ func (c *ShipConnection) getState() shipMessageExchangeState { // handle handshake state transitions func (c *ShipConnection) handleState(timeout bool, message []byte) { switch c.getState() { + case smeError: + logging.Log.Debug(c.RemoteSKI, "connection is in error state") + return + // cmiStateInit case cmiStateInitStart: // triggered without a message received @@ -84,8 +88,7 @@ func (c *ShipConnection) handleState(timeout bool, message []byte) { case cmiStateClientWait: if timeout { - logging.Log.Trace("timeout") - c.endHandshakeWithError(errors.New("ship handshake timeout")) + c.endHandshakeWithError(errors.New("ship client handshake timeout")) return } @@ -93,8 +96,7 @@ func (c *ShipConnection) handleState(timeout bool, message []byte) { case cmiStateServerWait: if timeout { - logging.Log.Trace("timeout") - c.endHandshakeWithError(errors.New("ship handshake timeout")) + c.endHandshakeWithError(errors.New("ship server handshake timeout")) return } c.handshakeInit_cmiStateServerWait(message) @@ -111,7 +113,6 @@ func (c *ShipConnection) handleState(timeout bool, message []byte) { case smeHelloStateReadyListen: if timeout { - logging.Log.Trace("timeout") c.setState(smeHelloStateAbort) c.handleState(false, nil) return @@ -124,7 +125,6 @@ func (c *ShipConnection) handleState(timeout bool, message []byte) { case smeHelloStatePendingListen: if timeout { - logging.Log.Trace("timeout") c.handshakeHello_PendingTimeout() return } diff --git a/ship/hs_hello.go b/ship/hs_hello.go index 1c6451c5..2f94895e 100644 --- a/ship/hs_hello.go +++ b/ship/hs_hello.go @@ -69,6 +69,8 @@ func (c *ShipConnection) handshakeHello_ReadyListen(message []byte) { // SME_HELLO_ABORT func (c *ShipConnection) handshakeHello_Abort() { + c.stopHandshakeTimer() + if err := c.handshakeHelloSend(model.ConnectionHelloPhaseTypeAborted, 0, false); err != nil { c.endHandshakeWithError(err) return diff --git a/ship/hs_helper_test.go b/ship/hs_helper_test.go index 94cc56b6..0a74ac0f 100644 --- a/ship/hs_helper_test.go +++ b/ship/hs_helper_test.go @@ -13,6 +13,8 @@ type dataHandlerTest struct { sentMessage []byte mux sync.Mutex + + handleConnectionClosedInvoked bool } func (s *dataHandlerTest) lastMessage() []byte { @@ -44,9 +46,11 @@ func (s *dataHandlerTest) HandleClosedConnection(connection *ShipConnection) {} var _ ShipServiceDataProvider = (*dataHandlerTest)(nil) -func (s *dataHandlerTest) IsRemoteServiceForSKIPaired(string) bool { return true } -func (s *dataHandlerTest) HandleConnectionClosed(*ShipConnection, bool) {} -func (s *dataHandlerTest) ReportServiceShipID(string, string) {} +func (s *dataHandlerTest) IsRemoteServiceForSKIPaired(string) bool { return true } +func (s *dataHandlerTest) HandleConnectionClosed(*ShipConnection, bool) { + s.handleConnectionClosedInvoked = true +} +func (s *dataHandlerTest) ReportServiceShipID(string, string) {} func initTest(role shipRole) (*ShipConnection, *dataHandlerTest) { localDevice := spine.NewDeviceLocalImpl("TestBrandName", "TestDeviceModel", "TestSerialNumber", "TestDeviceCode", diff --git a/ship/hs_init_client_test.go b/ship/hs_init_client_test.go index 88113116..d12861d0 100644 --- a/ship/hs_init_client_test.go +++ b/ship/hs_init_client_test.go @@ -58,6 +58,20 @@ func (s *InitClientSuite) Test_ClientWait() { shutdownTest(sut) } +func (s *InitClientSuite) Test_ClientWait_Timeout() { + sut, data := initTest(s.role) + + sut.setState(cmiStateClientWait) + + sut.handleState(true, nil) + + assert.Equal(s.T(), smeError, sut.getState()) + assert.NotNil(s.T(), data.lastMessage()) + assert.Equal(s.T(), data.handleConnectionClosedInvoked, true) + + shutdownTest(sut) +} + func (s *InitClientSuite) Test_ClientWait_InvalidMsgType() { sut, data := initTest(s.role) diff --git a/ship/websocket.go b/ship/websocket.go index 57914ec0..95f0325a 100644 --- a/ship/websocket.go +++ b/ship/websocket.go @@ -1,6 +1,7 @@ package ship import ( + "bytes" "errors" "fmt" "sync" @@ -97,9 +98,15 @@ func (w *websocketConnection) writeShipPump() { return } + var text string if len(message) > 2 { - logging.Log.Trace("Send:", w.remoteSki, string(message[1:])) + text = string(message[1:]) + } else if bytes.Equal(message, shipInit) { + text = "ship init" + } else { + text = "unknown single byte" } + logging.Log.Trace("Send:", w.remoteSki, text) case <-ticker.C: if w.isConnClosed() { @@ -133,9 +140,15 @@ func (w *websocketConnection) readShipPump() { return } + var text string if len(message) > 2 { - logging.Log.Trace("Recv:", w.remoteSki, string(message[1:])) + text = string(message[1:]) + } else if bytes.Equal(message, shipInit) { + text = "ship init" + } else { + text = "unknown single byte" } + logging.Log.Trace("Recv:", w.remoteSki, text) w.dataProcessing.HandleIncomingShipMessage(message) } diff --git a/spine/device_local.go b/spine/device_local.go index b93a724d..fc666fba 100644 --- a/spine/device_local.go +++ b/spine/device_local.go @@ -79,12 +79,18 @@ func (r *DeviceLocalImpl) RemoveRemoteDeviceConnection(ski string) { Events.Publish(payload) } -func (r *DeviceLocalImpl) AddRemoteDevice(ski string, writeI SpineDataConnection) SpineDataProcessing { - rDevice := NewDeviceRemoteImpl(r, ski, writeI) - +// Helper method used by tests and AddRemoteDevice +func (r *DeviceLocalImpl) AddRemoteDeviceForSki(ski string, rDevice *DeviceRemoteImpl) { r.mux.Lock() r.remoteDevices[ski] = rDevice r.mux.Unlock() +} + +// Adds a new remote device with a given SKI and triggers SPINE requesting device details +func (r *DeviceLocalImpl) AddRemoteDevice(ski string, writeI SpineDataConnection) SpineDataProcessing { + rDevice := NewDeviceRemoteImpl(r, ski, writeI) + + r.AddRemoteDeviceForSki(ski, rDevice) // Request Detailed Discovery Data _, _ = r.nodeManagement.RequestDetailedDiscovery(rDevice.ski, rDevice.address, rDevice.sender) diff --git a/spine/entity_local.go b/spine/entity_local.go index 3a791733..2fe0561a 100644 --- a/spine/entity_local.go +++ b/spine/entity_local.go @@ -34,11 +34,19 @@ func (r *EntityLocalImpl) AddFeature(f FeatureLocal) { // either returns an existing feature or creates a new one // for a given entity, featuretype and role -func (r *EntityLocalImpl) GetOrAddFeature(featureType model.FeatureTypeType, role model.RoleType, description string) FeatureLocal { +func (r *EntityLocalImpl) GetOrAddFeature(featureType model.FeatureTypeType, role model.RoleType) FeatureLocal { if f := r.FeatureOfTypeAndRole(featureType, role); f != nil { return f } f := NewFeatureLocalImpl(r.NextFeatureId(), r, featureType, role) + + description := string(featureType) + switch role { + case model.RoleTypeClient: + description += " Client" + case model.RoleTypeServer: + description += " Server" + } f.SetDescriptionString(description) r.features = append(r.features, f) return f diff --git a/spine/events.go b/spine/events.go index b5aa950e..103bf4a1 100644 --- a/spine/events.go +++ b/spine/events.go @@ -1,6 +1,10 @@ package spine -import "sync" +import ( + "sync" + + "github.com/enbility/eebus-go/spine/model" +) var Events events @@ -23,13 +27,14 @@ const ( ) type EventPayload struct { - Ski string // required - EventType EventType // required - ChangeType ElementChangeType // required - Device *DeviceRemoteImpl // required for DetailedDiscovery Call - Entity *EntityRemoteImpl // required for DetailedDiscovery Call and Notify - Feature *FeatureRemoteImpl - Data any + Ski string // required + EventType EventType // required + ChangeType ElementChangeType // required + Device *DeviceRemoteImpl // required for DetailedDiscovery Call + Entity *EntityRemoteImpl // required for DetailedDiscovery Call and Notify + Feature *FeatureRemoteImpl + CmdClassifier *model.CmdClassifierType // optional, used together with EventType EventTypeDataChange + Data any } type EventHandler interface { diff --git a/spine/feature_local.go b/spine/feature_local.go index fa849aac..11225c1a 100644 --- a/spine/feature_local.go +++ b/spine/feature_local.go @@ -6,6 +6,7 @@ import ( "github.com/enbility/eebus-go/logging" "github.com/enbility/eebus-go/spine/model" + "github.com/enbility/eebus-go/util" ) type FeatureLocal interface { @@ -411,13 +412,14 @@ func (r *FeatureLocalImpl) processReply(function model.FunctionType, data any, r // the data was updated, so send an event, other event handlers may watch out for this as well payload := EventPayload{ - Ski: featureRemote.Device().ski, - EventType: EventTypeDataChange, - ChangeType: ElementChangeUpdate, - Feature: featureRemote, - Device: featureRemote.Device(), - Entity: featureRemote.Entity(), - Data: data, + Ski: featureRemote.Device().ski, + EventType: EventTypeDataChange, + ChangeType: ElementChangeUpdate, + Feature: featureRemote, + Device: featureRemote.Device(), + Entity: featureRemote.Entity(), + CmdClassifier: util.Ptr(model.CmdClassifierTypeReply), + Data: data, } Events.Publish(payload) @@ -428,13 +430,14 @@ func (r *FeatureLocalImpl) processNotify(function model.FunctionType, data any, featureRemote.UpdateData(function, data, filterPartial, filterDelete) payload := EventPayload{ - Ski: featureRemote.Device().ski, - EventType: EventTypeDataChange, - ChangeType: ElementChangeUpdate, - Feature: featureRemote, - Device: featureRemote.Device(), - Entity: featureRemote.Entity(), - Data: data, + Ski: featureRemote.Device().ski, + EventType: EventTypeDataChange, + ChangeType: ElementChangeUpdate, + Feature: featureRemote, + Device: featureRemote.Device(), + Entity: featureRemote.Entity(), + CmdClassifier: util.Ptr(model.CmdClassifierTypeNotify), + Data: data, } Events.Publish(payload) diff --git a/spine/function_data_factory.go b/spine/function_data_factory.go index f7a0c280..6b9520db 100644 --- a/spine/function_data_factory.go +++ b/spine/function_data_factory.go @@ -7,84 +7,285 @@ import ( ) func CreateFunctionData[F any](featureType model.FeatureTypeType) []F { - switch featureType { - case model.FeatureTypeTypeNodeManagement: + if featureType == model.FeatureTypeTypeNodeManagement { return []F{} // NodeManagement implementation is not using function data - case model.FeatureTypeTypeDeviceClassification: - return []F{ + } + + // Some devices use generic for everything (e.g. Vaillant Arotherm heatpump) + // or for some things like the SMA HM 2.0 or Elli Wallbox, which uses Generic feature + // for Heartbeats, even though that should go into FeatureTypeTypeDeviceDiagnosis + // Hence we add everything to the Generic feature, as we don't know what might be needed + + var result []F + + if featureType == model.FeatureTypeTypeActuatorLevel || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.ActuatorLevelDataType, F](model.FunctionTypeActuatorLevelData), + createFunctionData[model.ActuatorLevelDescriptionDataType, F](model.FunctionTypeActuatorLevelDescriptionData), + }...) + } + + if featureType == model.FeatureTypeTypeActuatorSwitch || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.ActuatorSwitchDataType, F](model.FunctionTypeActuatorSwitchData), + createFunctionData[model.ActuatorSwitchDescriptionDataType, F](model.FunctionTypeActuatorSwitchDescriptionData), + }...) + } + + if featureType == model.FeatureTypeTypeAlarm || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.AlarmListDataType, F](model.FunctionTypeAlarmListData), + }...) + } + + if featureType == model.FeatureTypeTypeBill || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.BillDescriptionListDataType, F](model.FunctionTypeBillDescriptionListData), + createFunctionData[model.BillConstraintsListDataType, F](model.FunctionTypeBillConstraintsListData), + createFunctionData[model.BillListDataType, F](model.FunctionTypeBillListData), + }...) + } + + if featureType == model.FeatureTypeTypeDataTunneling || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.DataTunnelingCallType, F](model.FunctionTypeDataTunnelingCall), + }...) + } + + if featureType == model.FeatureTypeTypeDeviceClassification || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ createFunctionData[model.DeviceClassificationManufacturerDataType, F](model.FunctionTypeDeviceClassificationManufacturerData), createFunctionData[model.DeviceClassificationUserDataType, F](model.FunctionTypeDeviceClassificationUserData), - } - case model.FeatureTypeTypeDeviceDiagnosis: - return []F{ - createFunctionData[model.DeviceDiagnosisStateDataType, F](model.FunctionTypeDeviceDiagnosisStateData), - createFunctionData[model.DeviceDiagnosisHeartbeatDataType, F](model.FunctionTypeDeviceDiagnosisHeartbeatData), - } - case model.FeatureTypeTypeMeasurement: - return []F{ - createFunctionData[model.MeasurementDataType, F](model.FunctionTypeMeasurementListData), - createFunctionData[model.MeasurementDescriptionDataType, F](model.FunctionTypeMeasurementDescriptionListData), - createFunctionData[model.MeasurementDescriptionListDataType, F](model.FunctionTypeMeasurementDescriptionListData), - createFunctionData[model.MeasurementConstraintsListDataType, F](model.FunctionTypeMeasurementConstraintsListData), - createFunctionData[model.MeasurementListDataType, F](model.FunctionTypeMeasurementListData), - } - case model.FeatureTypeTypeDeviceConfiguration: - return []F{ + }...) + } + + if featureType == model.FeatureTypeTypeDeviceConfiguration || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.DeviceConfigurationKeyValueConstraintsListDataType, F](model.FunctionTypeDeviceConfigurationKeyValueConstraintsListData), createFunctionData[model.DeviceConfigurationKeyValueDescriptionListDataType, F](model.FunctionTypeDeviceConfigurationKeyValueDescriptionListData), createFunctionData[model.DeviceConfigurationKeyValueListDataType, F](model.FunctionTypeDeviceConfigurationKeyValueListData), - } - case model.FeatureTypeTypeLoadControl: - return []F{ - createFunctionData[model.LoadControlLimitConstraintsListDataType, F](model.FunctionTypeLoadControlLimitConstraintsListData), - createFunctionData[model.LoadControlLimitDescriptionListDataType, F](model.FunctionTypeLoadControlLimitDescriptionListData), - createFunctionData[model.LoadControlLimitListDataType, F](model.FunctionTypeLoadControlLimitListData), - } - case model.FeatureTypeTypeIdentification: - return []F{ - createFunctionData[model.IdentificationListDataType, F](model.FunctionTypeIdentificationListData), - } - case model.FeatureTypeTypeElectricalConnection: - return []F{ + }...) + } + + if featureType == model.FeatureTypeTypeDeviceDiagnosis || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.DeviceDiagnosisStateDataType, F](model.FunctionTypeDeviceDiagnosisStateData), + createFunctionData[model.DeviceDiagnosisHeartbeatDataType, F](model.FunctionTypeDeviceDiagnosisHeartbeatData), + createFunctionData[model.DeviceDiagnosisServiceDataType, F](model.FunctionTypeDeviceDiagnosisServiceData), + }...) + } + + if featureType == model.FeatureTypeTypeDirectControl || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.DirectControlActivityListDataType, F](model.FunctionTypeDirectControlActivityListData), + createFunctionData[model.DirectControlDescriptionDataType, F](model.FunctionTypeDirectControlDescriptionData), + }...) + } + + if featureType == model.FeatureTypeTypeElectricalConnection || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ createFunctionData[model.ElectricalConnectionDescriptionListDataType, F](model.FunctionTypeElectricalConnectionDescriptionListData), createFunctionData[model.ElectricalConnectionParameterDescriptionListDataType, F](model.FunctionTypeElectricalConnectionParameterDescriptionListData), createFunctionData[model.ElectricalConnectionPermittedValueSetListDataType, F](model.FunctionTypeElectricalConnectionPermittedValueSetListData), - } - case model.FeatureTypeTypeTimeSeries: - return []F{ - createFunctionData[model.TimeSeriesDescriptionListDataType, F](model.FunctionTypeTimeSeriesDescriptionListData), - createFunctionData[model.TimeSeriesConstraintsListDataType, F](model.FunctionTypeTimeSeriesConstraintsListData), - createFunctionData[model.TimeSeriesListDataType, F](model.FunctionTypeTimeSeriesListData), - } - case model.FeatureTypeTypeIncentiveTable: - return []F{ - createFunctionData[model.IncentiveTableDescriptionDataType, F](model.FunctionTypeIncentiveTableDescriptionData), - createFunctionData[model.IncentiveTableConstraintsDataType, F](model.FunctionTypeIncentiveTableConstraintsData), - createFunctionData[model.IncentiveTableDataType, F](model.FunctionTypeIncentiveTableData), - } - case model.FeatureTypeTypeBill: - return []F{ - createFunctionData[model.BillDescriptionListDataType, F](model.FunctionTypeBillDescriptionListData), - createFunctionData[model.BillConstraintsListDataType, F](model.FunctionTypeBillConstraintsListData), - createFunctionData[model.BillListDataType, F](model.FunctionTypeBillListData), - } - case model.FeatureTypeTypeHvac: - return []F{ + createFunctionData[model.ElectricalConnectionStateListDataType, F](model.FunctionTypeElectricalConnectionStateListData), + }...) + } + + if featureType == model.FeatureTypeTypeHvac || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.HvacOperationModeDescriptionDataType, F](model.FunctionTypeHvacOperationModeDescriptionListData), createFunctionData[model.HvacOverrunDescriptionListDataType, F](model.FunctionTypeHvacOverrunDescriptionListData), createFunctionData[model.HvacOverrunListDataType, F](model.FunctionTypeHvacOverrunListData), - createFunctionData[model.HvacSystemFunctionDataType, F](model.FunctionTypeHvacSystemFunctionListData), createFunctionData[model.HvacSystemFunctionDescriptionDataType, F](model.FunctionTypeHvacSystemFunctionDescriptionListData), - } - case model.FeatureTypeTypeGeneric: - // TODO: add the proper function data, this is reported e.g. by the SMA HM 2.0 - return []F{ - createFunctionData[model.DeviceDiagnosisHeartbeatDataType, F](model.FunctionTypeDeviceDiagnosisHeartbeatData), // Elli Charger uses this feature type for heartbeats - } - // TODO: Add more feature types - // default: - // return []F{} - } - - panic(fmt.Errorf("unknown featureType '%s'", featureType)) + createFunctionData[model.HvacSystemFunctionListDataType, F](model.FunctionTypeHvacSystemFunctionListData), + createFunctionData[model.HvacSystemFunctionOperationModeRelationListDataType, F](model.FunctionTypeHvacSystemFunctionOperationModeRelationListData), + createFunctionData[model.HvacSystemFunctionPowerSequenceRelationListDataType, F](model.FunctionTypeHvacSystemFunctionPowerSequenceRelationListData), + createFunctionData[model.HvacSystemFunctionSetpointRelationListDataType, F](model.FunctionTypeHvacSystemFunctionSetPointRelationListData), + }...) + } + + if featureType == model.FeatureTypeTypeIdentification || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.IdentificationListDataType, F](model.FunctionTypeIdentificationListData), + }...) + } + + if featureType == model.FeatureTypeTypeIncentiveTable || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.IncentiveTableDescriptionDataType, F](model.FunctionTypeIncentiveTableDescriptionData), + createFunctionData[model.IncentiveTableConstraintsDataType, F](model.FunctionTypeIncentiveTableConstraintsData), + createFunctionData[model.IncentiveTableDataType, F](model.FunctionTypeIncentiveTableData), + }...) + } + + if featureType == model.FeatureTypeTypeLoadControl || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.LoadControlEventListDataType, F](model.FunctionTypeLoadControlEventListData), + createFunctionData[model.LoadControlLimitConstraintsListDataType, F](model.FunctionTypeLoadControlLimitConstraintsListData), + createFunctionData[model.LoadControlLimitDescriptionListDataType, F](model.FunctionTypeLoadControlLimitDescriptionListData), + createFunctionData[model.LoadControlLimitListDataType, F](model.FunctionTypeLoadControlLimitListData), + createFunctionData[model.LoadControlNodeDataType, F](model.FunctionTypeLoadControlNodeData), + createFunctionData[model.LoadControlStateListDataType, F](model.FunctionTypeLoadControlStateListData), + }...) + } + + if featureType == model.FeatureTypeTypeMeasurement || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.MeasurementListDataType, F](model.FunctionTypeMeasurementListData), + createFunctionData[model.MeasurementDescriptionListDataType, F](model.FunctionTypeMeasurementDescriptionListData), + createFunctionData[model.MeasurementConstraintsListDataType, F](model.FunctionTypeMeasurementConstraintsListData), + createFunctionData[model.MeasurementThresholdRelationListDataType, F](model.FunctionTypeMeasurementThresholdRelationListData), + }...) + } + + if featureType == model.FeatureTypeTypeMessaging || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.MessagingListDataType, F](model.FunctionTypeMessagingListData), + }...) + } + + if featureType == model.FeatureTypeTypeNetworkManagement || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.NetworkManagementAbortCallType, F](model.FunctionTypeNetworkManagementAbortCall), + createFunctionData[model.NetworkManagementAddNodeCallType, F](model.FunctionTypeNetworkManagementAddNodeCall), + createFunctionData[model.NetworkManagementDeviceDescriptionListDataType, F](model.FunctionTypeNetworkManagementDeviceDescriptionListData), + createFunctionData[model.NetworkManagementDiscoverCallType, F](model.FunctionTypeNetworkManagementDiscoverCall), + createFunctionData[model.NetworkManagementEntityDescriptionListDataType, F](model.FunctionTypeNetworkManagementEntityDescriptionListData), + createFunctionData[model.NetworkManagementFeatureDescriptionListDataType, F](model.FunctionTypeNetworkManagementFeatureDescriptionListData), + createFunctionData[model.NetworkManagementJoiningModeDataType, F](model.FunctionTypeNetworkManagementJoiningModeData), + createFunctionData[model.NetworkManagementModifyNodeCallType, F](model.FunctionTypeNetworkManagementModifyNodeCall), + createFunctionData[model.NetworkManagementProcessStateDataType, F](model.FunctionTypeNetworkManagementProcessStateData), + createFunctionData[model.NetworkManagementRemoveNodeCallType, F](model.FunctionTypeNetworkManagementRemoveNodeCall), + createFunctionData[model.NetworkManagementReportCandidateDataType, F](model.FunctionTypeNetworkManagementReportCandidateData), + createFunctionData[model.NetworkManagementScanNetworkCallType, F](model.FunctionTypeNetworkManagementScanNetworkCall), + }...) + } + + if featureType == model.FeatureTypeTypeOperatingConstraints || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.OperatingConstraintsDurationListDataType, F](model.FunctionTypeOperatingConstraintsDurationListData), + createFunctionData[model.OperatingConstraintsInterruptListDataType, F](model.FunctionTypeOperatingConstraintsInterruptListData), + createFunctionData[model.OperatingConstraintsPowerDescriptionListDataType, F](model.FunctionTypeOperatingConstraintsPowerDescriptionListData), + createFunctionData[model.OperatingConstraintsPowerLevelListDataType, F](model.FunctionTypeOperatingConstraintsPowerLevelListData), + createFunctionData[model.OperatingConstraintsPowerRangeListDataType, F](model.FunctionTypeOperatingConstraintsPowerRangeListData), + createFunctionData[model.OperatingConstraintsResumeImplicationListDataType, F](model.FunctionTypeOperatingConstraintsResumeImplicationListData), + }...) + } + + if featureType == model.FeatureTypeTypePowerSequences || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.PowerSequenceAlternativesRelationListDataType, F](model.FunctionTypePowerSequenceAlternativesRelationListData), + createFunctionData[model.PowerSequenceDescriptionListDataType, F](model.FunctionTypePowerSequenceDescriptionListData), + createFunctionData[model.PowerSequenceNodeScheduleInformationDataType, F](model.FunctionTypePowerSequenceNodeScheduleInformationData), + createFunctionData[model.PowerSequencePriceCalculationRequestCallType, F](model.FunctionTypePowerSequencePriceCalculationRequestCall), + createFunctionData[model.PowerSequencePriceListDataType, F](model.FunctionTypePowerSequencePriceListData), + createFunctionData[model.PowerSequenceScheduleConfigurationRequestCallType, F](model.FunctionTypePowerSequenceScheduleConfigurationRequestCall), + createFunctionData[model.PowerSequenceScheduleConstraintsListDataType, F](model.FunctionTypePowerSequenceScheduleConstraintsListData), + createFunctionData[model.PowerSequenceScheduleListDataType, F](model.FunctionTypePowerSequenceScheduleListData), + createFunctionData[model.PowerSequenceSchedulePreferenceListDataType, F](model.FunctionTypePowerSequenceSchedulePreferenceListData), + createFunctionData[model.PowerSequenceStateListDataType, F](model.FunctionTypePowerSequenceStateListData), + createFunctionData[model.PowerTimeSlotScheduleConstraintsListDataType, F](model.FunctionTypePowerTimeSlotScheduleConstraintsListData), + createFunctionData[model.PowerTimeSlotScheduleListDataType, F](model.FunctionTypePowerTimeSlotScheduleListData), + createFunctionData[model.PowerTimeSlotValueListDataType, F](model.FunctionTypePowerTimeSlotValueListData), + }...) + } + + if featureType == model.FeatureTypeTypeSensing || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.SensingDescriptionDataType, F](model.FunctionTypeSensingDescriptionData), + createFunctionData[model.SensingListDataType, F](model.FunctionTypeSensingListData), + }...) + } + + if featureType == model.FeatureTypeTypeSetpoint || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.SetpointConstraintsListDataType, F](model.FunctionTypeSetpointConstraintsListData), + createFunctionData[model.SetpointDescriptionListDataType, F](model.FunctionTypeSetpointDescriptionListData), + createFunctionData[model.SetpointListDataType, F](model.FunctionTypeSetpointListData), + }...) + } + + if featureType == model.FeatureTypeTypeSmartEnergyManagementPs || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.SmartEnergyManagementPsConfigurationRequestCallType, F](model.FunctionTypeSmartEnergyManagementPsConfigurationRequestCall), + createFunctionData[model.SmartEnergyManagementPsDataType, F](model.FunctionTypeSmartEnergyManagementPsData), + createFunctionData[model.SmartEnergyManagementPsPriceCalculationRequestCallType, F](model.FunctionTypeSmartEnergyManagementPsPriceCalculationRequestCall), + createFunctionData[model.SmartEnergyManagementPsPriceDataType, F](model.FunctionTypeSmartEnergyManagementPsPriceData), + }...) + } + + if featureType == model.FeatureTypeTypeSupplyCondition || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.SupplyConditionDescriptionListDataType, F](model.FunctionTypeSupplyConditionDescriptionListData), + createFunctionData[model.SupplyConditionListDataType, F](model.FunctionTypeSupplyConditionListData), + createFunctionData[model.SupplyConditionThresholdRelationListDataType, F](model.FunctionTypeSupplyConditionThresholdRelationListData), + }...) + } + + if featureType == model.FeatureTypeTypeTariffInformation || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.IncentiveDescriptionListDataType, F](model.FunctionTypeIncentiveDescriptionListData), + createFunctionData[model.IncentiveListDataType, F](model.FunctionTypeIncentiveListData), + createFunctionData[model.TariffBoundaryRelationListDataType, F](model.FunctionTypeTariffBoundaryRelationListData), + createFunctionData[model.TariffDescriptionListDataType, F](model.FunctionTypeTariffDescriptionListData), + createFunctionData[model.TariffListDataType, F](model.FunctionTypeTariffListData), + createFunctionData[model.TariffOverallConstraintsDataType, F](model.FunctionTypeTariffOverallConstraintsData), + createFunctionData[model.TariffTierRelationListDataType, F](model.FunctionTypeTariffTierRelationListData), + createFunctionData[model.TierBoundaryDescriptionListDataType, F](model.FunctionTypeTierBoundaryDescriptionListData), + createFunctionData[model.TierBoundaryListDataType, F](model.FunctionTypeTierBoundaryListData), + createFunctionData[model.TierDescriptionListDataType, F](model.FunctionTypeTierDescriptionListData), + createFunctionData[model.TierIncentiveRelationListDataType, F](model.FunctionTypeTierIncentiveRelationListData), + createFunctionData[model.TierListDataType, F](model.FunctionTypeTierListData), + }...) + } + + if featureType == model.FeatureTypeTypeTaskManagement || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.TaskManagementJobDescriptionListDataType, F](model.FunctionTypeTaskManagementJobDescriptionListData), + createFunctionData[model.TaskManagementJobListDataType, F](model.FunctionTypeTaskManagementJobListData), + createFunctionData[model.TaskManagementJobRelationListDataType, F](model.FunctionTypeTaskManagementJobRelationListData), + createFunctionData[model.TaskManagementOverviewDataType, F](model.FunctionTypeTaskManagementOverviewData), + }...) + } + + if featureType == model.FeatureTypeTypeThreshold || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.ThresholdConstraintsListDataType, F](model.FunctionTypeThresholdConstraintsListData), + createFunctionData[model.ThresholdDescriptionListDataType, F](model.FunctionTypeThresholdDescriptionListData), + createFunctionData[model.ThresholdListDataType, F](model.FunctionTypeThresholdListData), + }...) + } + + if featureType == model.FeatureTypeTypeTimeInformation || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.TimeDistributorDataType, F](model.FunctionTypeTimeDistributorData), + createFunctionData[model.TimeDistributorEnquiryCallType, F](model.FunctionTypeTimeDistributorEnquiryCall), + createFunctionData[model.TimeInformationDataType, F](model.FunctionTypeTimeInformationData), + createFunctionData[model.TimePrecisionDataType, F](model.FunctionTypeTimePrecisionData), + }...) + } + + if featureType == model.FeatureTypeTypeTimeSeries || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.TimeSeriesDescriptionListDataType, F](model.FunctionTypeTimeSeriesDescriptionListData), + createFunctionData[model.TimeSeriesConstraintsListDataType, F](model.FunctionTypeTimeSeriesConstraintsListData), + createFunctionData[model.TimeSeriesListDataType, F](model.FunctionTypeTimeSeriesListData), + }...) + } + + if featureType == model.FeatureTypeTypeTimeTable || featureType == model.FeatureTypeTypeGeneric { + result = append(result, []F{ + createFunctionData[model.TimeTableConstraintsListDataType, F](model.FunctionTypeTimeTableConstraintsListData), + createFunctionData[model.TimeTableDescriptionListDataType, F](model.FunctionTypeTimeTableDescriptionListData), + createFunctionData[model.TimeTableListDataType, F](model.FunctionTypeTimeTableListData), + }...) + } + + if len(result) == 0 { + panic(fmt.Errorf("unknown featureType '%s'", featureType)) + } + + return result } func createFunctionData[T any, F any](functionType model.FunctionType) F { diff --git a/spine/function_data_factory_test.go b/spine/function_data_factory_test.go index 793f5466..368b8562 100644 --- a/spine/function_data_factory_test.go +++ b/spine/function_data_factory_test.go @@ -8,50 +8,45 @@ import ( ) func TestFunctionDataFactory_FunctionData(t *testing.T) { - result := CreateFunctionData[FunctionData](model.FeatureTypeTypeDeviceClassification) + result := CreateFunctionData[FunctionData](model.FeatureTypeTypeBill) + assert.Equal(t, 3, len(result)) + assert.IsType(t, &FunctionDataImpl[model.BillDescriptionListDataType]{}, result[0]) + assert.IsType(t, &FunctionDataImpl[model.BillConstraintsListDataType]{}, result[1]) + assert.IsType(t, &FunctionDataImpl[model.BillListDataType]{}, result[2]) + + result = CreateFunctionData[FunctionData](model.FeatureTypeTypeDeviceClassification) assert.Equal(t, 2, len(result)) assert.IsType(t, &FunctionDataImpl[model.DeviceClassificationManufacturerDataType]{}, result[0]) assert.IsType(t, &FunctionDataImpl[model.DeviceClassificationUserDataType]{}, result[1]) - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeDeviceDiagnosis) - assert.Equal(t, 2, len(result)) - assert.IsType(t, &FunctionDataImpl[model.DeviceDiagnosisStateDataType]{}, result[0]) - assert.IsType(t, &FunctionDataImpl[model.DeviceDiagnosisHeartbeatDataType]{}, result[1]) - - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeMeasurement) - assert.Equal(t, 5, len(result)) - assert.IsType(t, &FunctionDataImpl[model.MeasurementDataType]{}, result[0]) - assert.IsType(t, &FunctionDataImpl[model.MeasurementDescriptionDataType]{}, result[1]) - assert.IsType(t, &FunctionDataImpl[model.MeasurementDescriptionListDataType]{}, result[2]) - assert.IsType(t, &FunctionDataImpl[model.MeasurementConstraintsListDataType]{}, result[3]) - assert.IsType(t, &FunctionDataImpl[model.MeasurementListDataType]{}, result[4]) - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeDeviceConfiguration) - assert.Equal(t, 2, len(result)) - assert.IsType(t, &FunctionDataImpl[model.DeviceConfigurationKeyValueDescriptionListDataType]{}, result[0]) - assert.IsType(t, &FunctionDataImpl[model.DeviceConfigurationKeyValueListDataType]{}, result[1]) - - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeLoadControl) assert.Equal(t, 3, len(result)) - assert.IsType(t, &FunctionDataImpl[model.LoadControlLimitConstraintsListDataType]{}, result[0]) - assert.IsType(t, &FunctionDataImpl[model.LoadControlLimitDescriptionListDataType]{}, result[1]) - assert.IsType(t, &FunctionDataImpl[model.LoadControlLimitListDataType]{}, result[2]) + assert.IsType(t, &FunctionDataImpl[model.DeviceConfigurationKeyValueConstraintsListDataType]{}, result[0]) + assert.IsType(t, &FunctionDataImpl[model.DeviceConfigurationKeyValueDescriptionListDataType]{}, result[1]) + assert.IsType(t, &FunctionDataImpl[model.DeviceConfigurationKeyValueListDataType]{}, result[2]) - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeIdentification) - assert.Equal(t, 1, len(result)) - assert.IsType(t, &FunctionDataImpl[model.IdentificationListDataType]{}, result[0]) + result = CreateFunctionData[FunctionData](model.FeatureTypeTypeDeviceDiagnosis) + assert.Equal(t, 3, len(result)) + assert.IsType(t, &FunctionDataImpl[model.DeviceDiagnosisStateDataType]{}, result[0]) + assert.IsType(t, &FunctionDataImpl[model.DeviceDiagnosisHeartbeatDataType]{}, result[1]) result = CreateFunctionData[FunctionData](model.FeatureTypeTypeElectricalConnection) - assert.Equal(t, 3, len(result)) + assert.Equal(t, 4, len(result)) assert.IsType(t, &FunctionDataImpl[model.ElectricalConnectionDescriptionListDataType]{}, result[0]) assert.IsType(t, &FunctionDataImpl[model.ElectricalConnectionParameterDescriptionListDataType]{}, result[1]) assert.IsType(t, &FunctionDataImpl[model.ElectricalConnectionPermittedValueSetListDataType]{}, result[2]) - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeTimeSeries) - assert.Equal(t, 3, len(result)) - assert.IsType(t, &FunctionDataImpl[model.TimeSeriesDescriptionListDataType]{}, result[0]) - assert.IsType(t, &FunctionDataImpl[model.TimeSeriesConstraintsListDataType]{}, result[1]) - assert.IsType(t, &FunctionDataImpl[model.TimeSeriesListDataType]{}, result[2]) + result = CreateFunctionData[FunctionData](model.FeatureTypeTypeHvac) + assert.Equal(t, 8, len(result)) + assert.IsType(t, &FunctionDataImpl[model.HvacOperationModeDescriptionDataType]{}, result[0]) + assert.IsType(t, &FunctionDataImpl[model.HvacOverrunDescriptionListDataType]{}, result[1]) + assert.IsType(t, &FunctionDataImpl[model.HvacOverrunListDataType]{}, result[2]) + assert.IsType(t, &FunctionDataImpl[model.HvacSystemFunctionDescriptionDataType]{}, result[3]) + assert.IsType(t, &FunctionDataImpl[model.HvacSystemFunctionListDataType]{}, result[4]) + + result = CreateFunctionData[FunctionData](model.FeatureTypeTypeIdentification) + assert.Equal(t, 1, len(result)) + assert.IsType(t, &FunctionDataImpl[model.IdentificationListDataType]{}, result[0]) result = CreateFunctionData[FunctionData](model.FeatureTypeTypeIncentiveTable) assert.Equal(t, 3, len(result)) @@ -59,22 +54,28 @@ func TestFunctionDataFactory_FunctionData(t *testing.T) { assert.IsType(t, &FunctionDataImpl[model.IncentiveTableConstraintsDataType]{}, result[1]) assert.IsType(t, &FunctionDataImpl[model.IncentiveTableDataType]{}, result[2]) - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeBill) - assert.Equal(t, 3, len(result)) - assert.IsType(t, &FunctionDataImpl[model.BillDescriptionListDataType]{}, result[0]) - assert.IsType(t, &FunctionDataImpl[model.BillConstraintsListDataType]{}, result[1]) - assert.IsType(t, &FunctionDataImpl[model.BillListDataType]{}, result[2]) + result = CreateFunctionData[FunctionData](model.FeatureTypeTypeLoadControl) + assert.Equal(t, 6, len(result)) + assert.IsType(t, &FunctionDataImpl[model.LoadControlEventListDataType]{}, result[0]) + assert.IsType(t, &FunctionDataImpl[model.LoadControlLimitConstraintsListDataType]{}, result[1]) + assert.IsType(t, &FunctionDataImpl[model.LoadControlLimitDescriptionListDataType]{}, result[2]) + assert.IsType(t, &FunctionDataImpl[model.LoadControlLimitListDataType]{}, result[3]) - result = CreateFunctionData[FunctionData](model.FeatureTypeTypeHvac) + result = CreateFunctionData[FunctionData](model.FeatureTypeTypeMeasurement) assert.Equal(t, 4, len(result)) - assert.IsType(t, &FunctionDataImpl[model.HvacOverrunDescriptionListDataType]{}, result[0]) - assert.IsType(t, &FunctionDataImpl[model.HvacOverrunListDataType]{}, result[1]) - assert.IsType(t, &FunctionDataImpl[model.HvacSystemFunctionDataType]{}, result[2]) - assert.IsType(t, &FunctionDataImpl[model.HvacSystemFunctionDescriptionDataType]{}, result[3]) + assert.IsType(t, &FunctionDataImpl[model.MeasurementListDataType]{}, result[0]) + assert.IsType(t, &FunctionDataImpl[model.MeasurementDescriptionListDataType]{}, result[1]) + assert.IsType(t, &FunctionDataImpl[model.MeasurementConstraintsListDataType]{}, result[2]) + assert.IsType(t, &FunctionDataImpl[model.MeasurementThresholdRelationListDataType]{}, result[3]) + + result = CreateFunctionData[FunctionData](model.FeatureTypeTypeTimeSeries) + assert.Equal(t, 3, len(result)) + assert.IsType(t, &FunctionDataImpl[model.TimeSeriesDescriptionListDataType]{}, result[0]) + assert.IsType(t, &FunctionDataImpl[model.TimeSeriesConstraintsListDataType]{}, result[1]) + assert.IsType(t, &FunctionDataImpl[model.TimeSeriesListDataType]{}, result[2]) result = CreateFunctionData[FunctionData](model.FeatureTypeTypeGeneric) - assert.Equal(t, 1, len(result)) - assert.IsType(t, &FunctionDataImpl[model.DeviceDiagnosisHeartbeatDataType]{}, result[0]) + assert.Equal(t, 118, len(result)) } func TestFunctionDataFactory_FunctionDataCmd(t *testing.T) { @@ -84,9 +85,9 @@ func TestFunctionDataFactory_FunctionDataCmd(t *testing.T) { assert.IsType(t, &FunctionDataCmdImpl[model.DeviceClassificationUserDataType]{}, result[1]) } -func TestFunctionDataFactory_unknownFeatureType(t *testing.T) { - assert.PanicsWithError(t, "unknown featureType 'Alarm'", - func() { CreateFunctionData[FunctionDataCmd](model.FeatureTypeTypeAlarm) }) +func TestFunctionDataFactory_NodeMgmtFeatureType(t *testing.T) { + result := CreateFunctionData[FunctionDataCmd](model.FeatureTypeTypeNodeManagement) + assert.Equal(t, 0, len(result)) } func TestFunctionDataFactory_unknownFunctionDataType(t *testing.T) { diff --git a/spine/model/commondatatypes.go b/spine/model/commondatatypes.go index 899fab80..dd19e52d 100644 --- a/spine/model/commondatatypes.go +++ b/spine/model/commondatatypes.go @@ -530,6 +530,7 @@ const ( ScopeTypeTypeACCurrentA ScopeTypeType = "acCurrentA" ScopeTypeTypeACCurrentB ScopeTypeType = "acCurrentB" ScopeTypeTypeACCurrentC ScopeTypeType = "acCurrentC" + ScopeTypeTypeACFrequency ScopeTypeType = "acFrequency" ScopeTypeTypeACFrequencyGrid ScopeTypeType = "acFrequencyGrid" ScopeTypeTypeACPowerA ScopeTypeType = "acPowerA" ScopeTypeTypeACPowerB ScopeTypeType = "acPowerB" diff --git a/spine/model/commondatatypes_additions.go b/spine/model/commondatatypes_additions.go index 554c8a97..3486dd47 100644 --- a/spine/model/commondatatypes_additions.go +++ b/spine/model/commondatatypes_additions.go @@ -123,6 +123,12 @@ func NewAbsoluteOrRelativeTimeType(s string) *AbsoluteOrRelativeTimeType { return &value } +func NewAbsoluteOrRelativeTimeTypeFromDuration(t time.Duration) *AbsoluteOrRelativeTimeType { + s := NewDurationType(t) + value := AbsoluteOrRelativeTimeType(*s) + return &value +} + func NewAbsoluteOrRelativeTimeTypeFromTime(t time.Time) *AbsoluteOrRelativeTimeType { s := NewDateTimeTypeFromTime(t) value := AbsoluteOrRelativeTimeType(*s) diff --git a/spine/model/commondatatypes_additions_test.go b/spine/model/commondatatypes_additions_test.go index 73e39eb5..b29efbf9 100644 --- a/spine/model/commondatatypes_additions_test.go +++ b/spine/model/commondatatypes_additions_test.go @@ -153,6 +153,37 @@ func TestAbsoluteOrRelativeTimeTypeAbsolute(t *testing.T) { } } +func TestAbsoluteOrRelativeTimeTypeDuration(t *testing.T) { + tc := []struct { + in time.Duration + out string + }{ + {time.Duration(4) * time.Second, "PT4S"}, + } + + for _, tc := range tc { + a := NewAbsoluteOrRelativeTimeTypeFromDuration(tc.in) + got, err := a.GetDurationType() + if err != nil { + t.Errorf("Test Failure with %d: %s", tc.in, err) + continue + } + if string(*got) != tc.out { + t.Errorf("Test failure for %d, expected %s got %s", tc.in, tc.out, string(*got)) + } + + d, err := a.GetTimeDuration() + if err != nil { + t.Errorf("Test Failure with %d: %s", tc.in, err) + continue + } + got = NewDurationType(d) + if string(*got) != tc.out { + t.Errorf("Test failure for %d, expected %s got %s", tc.in, tc.out, string(*got)) + } + } +} + func TestAbsoluteOrRelativeTimeTypeRelative(t *testing.T) { tc := []struct { in string diff --git a/spine/model/tariffinformation.go b/spine/model/tariffinformation.go index 7bcf903f..9e2155a2 100644 --- a/spine/model/tariffinformation.go +++ b/spine/model/tariffinformation.go @@ -25,8 +25,8 @@ type TierCountType TierIdType type TierTypeType string const ( - TierTypeTypeFixedcost TierTypeType = "fixedCost" - TierTypeTypeDynamiccost TierTypeType = "dynamicCost" + TierTypeTypeFixedCost TierTypeType = "fixedCost" + TierTypeTypeDynamicCost TierTypeType = "dynamicCost" ) type IncentiveIdType uint @@ -36,9 +36,9 @@ type IncentiveCountType IncentiveIdType type IncentiveTypeType string const ( - IncentiveTypeTypeAbsolutecost IncentiveTypeType = "absoluteCost" - IncentiveTypeTypeRelativecost IncentiveTypeType = "relativeCost" - IncentiveTypeTypeRenewableenergypercentage IncentiveTypeType = "renewableEnergyPercentage" + IncentiveTypeTypeAbsoluteCost IncentiveTypeType = "absoluteCost" + IncentiveTypeTypeRelativeCost IncentiveTypeType = "relativeCost" + IncentiveTypeTypeRenewableEnergyPercentage IncentiveTypeType = "renewableEnergyPercentage" IncentiveTypeTypeCo2Emission IncentiveTypeType = "co2Emission" ) @@ -48,7 +48,7 @@ type IncentiveValueTypeType string const ( IncentiveValueTypeTypeValue IncentiveValueTypeType = "value" - IncentiveValueTypeTypeAveragevalue IncentiveValueTypeType = "averageValue" + IncentiveValueTypeTypeAverageValue IncentiveValueTypeType = "averageValue" IncentiveValueTypeTypeMinvalue IncentiveValueTypeType = "minValue" IncentiveValueTypeTypeMaxvalue IncentiveValueTypeType = "maxValue" ) diff --git a/spine/model/usecaseinformation.go b/spine/model/usecaseinformation.go index beee8f2d..8e531fb2 100644 --- a/spine/model/usecaseinformation.go +++ b/spine/model/usecaseinformation.go @@ -3,25 +3,63 @@ package model type UseCaseActorType string const ( - UseCaseActorTypeCEM UseCaseActorType = "CEM" - UseCaseActorTypeEVSE UseCaseActorType = "EVSE" - UseCaseActorTypeEV UseCaseActorType = "EV" - UseCaseActorTypeHeatPump UseCaseActorType = "HeatPump" + UseCaseActorTypeBatterySystem UseCaseActorType = "BatterySystem" + UseCaseActorTypeCEM UseCaseActorType = "CEM" + UseCaseActorTypeConfigurationAppliance UseCaseActorType = "ConfigurationAppliance" + UseCaseActorTypeCompressor UseCaseActorType = "Compressor" + UseCaseActorTypeControllableSystem UseCaseActorType = "ControllableSystem" + UseCaseActorTypeDHWCircuit UseCaseActorType = "DHWCircuit" + UseCaseActorTypeEnergyGuard UseCaseActorType = "EnergyGuard" + UseCaseActorTypeEVSE UseCaseActorType = "EVSE" + UseCaseActorTypeEV UseCaseActorType = "EV" + UseCaseActorTypeGridConnectionPoint UseCaseActorType = "GridConnectionPoint" + UseCaseActorTypeHeatPump UseCaseActorType = "HeatPump" + UseCaseActorTypeHeatingCircuit UseCaseActorType = "HeatingCircuit" + UseCaseActorTypeHeatingZone UseCaseActorType = "HeatingZone" + UseCaseActorTypeHVACRoom UseCaseActorType = "HVACRoom" + UseCaseActorTypeMonitoredUnit UseCaseActorType = "MonitoredUnit" + UseCaseActorTypeMonitoringAppliance UseCaseActorType = "MonitoringAppliance" + UseCaseActorTypePVSystem UseCaseActorType = "PVSystem" + UseCaseActorTypeVisualizationAppliance UseCaseActorType = "VisualizationAppliance" ) type UseCaseNameType string const ( - UseCaseNameTypeMeasurementOfElectricityDuringEVCharging UseCaseNameType = "measurementOfElectricityDuringEvCharging" - UseCaseNameTypeOptimizationOfSelfConsumptionDuringEVCharging UseCaseNameType = "optimizationOfSelfConsumptionDuringEvCharging" - UseCaseNameTypeOverloadProtectionByEVChargingCurrentCurtailment UseCaseNameType = "overloadProtectionByEvChargingCurrentCurtailment" - UseCaseNameTypeCoordinatedEVCharging UseCaseNameType = "coordinatedEvCharging" - UseCaseNameTypeEVCommissioningAndConfiguration UseCaseNameType = "evCommissioningAndConfiguration" - UseCaseNameTypeEVSECommissioningAndConfiguration UseCaseNameType = "evseCommissioningAndConfiguration" - UseCaseNameTypeEVChargingSummary UseCaseNameType = "evChargingSummary" - UseCaseNameTypeEVStateOfCharge UseCaseNameType = "evStateOfCharge" - UseCaseNameTypeMonitoringAndControlOfSmartGridReadyConditions UseCaseNameType = "monitoringAndControlOfSmartGridReadyConditions" - UseCaseNameTypeMonitoringOfPowerConsumption UseCaseNameType = "monitoringOfPowerConsumption" + UseCaseNameTypeConfigurationOfDhwSystemFunction UseCaseNameType = "configurationOfDhwSystemFunction" + UseCaseNameTypeConfigurationOfDhwTemperature UseCaseNameType = "configurationOfDhwTemperature" + UseCaseNameTypeConfigurationOfRoomCoolingSystemFunction UseCaseNameType = "configurationOfRoomCoolingSystemFunction" + UseCaseNameTypeConfigurationOfRoomCoolingTemperature UseCaseNameType = "configurationOfRoomCoolingTemperature" + UseCaseNameTypeConfigurationOfRoomHeatingSystemFunction UseCaseNameType = "configurationOfRoomHeatingSystemFunction" + UseCaseNameTypeConfigurationOfRoomHeatingTemperature UseCaseNameType = "configurationOfRoomHeatingTemperature" + UseCaseNameTypeControlOfBattery UseCaseNameType = "controlOfBattery" + UseCaseNameTypeCoordinatedEVCharging UseCaseNameType = "coordinatedEvCharging" + UseCaseNameTypeEVChargingSummary UseCaseNameType = "evChargingSummary" + UseCaseNameTypeEVCommissioningAndConfiguration UseCaseNameType = "evCommissioningAndConfiguration" + UseCaseNameTypeEVSECommissioningAndConfiguration UseCaseNameType = "evseCommissioningAndConfiguration" + UseCaseNameTypeEVStateOfCharge UseCaseNameType = "evStateOfCharge" + UseCaseNameTypeLimitationOfPowerConsumption UseCaseNameType = "limitationOfPowerConsumption" + UseCaseNameTypeLimitationOfPowerProduction UseCaseNameType = "limitationOfPowerProduction" + UseCaseNameTypeIncentiveTableBasedPowerConsumptionManagement UseCaseNameType = "incentiveTableBasedPowerConsumptionManagement" + UseCaseNameTypeMeasurementOfElectricityDuringEVCharging UseCaseNameType = "measurementOfElectricityDuringEvCharging" + UseCaseNameTypeMonitoringAndControlOfSmartGridReadyConditions UseCaseNameType = "monitoringAndControlOfSmartGridReadyConditions" + UseCaseNameTypeMonitoringOfBattery UseCaseNameType = "monitoringOfBattery" + UseCaseNameTypeMonitoringOfDhwSystemFunction UseCaseNameType = "monitoringOfDhwSystemFunction" + UseCaseNameTypeMonitoringOfDhwTemperature UseCaseNameType = "monitoringOfDhwTemperature" + UseCaseNameTypeMonitoringOfGridConnectionPoint UseCaseNameType = "monitoringOfGridConnectionPoint" + UseCaseNameTypeMonitoringOfInverter UseCaseNameType = "monitoringOfInverter" + UseCaseNameTypeMonitoringOfOutdoorTemperature UseCaseNameType = "monitoringOfOutdoorTemperature" + UseCaseNameTypeMonitoringOfPowerConsumption UseCaseNameType = "monitoringOfPowerConsumption" + UseCaseNameTypeMonitoringOfPvString UseCaseNameType = "monitoringOfPvString" + UseCaseNameTypeMonitoringOfRoomCoolingSystemFunction UseCaseNameType = "monitoringOfRoomCoolingSystemFunction" + UseCaseNameTypeMonitoringOfRoomHeatingSystemFunction UseCaseNameType = "monitoringOfRoomHeatingSystemFunction" + UseCaseNameTypeMonitoringOfRoomTemperature UseCaseNameType = "monitoringOfRoomTemperature" + UseCaseNameTypeOptimizationOfSelfConsumptionByHeatPumpCompressorFlexibility UseCaseNameType = "optimizationOfSelfConsumptionByHeatPumpCompressorFlexibility" + UseCaseNameTypeOptimizationOfSelfConsumptionDuringEVCharging UseCaseNameType = "optimizationOfSelfConsumptionDuringEvCharging" + UseCaseNameTypeOverloadProtectionByEVChargingCurrentCurtailment UseCaseNameType = "overloadProtectionByEvChargingCurrentCurtailment" + UseCaseNameTypeVisualizationOfAggregatedBatteryData UseCaseNameType = "visualizationOfAggregatedBatteryData" + UseCaseNameTypeVisualizationOfAggregatedPhotovoltaicData UseCaseNameType = "visualizationOfAggregatedPhotovoltaicData" + UseCaseNameTypeVisualizationOfHeatingAreaName UseCaseNameType = "visualizationOfHeatingAreaName" ) type UseCaseScenarioSupportType uint diff --git a/spine/usecase.go b/spine/usecase.go index 0d5eb428..b99e0885 100644 --- a/spine/usecase.go +++ b/spine/usecase.go @@ -8,9 +8,12 @@ import ( ) var entityTypeActorMap = map[model.EntityTypeType]model.UseCaseActorType{ - model.EntityTypeTypeEV: model.UseCaseActorTypeEV, - model.EntityTypeTypeEVSE: model.UseCaseActorTypeEVSE, - model.EntityTypeTypeCEM: model.UseCaseActorTypeCEM, + model.EntityTypeTypeEV: model.UseCaseActorTypeEV, + model.EntityTypeTypeEVSE: model.UseCaseActorTypeEVSE, + model.EntityTypeTypeCEM: model.UseCaseActorTypeCEM, + model.EntityTypeTypeGridConnectionPointOfPremises: model.UseCaseActorTypeMonitoringAppliance, + model.EntityTypeTypeElectricityStorageSystem: model.UseCaseActorTypeBatterySystem, + model.EntityTypeTypeElectricityGenerationSystem: model.UseCaseActorTypePVSystem, } var useCaseValidActorsMap = map[model.UseCaseNameType][]model.UseCaseActorType{ @@ -24,6 +27,9 @@ var useCaseValidActorsMap = map[model.UseCaseNameType][]model.UseCaseActorType{ model.UseCaseNameTypeOverloadProtectionByEVChargingCurrentCurtailment: {model.UseCaseActorTypeEV, model.UseCaseActorTypeCEM}, model.UseCaseNameTypeMonitoringOfPowerConsumption: {model.UseCaseActorTypeCEM, model.UseCaseActorTypeHeatPump}, model.UseCaseNameTypeMonitoringAndControlOfSmartGridReadyConditions: {model.UseCaseActorTypeCEM, model.UseCaseActorTypeHeatPump}, + model.UseCaseNameTypeMonitoringOfGridConnectionPoint: {model.UseCaseActorTypeCEM, model.UseCaseActorTypeMonitoringAppliance}, + model.UseCaseNameTypeVisualizationOfAggregatedBatteryData: {model.UseCaseActorTypeCEM, model.UseCaseActorTypeBatterySystem, model.UseCaseActorTypeVisualizationAppliance}, + model.UseCaseNameTypeVisualizationOfAggregatedPhotovoltaicData: {model.UseCaseActorTypeCEM, model.UseCaseActorTypePVSystem, model.UseCaseActorTypeVisualizationAppliance}, } type UseCaseImpl struct { @@ -36,10 +42,14 @@ type UseCaseImpl struct { } func NewUseCase(entity *EntityLocalImpl, ucEnumType model.UseCaseNameType, useCaseVersion model.SpecificationVersionType, scenarioSupport []model.UseCaseScenarioSupportType) *UseCaseImpl { - checkArguments(*entity.EntityImpl, ucEnumType) - actor := entityTypeActorMap[entity.EntityType()] + return NewUseCaseWithActor(entity, actor, ucEnumType, useCaseVersion, scenarioSupport) +} + +func NewUseCaseWithActor(entity *EntityLocalImpl, actor model.UseCaseActorType, ucEnumType model.UseCaseNameType, useCaseVersion model.SpecificationVersionType, scenarioSupport []model.UseCaseScenarioSupportType) *UseCaseImpl { + checkArguments(*entity.EntityImpl, ucEnumType) + ucManager := entity.Device().UseCaseManager() ucManager.Add(actor, ucEnumType, useCaseVersion, scenarioSupport)