2 from datetime
import datetime, timedelta, date
6 from threading
import Thread
8 _epoch = datetime.utcfromtimestamp(0).replace(tzinfo=tzutc())
15 Converts a datetime object to seconds since epoch
18 return delta.total_seconds()
22 Coverts a datetime.time object to seconds.
24 return (time.hour * 60 * 60) + (time.minute * 60) + (time.second) + (time.microsecond/1000.0)
29 """ Seems to be a bug in datetime when comparing localtz dates, so do this instead. """
32 elif t1.hour == t2.hour:
33 if t1.minute > t2.minute:
35 elif t1.minute == t2.minute:
36 if t1.second > t2.second:
38 elif t1.second == t2.second:
39 return t1.microsecond > t2.microsecond
44 """ Seems to be a bug in datetime when comparing localtz dates, so do this instead. """
47 elif t1.hour == t2.hour:
48 if t1.minute < t2.minute:
50 elif t1.minute == t2.minute:
51 if t1.second < t2.second:
53 elif t1.second == t2.second:
54 return t1.microsecond < t2.microsecond
61 start (datetime.time): start time of window
62 end (datetime.time): end time of window
64 datetime.timedelta: The delta of the window between start and end
66 sdt = datetime.combine(date.today(), start)
68 enddt = datetime.combine(date.today(), end)
70 enddt = datetime.combine(date.today(), end) + timedelta(days=1)
77 """ An object for setting up the daily schedule of a robot.
79 daily_start (datetime.time): The time of day when all tasks can start, local time.
80 daily_end (datetime.time): The time of day when all tasks should end start, local time.
85 if daily_start.tzinfo
is None:
88 if daily_end.tzinfo
is None:
97 Repeat the given tasks a number of times during the day.
103 def repeat_every_delta(self, tasks, delta=timedelta(hours=1), times=1, start_time=
None, duration=
None):
105 Repeat the given tasks every delta, a given number of times from the start time for the total given duration.
107 tasks: The list of tasks to repeat
108 delta (datetime.timedelta): The delta for the repetition (e.g. if this is one hour then the tasks repeat every hour, with each task having an hour time window for execution)
109 times (int): The number of times to repeat the tasks in each window
110 start_time (datetime.time): The time of day when the repetition should start.
111 duration (datetime.timedelta): The total length of time that should not be exceeded by the complete list of repetitions
113 if start_time
is None:
119 window_start = datetime.combine(date.today(), start_time)
120 window_end = window_start + delta
122 routine_end = window_start + duration
124 while window_end <= routine_end:
126 if window_start == window_end:
129 print '%s.%s - %s.%s' % (window_start.hour, window_start.minute, window_end.hour, window_end.minute)
130 self.
repeat_every(tasks, window_start.timetz(), delta, times)
132 window_start = window_end
133 window_end = window_start + delta
138 Repeat the given tasks x times every n minutes
146 Repeat the given tasks x times every n hours
153 Repeat the given task a number of times during the given time window.
155 daily_start (datetime.time): The time of day when the given tasks can start, local time.
156 daily_duration (datetime.timedelta): The duration of time during which the tasks can be executed
157 times: the number of times to execute the tasks in the window
161 if not isinstance(tasks, list):
165 raise Exception(
'Provided daily start %s is less than overall daily start %s' % (daily_start, self.
daily_start))
168 daily_end = datetime.combine(date.today(), daily_start) + daily_duration
172 if daily_end > overall_end:
173 raise Exception(
'Provided daily end %s is greater than overall daily end %s for tasks %s' % (daily_end, overall_end, tasks))
177 self.
routine_tasks += [(tasks, (daily_start, daily_duration))] * times
186 """ An object for running the daily schedule of a robot.
188 daily_start (datetime.time): The time of day when all tasks can start, local time.
189 daily_end (datetime.time): The time of day when all tasks should end start, local time.
190 pre_start_window (datetime.timedelta): The duration before a task's start that it should be passed to the scheduler. Defaults to 1 hour.
192 def __init__(self, daily_start, daily_end, add_tasks_srv, pre_start_window=timedelta(hours=0.25), day_start_cb=
None, day_end_cb=
None, tasks_allowed_fn=
None):
193 super(DailyRoutineRunner, self).
__init__()
196 if daily_start.tzinfo
is None:
199 if daily_end.tzinfo
is None:
206 rostime_now = rospy.get_rostime()
207 now = datetime.fromtimestamp(rostime_now.to_sec(), tzlocal()).time()
224 if self.current_routine_start.tzinfo
is None:
227 if self.current_routine_end.tzinfo
is None:
234 if tasks_allowed_fn
is None:
244 day_today = datetime.fromtimestamp(rospy.get_rostime().to_sec(), tz=tzlocal())
246 self.all_days.append(day_today.strftime(
"%A"))
247 day_today += timedelta(days=1)
256 """ Add a day of the week, e.g. Saturday, on which the routing should not be run """
258 raise Exception(
'Day name %s not allowed. Must be one of %s' % (day_name, self.
all_days))
259 self.days_off.append(day_name)
262 """ Add a datetime.date on which the robot should not work. """
263 self.dates_off.append(date)
266 datetime_today = datetime.fromtimestamp(rospy.get_rostime().to_sec(), tz=tzlocal())
267 day_today = datetime_today.strftime(
"%A")
268 date_today = datetime_today.date()
276 """ Runs a loop which triggers the start and end of day callbacks """
278 loop_delay = rospy.Duration(1)
280 while not rospy.is_shutdown():
283 now = datetime.fromtimestamp(rospy.get_rostime().to_sec(), tz=tzlocal())
287 while now < pre_delayed_start
and not rospy.is_shutdown():
288 rospy.sleep(loop_delay)
289 now = datetime.fromtimestamp(rospy.get_rostime().to_sec(), tz=tzlocal())
295 rospy.sleep(loop_delay)
296 now = datetime.fromtimestamp(rospy.get_rostime().to_sec(), tz=tzlocal())
298 if self.
day_start_cb is not None and not rospy.is_shutdown():
299 rospy.loginfo(
'triggering day start cb at %s' % now)
303 rospy.sleep(loop_delay)
304 now = datetime.fromtimestamp(rospy.get_rostime().to_sec(), tz=tzlocal())
306 if self.
day_end_cb is not None and not rospy.is_shutdown():
307 rospy.loginfo(
'triggering day end cb at %s' % now)
316 Adds a task to the daily routine.
318 routines: A list of tuples where each tuple is (task_list, window) where task_list is a list of task and window is a tuple (start, duration) representing a time window in which to execute those tasks each day.
325 for task_tuple
in routines:
328 daily_start = task_tuple[1][0]
329 daily_duration = task_tuple[1][1]
331 for task
in task_tuple[0]:
334 routine = (daily_start, daily_duration, task)
335 self.routine_tasks.append(routine)
336 new_routine.append(routine)
346 Should be called each new day, in advance of the overall daily start time.
362 rospy.loginfo(
'Scheduling %s tasks now and %s later' % (len(schedule_now), len(schedule_later)))
369 now = rospy.get_rostime()
384 if now + task.max_duration > task.end_before:
387 raise RoutineException(
'%s is too late to schedule task %s' % (now.secs, task))
389 rospy.logdebug(
'Ignoring task for today')
393 schedule_now.append(task)
395 schedule_later.append(task)
397 return schedule_now, schedule_later
402 Delays call to the scheduer until the first of these needs to start, then reruns check for scheduling
413 if task.start_after < min_start_date:
419 min_start_date = task.start_after
426 now = rospy.get_rostime()
432 delay = min_start_date - now
434 assert delay.secs > 0
443 rospy.loginfo(
'Delaying %s tasks for %s secs' % (len(tasks), delay.secs))
447 target = rospy.get_rostime() + delay
449 while rospy.get_rostime() < target
and not rospy.is_shutdown():
452 if not rospy.is_shutdown():
455 Thread(target=_check_tasks).
start()
464 if len(allowed_tasks) != len(tasks):
465 rospy.loginfo(
'Provided function prevented %s of %s tasks being send to the scheduler' % (len(tasks) - len(allowed_tasks), len(tasks)))
468 rospy.loginfo(
'Sending %s tasks to the scheduler' % (len(allowed_tasks)))
471 rospy.loginfo(
'Taking the day off')
476 Create a copy of the given task with start and end times instantiated from input variables relative to the start date provided.
478 instantiated_task = copy(task)
479 release_date = datetime.combine(start_of_day.date(), daily_start)
480 end_date = release_date + daily_duration
481 instantiated_task.start_after = rospy.Time(
unix_time(release_date))
482 instantiated_task.end_before = rospy.Time(
unix_time(end_date))
486 return instantiated_task
def time_greater_than(t1, t2)
def _schedule_tasks(self, tasks)
def _queue_for_scheduling
def _instantiate_for_day(self, start_of_day, daily_start, daily_duration, task)
def time_less_than(t1, t2)
def delta_between(start, end)
def add_day_off(self, day_name)
def _delay_tasks(self, tasks)
def add_date_off(self, date)
def add_tasks(self, routines)
def get_routine_tasks(self)
def _start_and_end_day(self)
def _delay_scheduling(self, tasks, delay)
def _tasks_allowed_fn(self, task)
def __init__(self, daily_start, daily_end)
def _instantiate_tasks_for_today(self, routine_tasks)