44 "github.com/enbility/eebus-go/api"
55 "github.com/enbility/eebus-go/features/client"
66 ucapi "github.com/enbility/eebus-go/usecases/api"
7+ "github.com/enbility/ship-go/logging"
78 spineapi "github.com/enbility/spine-go/api"
89 "github.com/enbility/spine-go/model"
910 "github.com/enbility/spine-go/util"
@@ -86,31 +87,38 @@ func (e *CDSF) CurrentOperationMode(entity spineapi.EntityRemoteInterface) (ucap
8687}
8788
8889// set the DHW operation mode of the DHW circuit,
89- // returns ErrNotSupported if the operation mode is not changeable or not supported
90- func (e * CDSF ) WriteOperationMode (entity spineapi.EntityRemoteInterface , mode ucapi.HvacOperationModeType ) error {
90+ // returns ErrNotSupported if the operation mode is not changeable or not supported.
91+ //
92+ // The returned message counter and the resultCB let the caller observe the
93+ // device result: a non-zero ResultData.ErrorNumber signals a rejected write.
94+ func (e * CDSF ) WriteOperationMode (
95+ entity spineapi.EntityRemoteInterface ,
96+ mode ucapi.HvacOperationModeType ,
97+ resultCB func (result model.ResultDataType , msgCounter model.MsgCounterType ),
98+ ) (* model.MsgCounterType , error ) {
9199 if ! e .IsCompatibleEntityType (entity ) {
92- return api .ErrNoCompatibleEntity
100+ return nil , api .ErrNoCompatibleEntity
93101 }
94102
95103 hvac , err := client .NewHvac (e .LocalEntity , entity )
96104 if err != nil {
97- return err
105+ return nil , err
98106 }
99107
100108 systemFunctionId , err := e .systemFunctionId (entity )
101109 if err != nil {
102- return err
110+ return nil , err
103111 }
104112
105113 data , err := hvac .GetHvacSystemFunctionForId (systemFunctionId )
106114 if err != nil {
107- return api .ErrDataNotAvailable
115+ return nil , api .ErrDataNotAvailable
108116 }
109117
110118 // only an explicit false blocks the write; an omitted flag is tolerated, as
111119 // some devices accept the write without advertising the changeability flag
112120 if data .IsOperationModeIdChangeable != nil && ! * data .IsOperationModeIdChangeable {
113- return api .ErrNotSupported
121+ return nil , api .ErrNotSupported
114122 }
115123
116124 // resolve the requested mode through the DHW system-function relation, so a
@@ -120,7 +128,7 @@ func (e *CDSF) WriteOperationMode(entity spineapi.EntityRemoteInterface, mode uc
120128 }
121129 relations , err := hvac .GetHvacSystemFunctionOperationModeRelationsForFilter (relationFilter )
122130 if err != nil || len (relations ) == 0 {
123- return api .ErrDataNotAvailable
131+ return nil , api .ErrDataNotAvailable
124132 }
125133
126134 var modeId * model.HvacOperationModeIdType
@@ -140,7 +148,7 @@ func (e *CDSF) WriteOperationMode(entity spineapi.EntityRemoteInterface, mode uc
140148 }
141149 }
142150 if modeId == nil {
143- return api .ErrNotSupported
151+ return nil , api .ErrNotSupported
144152 }
145153
146154 writeData := []model.HvacSystemFunctionDataType {
@@ -150,49 +158,63 @@ func (e *CDSF) WriteOperationMode(entity spineapi.EntityRemoteInterface, mode uc
150158 },
151159 }
152160
153- _ , err = hvac .WriteHvacSystemFunctionListData (writeData )
161+ msgCounter , err := hvac .WriteHvacSystemFunctionListData (writeData )
162+ e .registerResultCallback (hvac , msgCounter , resultCB )
154163
155- return err
164+ return msgCounter , err
156165}
157166
158167// Scenario 2
159168
160169// start the one-time DHW loading overrun of the DHW circuit,
161- // returns ErrNotSupported if the overrun status is not changeable
162- func (e * CDSF ) StartOneTimeDhw (entity spineapi.EntityRemoteInterface ) error {
163- return e .writeOverrunStatus (entity , model .HvacOverrunStatusTypeActive )
170+ // returns ErrNotSupported if the overrun status is not changeable.
171+ //
172+ // The returned message counter and the resultCB let the caller observe the
173+ // device result: a non-zero ResultData.ErrorNumber signals a rejected write.
174+ func (e * CDSF ) StartOneTimeDhw (
175+ entity spineapi.EntityRemoteInterface ,
176+ resultCB func (result model.ResultDataType , msgCounter model.MsgCounterType ),
177+ ) (* model.MsgCounterType , error ) {
178+ return e .writeOverrunStatus (entity , model .HvacOverrunStatusTypeActive , resultCB )
164179}
165180
166181// Scenario 3
167182
168183// stop the one-time DHW loading overrun of the DHW circuit,
169- // returns ErrNotSupported if the overrun status is not changeable
170- func (e * CDSF ) StopOneTimeDhw (entity spineapi.EntityRemoteInterface ) error {
171- return e .writeOverrunStatus (entity , model .HvacOverrunStatusTypeInactive )
184+ // returns ErrNotSupported if the overrun status is not changeable.
185+ //
186+ // The returned message counter and the resultCB let the caller observe the
187+ // device result: a non-zero ResultData.ErrorNumber signals a rejected write.
188+ func (e * CDSF ) StopOneTimeDhw (
189+ entity spineapi.EntityRemoteInterface ,
190+ resultCB func (result model.ResultDataType , msgCounter model.MsgCounterType ),
191+ ) (* model.MsgCounterType , error ) {
192+ return e .writeOverrunStatus (entity , model .HvacOverrunStatusTypeInactive , resultCB )
172193}
173194
174195// write the status of the one-time DHW overrun
175196func (e * CDSF ) writeOverrunStatus (
176197 entity spineapi.EntityRemoteInterface ,
177198 status model.HvacOverrunStatusType ,
178- ) error {
199+ resultCB func (result model.ResultDataType , msgCounter model.MsgCounterType ),
200+ ) (* model.MsgCounterType , error ) {
179201 if ! e .IsCompatibleEntityType (entity ) {
180- return api .ErrNoCompatibleEntity
202+ return nil , api .ErrNoCompatibleEntity
181203 }
182204
183205 hvac , err := client .NewHvac (e .LocalEntity , entity )
184206 if err != nil {
185- return err
207+ return nil , err
186208 }
187209
188210 overrunId , err := e .overrunId (entity )
189211 if err != nil {
190- return err
212+ return nil , err
191213 }
192214
193215 if data , err := hvac .GetHvacOverrunForId (overrunId ); err == nil &&
194216 data .IsOverrunStatusChangeable != nil && ! * data .IsOverrunStatusChangeable {
195- return api .ErrNotSupported
217+ return nil , api .ErrNotSupported
196218 }
197219
198220 writeData := []model.HvacOverrunDataType {
@@ -202,12 +224,35 @@ func (e *CDSF) writeOverrunStatus(
202224 },
203225 }
204226
205- _ , err = hvac .WriteHvacOverrunListData (writeData )
227+ msgCounter , err := hvac .WriteHvacOverrunListData (writeData )
228+ e .registerResultCallback (hvac , msgCounter , resultCB )
206229
207- return err
230+ return msgCounter , err
208231}
209232
210- // return the id of the one-time DHW overrun affecting the DHW system function
233+ // register a response callback that surfaces the device result of a write to
234+ // the caller, so a non-zero ResultData.ErrorNumber can be treated as a rejection
235+ func (e * CDSF ) registerResultCallback (
236+ hvac * client.Hvac ,
237+ msgCounter * model.MsgCounterType ,
238+ resultCB func (result model.ResultDataType , msgCounter model.MsgCounterType ),
239+ ) {
240+ if resultCB == nil || msgCounter == nil {
241+ return
242+ }
243+
244+ cb := func (msg spineapi.ResponseMessage ) {
245+ if response , ok := msg .Data .(* model.ResultDataType ); ok {
246+ resultCB (* response , * msgCounter )
247+ }
248+ }
249+ if err := hvac .AddResponseCallback (* msgCounter , cb ); err != nil {
250+ logging .Log ().Debug ("failed to add response callback for msgCounter %v: %v" , msgCounter , err )
251+ }
252+ }
253+
254+ // return the id of the one-time DHW overrun affecting the DHW system function,
255+ // returns ErrDataNotAvailable unless exactly one matching overrun exists
211256func (e * CDSF ) overrunId (entity spineapi.EntityRemoteInterface ) (model.HvacOverrunIdType , error ) {
212257 hvac , err := client .NewHvac (e .LocalEntity , entity )
213258 if err != nil {
@@ -227,22 +272,30 @@ func (e *CDSF) overrunId(entity spineapi.EntityRemoteInterface) (model.HvacOverr
227272 return 0 , api .ErrDataNotAvailable
228273 }
229274
275+ var overrunIds []model.HvacOverrunIdType
230276 for _ , description := range descriptions {
231277 if description .OverrunId == nil {
232278 continue
233279 }
234280
235281 for _ , affectedId := range description .AffectedSystemFunctionId {
236282 if affectedId == systemFunctionId {
237- return * description .OverrunId , nil
283+ overrunIds = append (overrunIds , * description .OverrunId )
284+ break
238285 }
239286 }
240287 }
241288
242- return 0 , api .ErrDataNotAvailable
289+ // fail closed on an ambiguous result so the wrong overrun is never controlled
290+ if len (overrunIds ) != 1 {
291+ return 0 , api .ErrDataNotAvailable
292+ }
293+
294+ return overrunIds [0 ], nil
243295}
244296
245- // return the id of the DHW system function of the DHW circuit
297+ // return the id of the DHW system function of the DHW circuit,
298+ // returns ErrDataNotAvailable unless exactly one matching system function exists
246299func (e * CDSF ) systemFunctionId (entity spineapi.EntityRemoteInterface ) (model.HvacSystemFunctionIdType , error ) {
247300 hvac , err := client .NewHvac (e .LocalEntity , entity )
248301 if err != nil {
@@ -253,7 +306,8 @@ func (e *CDSF) systemFunctionId(entity spineapi.EntityRemoteInterface) (model.Hv
253306 SystemFunctionType : util .Ptr (model .HvacSystemFunctionTypeTypeDhw ),
254307 }
255308 descriptions , err := hvac .GetHvacSystemFunctionDescriptionsForFilter (descFilter )
256- if err != nil || len (descriptions ) == 0 || descriptions [0 ].SystemFunctionId == nil {
309+ // fail closed on an ambiguous result so the wrong system function is never controlled
310+ if err != nil || len (descriptions ) != 1 || descriptions [0 ].SystemFunctionId == nil {
257311 return 0 , api .ErrDataNotAvailable
258312 }
259313
0 commit comments