Hi all,
I too had the need to get time_utils.py
and ephem_tod.py
working with OH3.
I did the changes which were needed to my best knowledge, but there may be still bugs.
It works fine for my usecases and the tests from time_utils-test.py
(which also needed some adaptions) pass.
I am adding the files here to the post - hopefully they can save someone a bit of work.
All the lines I changed are marked with comments at the end.
I am using @CrazyIvan359 Python stubs (see this post) and I highly recommend them. If you don’t want to use them, delete the lines starting with # improve typing and linting...
up to the next empty line.
time_utils.py
"""
Copyright June 25, 2020 Richard Koshak
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re
import traceback
from datetime import datetime, date, time, timedelta
from core.log import logging, LOG_PREFIX
from core.date import to_python_datetime, to_java_zoneddatetime
from core.jsr223 import scope
from java.time import ZonedDateTime
from java.time.temporal import ChronoUnit
# improve typing and linting as per
# https://github.com/CrazyIvan359/openhab-stubs/blob/master/Usage.md
import typing as t
if t.TYPE_CHECKING:
basestring = str
unicode = str
else:
basestring = basestring # type: ignore # pylint: disable=self-assigning-variable
unicode = unicode # type: ignore # pylint: disable=self-assigning-variable
duration_regex = re.compile(r'^((?P<days>[\.\d]+?)d)? *((?P<hours>[\.\d]+?)h)? *((?P<minutes>[\.\d]+?)m)? *((?P<seconds>[\.\d]+?)s)?$')
iso8601_regex = re.compile(r'^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$')
def parse_duration(time_str, log=logging.getLogger("{}.time_utils".format(LOG_PREFIX))):
"""Parse a time string e.g. (2h13m) into a timedelta object
https://stackoverflow.com/questions/4628122/how-to-construct-a-timedelta-object-from-a-simple-string
Arguments:
- time_str: A string identifying a duration. Use
- d: days
- h: hours
- m: minutes
- s: seconds
All options are optional but at least one needs to be supplied. Float
values are allowed (e.g. "1.5d" is the same as "1d12h"). Spaces
between each field is allowed. Examples:
- 1h 30m 45s
- 1h05s
- 55h 59m 12s
- log: optional, logger object for logging a warning if the passed in
string is not parsable. A "time_utils" logger will be used if not
supplied.
Returns:
A ``datetime.timedelta`` object representing the supplied time duration
or ``None`` if ``time_str`` cannot be parsed.
"""
parts = duration_regex.match(time_str)
if parts is None:
log.warn("Could not parse any time information from '{}'. Examples "
"of valid strings: '8h', '2d8h5m20s', '2m 4s'"
.format(time_str))
return None
else:
time_params = {name: float(param) for name, param in parts.groupdict().items() if param}
return timedelta(**time_params)
def delta_to_datetime(td):
"""Takes a Python timedelta Object and converts it to a ZonedDateTime from now.
Arguments:
- td: The Python datetime.timedelta Object
Returns:
A ZonedDateTime td from now.
"""
return (ZonedDateTime.now().plusDays(td.days)
.plusSeconds(td.seconds)
.plusNanos(td.microseconds//1000 * 1000000))
def parse_duration_to_datetime(time_str, log=logging.getLogger("{}.time_utils".format(LOG_PREFIX))):
"""Parses the passed in time string (see parse_duration) and returns a
ZonedDateTime that amount of time from now.
Arguments:
- time_str: A string identifying a duration. See parse_duration above
Returns:
A ZonedDateTime time_str from now
"""
return delta_to_datetime(parse_duration(time_str, log))
def is_iso8601(dt_str):
"""Returns True if dt_str conforms to ISO 8601
Arguments:
- dt_str: the String to check
Returns:
True if dt_str conforms to dt_str and False otherwise
"""
try:
if iso8601_regex.match(dt_str) is not None:
return True
except:
pass
return False
def to_datetime(when, log=logging.getLogger("{}.time_utils".format(LOG_PREFIX)), output = 'Java'):
"""Based on what type when is, converts when to a Python DateTime object.
Type:
- int: returns now.plusMillis(when)
- openHAB number type: returns now.plusMillis(when.intValue())
- ISO8601 string: DateTime(when)
- Duration definition: see parse_duration_to_datetime
- java ZonedDateTime
For python make sure the datetime object is not assigned to a variable when this function is called)
otherwise a java.time.sql object will be returned due to a bug in Jython
- Python datetime
- Python time: returns DateTime with today date and system timezone
Arguments:
- when: the Object to convert to a DateTime
- log: optional logger, when not supplied one is created for logging errors
- output: object returned as a string. If not specified returns a ZonedDateTime object
'Python': return datetime object
'Java': return a ZonedDateTime object
Returns:
- ZonedDateTime specified by when
- datetime specified by when if output = 'Python'
- ZonedDateTime specified by when if output = 'Java'
"""
log.debug('when is: ' + str(when) + ' output is ' + str(output))
dt_python = None
dt_java = None
try:
if isinstance(when, (str, unicode)):
if is_iso8601(when):
log.debug('when is iso8601: '+str(when))
dt_java = ZonedDateTime.parse(str(when))
else:
log.debug('when is duration: ' + str(when))
dt_python = datetime.now() + parse_duration(when, log)
elif isinstance(when, int):
log.debug('when is int: ' + str(when))
dt_java = ZonedDateTime.now().plus(when, ChronoUnit.MILLIS)
elif isinstance(when, (scope.DateTimeType)):
log.debug('when is DateTimeType: ' + str(when))
dt_java = when.getZonedDateTime()
elif isinstance(when, (scope.DecimalType, scope.PercentType, scope.QuantityType)):
log.debug('when is decimal, percent or quantity type: ' + str(when))
dt_python = datetime.now() + timedelta(milliseconds = when.intValue())
elif isinstance(when, datetime):
log.debug('when is datetime: ' + str(when))
dt_python = when
elif isinstance(when, ZonedDateTime):
log.debug('when is ZonedDateTime: ' + str(when))
dt_java = when
elif isinstance(when, time):
log.debug('when is python time object: ' + str(when))
dt_java = ZonedDateTime.now() \
.withHour(when.hour) \
.withMinute(when.minute) \
.withSecond(when.second) \
.withNano(when.microsecond * 1000) # nanos need to be set, otherwise they_ll be taken from the actual time
else:
log.warn('When is an unknown type {}'.format(type(when)))
return None
except:
log.error('Exception: {}'.format(traceback.format_exc()))
if output == 'Python':
log.debug('returning dt python')
return dt_python if dt_python is not None else to_python_datetime(dt_java)
elif output == 'Java':
log.debug("returning dt java")
return dt_java if dt_java is not None else to_java_zoneddatetime(dt_python)
elif output == 'Joda':
log.error("to_datetime trying to return dt joda - use output = 'Python' or output = 'Java' instead")
else:
log.error("to_datetime cannot output [{}]".format(output))
def to_today(when, log=logging.getLogger("{}.time_utils".format(LOG_PREFIX)), output='Java'):
"""Takes a when (see to_datetime) and updates the date to today.
Arguments:
- when : One of the types or formats supported by to_datetime
- log: optional logger, when not supplied one is created for logging errors
Returns:
- ZonedDateTime specified by when with today's date.
- datetime specified by when with today's date if output = 'Python'
- ZonedDateTime specified by when with today's date if output = 'Java'
"""
log.debug('output is: '+ str(output))
if output == 'Python':
dt = to_datetime(when, log=log, output = 'Python')
return datetime.combine(date.today(), dt.timetz())
elif output == 'Java':
dt = to_datetime(when, log=log, output = 'Java')
now = dt.now()
return (now.withHour(dt.getHour())
.withMinute(dt.getMinute())
.withSecond(dt.getSecond())
.withNano(dt.getNano()))
elif output == 'Joda':
log.error("to_today trying to return dt joda - use output = 'Python' or output = 'Java' instead")
else:
log.error("to_today cannot output [{}]".format(output))
time_utils-tests.py
from datetime import datetime, time
import community.time_utils
reload(community.time_utils)
from community.time_utils import to_today, to_datetime
from core.date import days_between, seconds_between, to_python_datetime, to_joda_datetime, to_java_zoneddatetime
from core.log import logging, LOG_PREFIX
# from org.joda.time import DateTime # changed
from java.time import ZonedDateTime, ZoneId, ZoneOffset
from java.time.temporal import ChronoUnit
# improve typing and linting as per
# https://github.com/CrazyIvan359/openhab-stubs/blob/master/Usage.md
import typing as t
if t.TYPE_CHECKING:
basestring = str
unicode = str
else:
basestring = basestring # type: ignore # pylint: disable=self-assigning-variable
unicode = unicode # type: ignore # pylint: disable=self-assigning-variable
log = logging.getLogger("{}.TEST.time_utils".format(LOG_PREFIX))
#To_today tests
today_time = to_today(time(23, 00, 00, 00))
today_datetime = to_today(datetime(2019, 10, 8, 23, 00, 00, 00))
today_ZonedDateTime = to_today(ZonedDateTime.of(2019, 11, 8, 23, 00, 00, 00, ZoneId.systemDefault()))
try:
log.info("start test to_today with different input and output Joda datetime")
#Check date was changed
assert days_between(ZonedDateTime.now(), today_time) == 0, "time object failed to change date for today"
assert days_between(ZonedDateTime.now(), today_datetime) == 0, "datetime object failed to change date for today"
assert days_between(ZonedDateTime.now(), today_ZonedDateTime) == 0, "ZonedDateTime object failed to change date for today"
#Check time wasn't changed
assert time(23, 00, 00, 00) == to_python_datetime(today_time).time(), "time object failed, time has changed"
# assert to_joda_datetime(datetime(2019, 10, 8, 23, 00, 00)).toLocalTime() == today_datetime.toLocalTime(), "datetime object failed, time has changed" # changed
# assert to_joda_datetime(ZonedDateTime.of(2019, 11, 8, 23, 00, 00, 00, ZoneId.systemDefault())).toLocalTime() == today_ZonedDateTime.toLocalTime() #changed
log.info("start test to_today with different input and output python datetime")
#Check date was changed
#cannot store python datetime in variable due to jython bug
assert days_between(ZonedDateTime.now(), \
to_today(time(23, 00, 00, 00), output= 'Python', log = log)) == 0, \
"time object failed to change date for today"
assert days_between(ZonedDateTime.now(), \
to_today(datetime(2019, 10, 8, 23, 00, 00), output= 'Python', log = log)) == 0, \
"datetime object failed to change date for today"
assert days_between(ZonedDateTime.now(), today_ZonedDateTime) == 0, \
"ZonedDateTime object failed to change date for today"
#Check time wasn't changed
assert time(22, 59, 59, 00) <= to_today(time(23,00,00, 00), output= 'Python', log = log).time() \
<= time(23, 00, 1, 00), \
"time object failed, time has changed"+str(today_time)
assert datetime(2019, 10, 8, 23, 00, 00).time() \
== to_today(datetime(2019, 10, 8, 23, 00, 00), output= 'Python', log = log).time(), \
"datetime object failed, time has changed"
assert to_python_datetime(ZonedDateTime.of(2019, 11, 8, 23, 00, 00, 00, ZoneId.systemDefault())).time() \
== to_today(ZonedDateTime.of(2019, 11, 8, 23, 00, 00, 00, ZoneId.systemDefault()), output= 'Python', log = log).time(), \
"ZonedDateTime object failed, time has changed"
log.info("start test to_today with different input and output Java ZonedDateTime")
today_time = to_today(time(23,00,00, 00), output='Java', log=log)
today_datetime = to_today(datetime(2019, 10, 8, 23, 00, 00), output='Java', log=log)
today_ZonedDateTime = to_today(ZonedDateTime.of(2019, 11, 8, 23, 00, 00, 00, ZoneId.systemDefault()), output='Java', log=log)
#Check date was changed
assert days_between(ZonedDateTime.now(), today_time) == 0, \
"time object failed to change date for today"
assert days_between(ZonedDateTime.now(), today_datetime) == 0, \
"datetime object failed to change date for today"
assert days_between(ZonedDateTime.now(), today_ZonedDateTime) == 0, \
"ZonedDateTime object failed to change date for today"
#Check time wasn't changed
assert time(22, 59, 59, 500000) <= to_python_datetime(today_time).time() <= time(23, 00, 1, 00), \
"time object failed, time has changed"
assert to_java_zoneddatetime(datetime(2019, 10, 8, 22, 59, 59,0)).toLocalTime() \
<= today_datetime.toLocalTime() <= \
to_java_zoneddatetime(datetime(2019, 10, 8, 23, 00, 1, 00)).toLocalTime(), \
"datetime object failed, time has changed {} {}" \
.format(str(to_java_zoneddatetime(datetime(2019, 10, 8, 23, 00, 00)).toLocalTime()), \
str(today_datetime.toLocalTime()))
assert ZonedDateTime.of(2019, 11, 8, 22, 59, 59, 500000, ZoneId.systemDefault()).toLocalTime() \
<= today_ZonedDateTime.toLocalTime()<= \
ZonedDateTime.of(2019, 11, 8, 23, 00, 1, 00, ZoneId.systemDefault()).toLocalTime() \
, "ZonedDateTime object failed, time has changed {} {}" \
.format(str(ZonedDateTime.of(2019, 11, 8, 23, 00, 00, 00, ZoneId.systemDefault()).toLocalTime()), \
str(today_ZonedDateTime.toLocalTime()))
#Test other format
test_dict={'integer: ': int(5000),
'duration: ': "5s",
'Decimal type: ': DecimalType(5000),
#'Percent type: ': PercentType(100),
'Quantity Type: ': QuantityType('5000ms'),
'ISO 8601 format': ZonedDateTime.now(ZoneOffset.ofHours(2)).plusSeconds(5).toString() # changed
}
#Test other format to Java # changed - used to be "to Joda..."
for keys in test_dict:
log.info("Checking " + keys + " to Java DateTime") # changed
assert abs(seconds_between(ZonedDateTime.now().plus(5000, ChronoUnit.MILLIS),
to_datetime(test_dict[keys], log = log))) < 1, \
"failed to return a datetime with offset of {} from {}" \
.format(str(test_dict[keys]),str(keys))
#Test other format to python
test_dict['ISO 8601 format'] = ZonedDateTime.now(ZoneOffset.ofHours(2)).plusSeconds(5).toString() # changed
for keys in test_dict:
log.info("Checking " + keys +" to Python datetime")
assert abs(seconds_between(ZonedDateTime.now().plus(5000, ChronoUnit.MILLIS),
to_datetime(test_dict[keys], output='Python', log = log))) < 1, \
"failed to return a datetime with offset of {} from {}" \
.format(str(test_dict[keys]),str(keys))
#Test other format to Java
test_dict['ISO 8601 format'] = ZonedDateTime.now(ZoneOffset.ofHours(2)).plusSeconds(5).toString() # changed
for keys in test_dict:
log.info("Checking " + keys +" to Java ZonedDateTime")
assert abs(seconds_between(ZonedDateTime.now().plus(5000, ChronoUnit.MILLIS),
to_datetime(test_dict[keys], output='Java', log = log))) < 1, \
"failed to return a ZonedDateTime with offset of {} from {}" \
.format(str(test_dict[keys]),str(keys))
except AssertionError:
import traceback
log.error("Exception: {}".format(traceback.format_exc()))
else:
log.info("Test passed!")
ephem_tod.py
"""
Copyright June 30, 2020 Richard Koshak
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from core.metadata import get_metadata, get_key_value, get_value
from core.actions import Ephemeris
from core.utils import send_command_if_different
from core.log import log_traceback, logging, LOG_PREFIX
from java.time import ZonedDateTime
from community.time_utils import to_today, to_datetime
from community.timer_mgr import TimerMgr
from community.rules_utils import create_simple_rule, delete_rule, load_rule_with_metadata
# improve typing and linting as per
# https://github.com/CrazyIvan359/openhab-stubs/blob/master/Usage.md
import typing as t
if t.TYPE_CHECKING: # imports used only for type hints
from core.jsr223.scope import events, items, UnDefType, DateTimeType
# Name of the Item to trigger reloading of the time of day rule.
ETOD_RELOAD_ITEM = "Reload_ETOD"
# Create the time of day state Item if it doesn't exist.
ETOD_ITEM = "TimeOfDay"
if ETOD_ITEM not in items:
from core.items import add_item
add_item(ETOD_ITEM, item_type="String")
# Metadata name space.
NAMESPACE = "etod"
# Timers that run at time of day transitions.
timers = TimerMgr()
# Logger to use before
log = logging.getLogger("{}.Ephemeris Time of Day".format(LOG_PREFIX))
@log_traceback
def check_config(i, log):
"""Verifies that all the required elements are present for an etod metadata."""
cfg = get_metadata(i, NAMESPACE)
if not cfg:
log.error("Item {} does not have {} metadata".format(i, NAMESPACE))
return None
if not cfg.value or cfg.value == "":
log.error("Item {} does not have a value".format(i))
return None
day_type = cfg.configuration["type"]
if not day_type:
log.error("Item {} does not have a type".format(i))
return None
if day_type == "dayset" and not cfg.configuration["set"]:
log.error("Item {} is of type dayset but doesn't have a set".format(i))
return None
elif day_type == "custom" and not cfg.configuration["file"]:
log.error("Item {} is of type custom but does't have a file".format(i))
return None
return cfg
@log_traceback
def get_times():
"""Gets the list of Items that define the start times for today. It uses
Ephemeris to determine which set of Items to select. The hierarchy is:
- custom: custom defined holidays
- holiday: default holidays
- dayset: custom defined dayset
- weekend: weekend as defined in Ephemeris
- weekday: not weekend days
- default: used when no other day type is detected for today
Returns:
- a list of names for DateTime Items; None if no valid start times were
found.
"""
def cond(lst, cond):
return [i for i in lst if cond(i)]
def types(type):
return [i for i in items if get_key_value(i, NAMESPACE, "type") == type]
# Get all of the etod Items that are valid for today.
start_times = {'default': types("default"),
'weekday': types("weekday") if not Ephemeris.isWeekend() else [],
'weekend': types("weekend") if Ephemeris.isWeekend() else [],
'dayset': cond(types('dayset'),
lambda i: Ephemeris.isInDayset(get_key_value(i, NAMESPACE, "set"))),
'holiday': types('holiday') if Ephemeris.isBankHoliday() else [], # changed to simpler way of getting holidays
'custom': cond(types('custom'),
lambda i: Ephemeris.isBankHoliday(0, get_key_value(i, NAMESPACE, "file")))}
# Determins which start time Items to use according to the hierarchy.
day_type = None
if start_times['custom']:
day_type = 'custom'
elif start_times['holiday']:
day_type = 'holiday'
elif start_times['dayset']:
day_type = 'dayset'
elif start_times['weekend']:
day_type = 'weekend'
elif start_times['weekday']:
day_type = 'weekday'
elif start_times['default']:
day_type = 'default'
log.info("Today is a {} day, there are {} time periods today.".format(day_type, len(start_times[day_type])))
return start_times[day_type] if day_type else None
@log_traceback
def etod_transition(state):
"""Called from the timers, transitions to the next time of day.
Arguments:
- state: the state to transition into
"""
log.info("Transitioning Time of Day from {} to {}"
.format(items[ETOD_ITEM], state))
events.sendCommand(ETOD_ITEM, state)
@log_traceback
def create_timers(start_times):
"""Creates Timers to transition the time of day based on the passed in list
of DateTime Item names. If an Item is dated with yesterday, the Item is
updated to today. The ETOD_ITEM is commanded to the current time of day if
it's not already the correct state.
Arguments:
- start_times: list of names for DateTime Items containing the start
times for each time period
"""
now = ZonedDateTime.now() # changed as DateTime is not available in OH3
most_recent_time = now.minusDays(1)
most_recent_state = items[ETOD_ITEM]
for time in start_times:
item_time = to_datetime(items[time]) # changed as DateTime is not available in OH3
trigger_time = to_today(items[time])
# Update the Item with today's date if it was for yesterday.
if item_time.isBefore(trigger_time):
log.debug("Item {} is yesterday, updating to today".format(time))
events.postUpdate(time, str(DateTimeType(trigger_time))) # changed as DateTime is not available in OH3
# Get the etod state from the metadata.
state = get_value(time, NAMESPACE)
# If it's in the past but after most_recent, update most_recent.
if trigger_time.isBefore(now) and trigger_time.isAfter(most_recent_time):
log.debug("NOW: {} start time {} is in the past but after {}"
.format(state, trigger_time, most_recent_time))
most_recent_time = trigger_time
most_recent_state = get_value(time, NAMESPACE)
# If it's in the future, schedule a Timer.
elif trigger_time.isAfter(now):
log.debug("FUTURE: {} Scheduleing Timer for {}"
.format(state, trigger_time))
timers.check(state, trigger_time,
function=lambda st=state: etod_transition(st))
# If it's in the past but not after most_recent_time we can ignore it.
else:
log.debug("PAST: {} start time of {} is before now {} and before {}"
.format(state, trigger_time, now, most_recent_time))
log.info("Created {} timers.".format(len(timers.timers)))
log.info("The current time of day is {}".format(most_recent_state))
send_command_if_different(ETOD_ITEM, most_recent_state)
def ephem_tod(event):
"""Rule to recalculate the times of day for today. It triggers at system
start, two minutes after midnight (to give Astro a chance to update the
times for today), when ETOD_TRIGGER_ITEM (default is CalculateETOD) receives
an ON command, or when any of the Items with etod metadata changes.
"""
log.info("Recalculating time of day")
# Get the start times.
start_times = get_times()
if not start_times:
log.error("No start times found! Cannot run the rule!")
return
# If any are NULL, kick off the init rule.
null_items = [i for i in start_times if isinstance(items[i], UnDefType)]
if null_items and "InitItems" in items:
log.warn("The following Items are are NULL/UNDEF, kicking off "
"initialization using item_init: {}"
.format(null_items))
events.sendCommand("InitItems", "ON")
from time import sleep
sleep(5)
# Check to see if we still have NULL/UNDEF Items.
null_items = [i for i in start_times if isinstance(items[i], UnDefType)]
if null_items:
log.error("The following Items are still NULL/UNDEF, "
"cannot create Time of Day timers: {}"
.format(null_items))
return
# Cancel existing Items and then generate all the timers for today.
timers.cancel_all()
create_timers(start_times)
# Create a timer to run this rule again a little after midnight. Work around
# to deal with the fact that cron triggers do not appear to be workind.
now = ZonedDateTime.now() # changed as DateTime is not available in OH3
reload_time = now.withHour(0).withMinute(2).withSecond(0).withNano(0) # changed as DateTime is not available in OH3
if reload_time.isBefore(now):
reload_time = reload_time.plusDays(1)
log.info("Creating reload timer for {}".format(reload_time))
timers.check("etod_reload", reload_time, function=lambda: ephem_tod(None))
@log_traceback
def load_etod(event):
"""Called at startup or when the Reload Ephemeris Time of Day rule is
triggered, deletes and recreates the Ephemeris Time of Day rule. Should be
called at startup and when the metadata is added to or removed from Items.
"""
# Remove the existing rule if it exists.
if not delete_rule(ephem_tod, log):
log.error("Failed to delete rule!")
return None
# Generate the rule triggers with the latest metadata configs.
etod_items = load_rule_with_metadata(NAMESPACE, check_config, "changed",
"Ephemeris Time of Day", ephem_tod,
log,
description=("Creates the timers that "
"drive the {} state"
"machine".format(ETOD_ITEM)),
tags=["openhab-rules-tools","etod"])
if etod_items:
for i in [i for i in timers.timers if not i in etod_items]:
timers.cancel(i)
# Generate the timers now.
ephem_tod(None)
@log_traceback
def scriptLoaded(*args):
"""Create the Ephemeris Time of Day rule."""
delete_rule(ephem_tod, log)
if create_simple_rule(ETOD_RELOAD_ITEM, "Reload Ephemeris Time of Day",
load_etod, log,
description=("Regenerates the Ephemeris Time of Day rule using the"
" latest {0} metadata. Run after adding or removing any"
" {0} metadata to/from and Item."
.format(NAMESPACE)),
tags=["openhab-rules-tools","etod"]):
load_etod(None)
@log_traceback
def scriptUnloaded():
"""Cancel all Timers at unload to avoid errors in the log and removes the
rules.
"""
timers.cancel_all()
delete_rule(ephem_tod, log)
delete_rule(load_etod, log)
@rlkoshak : I do not feel confident enough, to put in a pull request to your original repo. Apart from possible errors, I did just change stuff to make it work with OH3. I suspect there is a way of getting the code to work for OH2 and OH3 and I think this approach would be preferable.
But feel free to take the files or parts into your codebase if that is of any help.
Cheers,
Bastian