spacemanspiff2007 / HABApp

Easy home automation with MQTT and/or openHAB
Apache License 2.0
54 stars 23 forks source link

Having major problems upgrading to latest Version because of breaking changes #216

Closed NickWaterton closed 2 years ago

NickWaterton commented 3 years ago

I just upgraded to 0.30.0

I followed the instructions: • Search for self.run_ and replace with self.run. • Search for self.run.in and replace with self.run.at • Search for .get_next_call() and replace with .get_next_run() (But make sure it's a scheduled job) • Search for HABAppError and replace with HABAppException

But I am running into many errors. The first issue is that HABApp now requires obsolete versions of packages, and if you do not have the exact version required, will not start. Had to downgrade many of my packages, just to get it to run.

eg. HABApp requires ujson==4.0.2, I had 4.0.3 - so won't start. same for aiohttp==3.7.4 (I had 3.7.5). Why not allow aiohttp>=3.7.4 ?

When I finally was able to start, there are many breaking changes not mentioned. self.run.at has had the property is_finished removed, which breaks my createTimer class, and causes most of my rules to fail.

I am still trying to find a workaround for this. If a job is scheduled, how can I tell if it is pending, running, or has already run? I'm trying to use .get_next_run() but I am not sure what this returns if the job has completed.

When I cancel the task, if the task was previously cancelled, a new exception is raised JobAlreadyCanceledException() which I now have to trap. I see no point in raising this exception.

When I create a new run.every(None,) task, I get an exception FirstRunInThePastError() which causes the entire rule not to load. What is the point of this exception? why not just run the task immediately? if the task is scheduled now (ie not in the past) this exception breaks the rule.

This is the problem:

        #run these two api calls at the rate limit
        self.run.every(None, self.api_calls['config']['limit'], self.get_configuration)
        self.run.every(None, self.api_calls['query']['limit'], self.get_watering_status)
....
FirstRunInThePastError: First run must be in the future! Now: 2021-05-02T12:57:20.225630-04:00, run: 2021-05-02T12:57:20.225630-04:00

Also this fails when t==0:

if t >= 0:
    self.run.at(t, self.sendHttpRequest, command, taplinkerId=taplinkerId)

This errors are caused by "/usr/local/lib/python3.8/dist-packages/eascheduler/jobs/job_base.py", line 96:

    95       if new_base <= now:
    96           raise FirstRunInThePastError(f'First run must be in the future! Now: {now}, run: {new_base}')

This is a bug, as now is not in the past. The line should be:

    95       if new_base < now:
    96           raise FirstRunInThePastError(f'First run must be in the future! Now: {now}, run: {new_base}')

But again, why? why not just run immediately?

Apparently on_sun has been silently dropped (not a big deal, I don't use it).

I am still working through these errors, the biggest problem being task.is_finished being dropped. Any suggestions on how to re-create this vital property?

NickWaterton commented 3 years ago

This is my workaround for missing is_running in my createTimer class;

class createTimer(HABApp.Rule):
    '''
    General timer class
    Restartable Timer, with cancel and reschedule functions
    accepts float as seconds parameter (not claiming that the timer is that accurate though)
    '''

    def __init__(self, seconds=0, exec_func=None, *args, **kwargs):
        super().__init__()
        self.log = logging.getLogger('MyRule.'+self.__class__.__name__)
        self.t = seconds
        self.exec_func = exec_func
        run = kwargs.pop('run', False)
        self.args = args
        self.kwargs = kwargs
        if self.exec_func is None:
            self.exec_func = self.dummy
        self._task = self.run.at(self.get_milliseconds, self.exec_func, *args, **kwargs)
        if not run:
            self.cancel()

    @property
    def get_milliseconds(self):
        return datetime.timedelta(milliseconds=int(self.t*1000))

    @property
    def is_running(self):
        return bool(self._task.get_next_run() >= datetime.datetime.now())
        #was
        return not self._task.is_finished

    @property    
    def time_till_run(self):
        if self._task.get_next_run() > datetime.datetime.now():
            return (self._task.get_next_run() - datetime.datetime.now()).total_seconds()
        return 0
        #was
        if not self._task.is_finished:
            return (self._task.get_next_run() - datetime.datetime.now()).total_seconds()
        return 0

    def dummy(self):
        self.log.warning('This timer has no exec_func set')

    def reschedule(self, seconds=None):
        self.start(seconds)

    def start(self, seconds=None, *args, **kwargs):
        if seconds is not None:
            self.t = seconds
        if self.t >= 0:
            self.cancel()
            if not args:
                args = self.args
            if not kwargs:
                kwargs = self.kwargs
            self._task = self.run.at(self.get_milliseconds, self.exec_func, *args, **kwargs)

    def cancel(self):
        try:
            self._task.cancel()
        except Exception:
            pass

I'm hoping this will work.

spacemanspiff2007 commented 3 years ago

First sorry for the inconvenience! I changed so many things some have slipped the change log.


The first issue is that HABApp now requires obsolete versions of packages, and if you do not have the exact version required, will not start. Had to downgrade many of my packages, just to get it to run.

HABApp will start fine with the wrong package version, but if you do a pip install HABApp it will always try and install the pinned versions. I'll bump the versions with the next smaller update.


When I finally was able to start, there are many breaking changes not mentioned. self.run.at has had the property is_finished removed, which breaks my createTimer class, and causes most of my rules to fail.

I am still trying to find a workaround for this. If a job is scheduled, how can I tell if it is pending, running, or has already run? I'm trying to use .get_next_run() but I am not sure what this returns if the job has completed.

Once you create a timer you get the object. On run/cancel you set this object to None. If the variable is None nothing is scheduled,

def __init__()
    self._task = self.run.at(self.get_milliseconds, self.exec_wrap, *args, **kwargs)  # <-- exec_wrap

def is_running():
    return self.task is not None

def exec_wrap(*args, **kwargs):
    self.task = None
    self.exec_func(*args, **kwargs)

def cancel(self):
    if self.task is not None:
        self.task.cancel()
        self.task = None

But there is a new self.run.countdown(...) option on the scheduler which does basically what your class is doing. Have you already taken a look at it?


Apparently on_sun has been silently dropped (not a big deal, I don't use it).

It's still there, I just updated the migration description:


When I create a new run.every(None,) task, I get an exception FirstRunInThePastError() which causes the entire rule not to load. What is the point of this exception? why not just run the task immediately? if the task is scheduled now (ie not in the past) this exception breaks the rule.

run.every(None,) should work and this is likely a bug.

NickWaterton commented 3 years ago

HABApp will start fine with the wrong package version, but if you do a pip install HABApp it will always try and install the pinned versions. I'll bump the versions with the next smaller update.

This was not the case, I ran a git pull (HABApp setup as develop). HABApp would then not start with various error of the type:

package ujson==4.0.2 not found

And it wasn’t until I reinstalled each package with the exact versions required in requirements.txt That HABApp would start up.

Try upgrading ujson to 4.0.3 and see what happens.

I took a quick look at the countdown timer, maybe I can adapt my code and do that instead in ‘createTimer`.

Thanks.

spacemanspiff2007 commented 3 years ago

And it wasn’t until I reinstalled each package with the exact versions required in requirements.txt That HABApp would start up.

I just installed watchdog==2.0.3 (HABApp requires 2.0.2) and it launches fine. How do you start HABApp? Anyway you can always just do pip install -r requirements.txt and should be good to go.

ujson 4.0.3 is not available yet, I don't know how you can install it.

NickWaterton commented 3 years ago

I run it as a systemd service


[Unit]
Description=HABApp
After=network-online.target

[Service]
Type=simple
User=openhab
Group=openhab
ExecStart=/usr/local/bin/habapp -c /etc/openhab2/HABApp

[Install]
WantedBy=multi-user.target

Sorry, I was wrong about ujson. Maybe it was aiohttp. I’ll check tomorrow.

spacemanspiff2007 commented 3 years ago

Sorry, I was wrong about ujson. Maybe it was aiohttp. I’ll check tomorrow.

No worries, I was just confused and tried with something else.

spacemanspiff2007 commented 3 years ago

When I create a new run.every(None,) task, I get an exception FirstRunInThePastError() which causes the entire rule not to load.

Are you sure you are using eascheduler 0.1.1? I can't reproduce the issue.

NickWaterton commented 3 years ago

This is the whole rule:

class LinkTap(HABApp.Rule):
    '''
    LinkTap Irrigation system control
    Controlled by rate limited REST API calls (to LinkTap's servers, no local LAN API yet (Sept 2020)
    username    : Nick_W
    login       : ---
    password    : ---
    apiKey      : ---
    see https://www.link-tap.com/#!/api-for-developers
    '''

    username = '---'
    apiKey   = '---'

    base_item    = 'LinkTap_Devices'    #item name for self.devices
    base_valve   = 'LinkTap_Valve'      #base item name for taplink's eg LinkTap_Valve_1_xxx, where xxx is Name, ID, Status etc.
    watch_items  = []
    change_items = []
    command_item = [#valve items, these are per-valve properties (eg LinkTap_Valve_1_SetMode)
                    'SetMode',
                    'SetWatertime'] 
    items =        ['LinkTap_Gateway_ID',
                    'LinkTap_Gateway_Status',
                    'LinkTap_Gateway_Message',
                    'config_lastCall',
                    #valve items, these are per-valve properties (eg LinkTap_Valve_1_Name)
                    'instant_lastCall',
                    'interval_lastCall',
                    'oddeven_lastCall',
                    'sevenday_lastCall',
                    'month_lastCall',
                    'query_lastCall',
                    'Name',
                    'ID',
                    'Status',
                    'signal',
                    'Battery',
                    'Mode_Name',
                    'current_flow',
                    'vol',
                    'last_vol',
                    'last_flow',
                    'watering',
                    'fall',
                    'broken',
                    'noWater']

    rest = 'https://www.link-tap.com/api/'

    api_calls  =   {'config'    : {'rest' : 'getAllDevices', 'type' : 'POST', 'limit' : 300, 'code' : None},
                    'instant'   : {'rest' : 'activateInstantMode', 'type' : 'POST', 'limit' : 15, 'code' : 'M'},
                    'interval'  : {'rest' : 'activateIntervalMode', 'type' : 'POST', 'limit' : 15, 'code' : 'I'},
                    'oddeven'   : {'rest' : 'activateOddEvenMode', 'type' : 'POST', 'limit' : 15, 'code' : 'O'},
                    'sevenday'  : {'rest' : 'activateSevenDayMode', 'type' : 'POST', 'limit' : 15, 'code' : 'T'},
                    'month'     : {'rest' : 'activateMonthMode', 'type' : 'POST', 'limit' : 15, 'code' : 'Y'},
                    'query'     : {'rest' : 'getWateringStatus', 'type' : 'POST', 'limit' : 30, 'code' : None},
                   }

    modes      = {  'N' : 'None',
                    'M' : 'Instant',
                    'I' : 'Interval',
                    'O' : 'Odd Even',
                    'T' : '7 Day',
                    'Y' : 'Month'}

    devices = []    #contains all devices (by gateway, hence list), updated by api call, saved in OH as 'LinkTap_Devices'
    valves = {}     #contains all linktaps, by linktap ID, populated from self.devices above in 'update_items' (not saved in OH)
    queue = []      #used for rate limiting api calls. If an api call is already in the queue (pending call due to rate limiting), it is dropped if called twice

    def __init__(self):
        super().__init__()
        self.log = logging.getLogger('MyRule.'+self.__class__.__name__)
        #self.log.setLevel(logging.INFO)

        self.current_mode = {}      #current single letter mode for a taplink id, normally None, unless previous mode has to be reset after instant_on
        self.reset_mode = {}        #future event for a taplink id
        self.message = {}           #message to be displayed in OH, used in update_watering_status
        self.item = {}

        self.log.debug('LinkTap Rule Started')
        try:
            #restore self.devices from OH
            self.devices = json.loads(OpenhabItem.get_item(self.base_item).value)
            self.update_items()
        except TypeError:
            #or call api to get configuration and initialize self.devices
            self.get_configuration()

        self.get_watering_status()
        #run these two api calls at the rate limit
        self.run.every(None, self.api_calls['config']['limit'], self.get_configuration)
        self.run.every(None, self.api_calls['query']['limit'], self.get_watering_status)

    def init(self):
        '''
        Called the first time update_items is run after a restart to initialize all OH items
        Never called again after this.
        Usually this would be called by __init__, but moved here so that self.valves gets populated
        first as self.valves is used to determine the taplink item names
        self.valves depends on self.devices, and this could be retrieved asynchronously,
        we can't determine when self.devices gets populated, so can't just put this in __init__
        '''
        all_items = self.watch_items + self.change_items + self.command_item + self.items 
        all_items.append(self.base_item)

        for base_item in all_items:
            for v in self.valves.values():
                item = '{}{}'.format(v['item'],base_item)
                try:
                    self.item[item] = OpenhabItem.get_item(item)
                    if base_item in self.watch_items:
                        self.listen_event(self.item[item], self.update_received, ItemStateEvent)
                    if base_item in self.change_items:
                        self.listen_event(self.item[item], self.change_received, ItemStateChangedEvent)
                    if base_item in self.command_item:
                        self.listen_event(self.item[item], self.command_received, ItemCommandEvent)
                except HABApp.core.Items.ItemNotFoundException:
                    try:
                        self.item[base_item] = OpenhabItem.get_item(base_item)
                        break
                    except HABApp.core.Items.ItemNotFoundException:
                        self.log.warning('item {} not found, please add to taplink.items file'.format(item))

    def command_received(self, event):
        self.log.debug('Item %s command received %s' % (event.name, event.value))

        if 'SetMode' in event.name:
            self.set_mode(event)

        elif 'SetWatertime' in event.name:
            self.instant_on(event)

        else:
            self.log.debug('%s not found' % event.name)

    def change_received(self, event):
        self.log.debug('Item %s changed to %s' % (event.name, event.value))

        if event.name == '':
            pass

        else:
            self.log.debug('%s not found' % event.name)

    def update_received(self, event):
        self.log.debug('Item %s received update %s' % (event.name, event.value))
        try:

            if event.name == '':
                pass

            else:
                self.log.debug('%s not found' % event.name)

        except Exception as e:
            self.log.exception(e)

    def get_valve_from_item(self, item):
        '''
        Returns taplink id for a taplink, from the item name
        uses self.valves, so requires 'update_items' from self.devices to be processed first
        returns None if not found
        '''
        for k, v in self.valves.items():
            if v['item'] in item:
                return k
        return None

    def get_command_from_mode(self, mode):
        '''
        Gets the api command, for a given single letter mode
        returns None if a command for a mode is not found
        '''
        for k, v in self.api_calls.items():
            if v.get('code','') == mode:
                return k
        return None

    def get_taplink_value(self, taplinkerId, value):
        '''
        return 'value' for taplink from self.devices
        return None if value not found, or taplink not found
        'value' would be a taplink configuration like 'status'
        '''
        for device in self.devices:
            taplinkers = device.get('taplinker', [])
            for taplink in taplinkers:
                if taplink['taplinkerId'] == taplinkerId:
                    return taplink.get(value, None)
        return None

    def get_mode(self, taplinkerId):
        '''
        Returns the current single letter mode for a taplink from self.devices.
        '''
        return self.get_taplink_value(taplinkerId, 'workMode')    

    def get_status(self, taplinkerId):
        '''
        Returns True if taplink is currently connected, False otherwise, from self.devices
        '''
        return self.get_taplink_value(taplinkerId, 'status') == 'Connected'

    def update_mode(self, id, mode=None, update=True):
        '''
        Update OH mode item (single letter) for taplink (including mode_name item). 
        If no mode is given get it from self.devices.
        If mode is explicitly given, and update is True, update self.devices first.
        We do this as self.devices only updates every 5 minutes, so it would be out of sync
        if me manually change mode, but don't update self.devices. The exception is during update_items
        '''
        if not id: return
        if not mode:
            mode = self.get_mode(id)
        else:
            for index, device in enumerate(self.devices):
                taplinkers = device.get('taplinker', [])
                for tp, taplink in enumerate(taplinkers):
                    if taplink['taplinkerId'] == id:
                        self.devices[index]['taplinker'][tp]['workMode'] = mode
                        break

        self.post_update(('SetMode', id), mode)
        self.post_update(('Mode_Name', id), self.modes[mode])

    def instant_on(self, event, taplinkerId=None):
        '''
        Turn valve on/off instantly (ish)
        username: Required. String type. Your LinkTap account's username
        apiKey: Required. String type. Your API key
        gatewayId: Required. String type. Your LinkTap Gateway's first 16-digits/letters ID, case insensitive, no dash symbol, e,g, 3F7A23FE004B1200
        taplinkerId: Required. String type. Your LinkTap Taplinker's first 16-digits/letters ID, case insensitive, no dash symbol, e,g, 67ABCDEF004B1200
        action: Required. String or Boolean type. "true" for Watering ON, "false" for Watering OFF.
        duration: Required. Number type. The watering duration (unit is minute). For Watering OFF, this field is 0. For Watring ON, the range is from 1 minute to 1439 minutes.
        eco: Optional. String or Boolean type. "true" for ECO enabled, "false" for ECO disabled.
        ecoOn: Required when eco equals "true". Number type. The valve ON duration (unit is minute). This value needs to be less than duration.
        ecoOff: Required when eco equals "true". Number type. The valve OFF duration (unit is minute).
        autoBack: Optional. String or Boolean type. "true" for automatically re-activating the previous watering plan after watering in Instant Mode is completed.
        rate_limit is rate limiting delay
        '''
        if taplinkerId:
            duration = event
        else:
            taplinkerId = self.get_valve_from_item(event.name)
            duration = int(event.value)

        if taplinkerId:
            command = 'instant'
            action = 'true' if duration > 0 else 'false'
            autoBack = 'true' if duration > 0 else 'false'         #automatically return to previous mode after watering
            t = self.rate_limit(command, taplinkerId)
            if t >= 0:
                self.run.at(5, self.post_update, 'LinkTap_Gateway_Message', 'Watering change pending in {}s'.format(t+30))
                #remember previous mode, so it can be restored later
                if not self.current_mode.get(taplinkerId):
                    self.current_mode[taplinkerId] = self.get_mode(taplinkerId)
                self.run.at(t, self.sendHttpRequest, command, gatewayId=self.valves[taplinkerId]['gateway'], taplinkerId=taplinkerId, action=action, duration=duration, autoBack=autoBack)

                if self.reset_mode.get(taplinkerId) and not self.reset_mode[taplinkerId].is_finished:
                    self.reset_mode[taplinkerId].cancel()
                self.reset_mode[taplinkerId] = self.run.at((duration*60)+t, self.reset_previous_mode, taplinkerId)

    def reset_previous_mode(self, taplinkerId):
        '''
        Revert to previous mode after Instant mode has been called with an 'On' time
        '''
        if self.item[self.get_item('current_flow', taplinkerId)].value != 0:
            #wait 30 more seconds for watering to stop
            self.post_update('LinkTap_Gateway_Message', 'Restore previous mode in 30s')
            self.reset_mode[taplinkerId] = self.run.at(30, self.reset_previous_mode, taplinkerId)
            return
        if self.current_mode.get(taplinkerId) and self.current_mode[taplinkerId] != 'M':
            self.log.info('resetting previous mode: {}'.format(self.current_mode[taplinkerId]))
            self.set_mode(self.current_mode[taplinkerId], taplinkerId)
        self.reset_mode[taplinkerId] = None
        self.current_mode[taplinkerId] = None

    def set_mode(self, event, taplinkerId=None):
        '''
        Change taplink mode (mode must have been previously configured in TapLink app).
        Instant mode can't be set here, you have to set the 'On' time for Instant mode, so just revert
        back to the previous mode and display error message
        rate_limit is rate limiting delay
        '''
        if taplinkerId:
            mode = event
            command = self.get_command_from_mode(event)
        else:
            taplinkerId = self.get_valve_from_item(event.name)
            mode = event.value
        if mode == 'M':
            self.post_update('LinkTap_Gateway_Message', "Can't select Instant mode directly, use timing buttons")
            self.update_mode(taplinkerId)   #restore previous mode display
            return
        command = self.get_command_from_mode(mode)
        if command and taplinkerId:
            t = self.rate_limit(command, taplinkerId)
            if t >= 0:
                self.current_mode[taplinkerId] = None
                self.run.at(t, self.sendHttpRequest, command, gatewayId=self.valves[taplinkerId]['gateway'], taplinkerId=taplinkerId)

    def get_watering_status(self, taplinkerId=None):
        '''
        get taplink watering status if taplink is 'Connected'
        if no taplink id is given, return status for all defined taplinks in self.valves
        rate_limit is rate limiting delay
        '''
        if not taplinkerId:
            for taplinkerId in self.valves.keys():
                self.get_watering_status(taplinkerId)
        elif self.get_status(taplinkerId):
            command = 'query'
            t = self.rate_limit(command, taplinkerId)
            if t >= 0:
                self.run.at(t, self.sendHttpRequest, command, taplinkerId=taplinkerId)

    def rate_limit(self, command, taplinkerId, update=False):
        '''
        for rate limited calls, return time until next call allowed
        return -1 if call already pending
        if update is True, remove command from queue (if it is in the queue) and update
        the command last run time, return -1
        '''
        item = self.get_item(command, taplinkerId)
        last_run_item = self.item['{}_lastCall'.format(item)]
        if item in self.queue:
            if update:
                self.queue.remove(item)
                self.post_update(last_run_item, datetime.datetime.now())    #update last run time for command
            t = -1                   # do not execute command
        else:
            if last_run_item.value:
                #calculate whole seconds before command can be run
                t = max(0, round(self.api_calls[command].get('limit', 0) - (datetime.datetime.now() - last_run_item.value).total_seconds(), 0))
            else:
                t = 0
            self.queue.append(item)
        return int(t)

    def get_configuration(self):
        self.sendHttpRequest('config')

    def update_configuration(self):
        self.devices = self.response.get('devices', [])
        self.update_items()

    def update_items(self):
        '''
        Update OH items from values in self.devices
        self.valves is created/updated here.
        '''
        init = False
        self.post_update('LinkTap_Devices', json.dumps(self.devices))
        gateway=[]
        status=[]
        for device in self.devices:
            gateway.append(device['gatewayId'])
            status.append(device['status'])
            taplinkers = device.get('taplinker', [])
            for taplink in taplinkers:
                id = taplink['taplinkerId']
                if id not in self.valves:
                    self.valves[id] = {'name' : taplink['taplinkerName'], 'gateway' : device['gatewayId'], 'item' : '{}_{}_'.format(self.base_valve, len(self.valves.keys())+1)}
                    self.log.info('added {} to valves database : {}'.format(id, self.valves))
                    init=True
                self.post_update(('Name',id), taplink['taplinkerName'])
                self.post_update(('ID',id), taplink['taplinkerId'])
                self.post_update(('Status',id), taplink['status'])
                if taplink['signal']:
                    if isinstance(taplink['signal'], str):
                        taplink['signal'] = taplink['signal'].replace('%','')
                self.post_update(('signal',id), taplink['signal'] if taplink['signal'] else None)
                self.post_update(('Battery',id), taplink['batteryStatus'].replace('%','') if taplink['batteryStatus'] else None)
                self.update_mode(id, taplink['workMode'], False)
                self.post_update(('last_flow',id), taplink['vel']/1000)
                self.post_update(('fall',id), 'OPEN' if taplink['fall'] else 'CLOSED')
                self.post_update(('broken',id), 'OPEN' if taplink['valveBroken'] else 'CLOSED')
                self.post_update(('noWater',id), 'OPEN' if taplink['noWater'] else 'CLOSED')
        self.post_update('LinkTap_Gateway_ID', ','.join(gateway))
        self.post_update('LinkTap_Gateway_Status', ','.join(status))

        if init:
            self.init()
            self.update_items()

    def update_watering_status(self, id):
        '''
        watering status update for taplink device 'id'
        from self.response (api call response)
        Explanation of relevant fields in the watering status:
        total: the total watering duration of a watering slot.
        onDuration: the remaining watering duration of a watering slot.
        ecoTotal: the total watering duration of a watering plan when the ECO mode is enabled.
        ecoOn: valve ON duration.
        ecoOff: valve OFF duration.
        vel: current flow rate (unit: ml per minute. For G2 only).
        vol: volume dispensed
        pbCnt: unknown (usually "0")
        pcCnt: unknown (usually "0")
        "status": {
        "onDuration": "4",
        "total": "5",
        "onStamp": "1600798062158",
        "firstDt": "1600798062158",
        "vel": "3037",
        "vol": "0"
        }
        "status": None for watering off.
        '''
        if not self.response: return
        status = self.response.get('status', None)
        if status:
            self.post_update(('watering', id), '{}/{} m'.format(status['onDuration'], status['total']))
            self.post_update(('SetWatertime', id), status['onDuration'])
            self.post_update(('current_flow',id), int(status['vel'])/1000)  #flow rate in ml/m
            self.post_update(('vol',id), int(status['vol'])/1000)           #volume delivered in ml
            self.post_update(('last_vol',id), int(status['vol'])/1000)      #volume delivered in ml
            self.message[id] = self.valves[id]['name']

        else:
            self.post_update(('watering',id), 'Off')
            self.post_update(('current_flow',id), 0)
            self.post_update(('vol',id), 0)
            self.message.pop(id, None)
            if self.get_mode(id) != 'M':    #M is instant mode, it takes a while to activate watering, so this avoids resetting the timer display to 0 while we wait...
                self.post_update(('SetWatertime', id), 0)

        if self.message:
            message = 'Watering {}'.format(','.join(self.message.values()))
            self.post_update('LinkTap_Gateway_Message',  message)

    def get_item(self, item, taplinkerId=None):
        '''
        construct item name from taplink id, if given, from self.valves
        item could be a tuple or list (item, taplink_id)
        '''
        if isinstance(item, (tuple, list)):
            item = '{}{}'.format(self.valves[item[1]]['item'], item[0])
        elif taplinkerId:
            item = self.get_item((item, taplinkerId))
        return item

    def post_update(self, orgitem, value):
        '''
        Update an OH item with value. item could be a tuple of (item, taplink_id), to be expanded to a full item name
        or an actual OH item.
        '''
        item = self.get_item(orgitem)
        if isinstance(item, str):
            item = self.item.get(item)

        if item is not None:    
            self.oh.post_update(item, value)
            self.log.info('updated {} to {}'.format(item.name, value))
        else:
            self.log.warning('item: {} not found, not updating to {}'.format(orgitem, value))

    def sendHttpRequest(self, command, **kwargs):
        self.run.soon(self._sendHttpRequest, command, **kwargs)

    async def _sendHttpRequest(self, command, **kwargs):
        '''
        make actual REST call to LinkTaps's servers
        gateway id, taplink id plus other parameters (if required) are passed in **kwargs
        so the keywords are important and must match the api expected names
        eg an api call expects to be passed {'taplinkerId':'560B4221004B1200'} in the body of a POST,
        so we have to pass **kwargs with taplinkerId defined.
        API calls are rate limited, we get a '400 Bad Request' response if we call the API too soon
        '''
        url, mode, type, body = self.api_call(command, **kwargs)
        id = kwargs.get('taplinkerId')  #taplink id if one was passed
        if url:
            self.log.debug('{}ing url: {}, body {}'.format(type, url, body))
            headers = {'Content-Type' : 'application/x-www-form-urlencoded'}
            try:
                if type == 'POST':
                    async with self.async_http.post(url, data=body, headers=headers) as resp:
                        self.log.debug(resp)
                        self.response = await resp.json()
                elif type == 'GET':
                    async with self.async_http.get(url, params=body, headers=headers) as resp:
                        self.log.debug(resp)
                        self.response = await resp.json()
                self.log.debug(self.response)

                #process response
                if resp.status == 200:
                    if self.response.get('result', 'None') == 'ok':
                        if mode:                        #if a mode is associated with a command (eg activateMode), update the mode (no mode for config or query)
                            self.update_mode(id, mode)
                        if command == 'config':
                            self.update_configuration()
                        elif command == 'query':
                            self.update_watering_status(id)
                    else:
                        self.log.warning('data error : {}'.format(self.response.get('message', 'No Message')))
                else:
                    self.log.warning('connection error: {}, {}'.format(resp.status, self.response.get('message', 'No Message')))
                    if mode:
                        self.update_mode(id)    #revert mode display, as command to change mode failed
            except Exception as e:
                self.log.error('Error connecting to LinkTap: {}'.format(e))

            #rate limiting - remove command from queue and update timestamp
            self.rate_limit(command, id, update=True)

            if self.response.get('message'):
                self.post_update('LinkTap_Gateway_Message', self.response['message'])
        else:
            self.log.warning('command: {} not found'.format(command))

        self.response = {}

    def api_call(self, command, **kwargs):
        '''
        return rest api call, mode, type (GET/POST) from 'command'
        return body from authentication and kwargs
        '''
        http_command = mode = type = body = None
        if command in self.api_calls.keys():
            http_command = "{}{}".format(self.rest, self.api_calls[command]['rest'])
            mode = self.api_calls[command].get('code')
            type = self.api_calls[command].get('type', 'POST')  #Default is POST
            body = {'username' : self.username, 'apiKey' : self.apiKey}
            body.update(kwargs)

        return http_command, mode, type, body
magnuslsjoberg commented 3 years ago

Similar error as @NickWaterton with self.run.every(None,...):

2021-05-03 13:14:57.491 [ERROR] [HABApp                              ] - OverflowError: timestamp out of range for platform time_t
2021-05-03 13:14:57.719 [ERROR] [HABApp.Rules                        ] - Error "timestamp out of range for platform time_t" in load:
2021-05-03 13:14:57.720 [ERROR] [HABApp.Rules                        ] - Could not load /etc/openhab/habapp/rules/UPS.py!

I changed all my None to timedelta(seconds=5) which works. Setting to 0 seconds gave the same error as Nick:

2021-05-03 13:20:32.790 [ERROR] [HABApp.Rules                        ] - FirstRunInThePastError: First run must be in the future! Now: 2021-05-03T13:20:32.541628+02:00, run: 2021-05-03T13:20:32.541628+02:00
2021-05-03 13:20:32.794 [WARN ] [HABApp.Rules                        ] - Failed to load /etc/openhab/habapp/rules/UPS.py!
spacemanspiff2007 commented 3 years ago

Impressive rule. I deleted your traceback and edited your post so your credentials are not available in plain text here.

I was searching for the problem where self.run.at(None, ... would throw an error, but here it's the case for self.run.at(0, .... 0 as a value. I'll try to provide a fix which will allow 0 as a value.

@magnuslsjoberg Your rules seemed to also generate another error. Could you post the example?

NickWaterton commented 3 years ago

Thanks, I forgot my credentials were in the file.

I just tried run.every(None,) again, and it's working now - I think I'm just getting lost in the huge amount of error messages I'm trying to parse....

Please ignore this. Thanks for the quick response.

NickWaterton commented 3 years ago

I also tried self.run.on_sunset(self.sunset_func) which has an error as well (as I say, not a big deal, as I don't use it).

The error is ValueError: Sun is always below the horizon on this day, at this location.

2021-05-03 10:46:30,742[              HABApp.Rules]ERROR|            __exit__:     ..................................................
2021-05-03 10:46:30,742[              HABApp.Rules]ERROR|            __exit__:      observer = Observer(latitude=-79.70, longitude=-79.70, elevation=0.0)
2021-05-03 10:46:30,742[              HABApp.Rules]ERROR|            __exit__:      Observer = <class 'astral.Observer'>
2021-05-03 10:46:30,742[              HABApp.Rules]ERROR|            __exit__:      date = Date(2021, 5, 3)
2021-05-03 10:46:30,743[              HABApp.Rules]ERROR|            __exit__:      Optional = typing.Optional
2021-05-03 10:46:30,743[              HABApp.Rules]ERROR|            __exit__:      datetime.date = <class 'datetime.date'>
2021-05-03 10:46:30,743[              HABApp.Rules]ERROR|            __exit__:      tzinfo = Timezone('UTC')
2021-05-03 10:46:30,743[              HABApp.Rules]ERROR|            __exit__:      Union = typing.Union
2021-05-03 10:46:30,744[              HABApp.Rules]ERROR|            __exit__:      datetime.tzinfo = <class 'datetime.tzinfo'>
2021-05-03 10:46:30,744[              HABApp.Rules]ERROR|            __exit__:      pytz.utc = <UTC>
2021-05-03 10:46:30,744[              HABApp.Rules]ERROR|            __exit__:      datetime.datetime = <class 'datetime.datetime'>
2021-05-03 10:46:30,745[              HABApp.Rules]ERROR|            __exit__:      z = 95.53522333943233
2021-05-03 10:46:30,745[              HABApp.Rules]ERROR|            __exit__:      msg = 'Sun is always below the horizon on this day, at this location.'
2021-05-03 10:46:30,745[              HABApp.Rules]ERROR|            __exit__:     ..................................................

I see that the longitude is incorrect (ie it the same as the latitude), location set in config.yaml is:

location:
  latitude: 43.60
  longitude: -79.70
  elevation: 0.0

and I'm not convinced that the timezone is correct either.

Having said that, I thought that astral was no longer required?

magnuslsjoberg commented 3 years ago

Here is a minimum example that fails with None:

import HABApp
import logging

class RunEveryTest(HABApp.Rule):
    def __init__(self):
        super().__init__()
        self.run.every(None,10,self.test)

    def test(self):
        logging.info('in test()')

log = logging.getLogger('HABApp')
RunEveryTest()

I run HABApp on openhabian inside a virtual environment:

(habapp) openhabian@openhab:~ $ pip freeze
aiohttp==3.7.4
aiohttp-sse-client==0.2.1
astral==2.2
async-timeout==3.0.1
attrs==20.3.0
bidict==0.21.2
cffi==1.14.4
chardet==3.0.4
cryptography==3.3.1
EAScheduler==0.1.1
EasyCo==0.2.3
HABApp==0.30.0
idna==3.1
ifaddr==0.1.7
multidict==5.1.0
netifaces==0.10.9
paho-mqtt==1.5.1
pendulum==2.1.2
pkg-resources==0.0.0
protobuf==3.14.0
pyatv==0.7.7
pycparser==2.20
pydantic==1.8.1
python-dateutil==2.8.1
pytz==2020.5
pytzdata==2020.1
ruamel.yaml==0.16.12
ruamel.yaml.clib==0.2.2
six==1.15.0
srptools==1.0.1
stackprinter==0.2.5
typing-extensions==3.7.4.3
tzlocal==2.1
ujson==4.0.2
voluptuous==0.12.1
watchdog==2.0.2
yarl==1.6.3
zeroconf==0.28.8
(habapp) openhabian@openhab:~ $ 

Error log:

2021-05-03 17:17:35.690 [ERROR] [HABApp                              ] - Error timestamp out of range for platform time_t in HABApp.scheduler:
2021-05-03 17:17:35.690 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/schedulers/scheduler_async.py", line 89, in __run_next
2021-05-03 17:17:35.691 [ERROR] [HABApp                              ] -     65   async def __run_next(self):
2021-05-03 17:17:35.691 [ERROR] [HABApp                              ] -  (...)
2021-05-03 17:17:35.691 [ERROR] [HABApp                              ] -     85   
2021-05-03 17:17:35.691 [ERROR] [HABApp                              ] -     86               assert old_job is job, f'Job changed unexpectedly\n{old_job}\n{job}'
2021-05-03 17:17:35.691 [ERROR] [HABApp                              ] -     87   
2021-05-03 17:17:35.692 [ERROR] [HABApp                              ] -     88               try:
2021-05-03 17:17:35.692 [ERROR] [HABApp                              ] - --> 89                   job._execute()
2021-05-03 17:17:35.692 [ERROR] [HABApp                              ] -     90               except Exception as e:
2021-05-03 17:17:35.692 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.692 [ERROR] [HABApp                              ] -      self = <eascheduler.schedulers.scheduler_asyncthread.ThreadSafeAsyncScheduler object at 0x6173d850>
2021-05-03 17:17:35.693 [ERROR] [HABApp                              ] -      old_job = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.693 [ERROR] [HABApp                              ] -      job = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.693 [ERROR] [HABApp                              ] -      job._execute = <method 'ScheduledJobBase._execute' of <ReoccurringJob next_run: 2021-05-03T17:17:35.587000> job_base.py:26>
2021-05-03 17:17:35.693 [ERROR] [HABApp                              ] -      e = OverflowError('timestamp out of range for platform time_t')
2021-05-03 17:17:35.693 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.694 [ERROR] [HABApp                              ] - 
2021-05-03 17:17:35.694 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_base.py", line 27, in _execute
2021-05-03 17:17:35.694 [ERROR] [HABApp                              ] -     26   def _execute(self):
2021-05-03 17:17:35.694 [ERROR] [HABApp                              ] - --> 27       self._schedule_next_run()
2021-05-03 17:17:35.695 [ERROR] [HABApp                              ] -     28       self._func.execute()
2021-05-03 17:17:35.695 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.695 [ERROR] [HABApp                              ] -      self = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.695 [ERROR] [HABApp                              ] -      self._schedule_next_run = <method 'ReoccurringJob._schedule_next_run' of <ReoccurringJob next_run: 2021-05-03T17:17:35.587000> job_reoccuring.py:22>
2021-05-03 17:17:35.695 [ERROR] [HABApp                              ] -      self._func.execute = <method 'WrappedFunctionExecutor.execute' of <HABApp.rule.habappscheduler.WrappedFunctionExecutor object at 0x6173d970> habappscheduler.py:21>
2021-05-03 17:17:35.696 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.696 [ERROR] [HABApp                              ] - 
2021-05-03 17:17:35.696 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_reoccuring.py", line 27, in _schedule_next_run
2021-05-03 17:17:35.696 [ERROR] [HABApp                              ] -     22   def _schedule_next_run(self):
2021-05-03 17:17:35.697 [ERROR] [HABApp                              ] -     23       now = get_now(UTC).timestamp()
2021-05-03 17:17:35.697 [ERROR] [HABApp                              ] -     24       while self._next_run_base < now:
2021-05-03 17:17:35.697 [ERROR] [HABApp                              ] -     25           self._next_run_base += self._interval
2021-05-03 17:17:35.697 [ERROR] [HABApp                              ] -     26   
2021-05-03 17:17:35.697 [ERROR] [HABApp                              ] - --> 27       self._apply_boundaries()
2021-05-03 17:17:35.698 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.698 [ERROR] [HABApp                              ] -      self = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.698 [ERROR] [HABApp                              ] -      now = 1620055055.592741
2021-05-03 17:17:35.698 [ERROR] [HABApp                              ] -      get_now = <function 'now' __init__.py:197>
2021-05-03 17:17:35.698 [ERROR] [HABApp                              ] -      UTC = Timezone('UTC')
2021-05-03 17:17:35.699 [ERROR] [HABApp                              ] -      self._next_run_base = 3767538702.5874157
2021-05-03 17:17:35.699 [ERROR] [HABApp                              ] -      self._interval = 2147483647.0
2021-05-03 17:17:35.699 [ERROR] [HABApp                              ] -      self._apply_boundaries = <method 'DateTimeJobBase._apply_boundaries' of <ReoccurringJob next_run: 2021-05-03T17:17:35.587000> job_base_datetime.py:144>
2021-05-03 17:17:35.699 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.699 [ERROR] [HABApp                              ] - 
2021-05-03 17:17:35.700 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_base_datetime.py", line 148, in _apply_boundaries
2021-05-03 17:17:35.700 [ERROR] [HABApp                              ] -     144  def _apply_boundaries(self):
2021-05-03 17:17:35.700 [ERROR] [HABApp                              ] -     145      self._adjusting = True
2021-05-03 17:17:35.700 [ERROR] [HABApp                              ] -     146  
2021-05-03 17:17:35.700 [ERROR] [HABApp                              ] -     147      # Starting point is always the next call in local time
2021-05-03 17:17:35.701 [ERROR] [HABApp                              ] - --> 148      next_run_local: DateTime = from_timestamp(self._next_run_base, local_tz)
2021-05-03 17:17:35.701 [ERROR] [HABApp                              ] -     149  
2021-05-03 17:17:35.701 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.701 [ERROR] [HABApp                              ] -      self = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.701 [ERROR] [HABApp                              ] -      self._adjusting = True
2021-05-03 17:17:35.702 [ERROR] [HABApp                              ] -      DateTime = <class 'pendulum.datetime.DateTime'>
2021-05-03 17:17:35.702 [ERROR] [HABApp                              ] -      self._next_run_base = 3767538702.5874157
2021-05-03 17:17:35.702 [ERROR] [HABApp                              ] -      local_tz = Timezone('Europe/Stockholm')
2021-05-03 17:17:35.702 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.702 [ERROR] [HABApp                              ] - 
2021-05-03 17:17:35.703 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/pendulum/__init__.py", line 272, in from_timestamp
2021-05-03 17:17:35.703 [ERROR] [HABApp                              ] -     266  def from_timestamp(
2021-05-03 17:17:35.703 [ERROR] [HABApp                              ] -     267      timestamp, tz=UTC
2021-05-03 17:17:35.703 [ERROR] [HABApp                              ] -     268  ):  # type: (Union[int, float], Union[str, _Timezone]) -> DateTime
2021-05-03 17:17:35.703 [ERROR] [HABApp                              ] -     269      """
2021-05-03 17:17:35.704 [ERROR] [HABApp                              ] -     270      Create a DateTime instance from a timestamp.
2021-05-03 17:17:35.704 [ERROR] [HABApp                              ] -     271      """
2021-05-03 17:17:35.704 [ERROR] [HABApp                              ] - --> 272      dt = _datetime.datetime.utcfromtimestamp(timestamp)
2021-05-03 17:17:35.704 [ERROR] [HABApp                              ] -     273  
2021-05-03 17:17:35.704 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.705 [ERROR] [HABApp                              ] -      timestamp = 3767538702.5874157
2021-05-03 17:17:35.705 [ERROR] [HABApp                              ] -      tz = Timezone('Europe/Stockholm')
2021-05-03 17:17:35.705 [ERROR] [HABApp                              ] -      UTC = Timezone('UTC')
2021-05-03 17:17:35.705 [ERROR] [HABApp                              ] -     ..................................................
2021-05-03 17:17:35.705 [ERROR] [HABApp                              ] - 
2021-05-03 17:17:35.706 [ERROR] [HABApp                              ] - ---- (full traceback above) ----
2021-05-03 17:17:35.706 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/schedulers/scheduler_async.py", line 89, in __run_next
2021-05-03 17:17:35.706 [ERROR] [HABApp                              ] -     job._execute()
2021-05-03 17:17:35.706 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_base.py", line 27, in _execute
2021-05-03 17:17:35.706 [ERROR] [HABApp                              ] -     self._schedule_next_run()
2021-05-03 17:17:35.707 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_reoccuring.py", line 27, in _schedule_next_run
2021-05-03 17:17:35.707 [ERROR] [HABApp                              ] -     self._apply_boundaries()
2021-05-03 17:17:35.707 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_base_datetime.py", line 148, in _apply_boundaries
2021-05-03 17:17:35.707 [ERROR] [HABApp                              ] -     next_run_local: DateTime = from_timestamp(self._next_run_base, local_tz)
2021-05-03 17:17:35.707 [ERROR] [HABApp                              ] - File "/opt/habapp/lib/python3.7/site-packages/pendulum/__init__.py", line 272, in from_timestamp
2021-05-03 17:17:35.708 [ERROR] [HABApp                              ] -     dt = _datetime.datetime.utcfromtimestamp(timestamp)
2021-05-03 17:17:35.708 [ERROR] [HABApp                              ] - 
2021-05-03 17:17:35.708 [ERROR] [HABApp                              ] - OverflowError: timestamp out of range for platform time_t
2021-05-03 17:17:35.907 [ERROR] [HABApp.Rules                        ] - Error "timestamp out of range for platform time_t" in load:
2021-05-03 17:17:35.908 [ERROR] [HABApp.Rules                        ] - Could not load /etc/openhab/habapp/rules/RunEveryTest.py!
2021-05-03 17:17:35.908 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule_manager/rule_file.py", line 80, in load
2021-05-03 17:17:35.908 [ERROR] [HABApp.Rules                        ] -     self.create_rules(created_rules)
2021-05-03 17:17:35.908 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule_manager/rule_file.py", line 69, in create_rules
2021-05-03 17:17:35.908 [ERROR] [HABApp.Rules                        ] -     '__HABAPP__RULES': created_rules,
2021-05-03 17:17:35.909 [ERROR] [HABApp.Rules                        ] - File "/usr/lib/python3.7/runpy.py", line 263, in run_path
2021-05-03 17:17:35.909 [ERROR] [HABApp.Rules                        ] -     pkg_name=pkg_name, script_name=fname)
2021-05-03 17:17:35.909 [ERROR] [HABApp.Rules                        ] - File "/usr/lib/python3.7/runpy.py", line 96, in _run_module_code
2021-05-03 17:17:35.909 [ERROR] [HABApp.Rules                        ] -     mod_name, mod_spec, pkg_name, script_name)
2021-05-03 17:17:35.910 [ERROR] [HABApp.Rules                        ] - File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
2021-05-03 17:17:35.910 [ERROR] [HABApp.Rules                        ] -     exec(code, run_globals)
2021-05-03 17:17:35.910 [ERROR] [HABApp.Rules                        ] - File "/etc/openhab/habapp/rules/RunEveryTest.py", line 21, in RunEveryTest.py
2021-05-03 17:17:35.910 [ERROR] [HABApp.Rules                        ] -     17   
2021-05-03 17:17:35.910 [ERROR] [HABApp.Rules                        ] -     18   # Main init  
2021-05-03 17:17:35.911 [ERROR] [HABApp.Rules                        ] -     19   log = logging.getLogger('HABApp')
2021-05-03 17:17:35.911 [ERROR] [HABApp.Rules                        ] -     20   
2021-05-03 17:17:35.911 [ERROR] [HABApp.Rules                        ] - --> 21   RunEveryTest()
2021-05-03 17:17:35.911 [ERROR] [HABApp.Rules                        ] -     22   
2021-05-03 17:17:35.911 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.912 [ERROR] [HABApp.Rules                        ] -      log = <Logger HABApp (INFO)>
2021-05-03 17:17:35.912 [ERROR] [HABApp.Rules                        ] -      logging.getLogger = <function 'getLogger' __init__.py:1930>
2021-05-03 17:17:35.912 [ERROR] [HABApp.Rules                        ] -      RunEveryTest = <class '/etc/openhab/habapp/rules/RunEveryTest.py.RunEveryTest'>
2021-05-03 17:17:35.912 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.912 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.913 [ERROR] [HABApp.Rules                        ] - File "/etc/openhab/habapp/rules/RunEveryTest.py", line 12, in __init__
2021-05-03 17:17:35.913 [ERROR] [HABApp.Rules                        ] -     10   def __init__(self):
2021-05-03 17:17:35.913 [ERROR] [HABApp.Rules                        ] -     11       super().__init__()
2021-05-03 17:17:35.913 [ERROR] [HABApp.Rules                        ] - --> 12       self.run.every(None,10,self.test)
2021-05-03 17:17:35.913 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.914 [ERROR] [HABApp.Rules                        ] -      self = </etc/openhab/habapp/rules/RunEveryTest.py.RunEveryTest object at 0x6173dd50>
2021-05-03 17:17:35.914 [ERROR] [HABApp.Rules                        ] -      self.run.every = <method 'HABAppScheduler.every' of <HABApp.rule.habappscheduler.HABAppScheduler object at 0x6173dcf0> habappscheduler.py:38>
2021-05-03 17:17:35.914 [ERROR] [HABApp.Rules                        ] -      self.test = <method 'RunEveryTest.test' of </etc/openhab/habapp/rules/RunEveryTest.py.RunEveryTest object at 0x6173dd50> RunEveryTest.py:14>
2021-05-03 17:17:35.914 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.915 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.915 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule/habappscheduler.py", line 41, in every
2021-05-03 17:17:35.915 [ERROR] [HABApp.Rules                        ] -     38   def every(self, start_time: Union[None, dt_datetime, dt_timedelta, dt_time, int],
2021-05-03 17:17:35.915 [ERROR] [HABApp.Rules                        ] -     39             interval: Union[int, float, dt_timedelta], callback, *args, **kwargs) -> ReoccurringJob:
2021-05-03 17:17:35.915 [ERROR] [HABApp.Rules                        ] -     40       callback = WrappedFunction(callback, name=self._rule._get_cb_name(callback))
2021-05-03 17:17:35.916 [ERROR] [HABApp.Rules                        ] - --> 41       return super().every(start_time, interval, callback, *args, **kwargs)
2021-05-03 17:17:35.916 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.916 [ERROR] [HABApp.Rules                        ] -      self = <HABApp.rule.habappscheduler.HABAppScheduler object at 0x6173dcf0>
2021-05-03 17:17:35.916 [ERROR] [HABApp.Rules                        ] -      start_time = None
2021-05-03 17:17:35.916 [ERROR] [HABApp.Rules                        ] -      Union = typing.Union
2021-05-03 17:17:35.917 [ERROR] [HABApp.Rules                        ] -      dt_datetime = <class 'datetime.datetime'>
2021-05-03 17:17:35.917 [ERROR] [HABApp.Rules                        ] -      dt_timedelta = <class 'datetime.timedelta'>
2021-05-03 17:17:35.917 [ERROR] [HABApp.Rules                        ] -      dt_time = <class 'datetime.time'>
2021-05-03 17:17:35.917 [ERROR] [HABApp.Rules                        ] -      interval = 10
2021-05-03 17:17:35.917 [ERROR] [HABApp.Rules                        ] -      callback = <HABApp.core.wrappedfunction.WrappedFunction object at 0x6173d550>
2021-05-03 17:17:35.918 [ERROR] [HABApp.Rules                        ] -      args = ()
2021-05-03 17:17:35.918 [ERROR] [HABApp.Rules                        ] -      kwargs = {}
2021-05-03 17:17:35.918 [ERROR] [HABApp.Rules                        ] -      ReoccurringJob = <class 'eascheduler.jobs.job_reoccuring.ReoccurringJob'>
2021-05-03 17:17:35.918 [ERROR] [HABApp.Rules                        ] -      WrappedFunction = <class 'HABApp.core.wrappedfunction.WrappedFunction'>
2021-05-03 17:17:35.918 [ERROR] [HABApp.Rules                        ] -      self._rule._get_cb_name = <method 'Rule._get_cb_name' of </etc/openhab/habapp/rules/RunEveryTest.py.RunEveryTest object at 0x6173dd50> rule.py:251>
2021-05-03 17:17:35.919 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.919 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.919 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/scheduler_view.py", line 59, in every
2021-05-03 17:17:35.919 [ERROR] [HABApp.Rules                        ] -     46   def every(self, start_time: Union[None, dt_datetime, dt_timedelta, dt_time, int, float],
2021-05-03 17:17:35.920 [ERROR] [HABApp.Rules                        ] -     47             interval: Union[int, float, dt_timedelta], callback, *args, **kwargs) -> ReoccurringJob:
2021-05-03 17:17:35.920 [ERROR] [HABApp.Rules                        ] -  (...)
2021-05-03 17:17:35.920 [ERROR] [HABApp.Rules                        ] -     55       :return: Created job
2021-05-03 17:17:35.920 [ERROR] [HABApp.Rules                        ] -     56       """
2021-05-03 17:17:35.920 [ERROR] [HABApp.Rules                        ] -     57       job = ReoccurringJob(self._scheduler, self._executor(callback, *args, **kwargs))
2021-05-03 17:17:35.921 [ERROR] [HABApp.Rules                        ] -     58       job._schedule_first_run(start_time)
2021-05-03 17:17:35.921 [ERROR] [HABApp.Rules                        ] - --> 59       job.interval(interval)
2021-05-03 17:17:35.921 [ERROR] [HABApp.Rules                        ] -     60       return job
2021-05-03 17:17:35.921 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.921 [ERROR] [HABApp.Rules                        ] -      self = <HABApp.rule.habappscheduler.HABAppScheduler object at 0x6173dcf0>
2021-05-03 17:17:35.922 [ERROR] [HABApp.Rules                        ] -      start_time = None
2021-05-03 17:17:35.922 [ERROR] [HABApp.Rules                        ] -      Union = typing.Union
2021-05-03 17:17:35.922 [ERROR] [HABApp.Rules                        ] -      dt_datetime = <class 'datetime.datetime'>
2021-05-03 17:17:35.922 [ERROR] [HABApp.Rules                        ] -      dt_timedelta = <class 'datetime.timedelta'>
2021-05-03 17:17:35.922 [ERROR] [HABApp.Rules                        ] -      dt_time = <class 'datetime.time'>
2021-05-03 17:17:35.923 [ERROR] [HABApp.Rules                        ] -      interval = 10
2021-05-03 17:17:35.923 [ERROR] [HABApp.Rules                        ] -      callback = <HABApp.core.wrappedfunction.WrappedFunction object at 0x6173d550>
2021-05-03 17:17:35.923 [ERROR] [HABApp.Rules                        ] -      args = ()
2021-05-03 17:17:35.923 [ERROR] [HABApp.Rules                        ] -      kwargs = {}
2021-05-03 17:17:35.923 [ERROR] [HABApp.Rules                        ] -      ReoccurringJob = <class 'eascheduler.jobs.job_reoccuring.ReoccurringJob'>
2021-05-03 17:17:35.924 [ERROR] [HABApp.Rules                        ] -      job = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.924 [ERROR] [HABApp.Rules                        ] -      self._scheduler = <eascheduler.schedulers.scheduler_asyncthread.ThreadSafeAsyncScheduler object at 0x6173d850>
2021-05-03 17:17:35.924 [ERROR] [HABApp.Rules                        ] -      self._executor = <class 'HABApp.rule.habappscheduler.WrappedFunctionExecutor'>
2021-05-03 17:17:35.924 [ERROR] [HABApp.Rules                        ] -      job._schedule_first_run = <method 'DateTimeJobBase._schedule_first_run' of <ReoccurringJob next_run: 2021-05-03T17:17:35.587000> job_base_datetime.py:42>
2021-05-03 17:17:35.924 [ERROR] [HABApp.Rules                        ] -      job.interval = <method 'ReoccurringJob.interval' of <ReoccurringJob next_run: 2021-05-03T17:17:35.587000> job_reoccuring.py:32>
2021-05-03 17:17:35.925 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.925 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.925 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_reoccuring.py", line 45, in interval
2021-05-03 17:17:35.925 [ERROR] [HABApp.Rules                        ] -     32   def interval(self, interval: Union[int, float, timedelta]) -> ReoccurringJob:
2021-05-03 17:17:35.926 [ERROR] [HABApp.Rules                        ] -  (...)
2021-05-03 17:17:35.926 [ERROR] [HABApp.Rules                        ] -     41           interval = interval.total_seconds()
2021-05-03 17:17:35.926 [ERROR] [HABApp.Rules                        ] -     42       assert interval > 0, interval
2021-05-03 17:17:35.926 [ERROR] [HABApp.Rules                        ] -     43   
2021-05-03 17:17:35.926 [ERROR] [HABApp.Rules                        ] -     44       self._interval = interval
2021-05-03 17:17:35.927 [ERROR] [HABApp.Rules                        ] - --> 45       self._schedule_next_run()
2021-05-03 17:17:35.927 [ERROR] [HABApp.Rules                        ] -     46       return self
2021-05-03 17:17:35.927 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.927 [ERROR] [HABApp.Rules                        ] -      interval = 10
2021-05-03 17:17:35.927 [ERROR] [HABApp.Rules                        ] -      self = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.928 [ERROR] [HABApp.Rules                        ] -      Union = typing.Union
2021-05-03 17:17:35.928 [ERROR] [HABApp.Rules                        ] -      timedelta = <class 'datetime.timedelta'>
2021-05-03 17:17:35.928 [ERROR] [HABApp.Rules                        ] -      ReoccurringJob = <class 'eascheduler.jobs.job_reoccuring.ReoccurringJob'>
2021-05-03 17:17:35.928 [ERROR] [HABApp.Rules                        ] -      interval.total_seconds = # AttributeError
2021-05-03 17:17:35.928 [ERROR] [HABApp.Rules                        ] -           interval = 10
2021-05-03 17:17:35.929 [ERROR] [HABApp.Rules                        ] -      self._interval = 10
2021-05-03 17:17:35.929 [ERROR] [HABApp.Rules                        ] -      self._schedule_next_run = <method 'ReoccurringJob._schedule_next_run' of <ReoccurringJob next_run: 2021-05-03T17:17:35.587000> job_reoccuring.py:22>
2021-05-03 17:17:35.929 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.929 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.930 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_reoccuring.py", line 27, in _schedule_next_run
2021-05-03 17:17:35.930 [ERROR] [HABApp.Rules                        ] -     22   def _schedule_next_run(self):
2021-05-03 17:17:35.930 [ERROR] [HABApp.Rules                        ] -     23       now = get_now(UTC).timestamp()
2021-05-03 17:17:35.930 [ERROR] [HABApp.Rules                        ] -     24       while self._next_run_base < now:
2021-05-03 17:17:35.930 [ERROR] [HABApp.Rules                        ] -     25           self._next_run_base += self._interval
2021-05-03 17:17:35.931 [ERROR] [HABApp.Rules                        ] -     26   
2021-05-03 17:17:35.931 [ERROR] [HABApp.Rules                        ] - --> 27       self._apply_boundaries()
2021-05-03 17:17:35.931 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.931 [ERROR] [HABApp.Rules                        ] -      self = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.931 [ERROR] [HABApp.Rules                        ] -      now = 1620055055.709704
2021-05-03 17:17:35.932 [ERROR] [HABApp.Rules                        ] -      get_now = <function 'now' __init__.py:197>
2021-05-03 17:17:35.932 [ERROR] [HABApp.Rules                        ] -      UTC = Timezone('UTC')
2021-05-03 17:17:35.932 [ERROR] [HABApp.Rules                        ] -      self._next_run_base = 3767538702.5874157
2021-05-03 17:17:35.932 [ERROR] [HABApp.Rules                        ] -      self._interval = 10
2021-05-03 17:17:35.932 [ERROR] [HABApp.Rules                        ] -      self._apply_boundaries = <method 'DateTimeJobBase._apply_boundaries' of <ReoccurringJob next_run: 2021-05-03T17:17:35.587000> job_base_datetime.py:144>
2021-05-03 17:17:35.933 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.933 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.933 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_base_datetime.py", line 148, in _apply_boundaries
2021-05-03 17:17:35.933 [ERROR] [HABApp.Rules                        ] -     144  def _apply_boundaries(self):
2021-05-03 17:17:35.933 [ERROR] [HABApp.Rules                        ] -     145      self._adjusting = True
2021-05-03 17:17:35.934 [ERROR] [HABApp.Rules                        ] -     146  
2021-05-03 17:17:35.934 [ERROR] [HABApp.Rules                        ] -     147      # Starting point is always the next call in local time
2021-05-03 17:17:35.934 [ERROR] [HABApp.Rules                        ] - --> 148      next_run_local: DateTime = from_timestamp(self._next_run_base, local_tz)
2021-05-03 17:17:35.934 [ERROR] [HABApp.Rules                        ] -     149  
2021-05-03 17:17:35.934 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.935 [ERROR] [HABApp.Rules                        ] -      self = <ReoccurringJob next_run: 2021-05-03T17:17:35.587000>
2021-05-03 17:17:35.935 [ERROR] [HABApp.Rules                        ] -      self._adjusting = True
2021-05-03 17:17:35.935 [ERROR] [HABApp.Rules                        ] -      DateTime = <class 'pendulum.datetime.DateTime'>
2021-05-03 17:17:35.935 [ERROR] [HABApp.Rules                        ] -      self._next_run_base = 3767538702.5874157
2021-05-03 17:17:35.936 [ERROR] [HABApp.Rules                        ] -      local_tz = Timezone('Europe/Stockholm')
2021-05-03 17:17:35.936 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.936 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.936 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/pendulum/__init__.py", line 272, in from_timestamp
2021-05-03 17:17:35.936 [ERROR] [HABApp.Rules                        ] -     266  def from_timestamp(
2021-05-03 17:17:35.937 [ERROR] [HABApp.Rules                        ] -     267      timestamp, tz=UTC
2021-05-03 17:17:35.937 [ERROR] [HABApp.Rules                        ] -     268  ):  # type: (Union[int, float], Union[str, _Timezone]) -> DateTime
2021-05-03 17:17:35.937 [ERROR] [HABApp.Rules                        ] -     269      """
2021-05-03 17:17:35.937 [ERROR] [HABApp.Rules                        ] -     270      Create a DateTime instance from a timestamp.
2021-05-03 17:17:35.937 [ERROR] [HABApp.Rules                        ] -     271      """
2021-05-03 17:17:35.938 [ERROR] [HABApp.Rules                        ] - --> 272      dt = _datetime.datetime.utcfromtimestamp(timestamp)
2021-05-03 17:17:35.938 [ERROR] [HABApp.Rules                        ] -     273  
2021-05-03 17:17:35.938 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.938 [ERROR] [HABApp.Rules                        ] -      timestamp = 3767538702.5874157
2021-05-03 17:17:35.938 [ERROR] [HABApp.Rules                        ] -      tz = Timezone('Europe/Stockholm')
2021-05-03 17:17:35.939 [ERROR] [HABApp.Rules                        ] -      UTC = Timezone('UTC')
2021-05-03 17:17:35.939 [ERROR] [HABApp.Rules                        ] -     ..................................................
2021-05-03 17:17:35.939 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.939 [ERROR] [HABApp.Rules                        ] - ---- (full traceback above) ----
2021-05-03 17:17:35.939 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule_manager/rule_file.py", line 80, in load
2021-05-03 17:17:35.940 [ERROR] [HABApp.Rules                        ] -     self.create_rules(created_rules)
2021-05-03 17:17:35.940 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule_manager/rule_file.py", line 69, in create_rules
2021-05-03 17:17:35.940 [ERROR] [HABApp.Rules                        ] -     '__HABAPP__RULES': created_rules,
2021-05-03 17:17:35.940 [ERROR] [HABApp.Rules                        ] - File "/usr/lib/python3.7/runpy.py", line 263, in run_path
2021-05-03 17:17:35.941 [ERROR] [HABApp.Rules                        ] -     pkg_name=pkg_name, script_name=fname)
2021-05-03 17:17:35.941 [ERROR] [HABApp.Rules                        ] - File "/usr/lib/python3.7/runpy.py", line 96, in _run_module_code
2021-05-03 17:17:35.941 [ERROR] [HABApp.Rules                        ] -     mod_name, mod_spec, pkg_name, script_name)
2021-05-03 17:17:35.941 [ERROR] [HABApp.Rules                        ] - File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
2021-05-03 17:17:35.941 [ERROR] [HABApp.Rules                        ] -     exec(code, run_globals)
2021-05-03 17:17:35.942 [ERROR] [HABApp.Rules                        ] - File "/etc/openhab/habapp/rules/RunEveryTest.py", line 21, in RunEveryTest.py
2021-05-03 17:17:35.942 [ERROR] [HABApp.Rules                        ] -     RunEveryTest()
2021-05-03 17:17:35.942 [ERROR] [HABApp.Rules                        ] - File "/etc/openhab/habapp/rules/RunEveryTest.py", line 12, in __init__
2021-05-03 17:17:35.942 [ERROR] [HABApp.Rules                        ] -     self.run.every(None,10,self.test)
2021-05-03 17:17:35.942 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/HABApp/rule/habappscheduler.py", line 41, in every
2021-05-03 17:17:35.943 [ERROR] [HABApp.Rules                        ] -     return super().every(start_time, interval, callback, *args, **kwargs)
2021-05-03 17:17:35.944 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/scheduler_view.py", line 59, in every
2021-05-03 17:17:35.944 [ERROR] [HABApp.Rules                        ] -     job.interval(interval)
2021-05-03 17:17:35.945 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_reoccuring.py", line 45, in interval
2021-05-03 17:17:35.945 [ERROR] [HABApp.Rules                        ] -     self._schedule_next_run()
2021-05-03 17:17:35.945 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_reoccuring.py", line 27, in _schedule_next_run
2021-05-03 17:17:35.946 [ERROR] [HABApp.Rules                        ] -     self._apply_boundaries()
2021-05-03 17:17:35.946 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/eascheduler/jobs/job_base_datetime.py", line 148, in _apply_boundaries
2021-05-03 17:17:35.946 [ERROR] [HABApp.Rules                        ] -     next_run_local: DateTime = from_timestamp(self._next_run_base, local_tz)
2021-05-03 17:17:35.947 [ERROR] [HABApp.Rules                        ] - File "/opt/habapp/lib/python3.7/site-packages/pendulum/__init__.py", line 272, in from_timestamp
2021-05-03 17:17:35.947 [ERROR] [HABApp.Rules                        ] -     dt = _datetime.datetime.utcfromtimestamp(timestamp)
2021-05-03 17:17:35.947 [ERROR] [HABApp.Rules                        ] - 
2021-05-03 17:17:35.947 [ERROR] [HABApp.Rules                        ] - OverflowError: timestamp out of range for platform time_t
2021-05-03 17:17:35.953 [WARN ] [HABApp.Rules                        ] - Failed to load /etc/openhab/habapp/rules/RunEveryTest.py!
NickWaterton commented 3 years ago

@magnuslsjoberg

I have self.run.every(None,...) working now. I suspect the problem with your situation is something to do with the raspberry pi OS being 32 bit.

Not sure how to fix that though, as self.run.every(0,) isn't working either. I guess we just wait and see what @spacemanspiff2007 comes up with.

UPDATE: Looks like it's this:

Unix and POSIX-compliant systems implement the time_t type as a signed integer (typically 32 or 64 bits wide) which represents the number of seconds since the start of the Unix epoch: midnight UTC of January 1, 1970 (not counting leap seconds). Some systems correctly handle negative time values, while others do not. Systems using a 32-bit time_t type are susceptible to the Year 2038 problem.

The maximum timestamp on a 32 bit system is 2147483647 (ie 2038), the next_base given in your traceback is 3767538702.5874157, which is way too big to be a realistic timestamp.

now = 1620055055.592741
self._next_run_base = 3767538702.5874157
self._interval = 2147483647.0

From the traceback, it seems that self._interval gets added to now to give next_base and self._interval is MAX_INT Don't know why it does this, but it's obviously a problem when you try to convert it back to a datetime as a timestamp (which is probably expecting an int).

spacemanspiff2007 commented 3 years ago

Thanks, I forgot my credentials were in the file.

I always put them in a parameter file, that way I don't accidentally post them as a rule example. :wink:

The maximum timestamp on a 32 bit system is 2147483647 (ie 2038), the next_base given in your traceback is 3767538702.5874157, which is way too big to be a realistic timestamp.

I've already nailed the problem down to this, however I'll still have some digging to do and find a nice way to fix it

I guess we just wait and see what @spacemanspiff2007 comes up with.

I don't have much time the next couple of days, so in the meantime you can do

self.run.every(None if val <= 0 else val, interval)

or postpone the migration to HABApp 0.30 until we have further insight (I think I'll have something by Friday - Sunday).

I'll ping both of you once I have something. Thanks for reporting and the analysis!

magnuslsjoberg commented 3 years ago

@NickWaterton @spacemanspiff2007 Thank you both for the quick analysis!
To be honest, using timedelta(seconds=5) is not really any problem in my case. As a side note the old implementation of run_every(None,... actually had a small bug since it didn't run until the interval time had passed. I therefore had a number of run_soon just before each run_every. I no longer need that.

spacemanspiff2007 commented 3 years ago

@magnuslsjoberg I've updated the eascheduler to 0.1.2 and updated HABApp to use this. Would you mind updating from the dev branch and see if you issues disappear?

magnuslsjoberg commented 3 years ago

@spacemanspiff2007:

Sorry, I didn't have time to test the dev branch but 0.30.1 works! Thank you!

NickWaterton commented 3 years ago

I still get the error on 0. I’m on 0.30.1.

magnuslsjoberg commented 3 years ago

I realised I only tested with None. Haven’t tested with 0 yet…

On 9 May 2021, at 20:36, Nick Waterton @.***> wrote:

 I still get the error on 0. I’m on 0.30.1.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

spacemanspiff2007 commented 3 years ago

I still get the error on 0. I’m on 0.30.1.

Sorrry - I haven't had time to investigate that issue yet.

spacemanspiff2007 commented 3 years ago

@NickWaterton @magnuslsjoberg I've made changes to the dev branch. Together with the new eascheduler 0 is now an allowed value. Could you check and see if it's working as expected?

spacemanspiff2007 commented 2 years ago

This issue seems stale so I'm closing it.