コード例 #1
0
    def initialize(self):
        """AppDaemon required method for app init."""
        conf_data = dict(self.config['AppDaemon'])

        _lights_dim_on = self.args.get('lights_dim_on', '').split(',')
        _lights_dim_off = self.args.get('lights_dim_off', '').split(',')
        _lights_off = self.args.get('lights_off', '').split(',')
        _switch_dim_group = self.args.get('switch_dim_lights_use')
        self._lights = {
            "dim": {
                "on": _lights_dim_on,
                "off": _lights_dim_off
            },
            "off": _lights_off,
            "state": self.get_state(_switch_dim_group)
        }
        # Listen for ambilight changes to change light dim group:
        self.listen_state(self.ch_dim_lights_group, _switch_dim_group)

        self._media_player = conf_data.get('media_player')
        self._ios_notifier = conf_data.get('notifier').replace('.', '/')
        self._target_sensor = conf_data.get('chatid_sensor')

        # Listen for Kodi changes:
        self._last_play = utils.get_now()
        self.listen_state(self.kodi_state, self._media_player)
        self.listen_event(self._receive_kodi_result,
                          EVENT_KODI_CALL_METHOD_RESULT)
コード例 #2
0
 def run_daily(self, callback, start, **kwargs):
     name = self.name
     now = utils.get_now()
     today = now.date()
     event = datetime.datetime.combine(today, start)
     if event < now:
         event = event + datetime.timedelta(days=1)
     handle = self.run_every(callback, event, 24 * 60 * 60, **kwargs)
     return handle
コード例 #3
0
 def run_at(self, callback, start, **kwargs):
     name = self.name
     now = utils.get_now()
     if start < now:
         raise ValueError("{}: run_at() Start time must be "
                          "in the future".format(self.name))
     exec_time = start.timestamp()
     handle = utils.insert_schedule(name, exec_time, callback, False, None,
                                    **kwargs)
     return handle
コード例 #4
0
 def run_minutely(self, callback, start, **kwargs):
     name = self.name
     now = utils.get_now()
     if start is None:
         event = now + datetime.timedelta(minutes=1)
     else:
         event = now
         event = event.replace(second=start.second)
         if event < now:
             event = event + datetime.timedelta(minutes=1)
     handle = self.run_every(callback, event, 60, **kwargs)
     return handle
コード例 #5
0
 def run_once(self, callback, start, **kwargs):
     name = self.name
     now = utils.get_now()
     today = now.date()
     event = datetime.datetime.combine(today, start)
     if event < now:
         one_day = datetime.timedelta(days=1)
         event = event + one_day
     exec_time = event.timestamp()
     handle = utils.insert_schedule(name, exec_time, callback, False, None,
                                    **kwargs)
     return handle
コード例 #6
0
 def run_every(self, callback, start, interval, **kwargs):
     name = self.name
     now = utils.get_now()
     if start < now:
         raise ValueError("start cannot be in the past")
     utils.log(
         conf.logger, "DEBUG",
         "Registering run_every starting {} in {}s intervals for {}".format(
              start, interval, name
         )
     )
     exec_time = start.timestamp()
     handle = utils.insert_schedule(name, exec_time, callback, True, None,
                                 interval=interval, **kwargs)
     return handle
コード例 #7
0
 def _receive_kodi_result(self, event_id, payload_event, *args):
     result = payload_event['result']
     method = payload_event['input']['method']
     if event_id == EVENT_KODI_CALL_METHOD_RESULT \
             and method == METHOD_GET_ITEM:
         if 'item' in result:
             item = result['item']
             new_video = (self._item_playing is None
                          or self._item_playing != item)
             self._is_playing_video = item['type'] in TYPE_ITEMS_NOTIFY
             self._item_playing = item
             delta = utils.get_now() - self._last_play
             if (self._is_playing_video
                     and (new_video or delta > dt.timedelta(minutes=20))):
                 self._last_play = utils.get_now()
                 self._adjust_kodi_lights(play=True)
                 # Notifications
                 self._notify_ios_message(self._item_playing)
                 self._notify_telegram_message(self._item_playing)
         else:
             self.log('RECEIVED BAD KODI RESULT: {}'.format(result), 'warn')
     elif event_id == EVENT_KODI_CALL_METHOD_RESULT \
             and method == METHOD_GET_PLAYERS:
         self.log('KODI GET_PLAYERS RECEIVED: {}'.format(result))
コード例 #8
0
 def kodi_state(self, entity, attribute, old, new, kwargs):
     """Kodi state change main control."""
     if new == 'playing':
         kodi_attrs = self.get_state(entity_id=self._media_player,
                                     attribute="attributes")
         self._is_playing_video = ('media_content_type' in kodi_attrs
                                   and kodi_attrs['media_content_type']
                                   in TYPE_HA_ITEMS_NOTIFY)
         # self.log('KODI ATTRS: {}, is_playing_video={}'
         #          .format(kodi_attrs, self._is_playing_video))
         if self._is_playing_video:
             self._ask_for_playing_item()
     elif ((new == 'idle') and self._is_playing_video) or (new == 'off'):
         self._is_playing_video = False
         self._last_play = utils.get_now()
         self.log(
             'KODI STOP. old:{}, new:{}, type_lp={}'.format(
                 old, new, type(self._last_play)), LOG_LEVEL)
         # self._item_playing = None
         self._adjust_kodi_lights(play=False)
コード例 #9
0
def update_sun():
    # now = datetime.datetime.now(conf.tz)
    now = conf.tz.localize(utils.get_now())
    mod = -1
    while True:
        try:
            next_rising_dt = conf.location.sunrise(
                now + datetime.timedelta(days=mod), local=False
            )
            if next_rising_dt > now:
                break
        except astral.AstralError:
            pass
        mod += 1

    mod = -1
    while True:
        try:
            next_setting_dt = conf.location.sunset(
                now + datetime.timedelta(days=mod), local=False
            )
            if next_setting_dt > now:
                break
        except astral.AstralError:
            pass
        mod += 1

    old_next_rising_dt = conf.sun.get("next_rising")
    old_next_setting_dt = conf.sun.get("next_setting")
    conf.sun["next_rising"] = next_rising_dt
    conf.sun["next_setting"] = next_setting_dt

    if old_next_rising_dt is not None and old_next_rising_dt != conf.sun["next_rising"]:
        # dump_schedule()
        process_sun("next_rising")
        # dump_schedule()
    if old_next_setting_dt is not None and old_next_setting_dt != conf.sun["next_setting"]:
        # dump_schedule()
        process_sun("next_setting")
コード例 #10
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)
            )
コード例 #11
0
def today_is_constrained(days):
    day = utils.get_now().weekday()
    daylist = [utils.day_of_week(day) for day in days.split(",")]
    if day in daylist:
        return False
    return True
コード例 #12
0
def run_ad(loop, tasks):
    conf.appq = asyncio.Queue(maxsize=0)

    conf.loop = loop

    first_time = True

    conf.stopping = False

    utils.log(conf.logger, "DEBUG", "Entering run()")

    # Load App Config

    conf.app_config = read_config()

    # Save start time

    conf.start_time = datetime.datetime.now()

    # Take a note of DST

    conf.was_dst = is_dst()

    # Setup sun

    update_sun()

    conf.executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)

    utils.log(conf.logger, "DEBUG", "Creating worker threads ...")

    # Create Worker Threads
    for i in range(conf.threads):
        t = threading.Thread(target=worker)
        t.daemon = True
        t.start()

    utils.log(conf.logger, "DEBUG", "Done")


    if conf.ha_url is not None:
        # Read apps and get HA State before we start the timer thread
        utils.log(conf.logger, "DEBUG", "Calling HA for initial state with key: {} and url: {}".format(conf.ha_key, conf.ha_url))

        while conf.last_state is None:
            try:
                get_ha_state()
                conf.last_state = utils.get_now()
            except:
                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)
                time.sleep(5)

        utils.log(conf.logger, "INFO", "Got initial state")

        # Initialize appdaemon loop
        tasks.append(asyncio.async(appdaemon_loop()))

    else:
       conf.last_state = utils.get_now()

    # Load apps

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

    utils.log(conf.logger, "DEBUG", "Reading Apps")

    read_apps(True)

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

    # Create timer loop

    # First, update "now" for less chance of clock skew error
    if conf.realtime:
        conf.now = datetime.datetime.now().timestamp()

        utils.log(conf.logger, "DEBUG", "Starting timer loop")

        tasks.append(asyncio.async(appstate_loop()))

    tasks.append(asyncio.async(do_every(conf.tick, do_every_second)))
    appapi.reading_messages = True
コード例 #13
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")