Skip to content
Open
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
118 changes: 118 additions & 0 deletions features/client/hvac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package client

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

type Hvac struct {
*Feature

*internal.HvacCommon
}

// Get a new HVAC features helper
//
// - The feature on the local entity has to be of role client
// - The feature on the remote entity has to be of role server
func NewHvac(
localEntity spineapi.EntityLocalInterface,
remoteEntity spineapi.EntityRemoteInterface,
) (*Hvac, error) {
feature, err := NewFeature(model.FeatureTypeTypeHvac, localEntity, remoteEntity)
if err != nil {
return nil, err
}

hvac := &Hvac{
Feature: feature,
HvacCommon: internal.NewRemoteHvac(feature.featureRemote),
}

return hvac, nil
}

// request FunctionTypeHvacSystemFunctionDescriptionListData from a remote device
func (h *Hvac) RequestHvacSystemFunctionDescriptions(
selector *model.HvacSystemFunctionDescriptionListDataSelectorsType,
elements *model.HvacSystemFunctionDescriptionDataElementsType,
) (*model.MsgCounterType, error) {
return h.requestData(model.FunctionTypeHvacSystemFunctionDescriptionListData, selector, elements)
}

// request FunctionTypeHvacSystemFunctionListData from a remote device
func (h *Hvac) RequestHvacSystemFunctions(
selector *model.HvacSystemFunctionListDataSelectorsType,
elements *model.HvacSystemFunctionDataElementsType,
) (*model.MsgCounterType, error) {
return h.requestData(model.FunctionTypeHvacSystemFunctionListData, selector, elements)
}

// request FunctionTypeHvacOperationModeDescriptionListData from a remote device
func (h *Hvac) RequestHvacOperationModeDescriptions(
selector *model.HvacOperationModeDescriptionListDataSelectorsType,
elements *model.HvacOperationModeDescriptionDataElementsType,
) (*model.MsgCounterType, error) {
return h.requestData(model.FunctionTypeHvacOperationModeDescriptionListData, selector, elements)
}

// request FunctionTypeHvacSystemFunctionOperationModeRelationListData from a remote device
func (h *Hvac) RequestHvacSystemFunctionOperationModeRelations(
selector *model.HvacSystemFunctionOperationModeRelationListDataSelectorsType,
elements *model.HvacSystemFunctionOperationModeRelationDataElementsType,
) (*model.MsgCounterType, error) {
return h.requestData(model.FunctionTypeHvacSystemFunctionOperationModeRelationListData, selector, elements)
}

// request FunctionTypeHvacSystemFunctionSetPointRelationListData from a remote device
func (h *Hvac) RequestHvacSystemFunctionSetpointRelations(
selector *model.HvacSystemFunctionSetpointRelationListDataSelectorsType,
elements *model.HvacSystemFunctionSetpointRelationDataElementsType,
) (*model.MsgCounterType, error) {
return h.requestData(model.FunctionTypeHvacSystemFunctionSetPointRelationListData, selector, elements)
}

// write the given HVAC system function data to the remote device,
// e.g. to change the current operation mode of a system function
func (h *Hvac) WriteHvacSystemFunctionListData(
data []model.HvacSystemFunctionDataType,
) (*model.MsgCounterType, error) {
if len(data) == 0 {
return nil, api.ErrMissingData
}

// the remote server has to advertise the write operation for this function
operation := h.featureRemote.Operations()[model.FunctionTypeHvacSystemFunctionListData]
if operation == nil || !operation.Write() {
return nil, api.ErrNotSupported
}

// use a partial write when the server supports it, otherwise merge the
// modified entries into the cached list and write the complete list, so
// unrelated system functions are not dropped by a full replacement
filters := []model.FilterType{*model.NewFilterTypePartial()}
if !operation.WritePartial() {
filters = nil
updateData := &model.HvacSystemFunctionListDataType{
HvacSystemFunctionData: data,
}
if mergedData, err := h.featureRemote.UpdateData(false, model.FunctionTypeHvacSystemFunctionListData, updateData, nil, nil); err == nil {
data = mergedData.([]model.HvacSystemFunctionDataType)
}
}

cmd := model.CmdType{
HvacSystemFunctionListData: &model.HvacSystemFunctionListDataType{
HvacSystemFunctionData: data,
},
}
if filters != nil {
cmd.Filter = filters
cmd.Function = util.Ptr(model.FunctionTypeHvacSystemFunctionListData)
}

return h.remoteDevice.Sender().Write(h.featureLocal.Address(), h.featureRemote.Address(), cmd)
}
140 changes: 140 additions & 0 deletions features/client/hvac_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package client

import (
"testing"

shipapi "github.com/enbility/ship-go/api"
spineapi "github.com/enbility/spine-go/api"
"github.com/enbility/spine-go/model"
"github.com/enbility/spine-go/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

func TestHvacSuite(t *testing.T) {
suite.Run(t, new(HvacSuite))
}

type HvacSuite struct {
suite.Suite

localEntity spineapi.EntityLocalInterface
remoteEntity spineapi.EntityRemoteInterface

hvac *Hvac
sentMessage []byte
}

var _ shipapi.ShipConnectionDataWriterInterface = (*HvacSuite)(nil)

func (s *HvacSuite) WriteShipMessageWithPayload(message []byte) {
s.sentMessage = message
}

func (s *HvacSuite) BeforeTest(suiteName, testName string) {
s.localEntity, s.remoteEntity = setupFeatures(
s.T(),
s,
[]featureFunctions{
{
featureType: model.FeatureTypeTypeHvac,
functions: []model.FunctionType{
model.FunctionTypeHvacSystemFunctionDescriptionListData,
model.FunctionTypeHvacSystemFunctionListData,
model.FunctionTypeHvacOperationModeDescriptionListData,
model.FunctionTypeHvacSystemFunctionOperationModeRelationListData,
model.FunctionTypeHvacSystemFunctionSetPointRelationListData,
},
},
},
)

var err error
s.hvac, err = NewHvac(s.localEntity, nil)
assert.NotNil(s.T(), err)
assert.Nil(s.T(), s.hvac)

s.hvac, err = NewHvac(s.localEntity, s.remoteEntity)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), s.hvac)
}

func (s *HvacSuite) Test_RequestHvacSystemFunctionDescriptions() {
counter, err := s.hvac.RequestHvacSystemFunctionDescriptions(nil, nil)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), counter)
}

func (s *HvacSuite) Test_RequestHvacSystemFunctions() {
counter, err := s.hvac.RequestHvacSystemFunctions(nil, nil)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), counter)
}

func (s *HvacSuite) Test_RequestHvacOperationModeDescriptions() {
counter, err := s.hvac.RequestHvacOperationModeDescriptions(nil, nil)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), counter)
}

func (s *HvacSuite) Test_RequestHvacSystemFunctionOperationModeRelations() {
counter, err := s.hvac.RequestHvacSystemFunctionOperationModeRelations(nil, nil)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), counter)
}

func (s *HvacSuite) Test_RequestHvacSystemFunctionSetpointRelations() {
counter, err := s.hvac.RequestHvacSystemFunctionSetpointRelations(nil, nil)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), counter)
}

func (s *HvacSuite) Test_WriteHvacSystemFunctionListData() {
counter, err := s.hvac.WriteHvacSystemFunctionListData(nil)
assert.NotNil(s.T(), err)
assert.Nil(s.T(), counter)

data := []model.HvacSystemFunctionDataType{}
counter, err = s.hvac.WriteHvacSystemFunctionListData(data)
assert.NotNil(s.T(), err)
assert.Nil(s.T(), counter)

data = []model.HvacSystemFunctionDataType{
{
SystemFunctionId: util.Ptr(model.HvacSystemFunctionIdType(1)),
CurrentOperationModeId: util.Ptr(model.HvacOperationModeIdType(2)),
},
}
counter, err = s.hvac.WriteHvacSystemFunctionListData(data)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), counter)
}

func (s *HvacSuite) Test_WriteHvacSystemFunctionListData_Partial() {
localEntity, remoteEntity := setupFeatures(
s.T(),
s,
[]featureFunctions{
{
featureType: model.FeatureTypeTypeHvac,
functions: []model.FunctionType{
model.FunctionTypeHvacSystemFunctionListData,
},
partial: true,
},
},
)

hvac, err := NewHvac(localEntity, remoteEntity)
assert.Nil(s.T(), err)

data := []model.HvacSystemFunctionDataType{
{
SystemFunctionId: util.Ptr(model.HvacSystemFunctionIdType(1)),
CurrentOperationModeId: util.Ptr(model.HvacOperationModeIdType(2)),
},
}
counter, err := hvac.WriteHvacSystemFunctionListData(data)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), counter)
}
102 changes: 102 additions & 0 deletions features/client/setpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package client

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

type Setpoint struct {
*Feature

*internal.SetpointCommon
}

// Get a new Setpoint features helper
//
// - The feature on the local entity has to be of role client
// - The feature on the remote entity has to be of role server
func NewSetpoint(
localEntity spineapi.EntityLocalInterface,
remoteEntity spineapi.EntityRemoteInterface,
) (*Setpoint, error) {
feature, err := NewFeature(model.FeatureTypeTypeSetpoint, localEntity, remoteEntity)
if err != nil {
return nil, err
}

sp := &Setpoint{
Feature: feature,
SetpointCommon: internal.NewRemoteSetpoint(feature.featureRemote),
}

return sp, nil
}

// request FunctionTypeSetpointDescriptionListData from a remote device
func (s *Setpoint) RequestSetpointDescriptions(
selector *model.SetpointDescriptionListDataSelectorsType,
elements *model.SetpointDescriptionDataElementsType,
) (*model.MsgCounterType, error) {
return s.requestData(model.FunctionTypeSetpointDescriptionListData, selector, elements)
}

// request FunctionTypeSetpointConstraintsListData from a remote device
func (s *Setpoint) RequestSetpointConstraints(
selector *model.SetpointConstraintsListDataSelectorsType,
elements *model.SetpointConstraintsDataElementsType,
) (*model.MsgCounterType, error) {
return s.requestData(model.FunctionTypeSetpointConstraintsListData, selector, elements)
}

// request FunctionTypeSetpointListData from a remote device
func (s *Setpoint) RequestSetpoints(
selector *model.SetpointListDataSelectorsType,
elements *model.SetpointDataElementsType,
) (*model.MsgCounterType, error) {
return s.requestData(model.FunctionTypeSetpointListData, selector, elements)
}

// write the given setpoint data to the remote device,
// returns the message counter of the sent message
func (s *Setpoint) WriteSetpointListData(
data []model.SetpointDataType,
) (*model.MsgCounterType, error) {
if len(data) == 0 {
return nil, api.ErrMissingData
}

// the remote server has to advertise the write operation for this function
operation := s.featureRemote.Operations()[model.FunctionTypeSetpointListData]
if operation == nil || !operation.Write() {
return nil, api.ErrNotSupported
}

// use a partial write when the server supports it, otherwise merge the
// modified entries into the cached list and write the complete list, so
// unrelated setpoints are not dropped by a full replacement
filters := []model.FilterType{*model.NewFilterTypePartial()}
if !operation.WritePartial() {
filters = nil
updateData := &model.SetpointListDataType{
SetpointData: data,
}
if mergedData, err := s.featureRemote.UpdateData(false, model.FunctionTypeSetpointListData, updateData, nil, nil); err == nil {
data = mergedData.([]model.SetpointDataType)
}
}

cmd := model.CmdType{
SetpointListData: &model.SetpointListDataType{
SetpointData: data,
},
}
if filters != nil {
cmd.Filter = filters
cmd.Function = util.Ptr(model.FunctionTypeSetpointListData)
}

return s.remoteDevice.Sender().Write(s.featureLocal.Address(), s.featureRemote.Address(), cmd)
}
Loading
Loading