class AgoScheduler(agoclient.AgoApp): def __init__(self): agoclient.AgoApp.__init__(self) self.weekdayno = None self.weekday = None self.nexttime = None self.next_item = None self.groups = None self.scheduler = None self.scenario_controllerUUID = None self.mapfile = None self.print_schedule = False def event_handler(self, subject, content): """event handler - Processes incoming events and looks if they are of a relevant kind - For now, it's only time event that are of interest """ self.log.trace("event_handler start") if "event.environment.timechanged" in subject: self.log.trace("subject=%s content=%s", subject, content) js = json.loads(str(content).replace("u\'", '\"').replace("\'", '\"')) # TODO: check if replacement is necessary t_now = self.scheduler.now() self.log.trace("now={} - waiting for {}".format(t_now, self.nexttime)) if self.nexttime is not None and t_now == self.nexttime: while True: # Loop through all items with same time self.log.debug("Action to be triggered: {}".format(self.next_item)) try: self.send_message(self.next_item) except NameError as e: self.log.error("Oops, could not send message. Msg={}".format(e)) self.next_item = self.scheduler.get_next() if self.next_item is None: self.nexttime = None break if t_now != self.next_item["time"]: self.nexttime = self.next_item["time"] break if self.nexttime is not None: self.log.debug("Next item scheduled for {}".format(self.nexttime)) else: self.log.debug("No more scheduled items for today") if self.weekdayno != int(js["weekday"]): # TODO: Check config if a new schedule file is to be loaded self.weekdayno = int(js["weekday"]) self.new_day(self.weekdayno) self.next_item = self.scheduler.get_first("00:00") if self.next_item is not None: self.nexttime = self.next_item["time"] else: self.nexttime = None self.log.debug("First item scheduled for {}".format(self.nexttime)) # TODO: Event handler for "reload" event def device_msg(self, uuid, action, level=None): content = {"uuid": uuid, # TODO: Check unicode encoded strings "action": action} if action == "setlevel": content["level"] = level elif action == "fade": pass if action in {"on", "off", "setlevel"}: msg = "event.device.statechanged" self.log.debug("About to set device content={}".format(content)) self.connection.send_message(msg, content) def send_message(self, item): self.log.debug(("In send_message. item={}".format(item))) if item["device"] is not None: if item["action"] in {"on", "off"}: self.device_msg(item["device"], item["action"]) elif item["action"] in {"setlevel"}: self.device_msg(item["device"], item["action"], item["level"]) # todo: aDD ACTION SPECIFIC FIELDS if item["scenario"] is not None: content = {"uuid": item["scenario"], "command": "run"} self.log.debug("About to execute scenario content={}".format(content)) self.connection.send_message(None, content) if item["group"] is not None: self.log.info(("About to send message to device group {}".format(item["group"]))) grp = self.groups.find(item["group"]) if grp is not None: for dev in grp.devices: if dev is not None: if item["action"] in {"on", "off"}: self.device_msg(dev, item["action"]) elif item["action"] in {"setlevel"}: self.device_msg(dev, item["action"], item["level"]) def get_scenario_controller_uuid(self): """Get UUID for the scenario controller""" inventory = self.connection.get_inventory() for uuid in inventory['devices']: if inventory['devices'][uuid]['devicetype'] == 'scenariocontroller': self.log.debug("Found Scenario Controller {}".format(uuid)) return uuid return None def new_day(self, weekdayno): """ Load schedules for a new day @param weekdayno: 1-7 @return: nothing """ self.weekday = weekday = all_days[weekdayno - 1] self.log.trace("new_day() weekday={}".format(weekday)) no_activities = self.scheduler.new_day(weekday) self.next_item = self.scheduler.get_first("00:00") self.weekdayno = weekdayno if self.print_schedule: self.scheduler.schedules.list_full_day() self.log.info("New day: {}. Loaded {} schedule items for today.".format(weekday, no_activities)) def setup_app(self): self.log.trace("setup_app start") self.connection.add_event_handler(self.event_handler) app = "scheduler" self.scenario_controllerUUID = self.get_scenario_controller_uuid() if self.scenario_controllerUUID is None: self.log.error("Scenario Controller not found.") mapfile = self.get_config_option('schedule', None, section=app, app=app) self.print_schedule = self.get_config_option('print_daily_schedule', "Yes", section=app, app=app) == "Yes" self.log.info("Print daily schedule? {}".format(self.print_schedule)) if mapfile is None: self.mapfile = None self.log.error("No Schedule file found in config file. Idling.") # TODO: Follow up - set flag to avoid processing! else: self.mapfile = agoclient.config.CONFDIR + '/conf.d/' + mapfile self.log.info("Reading schedules from {}".format(self.mapfile)) self.groups = Groups(agoclient.config.CONFDIR + '/conf.d/' + 'groups.json') # TODO: Change to proper file lat = self.get_config_option("lat", "47.07", "system") lon = self.get_config_option("lon", "15.42", "system") self.log.info("Getting position. lat={} lon={}".format(lat, lon)) self.scheduler = Scheduler(lat=float(lat), lon=float(lon), log=self.log, groups=self.groups) self.scheduler.parse_conf_file(self.mapfile) dl, day_no = self.scheduler.get_weekday() self.scheduler.weekday = dl self.log.trace("day_no={}".format(day_no)) self.new_day(day_no) self.next_item = self.scheduler.get_first(self.scheduler.now()) if self.next_item is not None: self.nexttime = self.next_item["time"] else: self.nexttime = None self.log.debug("First item scheduled for {}".format(self.nexttime))
class SchedulerTest1(unittest.TestCase): def setUp(self): self.groups = Groups("groups.json") lat = 56.045000000000002 lon = 12.718400000000001 self.s = Scheduler(lat=lat, lon=lon, groups=self.groups) self.s.parse_conf_file("schedule.json") # Prepped schedule with 11 schedule items and 2 rules def test1_start_at_midnight(self): """ This is a normal situation, where the schedules are reloaded when it's 00:00/a new day """ no = self.s.new_day("mo") self.assertEqual(no, 9) # 9 items expected for Monday # Start at midnight item = self.s.get_first() self.assertEqual(item["time"], "06:00") # Monday, first item should be 06:00 self.assertEqual(item["scenario"], "Plant lights on") # Start later in the day, search for exact match in time item = self.s.get_first("08:10") self.assertEqual(item["time"], "08:10") self.assertEqual(item["device"], "2222-2222") def test2_start_in_evening(self): """ Start later in the day. The search is inexact, expecting a schedule item at a later point-in-time """ self.s.new_day("mo") item = self.s.get_first("21:35") self.assertEqual(item["time"], "21:40") item = self.s.get_next() self.assertEqual(item["time"], "22:00") def test2b_start_with_errors(self): """ Start with invalid days. 0 scheduled items expected as result """ no = self.s.new_day("xx") # Needs to be "mo", "tu", etc. self.assertEqual(no, 0) no = self.s.new_day(9) # Needs to be 1..7 self.assertEqual(no, 0) def test3_new_day(self): """ Normal shift of day. """ self.s.new_day("mo") self.s.schedules.list_full_day() # Get last item for Monday item = self.s.get_first("23:00") self.assertEqual(item["time"], "23:00") # This should fail, there are no more items self.assertIsNone(self.s.get_next()) # It's midnight, let's shift over to a new day no = self.s.new_day("tu") self.assertEqual(no, 11) # 11 items expected for Tuesday #self.s.schedules.list_full_day() item = self.s.get_next() self.assertEqual(item["time"], "06:00") # Tuesday, first item should be 06:00 item = self.s.get_next() self.assertEqual(item["time"], "06:00") # Tuesday, second item should also be 06:00 def test3b_new_day(self): """ Normal shift of day. """ self.s.new_day("mo") # Get last item for Monday item = self.s.get_first("23:00") self.assertEqual(item["time"], "23:00") # This should fail, there are no more items self.assertIsNone(self.s.get_next()) # It's midnight, let's shift over to a new day no = self.s.new_day("tu") self.assertEqual(no, 11) # 11 items expected for Tuesday item = self.s.get_next() self.assertEqual(item["time"], "06:00") # Tuesday, first item should be 06:00 item = self.s.get_next() self.assertEqual(item["time"], "06:00") # Tuesday, second item should also be 06:00 def test4_execute_rule(self): """ Locate and execute a rule, use day index to locate Monday """ self.s.new_day(1) item = self.s.get_first("00:00") self.assertEqual(item["time"], "06:00") r = item["rule"] self.assertFalse(r.execute()) def test5_get_current_weekday(self): """ Get current weekday. Cannot be asserted """ dl, d = self.s.get_weekday() print ("dl={}, d={}".format(dl, d)) def test6_list_items(self): """ List all items for this day """ self.s.new_day("mo") item = self.s.schedules.list_full_day(now="04:01") # Before first item = self.s.schedules.list_full_day(now="09:00") # In between item = self.s.schedules.list_full_day(now="23:01") # After last item = self.s.schedules.list_full_day() # Cannot be validated easily self.s.new_day("fr") item = self.s.schedules.list_full_day(now="00:00") # Should contain 2 sunset/sunrise based times def test7_double_assign(self): self.weekday = weekday = all_days[2-1] self.assertEqual(self.weekday, "tu") self.assertEqual(weekday, "tu") def test8a_random_time(self): self.s.new_day("mo") item = self.s.get_first("00:00") print("random times") for i in range(1, 10): time = self.s.schedules.schedules[0].get_random_time("08:00", 5) self.assertIsNotNone(time) print("{}".format(time)) def test8b_random_time(self): self.s.random_minutes = 10 self.s.new_day("mo") item = self.s.get_first("00:00") item = self.s.schedules.list_full_day(now="08:00") def tearDown(self): pass