Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions usecases/cs/lpc/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
features "github.com/enbility/eebus-go/features/client"
"github.com/enbility/eebus-go/features/server"
ucapi "github.com/enbility/eebus-go/usecases/api"
"github.com/enbility/eebus-go/usecases/internal"
"github.com/enbility/eebus-go/usecases/usecase"
"github.com/enbility/ship-go/logging"
spineapi "github.com/enbility/spine-go/api"
Expand All @@ -31,9 +32,11 @@ var _ ucapi.CsLPCInterface = (*LPC)(nil)
// Add support for the Limitation of Power Consumption (LPC) use case
// as a Controllable System actor
//
// Parameters:
// - localEntity: The local entity which should support the use case
// - eventCB: The callback to be called when an event is triggered (optional, can be nil)
// Note: if the Monitoring of Power Consumption (MPC) or Monitoring of Grid Connection Point (MGCP) will be supported, add them first
//
// Parameters:
// - localEntity: The local entity which should support the use case
// - eventCB: The callback to be called when an event is triggered (optional, can be nil)
func NewLPC(localEntity spineapi.EntityLocalInterface, eventCB api.EntityEventCallback) *LPC {
validActorTypes := []model.UseCaseActorType{model.UseCaseActorTypeEnergyGuard}
validEntityTypes := []model.EntityTypeType{
Expand Down Expand Up @@ -182,11 +185,12 @@ func (e *LPC) AddFeatures() {
f.AddFunctionType(model.FunctionTypeLoadControlLimitListData, true, true)
_ = f.AddWriteApprovalCallback(e.loadControlWriteCB)

measurementId := internal.GetPowerTotalMeasurementId(e.LocalEntity)
newLimitDesc := model.LoadControlLimitDescriptionDataType{
LimitType: util.Ptr(model.LoadControlLimitTypeTypeSignDependentAbsValueLimit),
LimitCategory: util.Ptr(model.LoadControlCategoryTypeObligation),
LimitDirection: util.Ptr(model.EnergyDirectionTypeConsume),
MeasurementId: util.Ptr(model.MeasurementIdType(0)), // This is a fake Measurement ID, as there is no Electrical Connection server defined, it can't provide any meaningful. But KEO requires this to be set :(
MeasurementId: util.Ptr(measurementId),
Unit: util.Ptr(model.UnitOfMeasurementTypeW),
ScopeType: util.Ptr(model.ScopeTypeTypeActivePowerLimit),
}
Expand Down Expand Up @@ -268,11 +272,11 @@ func (e *LPC) AddFeatures() {
f.AddFunctionType(model.FunctionTypeElectricalConnectionCharacteristicListData, true, false)

if ec, err := server.NewElectricalConnection(e.LocalEntity); err == nil {
// ElectricalConnectionId and ParameterId should be identical to the ones used
// in a MPC Server role implementation, which is not done here (yet)
electricalConnectionId := internal.GetElectricalConnectionId(e.LocalEntity)
parameterId := internal.GetParameterIdForACPowerTotalMeasurement(e.LocalEntity, electricalConnectionId, measurementId)
newCharData := model.ElectricalConnectionCharacteristicDataType{
ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)),
ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(0)),
ElectricalConnectionId: util.Ptr(electricalConnectionId),
ParameterId: util.Ptr(parameterId),
CharacteristicContext: util.Ptr(model.ElectricalConnectionCharacteristicContextTypeEntity),
CharacteristicType: util.Ptr(e.characteristicType()),
Unit: util.Ptr(model.UnitOfMeasurementTypeW),
Expand Down
14 changes: 9 additions & 5 deletions usecases/cs/lpp/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
features "github.com/enbility/eebus-go/features/client"
"github.com/enbility/eebus-go/features/server"
ucapi "github.com/enbility/eebus-go/usecases/api"
"github.com/enbility/eebus-go/usecases/internal"
"github.com/enbility/eebus-go/usecases/usecase"
"github.com/enbility/ship-go/logging"
spineapi "github.com/enbility/spine-go/api"
Expand All @@ -31,6 +32,8 @@ var _ ucapi.CsLPPInterface = (*LPP)(nil)
// Add support for the Limitation of Power Production (LPC) use case
// as a Controllable System actor
//
// Note: if the Monitoring of Power Consumption (MPC) or Monitoring of Grid Connection Point (MGCP) will be supported, add them first
//
// Parameters:
// - localEntity: The local entity which should support the use case
// - eventCB: The callback to be called when an event is triggered (optional, can be nil)
Expand Down Expand Up @@ -182,11 +185,12 @@ func (e *LPP) AddFeatures() {
f.AddFunctionType(model.FunctionTypeLoadControlLimitListData, true, true)
_ = f.AddWriteApprovalCallback(e.loadControlWriteCB)

measurementId := internal.GetPowerTotalMeasurementId(e.LocalEntity)
newLimitDesc := model.LoadControlLimitDescriptionDataType{
LimitType: util.Ptr(model.LoadControlLimitTypeTypeSignDependentAbsValueLimit),
LimitCategory: util.Ptr(model.LoadControlCategoryTypeObligation),
LimitDirection: util.Ptr(model.EnergyDirectionTypeProduce),
MeasurementId: util.Ptr(model.MeasurementIdType(0)), // This is a fake Measurement ID, as there is no Electrical Connection server defined, it can't provide any meaningful. But KEO requires this to be set :(
MeasurementId: util.Ptr(measurementId),
Unit: util.Ptr(model.UnitOfMeasurementTypeW),
ScopeType: util.Ptr(model.ScopeTypeTypeActivePowerLimit),
}
Expand Down Expand Up @@ -268,11 +272,11 @@ func (e *LPP) AddFeatures() {
f.AddFunctionType(model.FunctionTypeElectricalConnectionCharacteristicListData, true, false)

if ec, err := server.NewElectricalConnection(e.LocalEntity); err == nil {
// ElectricalConnectionId and ParameterId should be identical to the ones used
// in a MPC Server role implementation, which is not done here (yet)
electricalConnectionId := internal.GetElectricalConnectionId(e.LocalEntity)
parameterId := internal.GetParameterIdForACPowerTotalMeasurement(e.LocalEntity, electricalConnectionId, measurementId)
newCharData := model.ElectricalConnectionCharacteristicDataType{
ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(0)),
ParameterId: util.Ptr(model.ElectricalConnectionParameterIdType(0)),
ElectricalConnectionId: util.Ptr(electricalConnectionId),
ParameterId: util.Ptr(parameterId),
CharacteristicContext: util.Ptr(model.ElectricalConnectionCharacteristicContextTypeEntity),
CharacteristicType: util.Ptr(e.characteristicType()),
Unit: util.Ptr(model.UnitOfMeasurementTypeW),
Expand Down
58 changes: 58 additions & 0 deletions usecases/internal/electricalConnection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package internal

import (
"github.com/enbility/eebus-go/features/server"
spineapi "github.com/enbility/spine-go/api"
"github.com/enbility/spine-go/model"
"github.com/enbility/spine-go/util"
)

// GetElectricalConnectionId returns the ElectricalConnectionId for the AC Consume electrical connection
func GetElectricalConnectionId(localEntity spineapi.EntityLocalInterface) model.ElectricalConnectionIdType {
if localEntity == nil {
return model.ElectricalConnectionIdType(0)
}
electricalConnectionFeat := localEntity.FeatureOfTypeAndRole(model.FeatureTypeTypeElectricalConnection, model.RoleTypeServer)
if electricalConnectionFeat == nil {
return model.ElectricalConnectionIdType(0)
}
electricalConnection, err := server.NewElectricalConnection(localEntity)
if err != nil || electricalConnection == nil {
return model.ElectricalConnectionIdType(0)
}
electricalConnectionDescriptions, err := electricalConnection.GetDescriptionsForFilter(model.ElectricalConnectionDescriptionDataType{
PowerSupplyType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc),
PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume),
})
if err != nil || len(electricalConnectionDescriptions) != 1 || electricalConnectionDescriptions[0].ElectricalConnectionId == nil {
return model.ElectricalConnectionIdType(0)
}
return *electricalConnectionDescriptions[0].ElectricalConnectionId
}

// GetParameterIdForACPowerTotalMeasurement returns the ParameterId for the AC Power Total measurement on the specified electrical connection
func GetParameterIdForACPowerTotalMeasurement(localEntity spineapi.EntityLocalInterface,
electricalConnectionId model.ElectricalConnectionIdType,
measurementId model.MeasurementIdType) model.ElectricalConnectionParameterIdType {
if localEntity == nil {
return model.ElectricalConnectionParameterIdType(0)
}
electricalConnectionFeat := localEntity.FeatureOfTypeAndRole(model.FeatureTypeTypeElectricalConnection, model.RoleTypeServer)
if electricalConnectionFeat == nil {
return model.ElectricalConnectionParameterIdType(0)
}
electricalConnection, err := server.NewElectricalConnection(localEntity)
if err != nil || electricalConnection == nil {
return model.ElectricalConnectionParameterIdType(0)
}
parameterDescriptions, err := electricalConnection.GetParameterDescriptionsForFilter(model.ElectricalConnectionParameterDescriptionDataType{
ElectricalConnectionId: util.Ptr(electricalConnectionId),
MeasurementId: util.Ptr(measurementId),
VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc),
AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal),
})
if err != nil || len(parameterDescriptions) != 1 || parameterDescriptions[0].ParameterId == nil {
return model.ElectricalConnectionParameterIdType(0)
}
return *parameterDescriptions[0].ParameterId
}
125 changes: 125 additions & 0 deletions usecases/internal/electricalConnection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package internal

import (
"github.com/enbility/eebus-go/features/server"
"github.com/enbility/spine-go/model"
"github.com/enbility/spine-go/spine"
"github.com/enbility/spine-go/util"
"github.com/stretchr/testify/assert"
)

func (s *InternalSuite) Test_GetElectricalConnectionId() {
// Testing nil localEntity should return 0
electricalConnectionId := GetElectricalConnectionId(nil)
assert.Equal(s.T(), model.ElectricalConnectionIdType(0), electricalConnectionId)

// Testing localEntity without electrical connection server feature should return 0
localDevice := s.service.LocalDevice()
testEntity := localDevice.EntityForType(model.EntityTypeTypeCEM)

electricalConnectionId = GetElectricalConnectionId(testEntity)
assert.Equal(s.T(), model.ElectricalConnectionIdType(0), electricalConnectionId)

// Testing Create a test entity with electrical connection server feature
// Add an electrical connection server feature
electricalConnectionServerFeature := spine.NewFeatureLocal(20, testEntity, model.FeatureTypeTypeElectricalConnection, model.RoleTypeServer)
electricalConnectionServerFeature.AddFunctionType(model.FunctionTypeElectricalConnectionDescriptionListData, true, false)
electricalConnectionServerFeature.AddFunctionType(model.FunctionTypeElectricalConnectionParameterDescriptionListData, true, false)
testEntity.AddFeature(electricalConnectionServerFeature)

// Initially, there should be no electrical connection descriptions, so it should return 0
electricalConnectionId = GetElectricalConnectionId(testEntity)
assert.Equal(s.T(), model.ElectricalConnectionIdType(0), electricalConnectionId)

// Testing Add electrical connection descriptions that don't match the filter
// We need to create a server.ElectricalConnection instance to add descriptions
electricalConnectionServer, err := server.NewElectricalConnection(testEntity)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), electricalConnectionServer)

descDataNoMatch := model.ElectricalConnectionDescriptionDataType{
ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(100)),
PowerSupplyType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeDc), // Different type (DC instead of AC)
PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume),
}

err = electricalConnectionServer.AddDescription(descDataNoMatch)
assert.Nil(s.T(), err)

// Should still return 0 as no matching descriptions
electricalConnectionId = GetElectricalConnectionId(testEntity)
assert.Equal(s.T(), model.ElectricalConnectionIdType(0), electricalConnectionId)

// Testing Add a matching electrical connection description
descData := model.ElectricalConnectionDescriptionDataType{
ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(200)),
PowerSupplyType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc),
PositiveEnergyDirection: util.Ptr(model.EnergyDirectionTypeConsume),
}

err = electricalConnectionServer.AddDescription(descData)
assert.Nil(s.T(), err)

// Now it should return the correct electrical connection ID
electricalConnectionId = GetElectricalConnectionId(testEntity)
assert.Equal(s.T(), model.ElectricalConnectionIdType(200), electricalConnectionId)
}

func (s *InternalSuite) Test_GetParameterIdForACPowerTotalMeasurement() {
// Testing nil localEntity should return 0
parameterId := GetParameterIdForACPowerTotalMeasurement(nil, model.ElectricalConnectionIdType(1), model.MeasurementIdType(1))
assert.Equal(s.T(), model.ElectricalConnectionParameterIdType(0), parameterId)

// Testing localEntity without electrical connection server feature should return 0
localDevice := s.service.LocalDevice()
testEntity := localDevice.EntityForType(model.EntityTypeTypeCEM)

parameterId = GetParameterIdForACPowerTotalMeasurement(testEntity, model.ElectricalConnectionIdType(1), model.MeasurementIdType(1))
assert.Equal(s.T(), model.ElectricalConnectionParameterIdType(0), parameterId)

// Testing Create a test entity with electrical connection server feature
// Add an electrical connection server feature
electricalConnectionServerFeature := spine.NewFeatureLocal(21, testEntity, model.FeatureTypeTypeElectricalConnection, model.RoleTypeServer)
electricalConnectionServerFeature.AddFunctionType(model.FunctionTypeElectricalConnectionDescriptionListData, true, false)
electricalConnectionServerFeature.AddFunctionType(model.FunctionTypeElectricalConnectionParameterDescriptionListData, true, false)
testEntity.AddFeature(electricalConnectionServerFeature)

// Initially, there should be no parameter descriptions, so it should return 0
parameterId = GetParameterIdForACPowerTotalMeasurement(testEntity, model.ElectricalConnectionIdType(1), model.MeasurementIdType(1))
assert.Equal(s.T(), model.ElectricalConnectionParameterIdType(0), parameterId)

// Add parameter descriptions
electricalConnectionServer, err := server.NewElectricalConnection(testEntity)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), electricalConnectionServer)

// Testing Add parameter descriptions that don't match the filter
paramDataNoMatch := model.ElectricalConnectionParameterDescriptionDataType{
ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(1)),
MeasurementId: util.Ptr(model.MeasurementIdType(2)), // Different measurement ID
VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc),
AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal),
}

paramId1 := electricalConnectionServer.AddParameterDescription(paramDataNoMatch)
assert.NotNil(s.T(), paramId1)

// Should still return 0 as no matching descriptions
parameterId = GetParameterIdForACPowerTotalMeasurement(testEntity, model.ElectricalConnectionIdType(1), model.MeasurementIdType(1))
assert.Equal(s.T(), model.ElectricalConnectionParameterIdType(0), parameterId)

// Testing Add a matching parameter description
paramData := model.ElectricalConnectionParameterDescriptionDataType{
ElectricalConnectionId: util.Ptr(model.ElectricalConnectionIdType(1)),
MeasurementId: util.Ptr(model.MeasurementIdType(1)),
VoltageType: util.Ptr(model.ElectricalConnectionVoltageTypeTypeAc),
AcMeasurementType: util.Ptr(model.ElectricalConnectionAcMeasurementTypeTypeReal),
}

paramId4 := electricalConnectionServer.AddParameterDescription(paramData)
assert.NotNil(s.T(), paramId4)

// Now it should return the correct parameter ID
parameterId = GetParameterIdForACPowerTotalMeasurement(testEntity, model.ElectricalConnectionIdType(1), model.MeasurementIdType(1))
assert.Equal(s.T(), *paramId4, parameterId)
}
31 changes: 31 additions & 0 deletions usecases/internal/measurement.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import (

"github.com/enbility/eebus-go/api"
"github.com/enbility/eebus-go/features/client"
"github.com/enbility/eebus-go/features/server"
spineapi "github.com/enbility/spine-go/api"
"github.com/enbility/spine-go/model"
"github.com/enbility/spine-go/util"
)

// According to the LPC Installation Guide, this value is relatively high to make sure it doesn't conflict with other IDs on this entity
var defaultPowerTotalMeasurementId = model.MeasurementIdType(50)

// return the phase specific measurement data
func MeasurementPhaseSpecificDataForFilter(
localEntity spineapi.EntityLocalInterface,
Expand Down Expand Up @@ -75,3 +80,29 @@ func MeasurementPhaseSpecificDataForFilter(

return result, nil
}

// GetPowerTotalMeasurementId returns the MeasurementId for the AC Power Total measurement
func GetPowerTotalMeasurementId(localEntity spineapi.EntityLocalInterface) model.MeasurementIdType {
if localEntity == nil {
return defaultPowerTotalMeasurementId
}
measurementFeat := localEntity.FeatureOfTypeAndRole(model.FeatureTypeTypeMeasurement, model.RoleTypeServer)
if measurementFeat == nil {
return defaultPowerTotalMeasurementId
}
measurement, err := server.NewMeasurement(localEntity)
if err != nil || measurement == nil {
return defaultPowerTotalMeasurementId
}
MeasurementDescriptionData, err := measurement.GetDescriptionsForFilter(model.MeasurementDescriptionDataType{
MeasurementType: util.Ptr(model.MeasurementTypeTypePower),
CommodityType: util.Ptr(model.CommodityTypeTypeElectricity),
Unit: util.Ptr(model.UnitOfMeasurementTypeW),
ScopeType: util.Ptr(model.ScopeTypeTypeACPowerTotal),
})
if err != nil || len(MeasurementDescriptionData) != 1 || MeasurementDescriptionData[0].MeasurementId == nil {
return defaultPowerTotalMeasurementId
}

return *MeasurementDescriptionData[0].MeasurementId
}
Loading