Skip to content

Commit

Permalink
Feat: Add CDT Support as CEM Client Actor
Browse files Browse the repository at this point in the history
This commit introduces CDT support as a CEM 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 Oct 30, 2024
1 parent 2a6167e commit 6a7a862
Show file tree
Hide file tree
Showing 15 changed files with 1,215 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)
}
51 changes: 51 additions & 0 deletions features/internal/hvac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package internal

import (
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
}

// GetHvacSystemFunctionOperationModeRelations returns the operation mode relations (used to map operation modes to setpoints)
func (h *HvacCommon) GetHvacSystemFunctionOperationModeRelations() ([]model.HvacSystemFunctionSetpointRelationDataType, error) {
function := model.FunctionTypeHvacSystemFunctionSetPointRelationListData
relations := make([]model.HvacSystemFunctionSetpointRelationDataType, 0)

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

return relations, 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
47 changes: 47 additions & 0 deletions usecases/api/cem_cdt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package api

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

type CemCDTInterface interface {
api.UseCaseInterface

// Scenario 1

// Return the current setpoints data
//
// parameters:
// - entity: the entity to get the setpoints data from
//
// return values:
// - setpoints: A map of the setpoints for supported modes
//
// possible errors:
// - ErrDataNotAvailable if no such limit is (yet) available
// - and others
Setpoints(entity spineapi.EntityRemoteInterface) ([]Setpoint, error)

// Return the constraints for the setpoints
//
// parameters:
// - entity: the entity to get the setpoints constraints from
//
// return values:
// - setpointConstraints: A map of the constraints for supported modes
//
// possible errors:
// - ErrDataNotAvailable if no such limit is (yet) available
// - and others
SetpointConstraints(entity spineapi.EntityRemoteInterface) ([]SetpointConstraints, error)

// Write a setpoint
//
// parameters:
// - entity: the entity to write the setpoint to
// - mode: the mode to write the setpoint for
// - degC: the temperature setpoint value to write
WriteSetpoint(entity spineapi.EntityRemoteInterface, mode model.HvacOperationModeTypeType, degC float64) error
}
Loading

0 comments on commit 6a7a862

Please sign in to comment.