Skip to content

Commit 0a2f102

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 97be37e commit 0a2f102

3 files changed

Lines changed: 85 additions & 0 deletions

File tree

features/client/hvac.go

Lines changed: 37 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 {
@@ -105,11 +106,29 @@ func (h *Hvac) WriteHvacOverrunListData(
105106
return nil, api.ErrNotSupported
106107
}
107108

109+
// use a partial write when the server supports it, otherwise merge the
110+
// modified entries into the cached list and write the complete list, so
111+
// unrelated overruns are not dropped by a full replacement
112+
filters := []model.FilterType{*model.NewFilterTypePartial()}
113+
if !operation.WritePartial() {
114+
filters = nil
115+
updateData := &model.HvacOverrunListDataType{
116+
HvacOverrunData: data,
117+
}
118+
if mergedData, err := h.featureRemote.UpdateData(false, model.FunctionTypeHvacOverrunListData, updateData, nil, nil); err == nil {
119+
data = mergedData.([]model.HvacOverrunDataType)
120+
}
121+
}
122+
108123
cmd := model.CmdType{
109124
HvacOverrunListData: &model.HvacOverrunListDataType{
110125
HvacOverrunData: data,
111126
},
112127
}
128+
if filters != nil {
129+
cmd.Filter = filters
130+
cmd.Function = util.Ptr(model.FunctionTypeHvacOverrunListData)
131+
}
113132

114133
return h.remoteDevice.Sender().Write(h.featureLocal.Address(), h.featureRemote.Address(), cmd)
115134
}
@@ -129,11 +148,29 @@ func (h *Hvac) WriteHvacSystemFunctionListData(
129148
return nil, api.ErrNotSupported
130149
}
131150

151+
// use a partial write when the server supports it, otherwise merge the
152+
// modified entries into the cached list and write the complete list, so
153+
// unrelated system functions are not dropped by a full replacement
154+
filters := []model.FilterType{*model.NewFilterTypePartial()}
155+
if !operation.WritePartial() {
156+
filters = nil
157+
updateData := &model.HvacSystemFunctionListDataType{
158+
HvacSystemFunctionData: data,
159+
}
160+
if mergedData, err := h.featureRemote.UpdateData(false, model.FunctionTypeHvacSystemFunctionListData, updateData, nil, nil); err == nil {
161+
data = mergedData.([]model.HvacSystemFunctionDataType)
162+
}
163+
}
164+
132165
cmd := model.CmdType{
133166
HvacSystemFunctionListData: &model.HvacSystemFunctionListDataType{
134167
HvacSystemFunctionData: data,
135168
},
136169
}
170+
if filters != nil {
171+
cmd.Filter = filters
172+
cmd.Function = util.Ptr(model.FunctionTypeHvacSystemFunctionListData)
173+
}
137174

138175
return h.remoteDevice.Sender().Write(h.featureLocal.Address(), h.featureRemote.Address(), cmd)
139176
}

features/client/hvac_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,32 @@ func (s *HvacSuite) Test_WriteHvacOverrunListData() {
139139
assert.Nil(s.T(), err)
140140
assert.NotNil(s.T(), counter)
141141
}
142+
143+
func (s *HvacSuite) Test_WriteHvacSystemFunctionListData_Partial() {
144+
localEntity, remoteEntity := setupFeatures(
145+
s.T(),
146+
s,
147+
[]featureFunctions{
148+
{
149+
featureType: model.FeatureTypeTypeHvac,
150+
functions: []model.FunctionType{
151+
model.FunctionTypeHvacSystemFunctionListData,
152+
},
153+
partial: true,
154+
},
155+
},
156+
)
157+
158+
hvac, err := NewHvac(localEntity, remoteEntity)
159+
assert.Nil(s.T(), err)
160+
161+
data := []model.HvacSystemFunctionDataType{
162+
{
163+
SystemFunctionId: util.Ptr(model.HvacSystemFunctionIdType(1)),
164+
CurrentOperationModeId: util.Ptr(model.HvacOperationModeIdType(2)),
165+
},
166+
}
167+
counter, err := hvac.WriteHvacSystemFunctionListData(data)
168+
assert.Nil(s.T(), err)
169+
assert.NotNil(s.T(), counter)
170+
}

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)