Skip to content

Commit

Permalink
Feat: Add CDT Support as CA (Configuration Appliance) Client Actor
Browse files Browse the repository at this point in the history
This commit introduces CDT support as a CA (Configuration Appliance)
client actor.

It provides full support for the CDT use case, specifically Scenario 1.
This includes the ability to retrieve setpoints, their constraints, and
map them to their corresponding operation modes via
HvacSetpointRelations.

For the use case to fully function, support for the CDSF (Configuration
of DHW System Function) use case is necessary. Specifically, we need to
request HvacOperationModeDescriptionListDataType, which is used in
conjunction with HvacSystemFunctionSetpointRelationListDataType to
establish the mapping between operation modes and their setpoints, and
to enable the ability to write setpoints.

Note: Writing setpoints was tested and confirmed to work with Vaillant's
HeatPump by requesting the
HvacOperationModeDescriptionListDataType message and performing the
mapping without the CDSF use case.

Resources used (specifications):
- EEBus UC Technical Specification Configuration of DHW Temperature
- EEBus SPINE Technical Specification Resource Specification
  • Loading branch information
daviddsapir authored and David Sapir committed Nov 3, 2024
1 parent 2a6167e commit c1ba25b
Show file tree
Hide file tree
Showing 15 changed files with 1,252 additions and 6 deletions.
50 changes: 50 additions & 0 deletions features/client/hvac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package client

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

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 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)
}

// 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)
}
83 changes: 83 additions & 0 deletions features/client/setpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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"
)

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)
}

// WriteSetPointListData writes the given setpoint data
//
// Parameters:
// - data: the setpoint data to write
//
// Returns:
// - the message counter of the sent message
// - an error if the data could not be written
func (s *Setpoint) WriteSetPointListData(
data []model.SetpointDataType,
) (*model.MsgCounterType, error) {
if len(data) == 0 {
return nil, api.ErrMissingData
}

cmd := model.CmdType{
SetpointListData: &model.SetpointListDataType{
SetpointData: data,
},
}

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

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

type HvacCommon struct {
featureLocal spineapi.FeatureLocalInterface
featureRemote spineapi.FeatureRemoteInterface
}

// NewLocalHvac creates a new HvacCommon helper for local entities
func NewLocalHvac(featureLocal spineapi.FeatureLocalInterface) *HvacCommon {
return &HvacCommon{
featureLocal: featureLocal,
}
}

// NewRemoteHvac creates a new HvacCommon helper for remote entities
func NewRemoteHvac(featureRemote spineapi.FeatureRemoteInterface) *HvacCommon {
return &HvacCommon{
featureRemote: featureRemote,
}
}

// GetHvacOperationModeDescriptions returns the operation mode descriptions
func (h *HvacCommon) GetHvacOperationModeDescriptions() ([]model.HvacOperationModeDescriptionDataType, error) {
function := model.FunctionTypeHvacOperationModeDescriptionListData
operationModeDescriptions := make([]model.HvacOperationModeDescriptionDataType, 0)

data, err := featureDataCopyOfType[model.HvacOperationModeDescriptionListDataType](h.featureLocal, h.featureRemote, function)
if err == nil || data != nil {
operationModeDescriptions = append(operationModeDescriptions, data.HvacOperationModeDescriptionData...)
}

return operationModeDescriptions, nil
}

// GetHvacSystemFunctionSetpointRelations returns the operation mode relations (used to map operation modes to setpoints)
func (h *HvacCommon) GetHvacSystemFunctionSetpointRelationsForSystemFunctionId(
id model.HvacSystemFunctionIdType,
) ([]model.HvacSystemFunctionSetpointRelationDataType, error) {
function := model.FunctionTypeHvacSystemFunctionSetPointRelationListData
filter := model.HvacSystemFunctionSetpointRelationDataType{
SystemFunctionId: &id,
}

data, err := featureDataCopyOfType[model.HvacSystemFunctionSetpointRelationListDataType](h.featureLocal, h.featureRemote, function)
if err != nil || data == nil || data.HvacSystemFunctionSetpointRelationData == nil {
return nil, api.ErrDataNotAvailable
}

result := searchFilterInList[model.HvacSystemFunctionSetpointRelationDataType](data.HvacSystemFunctionSetpointRelationData, filter)
if len(result) == 0 {
return nil, api.ErrDataNotAvailable
}

return result, nil
}

func (h *HvacCommon) GetHvacSystemFunctionDescriptionsForFilter(
filter model.HvacSystemFunctionDescriptionDataType,
) ([]model.HvacSystemFunctionDescriptionDataType, error) {
function := model.FunctionTypeHvacSystemFunctionDescriptionListData

data, err := featureDataCopyOfType[model.HvacSystemFunctionDescriptionListDataType](h.featureLocal, h.featureRemote, function)
if err != nil || data == nil || data.HvacSystemFunctionDescriptionData == nil {
return nil, api.ErrDataNotAvailable
}

result := searchFilterInList[model.HvacSystemFunctionDescriptionDataType](data.HvacSystemFunctionDescriptionData, filter)

return result, nil
}
127 changes: 127 additions & 0 deletions features/internal/setpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package internal

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

type SetPointCommon struct {
featureLocal spineapi.FeatureLocalInterface
featureRemote spineapi.FeatureRemoteInterface
}

// NewLocalSetPoint creates a new SetPointCommon helper for local entities
func NewLocalSetPoint(featureLocal spineapi.FeatureLocalInterface) *SetPointCommon {
return &SetPointCommon{
featureLocal: featureLocal,
}
}

// NewRemoteSetPoint creates a new SetPointCommon helper for remote entities
func NewRemoteSetPoint(featureRemote spineapi.FeatureRemoteInterface) *SetPointCommon {
return &SetPointCommon{
featureRemote: featureRemote,
}
}

// GetSetpointDescriptions returns the setpoint descriptions
func (s *SetPointCommon) GetSetpointDescriptions() ([]model.SetpointDescriptionDataType, error) {
function := model.FunctionTypeSetpointDescriptionListData

data, err := featureDataCopyOfType[model.SetpointDescriptionListDataType](s.featureLocal, s.featureRemote, function)
if err != nil || data == nil || data.SetpointDescriptionData == nil {
return nil, api.ErrDataNotAvailable
}

return data.SetpointDescriptionData, nil
}

// GetSetpointForId returns the setpoint data for a given setpoint ID
func (s *SetPointCommon) GetSetpointForId(
id model.SetpointIdType,
) (*model.SetpointDataType, error) {
filter := model.SetpointDataType{
SetpointId: &id,
}

result, err := s.GetSetpointDataForFilter(filter)
if err != nil || len(result) == 0 {
return nil, api.ErrDataNotAvailable
}

return util.Ptr(result[0]), nil
}

// GetSetpoints returns the setpoints
func (s *SetPointCommon) GetSetpoints() []model.SetpointDataType {
function := model.FunctionTypeSetpointListData

data, err := featureDataCopyOfType[model.SetpointListDataType](s.featureLocal, s.featureRemote, function)
if err != nil || data == nil || data.SetpointData == nil {
return []model.SetpointDataType{}
}

return data.SetpointData
}

// GetSetpointDataForFilter returns the setpoint data for a given filter
func (s *SetPointCommon) GetSetpointDataForFilter(
filter model.SetpointDataType,
) ([]model.SetpointDataType, error) {
function := model.FunctionTypeSetpointListData

data, err := featureDataCopyOfType[model.SetpointListDataType](s.featureLocal, s.featureRemote, function)
if err != nil || data == nil || data.SetpointData == nil {
return nil, api.ErrDataNotAvailable
}

result := searchFilterInList[model.SetpointDataType](data.SetpointData, filter)

return result, nil
}

// GetSetpointConstraints returns the setpoints constraints.
func (s *SetPointCommon) GetSetpointConstraints() []model.SetpointConstraintsDataType {
function := model.FunctionTypeSetpointConstraintsListData

data, err := featureDataCopyOfType[model.SetpointConstraintsListDataType](s.featureLocal, s.featureRemote, function)
if err != nil || data == nil || data.SetpointConstraintsData == nil {
return []model.SetpointConstraintsDataType{}
}

return data.SetpointConstraintsData
}

// GetSetpointConstraintsForId returns the setpoint constraints for a given setpoint ID
func (s *SetPointCommon) GetSetpointConstraintsForId(
id model.SetpointIdType,
) (*model.SetpointConstraintsDataType, error) {
filter := model.SetpointConstraintsDataType{
SetpointId: &id,
}

result, err := s.GetSetpointConstraintsForFilter(filter)
if err != nil || len(result) == 0 {
return nil, api.ErrDataNotAvailable
}

return util.Ptr(result[0]), nil
}

// GetSetpointConstraintsForFilter returns the setpoint constraints for a given filter
func (s *SetPointCommon) GetSetpointConstraintsForFilter(
filter model.SetpointConstraintsDataType,
) ([]model.SetpointConstraintsDataType, error) {
function := model.FunctionTypeSetpointConstraintsListData

data, err := featureDataCopyOfType[model.SetpointConstraintsListDataType](s.featureLocal, s.featureRemote, function)
if err != nil || data == nil || data.SetpointConstraintsData == nil {
return nil, api.ErrDataNotAvailable
}

result := searchFilterInList[model.SetpointConstraintsDataType](data.SetpointConstraintsData, filter)

return result, nil
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/enbility/eebus-go
go 1.22.0

require (
github.com/enbility/ship-go v0.0.0-20241006160314-3a4325a1a6d6
github.com/enbility/spine-go v0.0.0-20241007182100-30ee8bc405a7
github.com/enbility/ship-go v0.6.1-0.20241023165311-5963bf4d9424
github.com/enbility/spine-go v0.7.1-0.20241023170915-0b14938a9a37
github.com/stretchr/testify v1.9.0
)

Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a h1:foChWb8lhzqa6lWDRs6COYMdp649YlUirFP8GqoT0JQ=
github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a/go.mod h1:H64mhYcAQUGUUnVqMdZQf93kPecH4M79xwH95Lddt3U=
github.com/enbility/ship-go v0.0.0-20241006160314-3a4325a1a6d6 h1:bjrcJ4wxEsG5rXHlXnedRzqAV9JYglj82S14Nf1oLvs=
github.com/enbility/ship-go v0.0.0-20241006160314-3a4325a1a6d6/go.mod h1:JJp8EQcJhUhTpZ2LSEU4rpdaM3E2n08tswWFWtmm/wU=
github.com/enbility/spine-go v0.0.0-20241007182100-30ee8bc405a7 h1:n6tv+YUMncSR9qxUs6k7d/YsKD9ujHHp5pUspIvM6sc=
github.com/enbility/spine-go v0.0.0-20241007182100-30ee8bc405a7/go.mod h1:ZoI9TaJO/So/677uknrli8sc6iryD7wC5iWhVIre+MI=
github.com/enbility/ship-go v0.6.1-0.20241023165311-5963bf4d9424 h1:yzf1pWKZn+vhxtWE1ZNyspAX8GiX/r4uBXZU+SdidNY=
github.com/enbility/ship-go v0.6.1-0.20241023165311-5963bf4d9424/go.mod h1:JJp8EQcJhUhTpZ2LSEU4rpdaM3E2n08tswWFWtmm/wU=
github.com/enbility/spine-go v0.7.0 h1:UZeghFgnM3VFU0ghc57Htt6gnxwP9jLppfU2GUMJGgY=
github.com/enbility/spine-go v0.7.0/go.mod h1:IF1sBTr7p3wXqlejeBJcJ8BYFlzzRaZcJsGw8XjgEgc=
github.com/enbility/spine-go v0.7.1-0.20241023170915-0b14938a9a37 h1:oZFPU4fHYBbSMVCwP3c9GHov8dFXqqQ2McvEyalsBY8=
github.com/enbility/spine-go v0.7.1-0.20241023170915-0b14938a9a37/go.mod h1:ZoI9TaJO/So/677uknrli8sc6iryD7wC5iWhVIre+MI=
github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6 h1:XOYvxKtT1oxT37w/5oEiRLuPbm9FuJPt3fiYhX0h8Po=
github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6/go.mod h1:BszP9qFV14mPXgyIREbgIdQtWxbAj3OKqvK02HihMoM=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
Expand Down
Loading

0 comments on commit c1ba25b

Please sign in to comment.