HabApp: How to cancel a job properly

Hi,
my idea is to implement an “away until” function: The heating thermostats are set to low temperature until a defined time is reached. Then the thermostats are set to warm again (see my code at the end of the post).

I found out that it cancelling a job that is not active raises an error, so I check that the “Next run time” of the job is in the past.
However, the fllowing error occurs most of the time (but not always). What can be the reason for it?


2022-04-27 17:33:15.612 [INFO ] [HABApp                              ] - Return time changed to: 2022-04-27 19:52:57
2022-04-27 17:33:15.615 [INFO ] [HABApp                              ] - Old job: <OneTimeJob next_run: 2022-04-21T23:35:54>
2022-04-27 17:33:15.703 [ERROR] [HABApp.Worker                       ] - Error in ReturnAfter.returnTimeChanged: 
2022-04-27 17:33:15.704 [ERROR] [HABApp.Worker                       ] - File "/opt/habapp/lib/python3.9/site-packages/HABApp/core/wrappedfunction.py", line 93, in __run
2022-04-27 17:33:15.704 [ERROR] [HABApp.Worker                       ] -     self._func(*args, **kwargs)
2022-04-27 17:33:15.704 [ERROR] [HABApp.Worker                       ] - File "/etc/openhab/habapp/rules/HeatingReturnAfter.py", line 44, in returnTimeChanged
2022-04-27 17:33:15.705 [ERROR] [HABApp.Worker                       ] -     34   def returnTimeChanged(self, event):
2022-04-27 17:33:15.705 [ERROR] [HABApp.Worker                       ] -  (...)
2022-04-27 17:33:15.705 [ERROR] [HABApp.Worker                       ] -     40       print(logmessage)
2022-04-27 17:33:15.706 [ERROR] [HABApp.Worker                       ] -     41       log.info(logmessage)
2022-04-27 17:33:15.706 [ERROR] [HABApp.Worker                       ] -     42       if self.returnTime.get_value(0) > datetime.now():
2022-04-27 17:33:15.706 [ERROR] [HABApp.Worker                       ] -     43           if self.returnJob.get_next_run().time() > datetime.now().time():
2022-04-27 17:33:15.707 [ERROR] [HABApp.Worker                       ] - --> 44               self.returnJob.cancel()
2022-04-27 17:33:15.707 [ERROR] [HABApp.Worker                       ] -     45           self.returnJob = self.run.at(self.returnTime.get_value(0), self.returnTimeReached)
2022-04-27 17:33:15.707 [ERROR] [HABApp.Worker                       ] -     ..................................................
2022-04-27 17:33:15.708 [ERROR] [HABApp.Worker                       ] -      self = </etc/openhab/habapp/rules/HeatingReturnAfter.py.ReturnAfter object at 0x73672f70>
2022-04-27 17:33:15.708 [ERROR] [HABApp.Worker                       ] -      event = <ItemNoChangeEvent name: heating_returnTime, seconds: 10>
2022-04-27 17:33:15.708 [ERROR] [HABApp.Worker                       ] -      logmessage = 'Old job: <OneTimeJob next_run: 2022-04-21T23:35:54>'
2022-04-27 17:33:15.709 [ERROR] [HABApp.Worker                       ] -      log.info = <method 'Logger.info' of <Logger HABApp (INFO)> __init__.py:1432>
2022-04-27 17:33:15.709 [ERROR] [HABApp.Worker                       ] -      self.returnTime.get_value = <method 'BaseValueItem.get_value' of <DatetimeItem name: heating_returnTime, value: 2022-04-27 19:52:57, last_change: 2022-04-27T17:33:05.598049, last_update: 2022-04-27T17:33:05.598049> base_valueitem.py:62>
2022-04-27 17:33:15.709 [ERROR] [HABApp.Worker                       ] -      self.returnJob.get_next_run = <method 'ScheduledJobBase.get_next_run' of <OneTimeJob next_run: 2022-04-21T23:35:54> job_base.py:64>
2022-04-27 17:33:15.710 [ERROR] [HABApp.Worker                       ] -      time = <class 'datetime.time'>
2022-04-27 17:33:15.710 [ERROR] [HABApp.Worker                       ] -      self.returnJob.cancel = <method 'ScheduledJobBase.cancel' of <OneTimeJob next_run: 2022-04-21T23:35:54> job_base.py:47>
2022-04-27 17:33:15.710 [ERROR] [HABApp.Worker                       ] -      self.returnJob = <OneTimeJob next_run: 2022-04-21T23:35:54>
2022-04-27 17:33:15.711 [ERROR] [HABApp.Worker                       ] -      self.run.at = <method 'HABAppSchedulerView.at' of <HABApp.rule.scheduler.habappschedulerview.HABAppSchedulerView object at 0x73672e68> habappschedulerview.py:20>
2022-04-27 17:33:15.711 [ERROR] [HABApp.Worker                       ] -      self.returnTimeReached = <method 'ReturnAfter.returnTimeReached' of </etc/openhab/habapp/rules/HeatingReturnAfter.py.ReturnAfter object at 0x73672f70> HeatingReturnAfter.py:51>
2022-04-27 17:33:15.711 [ERROR] [HABApp.Worker                       ] -     ..................................................
2022-04-27 17:33:15.712 [ERROR] [HABApp.Worker                       ] - 
2022-04-27 17:33:15.712 [ERROR] [HABApp.Worker                       ] - File "/opt/habapp/lib/python3.9/site-packages/eascheduler/jobs/job_base.py", line 50, in cancel
2022-04-27 17:33:15.712 [ERROR] [HABApp.Worker                       ] -     47   def cancel(self):
2022-04-27 17:33:15.713 [ERROR] [HABApp.Worker                       ] -     48       """Cancel the job."""
2022-04-27 17:33:15.713 [ERROR] [HABApp.Worker                       ] -     49       if self._parent is None:
2022-04-27 17:33:15.713 [ERROR] [HABApp.Worker                       ] - --> 50           raise JobAlreadyCanceledException()
2022-04-27 17:33:15.714 [ERROR] [HABApp.Worker                       ] -     51   
2022-04-27 17:33:15.714 [ERROR] [HABApp.Worker                       ] -     ..................................................
2022-04-27 17:33:15.714 [ERROR] [HABApp.Worker                       ] -      self = <OneTimeJob next_run: 2022-04-21T23:35:54>
2022-04-27 17:33:15.715 [ERROR] [HABApp.Worker                       ] -      self._parent = None
2022-04-27 17:33:15.715 [ERROR] [HABApp.Worker                       ] -      JobAlreadyCanceledException = <class 'eascheduler.errors.errors.JobAlreadyCanceledException'>
2022-04-27 17:33:15.715 [ERROR] [HABApp.Worker                       ] -     ..................................................
2022-04-27 17:33:15.715 [ERROR] [HABApp.Worker                       ] - 
2022-04-27 17:33:15.716 [ERROR] [HABApp.Worker                       ] - JobAlreadyCanceledException

My complete code here:

class ReturnAfter(HABApp.Rule):
    def __init__(self):
        super().__init__()
        print('heating return rule started')
        self.returnAfter = SwitchItem.get_item('heating_awayUntil')
        self.returnAfter.listen_event(self.returnSwitchToggled, ItemStateChangedEvent)

        self.returnTime = DatetimeItem.get_item('heating_returnTime')
        self.returnTime.watch_change(10).listen_event(self.returnTimeChanged)
        self.returnJob = self.run.soon(self.returnTimeReached)

        self.heatingMode = StringItem.get_item('mode_heating')

    def returnSwitchToggled(self, event):
        if self.returnAfter.is_off():
            self.returnTime.oh_send_command('1970-01-01T00:00:00.000+0100')
        elif self.returnAfter.is_on():
            self.heatingMode.oh_send_command(HeatingModeNames.Away.value)
            self.returnTime.oh_send_command(datetime.now())

    def returnTimeChanged(self, event):
        logmessage = "Return time changed to: " + str(self.returnTime.get_value(0))
        print(logmessage)
        log.info(logmessage)
        print("Old job: ", self.returnJob)
        logmessage = "Old job: " + str(self.returnJob)
        print(logmessage)
        log.info(logmessage)
        if self.returnTime.get_value(0) > datetime.now():
            if self.returnJob.get_next_run().time() > datetime.now().time():
                self.returnJob.cancel()
            self.returnJob = self.run.at(self.returnTime.get_value(0), self.returnTimeReached)
            logmessage = "New job: " + str(self.returnJob)
            print(logmessage)
            log.info(logmessage)


    def returnTimeReached(self):
        logmessage = "Return time reached: " + str(self.returnTime.get_value(0))
        print(logmessage)
        log.info(logmessage)
        self.returnAfter.off()


ReturnAfter()

Job:

<OneTimeJob next_run: 2022-04-21T23:35:54>

Item:

self.returnTime= <DatetimeItem name: heating_returnTime, value: 2022-04-27 19:52:57 ...>

Do you see the issue?


However I would solve this differently:
If it’s a fixed offset I would use a CountdownJob or a NoChangeEvent. If it’s not I would use a reoccuring job that enables the heating.
Are you sure you need the datetime when it’s enabled again in openhab?
If so you can always use the get_next_run() of the CountdownJob and just post it to a item on .reset()

Oh, I forgot checking the date is in the future. I only checked the time. I didn’t find a command to compare a complete DateTime variable.

When I leave home, I enter the return time in the UI so it is warm when I come back. What would be the advantage of a reoccuring job here?

Sorry, I don’t understand this part of your answer:

Just compare the datetimes. However you can manually install eascheduler 0.1.5.
There the jobs have a remaining function which will return None if the job is not scheduled.

Yes - it doesn’t make much sense if you enter the time manually.