From bb319258f93e05d74a89660e6e50a0a2c4431cc4 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 25 Jun 2026 10:57:23 +0200 Subject: [PATCH 1/3] Register OHPCF event handler in constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NewOHPCF never subscribed the concrete *OHPCF to the device event bus, so only the embedded UseCaseBase.HandleEvent ran (firing UseCaseSupportUpdate). OHPCF.HandleEvent — which subscribes/binds SmartEnergyManagementPs on entity add and processes the data updates — was never invoked, so a CEM received no process-state data and could not schedule the compressor. Add the localEntity.Device().Events().Subscribe(uc) call that every other use case constructor already performs. --- usecases/cem/ohpcf/usecase.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/usecases/cem/ohpcf/usecase.go b/usecases/cem/ohpcf/usecase.go index c40284b2..2a81ed56 100644 --- a/usecases/cem/ohpcf/usecase.go +++ b/usecases/cem/ohpcf/usecase.go @@ -49,6 +49,8 @@ func NewOHPCF(localEntity spineapi.EntityLocalInterface, eventCB api.EntityEvent UseCaseBase: usecase, } + _ = localEntity.Device().Events().Subscribe(uc) + return uc } From ecf3fdc30479902df41b9209557cd4b37c3aa496 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 25 Jun 2026 12:56:43 +0200 Subject: [PATCH 2/3] Read OHPCF data after subscribing A subscription only delivers future updates, so without an initial read the CEM never learns the current optional power consumption until the heat pump pushes a change. Request the data on connect, as the other use cases do. --- usecases/cem/ohpcf/events.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usecases/cem/ohpcf/events.go b/usecases/cem/ohpcf/events.go index ebc1656e..23a4ccf6 100644 --- a/usecases/cem/ohpcf/events.go +++ b/usecases/cem/ohpcf/events.go @@ -43,6 +43,11 @@ func (o *OHPCF) connected(entity spineapi.EntityRemoteInterface) { logging.Log().Debug(err) } } + + // read the current data as a subscription only delivers future updates + if _, err := semp.RequestData(); err != nil { + logging.Log().Debug(err) + } } } From a9cecc2a97b9e2e9bc8be4bd1921e6771f5e87da Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 25 Jun 2026 18:29:43 +0200 Subject: [PATCH 3/3] OHPCF: schedule with relative start duration --- usecases/api/cem_ohpcf.go | 4 ++-- usecases/cem/ohpcf/public.go | 8 +++++--- usecases/cem/ohpcf/public_test.go | 7 +++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/usecases/api/cem_ohpcf.go b/usecases/api/cem_ohpcf.go index 2dbde8e0..1fd930ea 100644 --- a/usecases/api/cem_ohpcf.go +++ b/usecases/api/cem_ohpcf.go @@ -71,8 +71,8 @@ type CemOHPCFInterface interface { // scheduled process has not startet yet. // // parameters: - // - start: The start time of the power consumption - SchedulePowerConsumptionProcess(entity spineapi.EntityRemoteInterface, start time.Time, resultCB func(result model.ResultDataType)) (*model.MsgCounterType, error) + // - startIn: Delay from now until the power consumption starts (0 = start immediately) + SchedulePowerConsumptionProcess(entity spineapi.EntityRemoteInterface, startIn time.Duration, resultCB func(result model.ResultDataType)) (*model.MsgCounterType, error) // stop (abort) the process [OHPCF-022/1]. AbortPowerConsumptionProcess(entity spineapi.EntityRemoteInterface, resultCB func(result model.ResultDataType)) (*model.MsgCounterType, error) diff --git a/usecases/cem/ohpcf/public.go b/usecases/cem/ohpcf/public.go index 781fcd40..88f8e4df 100644 --- a/usecases/cem/ohpcf/public.go +++ b/usecases/cem/ohpcf/public.go @@ -291,8 +291,8 @@ func (o *OHPCF) PowerConsumptionMinimalPauseDuration(entity spineapi.EntityRemot // scheduled process did not start. // // parameters: -// - start: The start time of the power consumption -func (o *OHPCF) SchedulePowerConsumptionProcess(entity spineapi.EntityRemoteInterface, start time.Time, resultCB func(result model.ResultDataType)) (*model.MsgCounterType, error) { +// - startIn: Delay from now until the power consumption starts (0 = start immediately) +func (o *OHPCF) SchedulePowerConsumptionProcess(entity spineapi.EntityRemoteInterface, startIn time.Duration, resultCB func(result model.ResultDataType)) (*model.MsgCounterType, error) { info, err := o.OptionalPowerConsumption(entity) if err != nil { return nil, err @@ -305,7 +305,9 @@ func (o *OHPCF) SchedulePowerConsumptionProcess(entity spineapi.EntityRemoteInte SequenceId: &info.PowerSequenceId, }, Schedule: &model.PowerSequenceScheduleDataType{ - StartTime: model.NewAbsoluteOrRelativeTimeTypeFromTime(start), + // relative start time (ISO 8601 duration); heat pumps advertise their + // scheduling constraints relative as well, so keep the schedule relative + StartTime: model.NewAbsoluteOrRelativeTimeTypeFromDuration(startIn), }, }}, }}, diff --git a/usecases/cem/ohpcf/public_test.go b/usecases/cem/ohpcf/public_test.go index 2f988a9f..2ebef37f 100644 --- a/usecases/cem/ohpcf/public_test.go +++ b/usecases/cem/ohpcf/public_test.go @@ -265,11 +265,11 @@ func (s *CemOhPCFSuite) Test_PowerConsumptionMinimalPauseDuration() { // Scenario 2 func (s *CemOhPCFSuite) Test_SchedulePowerConsumptionProcess() { - _, err := s.sut.SchedulePowerConsumptionProcess(s.mockRemoteEntity, time.Now(), nil) + _, err := s.sut.SchedulePowerConsumptionProcess(s.mockRemoteEntity, 0, nil) assert.NotNil(s.T(), err) // Without valid data, the call should fail - _, err = s.sut.SchedulePowerConsumptionProcess(s.monitoredEntity, time.Now(), nil) + _, err = s.sut.SchedulePowerConsumptionProcess(s.monitoredEntity, 0, nil) assert.NotNil(s.T(), err) // Set up valid SmartEnergyManagementPs data @@ -302,8 +302,7 @@ func (s *CemOhPCFSuite) Test_SchedulePowerConsumptionProcess() { _, fErr := rFeature.UpdateData(true, model.FunctionTypeSmartEnergyManagementPsData, data, nil, nil) assert.Nil(s.T(), fErr) - startTime := time.Now().Add(time.Hour) - msgCounter, err := s.sut.SchedulePowerConsumptionProcess(s.monitoredEntity, startTime, nil) + msgCounter, err := s.sut.SchedulePowerConsumptionProcess(s.monitoredEntity, time.Hour, nil) assert.NotNil(s.T(), msgCounter) assert.Nil(s.T(), err) }