Example #1
0
def update_rss():
    # Grab RSS Feeds

    if conf.rss_feeds is not None and conf.rss_update is not None:
        while not conf.stopping:
            if conf.rss_last_update == None or (
                    conf.rss_last_update + conf.rss_update) <= time.time():
                conf.rss_last_update = time.time()

                for feed_data in conf.rss_feeds:
                    feed = yield from utils.run_in_executor(
                        conf.loop, conf.executor, feedparser.parse,
                        feed_data["feed"])

                    new_state = {"feed": feed}
                    with conf.ha_state_lock:
                        conf.ha_state[feed_data["target"]] = new_state

                    data = {
                        "event_type": "state_changed",
                        "data": {
                            "entity_id": feed_data["target"],
                            "new_state": new_state
                        }
                    }
                    ws_update(data)

            yield from asyncio.sleep(1)
Example #2
0
def load_dash(request):
    name = request.match_info.get('name', "Anonymous")
    params = request.query
    skin = params.get("skin", "default")
    recompile = params.get("recompile", False)
    if recompile == '1':
        recompile = True

    response = yield from utils.run_in_executor(
        conf.loop, conf.executor, conf.dashboard_obj.get_dashboard, name, skin,
        recompile)

    return web.Response(text=response, content_type="text/html")
Example #3
0
    async def get_updates(self):
        already_initialized = False
        already_notified = False
        first_time = True
        first_time_service = True

        while not self.stopping: 
            while not self.initialized or not already_initialized: #continue until initialization is successful
                if not already_initialized and not already_notified: #if it had connected before, it need not run this. Run if just trying for the first time 
                    try:
                        await asyncio.wait_for(utils.run_in_executor(self.AD.loop, self.AD.executor, self.start_mqtt_service, first_time_service), 5.0, loop=self.loop)
                        await asyncio.wait_for(self.mqtt_connect_event.wait(), 5.0, loop=self.loop) # wait for it to return true for 5 seconds in case still processing connect
                    except asyncio.TimeoutError:
                        self.AD.log(
                            "CRITICAL", 
                                "{}: Could not Complete Connection to Broker, please Ensure Broker at URL {}:{} is correct or broker not down and restart Appdaemon".format(self.name, self.mqtt_client_host, self.mqtt_client_port))
                        self.mqtt_client.loop_stop()
                        self.mqtt_client.disconnect() #disconnect so it won't attempt reconnection if the broker was to come up

                    first_time_service = False

                if self.initialized : #meaning the plugin started as expected
                    await self.AD.notify_plugin_started(self.namespace, first_time)
                    already_notified = False
                    already_initialized = True
                    self.AD.log("INFO", "{}: MQTT Plugin initialization complete".format(self.name))
                else:
                    if not already_notified and already_initialized:
                        self.AD.notify_plugin_stopped(self.namespace)
                        self.AD.log("CRITICAL", "{}: MQTT Plugin Stopped Unexpectedly".format(self.name))
                        already_notified = True
                        already_initialized = False
                        first_time = False
                    if not already_initialized and not already_notified:
                        self.AD.log("CRITICAL", "{}: Could not complete MQTT Plugin initialization, trying again in 5 seconds".format(self.name))
                    else:
                        self.AD.log("CRITICAL", "{}: Unable to reinitialize MQTT Plugin, will keep trying again until complete".format(self.name))
                    await asyncio.sleep(5)
            await asyncio.sleep(5)
    async def get_updates(self):
        already_initialized = False
        already_notified = False
        first_time = True
        first_time_service = True

        self.mqtt_connect_event = asyncio.Event()

        while not self.stopping:
            while (
                    not self.initialized or not already_initialized
            ) and not self.stopping:  # continue until initialization is successful
                if (
                        not already_initialized and not already_notified
                ):  # if it had connected before, it need not run this. Run if just trying for the first time
                    try:
                        await asyncio.wait_for(
                            utils.run_in_executor(self,
                                                  self.start_mqtt_service,
                                                  first_time_service), 5.0)
                        await asyncio.wait_for(
                            self.mqtt_connect_event.wait(), 5.0
                        )  # wait for it to return true for 5 seconds in case still processing connect
                    except asyncio.TimeoutError:
                        self.logger.critical(
                            "Could not Complete Connection to Broker, please Ensure Broker at URL %s:%s is correct and broker is not down and restart Appdaemon",
                            self.mqtt_client_host,
                            self.mqtt_client_port,
                        )

                        # meaning it should start anyway even if broker is down
                        if self.mqtt_client_force_start:
                            self.mqtt_connected = True
                        else:
                            self.mqtt_client.loop_stop()
                            # disconnect so it won't attempt reconnection if the broker was to come up
                            self.mqtt_client.disconnect()

                    first_time_service = False

                state = await self.get_complete_state()
                meta = await self.get_metadata()

                # meaning the client has connected to the broker
                if self.mqtt_connected:
                    await self.AD.plugins.notify_plugin_started(
                        self.name, self.namespace, meta, state, first_time)
                    already_notified = False
                    already_initialized = True
                    self.logger.info("MQTT Plugin initialization complete")
                    self.initialized = True
                else:
                    if not already_notified and already_initialized:
                        await self.AD.plugins.notify_plugin_stopped(
                            self.name, self.namespace)
                        self.logger.critical(
                            "MQTT Plugin Stopped Unexpectedly")
                        already_notified = True
                        already_initialized = False
                        first_time = False
                    if not already_initialized and not already_notified:
                        self.logger.critical(
                            "Could not complete MQTT Plugin initialization, trying again in 5 seconds"
                        )
                        if self.stopping:
                            break
                    else:
                        self.logger.critical(
                            "Unable to reinitialize MQTT Plugin, will keep trying again until complete"
                        )
                    await asyncio.sleep(5)
            await asyncio.sleep(5)
Example #5
0
    async def read_config(self):

        new_config = None

        if await utils.run_in_executor(self, os.path.isfile, self.app_config_file):
            self.logger.warning("apps.yaml in the Config directory is deprecated. Please move apps.yaml to the apps directory.")
            new_config = utils.run_in_executor(self.read_config_file, self.app_config_file)
        else:
            for root, subdirs, files in os.walk(self.AD.app_dir):
                subdirs[:] = [d for d in subdirs if d not in self.AD.exclude_dirs]
                if root[-11:] != "__pycache__":
                    for file in files:
                        if file[-5:] == ".yaml" and file[0] != ".":
                            self.logger.debug("Reading %s", os.path.join(root, file))
                            config = await utils.run_in_executor(self, self.read_config_file, os.path.join(root, file))
                            valid_apps = {}
                            if type(config).__name__ == "dict":
                                for app in config:
                                    if config[app] is not None:
                                        if app == "global_modules":
                                            #
                                            # Check the parameter format for string or list
                                            #
                                            if isinstance(config[app], str):
                                                valid_apps[app] = [config[app]]
                                            elif isinstance(config[app], list):
                                                valid_apps[app] = config[app]
                                            else:
                                                if self.AD.invalid_yaml_warnings:
                                                    self.logger.warning("global_modules should be a list or a string in File '%s' - ignoring", file)
                                        elif app == "sequence":
                                            #
                                            # We don't care what it looks like just pass it through
                                            #
                                            valid_apps[app] = config[app]
                                        elif "class" in config[app] and "module" in config[app]:
                                            valid_apps[app] = config[app]
                                        else:
                                            if self.AD.invalid_yaml_warnings:
                                                self.logger.warning("App '%s' missing 'class' or 'module' entry - ignoring", app)
                            else:
                                if self.AD.invalid_yaml_warnings:
                                    self.logger.warning("File '%s' invalid structure - ignoring", os.path.join(root, file))

                            if new_config is None:
                                new_config = {}
                            for app in valid_apps:
                                if app == "global_modules":
                                    if app in new_config:
                                        new_config[app].extend(valid_apps[app])
                                        continue
                                if app == "sequence":
                                    if app in new_config:
                                        new_config[app] = {**new_config[app], **valid_apps[app]}
                                        continue

                                if app in new_config:
                                    self.logger.warning("File '%s' duplicate app: %s - ignoring", os.path.join(root, file), app)
                                else:
                                    new_config[app] = valid_apps[app]

        await self.AD.sequences.add_sequences(new_config.get("sequence", {}))
        return new_config
Example #6
0
def do_every_second(utc):

    try:
        start_time = datetime.datetime.now().timestamp()
        now = datetime.datetime.fromtimestamp(utc)
        conf.now = utc

        # If we have reached endtime bail out

        if conf.endtime is not None and utils.get_now() >= conf.endtime:
            utils.log(conf.logger, "INFO", "End time reached, exiting")
            stopit()

        if conf.realtime:
            real_now = datetime.datetime.now().timestamp()
            delta = abs(utc - real_now)
            if delta > 1:
                utils.log(conf.logger, "WARNING", "Scheduler clock skew detected - delta = {} - resetting".format(delta))
                return real_now

        # Update sunrise/sunset etc.

        update_sun()

        # Check if we have entered or exited DST - if so, reload apps
        # to ensure all time callbacks are recalculated

        now_dst = is_dst()
        if now_dst != conf.was_dst:
            utils.log(
                conf.logger, "INFO",
                "Detected change in DST from {} to {} -"
                " reloading all modules".format(conf.was_dst, now_dst)
            )
            # dump_schedule()
            utils.log(conf.logger, "INFO", "-" * 40)
            yield from utils.run_in_executor(conf.loop, conf.executor, read_apps, True)
            # dump_schedule()
        conf.was_dst = now_dst

        # dump_schedule()

        # test code for clock skew
        #if random.randint(1, 10) == 5:
        #    time.sleep(random.randint(1,20))

        # Check to see if any apps have changed but only if we have valid state

        if conf.last_state is not None and appapi.reading_messages:
            yield from utils.run_in_executor(conf.loop, conf.executor, read_apps)

        # Check to see if config has changed

        if appapi.reading_messages:
            yield from utils.run_in_executor(conf.loop, conf.executor, check_config)

        # Call me suspicious, but lets update state form HA periodically
        # in case we miss events for whatever reason
        # Every 10 minutes seems like a good place to start

        if conf.last_state is not None and appapi.reading_messages and now - conf.last_state > datetime.timedelta(minutes=10) and conf.ha_url is not None:
            try:
                yield from utils.run_in_executor(conf.loop, conf.executor, get_ha_state)
                conf.last_state = now
            except:
                utils.log(conf.logger, "WARNING", "Unexpected error refreshing HA state - retrying in 10 minutes")

        # Check on Queue size

        qsize = q.qsize()
        if qsize > 0 and qsize % 10 == 0:
            conf.logger.warning("Queue size is {}, suspect thread starvation".format(q.qsize()))

        # Process callbacks

        # utils.log(conf.logger, "DEBUG", "Scheduler invoked at {}".format(now))
        with conf.schedule_lock:
            for name in conf.schedule.keys():
                for entry in sorted(
                        conf.schedule[name].keys(),
                        key=lambda uuid_: conf.schedule[name][uuid_]["timestamp"]
                ):

                    if conf.schedule[name][entry]["timestamp"] <= utc:
                        exec_schedule(name, entry, conf.schedule[name][entry])
                    else:
                        break
            for k, v in list(conf.schedule.items()):
                if v == {}:
                    del conf.schedule[k]

        end_time = datetime.datetime.now().timestamp()

        loop_duration = (int((end_time - start_time)*1000) / 1000) * 1000
        utils.log(conf.logger, "DEBUG", "Main loop compute time: {}ms".format(loop_duration))

        if loop_duration > 900:
            utils.log(conf.logger, "WARNING", "Excessive time spent in scheduler loop: {}ms".format(loop_duration))

        return utc

    except:
        utils.log(conf.error, "WARNING", '-' * 60)
        utils.log(conf.error, "WARNING", "Unexpected error during do_every_second()")
        utils.log(conf.error, "WARNING", '-' * 60)
        utils.log(conf.error, "WARNING", traceback.format_exc())
        utils.log(conf.error, "WARNING", '-' * 60)
        if conf.errorfile != "STDERR" and conf.logfile != "STDOUT":
            # When explicitly logging to stdout and stderr, suppress
            # log messages about writing an error (since they show up anyway)
            utils.log(
                conf.logger, "WARNING",
                "Logged an error to {}".format(conf.errorfile)
            )
Example #7
0
def appdaemon_loop():
    first_time = True
    disconnected_event = False
    global ws

    conf.stopping = False

    _id = 0

    while not conf.stopping:
        _id += 1
        try:
            if first_time is False:
                # Get initial state
                get_ha_state()
                conf.last_state = utils.get_now()
                utils.log(conf.logger, "INFO", "Got initial state")

                disconnected_event = False

                # Let other parts know we are in business,
                appapi.reading_messages = True

                # Load apps
                read_apps(True)

                utils.log(conf.logger, "INFO", "App initialization complete")

            #
            # Fire HA_STARTED and APPD_STARTED Events
            #
            if first_time is True:
                process_event({"event_type": "appd_started", "data": {}})
                first_time = False
            elif conf.ha_url is not None:

                process_event({"event_type": "ha_started", "data": {}})

            if conf.version < parse_version('0.34') or conf.commtype == "SSE":
                #
                # Older version of HA - connect using SSEClient
                #
                if conf.commtype == "SSE":
                    utils.log(conf.logger, "INFO", "Using SSE")
                else:
                    utils.log(
                        conf.logger, "INFO",
                        "Home Assistant version < 0.34.0 - "
                        "falling back to SSE"
                    )
                headers = {'x-ha-access': conf.ha_key}
                if conf.timeout is None:
                    messages = SSEClient(
                        "{}/api/stream".format(conf.ha_url),
                        verify=False, headers=headers, retry=3000
                    )
                    utils.log(
                        conf.logger, "INFO",
                        "Connected to Home Assistant".format(conf.timeout)
                    )
                else:
                    messages = SSEClient(
                        "{}/api/stream".format(conf.ha_url),
                        verify=False, headers=headers, retry=3000,
                        timeout=int(conf.timeout)
                    )
                    utils.log(
                        conf.logger, "INFO",
                        "Connected to Home Assistant with timeout = {}".format(
                            conf.timeout
                        )
                    )
                while True:
                    msg = yield from utils.run_in_executor(conf.loop, conf.executor, messages.__next__)
                    if msg.data != "ping":
                        process_message(json.loads(msg.data))
            else:
                #
                # Connect to websocket interface
                #
                url = conf.ha_url
                if url.startswith('https://'):
                    url = url.replace('https', 'wss', 1)
                elif url.startswith('http://'):
                    url = url.replace('http', 'ws', 1)

                sslopt = {}
                if conf.certpath:
                    sslopt['ca_certs'] = conf.certpath
                ws = create_connection(
                    "{}/api/websocket".format(url), sslopt=sslopt
                )
                result = json.loads(ws.recv())
                utils.log(conf.logger, "INFO",
                       "Connected to Home Assistant {}".format(
                           result["ha_version"]))
                #
                # Check if auth required, if so send password
                #
                if result["type"] == "auth_required":
                    auth = json.dumps({
                        "type": "auth",
                        "api_password": conf.ha_key
                    })
                    ws.send(auth)
                    result = json.loads(ws.recv())
                    if result["type"] != "auth_ok":
                        utils.log(conf.logger, "WARNING",
                               "Error in authentication")
                        raise ValueError("Error in authentication")
                #
                # Subscribe to event stream
                #
                sub = json.dumps({
                    "id": _id,
                    "type": "subscribe_events"
                })
                ws.send(sub)
                result = json.loads(ws.recv())
                if not (result["id"] == _id and result["type"] == "result" and
                                result["success"] is True):
                    utils.log(
                        conf.logger, "WARNING",
                        "Unable to subscribe to HA events, id = {}".format(_id)
                    )
                    utils.log(conf.logger, "WARNING", result)
                    raise ValueError("Error subscribing to HA Events")

                #
                # Loop forever consuming events
                #

                while not conf.stopping:
                    ret = yield from utils.run_in_executor(conf.loop, conf.executor, ws.recv)
                    result = json.loads(ret)
                    result = json.loads(ret)

                    if not (result["id"] == _id and result["type"] == "event"):
                        utils.log(
                            conf.logger, "WARNING",
                            "Unexpected result from Home Assistant, "
                            "id = {}".format(_id)
                        )
                        utils.log(conf.logger, "WARNING", result)
                        raise ValueError(
                            "Unexpected result from Home Assistant"
                        )

                    process_message(result["event"])

        except:
            appapi.reading_messages = False
            if not conf.stopping:
                if disconnected_event == False:
                    process_event({"event_type": "ha_disconnected", "data": {}})
                    disconnected_event = True
                utils.log(
                    conf.logger, "WARNING",
                    "Disconnected from Home Assistant, retrying in 5 seconds"
                )
                if conf.loglevel == "DEBUG":
                    utils.log(conf.logger, "WARNING", '-' * 60)
                    utils.log(conf.logger, "WARNING", "Unexpected error:")
                    utils.log(conf.logger, "WARNING", '-' * 60)
                    utils.log(conf.logger, "WARNING", traceback.format_exc())
                    utils.log(conf.logger, "WARNING", '-' * 60)
                yield from asyncio.sleep(5)

    utils.log(conf.logger, "INFO", "Disconnecting from Home Assistant")
Example #8
0
def _list_dash(request):
    response = yield from utils.run_in_executor(
        conf.loop, conf.executor, conf.dashboard_obj.get_dashboard_list)
    return web.Response(text=response, content_type="text/html")
Example #9
0
    async def get_updates(self):
        already_initialized = False
        already_notified = False
        first_time = True
        first_time_service = True

        while not self.stopping:
            while not self.initialized or not already_initialized:  #continue until initialization is successful
                if not already_initialized and not already_notified:  #if it had connected before, it need not run this. Run if just trying for the first time
                    try:
                        await asyncio.wait_for(utils.run_in_executor(
                            self.AD.loop, self.AD.executor,
                            self.start_mqtt_service, first_time_service),
                                               5.0,
                                               loop=self.loop)
                        await asyncio.wait_for(
                            self.mqtt_connect_event.wait(),
                            5.0,
                            loop=self.loop
                        )  # wait for it to return true for 5 seconds in case still processing connect
                    except asyncio.TimeoutError:
                        self.AD.log(
                            "CRITICAL",
                            "{}: Could not Complete Connection to Broker, please Ensure Broker at URL {}:{} is correct or broker not down and restart Appdaemon"
                            .format(self.name, self.mqtt_client_host,
                                    self.mqtt_client_port))
                        self.mqtt_client.loop_stop()
                        self.mqtt_client.disconnect(
                        )  #disconnect so it won't attempt reconnection if the broker was to come up

                    first_time_service = False

                if self.initialized:  #meaning the plugin started as expected
                    await self.AD.notify_plugin_started(
                        self.namespace, first_time)
                    already_notified = False
                    already_initialized = True
                    self.AD.log(
                        "INFO",
                        "{}: MQTT Plugin initialization complete".format(
                            self.name))
                else:
                    if not already_notified and already_initialized:
                        self.AD.notify_plugin_stopped(self.namespace)
                        self.AD.log(
                            "CRITICAL",
                            "{}: MQTT Plugin Stopped Unexpectedly".format(
                                self.name))
                        already_notified = True
                        already_initialized = False
                        first_time = False
                    if not already_initialized and not already_notified:
                        self.AD.log(
                            "CRITICAL",
                            "{}: Could not complete MQTT Plugin initialization, trying again in 5 seconds"
                            .format(self.name))
                    else:
                        self.AD.log(
                            "CRITICAL",
                            "{}: Unable to reinitialize MQTT Plugin, will keep trying again until complete"
                            .format(self.name))
                    await asyncio.sleep(5)
            await asyncio.sleep(5)
Example #10
0
    async def call_service(self, namespace: str, domain: str, service: str,
                           data: dict) -> Any:
        self.logger.debug(
            "call_service: namespace=%s domain=%s service=%s data=%s",
            namespace,
            domain,
            service,
            data,
        )
        with self.services_lock:
            name = data.pop("__name", None)

            if namespace not in self.services:
                raise NamespaceException(
                    "Unknown namespace (%s) in call_service from %s",
                    namespace, name)

            if domain not in self.services[namespace]:
                raise DomainException(
                    "Unknown domain (%s/%s) in call_service from %s",
                    namespace, domain, name)

            if service not in self.services[namespace][domain]:
                raise ServiceException(
                    "Unknown service (%s/%s/%s) in call_service from %s",
                    namespace,
                    domain,
                    service,
                    name,
                )

            # If we have namespace in data it's an override for the domain of the eventual service call, as distinct
            # from the namespace the call itself is executed from. e.g. set_state() is in the AppDaemon namespace but
            # needs to operate on a different namespace, e.g. "default"

            if "namespace" in data:
                ns = data["namespace"]
                del data["namespace"]
            else:
                ns = namespace

            funcref = self.services[namespace][domain][service]["callback"]

            # Decide whether or not to call this as async

            # Default to true
            isasync = True

            # if to wait for results, default to False
            return_result = data.pop("return_result", False)

            # if to return results via callback
            callback = data.pop("callback", None)

            if "__async" in self.services[namespace][domain][service]:
                # We have a kwarg to tell us what to do
                if self.services[namespace][domain][service][
                        "__async"] == "auto":
                    # We decide based on introspection
                    if not asyncio.iscoroutinefunction(funcref):
                        isasync = False
                else:
                    # We do what the kwarg tells us
                    isasync = self.services[namespace][domain][service][
                        "__async"]

            if isasync is True:
                # it's a coroutine just await it.
                coro = funcref(ns, domain, service, data)
            else:
                # It's not a coroutine, , run it in an executor
                coro = utils.run_in_executor(self, funcref, ns, domain,
                                             service, data)

            if return_result is True:
                return await self.run_service(coro)

            elif callback is not None and name is not None:
                # results expected and it must belong to an app
                app_object = await self.AD.app_management.get_app(name)
                app_object.create_task(self.run_service(coro),
                                       callback=callback)

            else:
                asyncio.create_task(self.run_service(coro))