Skip to content

Commit e5909c0

Browse files
andigclaude
andcommitted
Write a partial list when supported, otherwise the complete cached list
The Hvac and Setpoint write helpers sent a list containing only the modified entry. Servers that interpret the payload as a full replacement would then drop unrelated system functions, overruns or setpoints. Send a proper partial write when the remote advertises WritePartial(). Otherwise copy the cached list, merge the modified entries and write the complete list, matching the LoadControl/DeviceConfiguration behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 92b7a8f commit e5909c0

3 files changed

Lines changed: 67 additions & 0 deletions

File tree

features/client/hvac.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/enbility/eebus-go/features/internal"
66
spineapi "github.com/enbility/spine-go/api"
77
"github.com/enbility/spine-go/model"
8+
"github.com/enbility/spine-go/util"
89
)
910

1011
type Hvac struct {
@@ -89,11 +90,29 @@ func (h *Hvac) WriteHvacSystemFunctionListData(
8990
return nil, api.ErrNotSupported
9091
}
9192

93+
// use a partial write when the server supports it, otherwise merge the
94+
// modified entries into the cached list and write the complete list, so
95+
// unrelated system functions are not dropped by a full replacement
96+
filters := []model.FilterType{*model.NewFilterTypePartial()}
97+
if !operation.WritePartial() {
98+
filters = nil
99+
updateData := &model.HvacSystemFunctionListDataType{
100+
HvacSystemFunctionData: data,
101+
}
102+
if mergedData, err := h.featureRemote.UpdateData(false, model.FunctionTypeHvacSystemFunctionListData, updateData, nil, nil); err == nil {
103+
data = mergedData.([]model.HvacSystemFunctionDataType)
104+
}
105+
}
106+
92107
cmd := model.CmdType{
93108
HvacSystemFunctionListData: &model.HvacSystemFunctionListDataType{
94109
HvacSystemFunctionData: data,
95110
},
96111
}
112+
if filters != nil {
113+
cmd.Filter = filters
114+
cmd.Function = util.Ptr(model.FunctionTypeHvacSystemFunctionListData)
115+
}
97116

98117
return h.remoteDevice.Sender().Write(h.featureLocal.Address(), h.featureRemote.Address(), cmd)
99118
}

features/client/hvac_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,32 @@ func (s *HvacSuite) Test_WriteHvacSystemFunctionListData() {
109109
assert.Nil(s.T(), err)
110110
assert.NotNil(s.T(), counter)
111111
}
112+
113+
func (s *HvacSuite) Test_WriteHvacSystemFunctionListData_Partial() {
114+
localEntity, remoteEntity := setupFeatures(
115+
s.T(),
116+
s,
117+
[]featureFunctions{
118+
{
119+
featureType: model.FeatureTypeTypeHvac,
120+
functions: []model.FunctionType{
121+
model.FunctionTypeHvacSystemFunctionListData,
122+
},
123+
partial: true,
124+
},
125+
},
126+
)
127+
128+
hvac, err := NewHvac(localEntity, remoteEntity)
129+
assert.Nil(s.T(), err)
130+
131+
data := []model.HvacSystemFunctionDataType{
132+
{
133+
SystemFunctionId: util.Ptr(model.HvacSystemFunctionIdType(1)),
134+
CurrentOperationModeId: util.Ptr(model.HvacOperationModeIdType(2)),
135+
},
136+
}
137+
counter, err := hvac.WriteHvacSystemFunctionListData(data)
138+
assert.Nil(s.T(), err)
139+
assert.NotNil(s.T(), counter)
140+
}

features/client/setpoint.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/enbility/eebus-go/features/internal"
66
spineapi "github.com/enbility/spine-go/api"
77
"github.com/enbility/spine-go/model"
8+
"github.com/enbility/spine-go/util"
89
)
910

1011
type Setpoint struct {
@@ -73,11 +74,29 @@ func (s *Setpoint) WriteSetpointListData(
7374
return nil, api.ErrNotSupported
7475
}
7576

77+
// use a partial write when the server supports it, otherwise merge the
78+
// modified entries into the cached list and write the complete list, so
79+
// unrelated setpoints are not dropped by a full replacement
80+
filters := []model.FilterType{*model.NewFilterTypePartial()}
81+
if !operation.WritePartial() {
82+
filters = nil
83+
updateData := &model.SetpointListDataType{
84+
SetpointData: data,
85+
}
86+
if mergedData, err := s.featureRemote.UpdateData(false, model.FunctionTypeSetpointListData, updateData, nil, nil); err == nil {
87+
data = mergedData.([]model.SetpointDataType)
88+
}
89+
}
90+
7691
cmd := model.CmdType{
7792
SetpointListData: &model.SetpointListDataType{
7893
SetpointData: data,
7994
},
8095
}
96+
if filters != nil {
97+
cmd.Filter = filters
98+
cmd.Function = util.Ptr(model.FunctionTypeSetpointListData)
99+
}
81100

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

0 commit comments

Comments
 (0)