def test_except_invalid_action_function(self): spec = clock.TimeSpec.from_dict({"tz": "UTC"}) spec_id = uuid.uuid4() # None reference should fail the assert self.assertRaises(AssertionError, self.clock.add_timespec_action, id=spec_id, action_function=None, timespec=spec, nowtime=nowutc()) # Non-async function should fail the assert def _not_async_function(): pass self.assertRaises(AssertionError, self.clock.add_timespec_action, id=spec_id, action_function=_not_async_function, timespec=spec, nowtime=nowutc()) # This should pass async def _is_async_function(): pass try: self.clock.add_timespec_action(id=spec_id, action_function=_is_async_function, timespec=spec, nowtime=nowutc()) except AssertionError as e: self.fail(str(e))
def evaluate(self, engine) -> bool: now = helpers.nowutc() # datetime.datetime if self._before_offset is None: self._before_offset = datetime.timedelta(0) # datetime.timedelta if self._after_offset is None: self._after_offset = datetime.timedelta(0) # datetime.timedelta sun_state = engine.states.get_entity_state(self._entity_id) next_rising = dateutil.parser.parse( sun_state.attributes.get('next_rising')) # datetime.datetime next_setting = dateutil.parser.parse( sun_state.attributes.get('next_setting')) # datetime.datetime # False if now is already after the specified time before sunrise if self._before == 'sunrise' and now > (next_rising + self._before_offset): return False # False if now is already after the specified time before sunset elif self._before == 'sunset' and now > (next_setting + self._before_offset): return False # False if now is still before the specified time after sunrise if self._after == 'sunrise' and now < (next_rising + self._after_offset): return False # False if now is still before the specified timea fter sunset elif self._after == 'sunset' and now < (next_setting + self._after_offset): return False return True
def test_remove_action(self): """Tests the removal of a TimeSpec from the timeline """ async def _noop(): pass spec = clock.TimeSpec.from_dict({"tz": "UTC"}) spec_id = uuid.uuid4() self.clock.add_timespec_action(id=spec_id, action_function=_noop, timespec=spec, nowtime=nowutc()) # Add the TimeSpecAction self.assertEqual(len(self.clock.timeline), 1) actual_id = self.clock.timeline[0].actions[0].id print("Expected: {}, Actual: {}".format(spec_id, actual_id)) self.assertEqual(actual_id, spec_id) # Remove the TimeSpecAction self.clock.remove_timespec_action(spec_id) print(self.clock._format_timeline()) self.assertEqual( len(self.clock.timeline), 0, msg="Expecing an empty timeline, but it found non-empty")
def test_action_condition(self): rule_id = "action_condition" cfg = config.EngineConfig() cfg.json_rules_dir = self.test_rules_dir self._setup_engine(config_obj=cfg) # Set the simulation time self.sim_time = helpers.nowutc() print("Loading the rule") self.loop.run_until_complete( self._load_one_rule(rule_id, self.test_rules_dir)) print("Setting pre-state") self.loop.run_until_complete( self._set_and_verify_entity_state( "input_boolean.action_light", "off", "on", )) self.engine_obj._websocket.clear() print("Triggering rule") self.loop.run_until_complete( self._set_and_verify_entity_state( "input_boolean.test", "on", "off", )) # Check that the correct service was called self._verify_websocket_service_call(0, "input_boolean.action_light", "turn_on") self.engine_obj._websocket.clear()
def event_state_changed(id, entity_id: str, old_state: str, new_state: str): now = helpers.nowutc() event = { "id": id, "type": "event", "event": { "event_type": "state_changed", "data": { "entity_id": entity_id, "old_state": { "entity_id": entity_id, "state": old_state, "attributes": {}, "last_changed": now.isoformat(), "last_updated": now.isoformat() }, "new_state": { "entity_id": entity_id, "state": new_state, "attributes": {}, "last_changed": now.isoformat(), "last_updated": now.isoformat() } }, "origin": "LOCAL", "time_fired": now.isoformat() } } return event
async def _async_run(self): """Override of Fiber base class. Called by async Fiber.async_run() """ # Start the _tick() loop while self._running: await self._async_tick(helpers.nowutc()) # Execute tick await asyncio.sleep(TICK_INTERVAL_SECONDS) # Sleep until next tick
def add(self, logtype: str, logentry: dict): if self._max_logs > 0: self._log.append({ "ts": str(helpers.nowutc()), "type": logtype, "entry": logentry, }) self._trim_log()
def test_2764351_away_lights(self): rule_id = "2764351" cfg = config.EngineConfig() cfg.json_rules_dir = self.realworld_rules_dir self._setup_engine(config_obj=cfg) # Set the simulation time self._monkey_patch_nowutc() local_tz = pytz.timezone(tz_name) self.sim_time = local_tz.localize(dt.datetime(2018, 7, 14, 0, 0, 00)) print("Loading the rule") self.loop.run_until_complete( self._load_one_rule(rule_id, self.realworld_rules_dir)) # Verify rule loaded properly print("Clock should have two ClockAlarms defined on the timeline") self.assertEqual(len(self.engine_obj._clock.timeline), 2) print("Setting the pre-state") self.loop.run_until_complete( self._set_and_verify_entity_state("input_boolean.vacation_mode", "on", "off")) print("Trigger lights on") self.sim_time = local_tz.localize(dt.datetime(2018, 7, 14, 19, 21, 00)) print("Running sim at: ", str(helpers.nowutc())) self.loop.run_until_complete( self.engine_obj._clock._async_tick(self.sim_time)) # Check that the correct service was called self._verify_websocket_service_call(0, "scene.living_room_bright", "turn_on") self.engine_obj._websocket.clear() print("Trigger lights off") self.sim_time = local_tz.localize(dt.datetime(2018, 7, 14, 22, 7, 00)) print("Running sim at: ", str(helpers.nowutc())) self.loop.run_until_complete( self.engine_obj._clock._async_tick(self.sim_time)) # Check that the correct service was called self._verify_websocket_service_call(0, "scene.all_lights_off", "turn_on") self.engine_obj._websocket.clear()
def check_timespec_threadsafe(self, spec_dict): try: spec = clock.TimeSpec.from_dict(spec_dict) next_time = spec.next_time_from(helpers.nowutc()).isoformat() except Exception as e: message = "Exception checking TimeSpec: {} ({})".format( spec_dict, sys.exc_info()[1]) _LOG.error(message) return {"success": False, "message": message} return {"success": True, "next_time": next_time}
def event_hass_event(id, event_type: str, event_data: str): now = helpers.nowutc() event = { "id": id, "type": "event", "event": { "event_type": event_type, "data": event_data, "origin": "LOCAL", "time_fired": now.isoformat() } } return event
def __init__(self): # This is intentionally named different from OttoEngine's public state attribute # to ensure the REST API is not accessing the OttoEngine state directly. # The restapi runs in its own thread, and therefore should only be going through # threadsafe methods. self._hidden_states = state.OttoEngineState() self._hidden_states.set_engine_state("start_time", helpers.nowutc()) # Add some rules for i in range(1, 6): id = str(i)*5 self._hidden_states.add_rule( AutomationRule(id, "Rule {}".format(id), enabled=True, group="unittest") )
def test_disabled_rule(self): rule_id = "1116" cfg = config.EngineConfig() cfg.json_rules_dir = self.test_rules_dir self._setup_engine(config_obj=cfg) # Set the simulation time self._monkey_patch_nowutc() local_tz = pytz.timezone(tz_name) self.sim_time = local_tz.localize(dt.datetime(2018, 7, 14, 9, 0, 0)) print("Loading the rule") self.loop.run_until_complete( self._load_one_rule(rule_id, self.test_rules_dir)) self.sim_time = local_tz.localize(dt.datetime(2018, 7, 14, 9, 1, 00)) print("Running sim at: ", str(helpers.nowutc())) self.loop.run_until_complete( self.engine_obj._clock._async_tick(helpers.nowutc())) # Rule should not fire since it's disabled # self._verify_websocket_service_call(0, "input_boolean.test", "turn_on") self.assertEqual(len(self.engine_obj._websocket.service_calls), 0) self.engine_obj._websocket.clear()
def start_engine(self): '''Starts the Otto Engine until it is shutdown''' # Setup signal handlers self._loop.add_signal_handler(signal.SIGINT, self._stop_engine) self._loop.add_signal_handler(signal.SIGTERM, self._stop_engine) # Start the event loop try: _LOG.info("Starting event loop") self.englog.add_event("Otto-Engine starting") self._loop.call_soon(self._states.set_engine_state, "start_time", helpers.nowutc()) self._loop.create_task(self._async_setup_engine()) self._loop.run_forever() finally: _LOG.info("Shutting down OttoEngine") self._loop.close()
def _load_listeners(self, rule: rule_objects.AutomationRule): for listener in rule_objects.get_listeners(rule): # State and Event triggers if isinstance(listener.trigger, trigger_objects.ListenerTrigger): if isinstance(listener.trigger, trigger_objects.EventTrigger): listener_id = listener.trigger.event_type elif isinstance(listener.trigger, (trigger_objects.StateTrigger, trigger_objects.NumericStateTrigger)): listener_id = listener.trigger.entity_id _LOG.info("Adding listener for {} (rule: {})".format( listener_id, rule.id)) if listener_id in self._event_listeners: self._event_listeners[listener_id].append(listener) else: self._event_listeners[listener_id] = [listener] # Time triggers if isinstance(listener.trigger, trigger_objects.TimeTrigger): _LOG.info("Adding time listener: (rule: {}) {}".format( listener.rule.id, listener.trigger.timespec.serialize())) async def async_time_triggered(engine_obj=self): await async_invoke_rule(engine_obj, rule, trigger=None, event=None), self._clock.add_timespec_action(listener.trigger.id, async_time_triggered, listener.trigger.timespec, helpers.nowutc()) # Add reference so we can find the listener id to remove it self._time_listeners.append(listener.trigger.id)
def evaluate(self, engine) -> bool: return self.evaluate_at(helpers.nowutc())
def test_rule_condition(self): rule_id = "rule_condition" cfg = config.EngineConfig() cfg.json_rules_dir = self.test_rules_dir self._setup_engine(config_obj=cfg) # Set the simulation time self.sim_time = helpers.nowutc() print("Loading the rule") self.loop.run_until_complete( self._load_one_rule(rule_id, self.test_rules_dir)) print("Setting pre-state") self.loop.run_until_complete( self._set_and_verify_entity_state( "input_boolean.action_light", "off", "on", )) self.engine_obj._websocket.clear() print("Triggering rule, rule should pass") self.loop.run_until_complete( self._set_and_verify_entity_state( "input_boolean.test", "on", "off", )) # Check that the correct service was called self._verify_websocket_service_call(0, "input_boolean.action_light", "turn_on") self.engine_obj._websocket.clear() # Update the light's state, since Home Assistant would respond to the service_call # with any state changed events self.loop.run_until_complete( self._set_and_verify_entity_state("input_boolean.action_light", "on", None)) # Now that light is on, triggering input_boolean.test to on again # should not run the rule print("Turning off input_boolean.test, should not trigger rule") self.loop.run_until_complete( self._set_and_verify_entity_state( "input_boolean.test", "off", "on", )) self.assertEqual(len(self.engine_obj._websocket.service_calls), 0) print("Triggering again, but light is already on, rule should not run") self.loop.run_until_complete( self._set_and_verify_entity_state( "input_boolean.test", "on", "off", )) self.assertEqual(len(self.engine_obj._websocket.service_calls), 0)