Ejemplo n.º 1
0
    def dump_alarms(self, tag=""):
        # Useful when debugging
        dump = "\n" + "="*30 + " ALARMS " + tag + " " + "="*30 + "\n"
        dump += "raw = " + str(self.settings["alarm"]) + "\n\n"

        now_ts = to_utc(now_utc()).timestamp()
        dt = datetime.fromtimestamp(now_ts)
        dump += "now = {} ({})\n".format(
            nice_time(self.get_alarm_local(timestamp=now_ts),
                      speech=False, use_ampm=not self.use_24hour, use_24hour = self.use_24hour),
            now_ts)
        dump += "      U{} L{}\n".format(to_utc(dt), to_local(dt))
        dump += "\n\n"

        idx = 0
        for alarm in self.settings["alarm"]:
            dt = self.get_alarm_local(alarm)
            dump += "alarm[{}] - {} \n".format(idx, alarm)
            dump += "           Next: {} {}\n".format(
                nice_time(dt, speech=False, use_ampm= not self.use_24hour, use_24hour = self.use_24hour),
                nice_date(dt, now=now_local()))
            dump += "                 U{} L{}\n".format(dt, to_local(dt))
            if 'snooze' in alarm:
                dtOrig = self.get_alarm_local(timestamp=alarm['snooze'])
                dump += "           Orig: {} {}\n".format(
                    nice_time(dtOrig, speech=False, use_ampm= not self.use_24hour, use_24hour = self.use_24hour),
                    nice_date(dtOrig, now=now_local()))
            idx += 1

        dump += "="*75

        self.log.info(dump)
Ejemplo n.º 2
0
    def _create_recurring_alarm(self, when, recur):
        # 'recur' is a set of day index strings, e.g. {"3", "4"}
        # convert rule into an iCal rrule
        # TODO: Support more complex alarms, e.g. first monday, monthly, etc
        rule = ""
        abbr = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]
        days = []
        for day in recur:
            days.append(abbr[int(day)])
        if days:
            rule = "FREQ=WEEKLY;INTERVAL=1;BYDAY=" + ",".join(days)

        if when and rule:
            when = to_utc(when)

            # Create a repeating rule that starts in the past, enough days
            # back that it encompasses any repeat.
            past = when + timedelta(days=-45)
            rr = rrulestr("RRULE:" + rule, dtstart=past)
            now = to_utc(now_utc())
            # Get the first repeat that happens after right now
            next = rr.after(now)
            return {
                "timestamp": to_utc(next).timestamp(),
                "repeat_rule": rule,
            }
        else:
            return {
                "timestamp": None,
                "repeat_rule": rule,
            }
Ejemplo n.º 3
0
 def test_to_utc(self, mock_conf):
     mock_conf.get.return_value = test_config
     dt = datetime(year=2000, month=1, day=1,
                   hour=0, minute=0, second=0,
                   tzinfo=gettz('Europe/Stockholm'))
     self.assertEqual(to_utc(dt), dt)
     self.assertEqual(to_utc(dt).tzinfo, gettz('UTC'))
Ejemplo n.º 4
0
    def snooze_alarm(self, message):
        if not self.has_expired_alarm():
            return

        self.__end_beep()
        self.__end_flash()

        utt = message.data.get('utterance') or ""
        snooze_for = extract_number(utt)
        if not snooze_for or snooze_for < 1:
            snooze_for = 9  # default to 9 minutes

        # Snooze always applies the the first alarm in the sorted array
        alarm = self.settings["alarm"][0]
        dt = self.get_alarm_local(alarm)
        snooze = to_utc(dt) + timedelta(minutes=snooze_for)

        if "snooze" in alarm:
            # already snoozed
            original_time = alarm["snooze"]
        else:
            original_time = alarm["timestamp"]

        # Fill schedule with a snoozed entry -- 3 items:
        #    snooze_expire_timestamp, repeat_rule, original_timestamp
        self.settings["alarm"][0] = {
                                        "timestamp": snooze.timestamp(),
                                        "repeat_rule": alarm["repeat_rule"],
                                        "name": alarm["name"],
                                        "snooze": original_time
                                     }
        self._schedule()
Ejemplo n.º 5
0
    def _curate_alarms(self, curation_limit=1):
        """[summary]
            curation_limit (int, optional): Seconds past expired at which to
                                            remove the alarm
        """
        alarms = []
        now_ts = to_utc(now_utc()).timestamp()

        for alarm in self.settings["alarm"]:
            # Alarm format == [timestamp, repeat_rule[, orig_alarm_timestamp]]
            if alarm["timestamp"] < now_ts:
                if alarm["timestamp"] < (now_ts - curation_limit):
                    # skip playing an old alarm
                    if alarm["repeat_rule"]:
                        # reschedule in future if repeat rule exists
                        alarms.append(self._next_repeat(alarm))
                else:
                    # schedule for right now, with the
                    # third entry as the original base time
                    base = alarm["name"] if alarm["name"] == ''\
                                         else alarm["timestamp"]
                    alarms.append({
                        "timestamp": now_ts+1,
                        "repeat_rule": alarm["repeat_rule"],
                        "name": alarm["name"],
                        "snooze": base
                    })
            else:
                alarms.append(alarm)

        alarms = sorted(alarms, key=lambda a: a["timestamp"])
        self.settings["alarm"] = alarms
Ejemplo n.º 6
0
 def __to_UTC(self, when):
     try:
         # First try with modern mycroft.util.time functions
         return to_utc(when)
     except Exception:
         # TODO: This uses the device timezone -- should probably use
         #       the timezone of the location being forecasted
         timezone = pytz.timezone(self.location["timezone"]["code"])
         return timezone.localize(when).astimezone(pytz.utc)
Ejemplo n.º 7
0
    def has_expired_alarm(self):
        # True is an alarm should be 'going off' now.  Snoozed alarms don't
        # count until they are triggered again.
        if not self.settings["alarm"]:
            return False

        now_ts = to_utc(now_utc()).timestamp()
        for alarm in self.settings["alarm"]:
            if alarm['timestamp'] <= now_ts:
                return True

        return False
Ejemplo n.º 8
0
    def waqi_query_and_report(self, city, pollutant):
        if self.settings.get('APIKey') is not None:
            reqAQI = requests.get('https://api.waqi.info/feed/' + city +
                                  '/?token=' + self.settings.get('APIKey'))
            objAQI = json.loads(reqAQI.text)
            if objAQI['status'] == 'ok':
                try:
                    value = objAQI['data']['iaqi'][lookup[pollutant]]['v']
                except:
                    self.speak_dialog('pollutant.not.reported', {
                        'pollutant': nice_name[pollutant],
                        'city': city
                    })
                else:
                    config = Configuration.get()
                    station_string = objAQI["data"]["city"]["name"]
                    station = station_string.split(',')[0].split('(')[0]
                    utc_time = now_utc(
                    )  # alternatively, set utc_time to datetime.now(timezone.utc)
                    rec_time = datetime.strptime(
                        objAQI["data"]["time"]["s"] +
                        objAQI["data"]["time"]["tz"].split(':')[0] +
                        objAQI["data"]["time"]["tz"].split(':')[1],
                        "%Y-%m-%d %H:%M:%S%z")
                    rec_time = to_utc(rec_time)
                    timediff = (utc_time - rec_time).total_seconds()
                    if timediff // 3600 >= 3:  # reading is more than 3 hours old
                        self.dialog(
                            'Current readings for this location are not available. Please check back another time.'
                        )
                    else:
                        if city in station:
                            incity = ''
                        else:
                            incity = 'in ' + city
                        self.speak_dialog(
                            'pollutant.level.is', {
                                'pollutant': nice_name[pollutant],
                                'incity': incity,
                                'value': value,
                                'station': station
                            })
                    if nice_name[pollutant] == 'pm 2.5':
                        self.health_advisory(value)

            elif objAQI['data'] == 'Unknown station':
                self.speak_dialog('city.not.reported', {'city': city})
            elif objAQI['data'] == 'Invalid key':
                self.speak_dialog('invalid.key', {'city': city})
        else:
            self.speak_dialog('key.not.found')
Ejemplo n.º 9
0
    def _next_repeat(self, alarm):
        # evaluate recurrence to the next instance
        if 'snooze' in alarm:
            # repeat from original time (it was snoozed)
            ref = datetime.fromtimestamp(alarm["repeat_rule"])
        else:
            ref = datetime.fromtimestamp(alarm["timestamp"])

        # Create a repeat rule and get the next alarm occurrance after that
        start = to_utc(ref)
        rr = rrulestr("RRULE:" + alarm["repeat_rule"], dtstart=start)
        now = to_utc(now_utc())
        next = rr.after(now)

        self.log.debug("     Now={}".format(now))
        self.log.debug("Original={}".format(start))
        self.log.debug("    Next={}".format(next))

        return {
            "timestamp": to_utc(next).timestamp(),
            "repeat_rule": alarm["repeat_rule"],
            "name": alarm["name"],
        }
Ejemplo n.º 10
0
    def set_alarm(self, when, name=None, repeat=None):
        time = when.replace(second=0, microsecond=0)
        if repeat:
            alarm = self._create_recurring_alarm(time, repeat)
            alarm = {
                "timestamp": alarm["timestamp"],
                "repeat_rule": alarm["repeat_rule"],
                "name": name or ""
            }
        else:
            alarm = {
                "timestamp": to_utc(time).timestamp(),
                "repeat_rule": "",
                "name": name or ""
            }

        for existing in self.settings["alarm"]:
            if alarm == existing:
                self.speak_dialog("alarm.already.exists")
                return None
        self.settings["alarm"].append(alarm)
        self._schedule()
        return alarm
Ejemplo n.º 11
0
    def _get_alarm_matches(self, utt, alarm=None, max_results=1,
                           dialog='ask.which.alarm', is_response=False):
        """Get list of alarms that match based on a user utterance.
        Arguments:
            utt (str): string spoken by the user
            alarm (list): list of alarm to match against
            max_results (int): max number of results desired
            dialog (str): name of dialog file used for disambiguation
            is_response (bool): is this being called by get_response
        Returns:
            (str): ["All", "Matched", "No Match Found", or "User Cancelled"]
            (list): list of matched alarm
        """
        alarms = alarm or self.settings['alarm']
        all_words = self.translate_list('all')
        next_words = self.translate_list('next')
        status = ["All", "Matched", "No Match Found", "User Cancelled", "Next"]

        # No alarms
        if alarms is None or len(alarms) == 0:
            self.log.error("Cannot get match. No active alarms.")
            return (status[2], None)

        # Extract Alarm Time
        when, utt_no_datetime = extract_datetime(utt, lang=self.lang) or (None, None)

        # Will return dt of unmatched string
        today = extract_datetime(self.texts.get('today'), lang=self.lang)
        today = today[0]

        # Check the time if it's midnight. This is to check if the user
        # said a recurring alarm with only the Day or if the user did
        # specify to set an alarm on midnight. If it's confirmed that
        # it's for a day only, then get another response from the user
        # to clarify what time on that day the recurring alarm is.
        is_midnight = self._check_if_utt_has_midnight(utt,
                                                      when,
                                                      self.threshold)

        if when == today and not is_midnight:
            when = None

        time_matches = None
        time_alarm = None
        if when:
            time_alarm = to_utc(when).timestamp()
            if is_midnight:
                time_alarm = time_alarm + 86400.0
            time_matches = [a for a in alarms
                            if abs(a["timestamp"] - time_alarm) <= 60]

        # Extract Recurrence
        recur = None
        recurrence_matches = None
        for word in self.recurrence_dict:
            is_match = self._fuzzy_match(word, utt.lower(), self.threshold)
            if is_match:
                recur = self._create_day_set(utt)
                alarm_recur = self._create_recurring_alarm(when, recur)
                recurrence_matches = [a for a in alarms
                    if a["repeat_rule"] == alarm_recur["repeat_rule"]
                ]
                break

        utt = utt_no_datetime or utt

        # Extract Ordinal/Cardinal Numbers
        number = extract_number(utt, ordinals=True, lang=self.lang)
        if number and number > 0:
            number = int(number)
        else:
            number = None

        # Extract Name
        name_matches = [a for a in alarms if a["name"] and
                        self._fuzzy_match(a["name"], utt, self.threshold)]

        # Match Everything
        alarm_to_match = None
        if when:
            if recur:
                alarm_to_match = alarm_recur
            else:
                alarm_to_match = {"timestamp": time_alarm, "repeat_rule": ""}

        # Find the Intersection of the Alarms list and all the matched alarms
        orig_count = len(alarms)
        if when and time_matches:
            alarms = [a for a in alarms if a in time_matches]
        if recur and recurrence_matches:
            alarms = [a for a in alarms if a in recurrence_matches]
        if name_matches:
            alarms = [a for a in alarms if a in name_matches]

        # Utterance refers to all alarms
        if utt and any(self._fuzzy_match(i, utt, 1) for i in all_words):
            return (status[0], alarms)
        # Utterance refers to the next alarm to go off
        elif utt and any(self._fuzzy_match(i, utt, 1) for i in next_words):
            return (status[4], [alarms[0]])

        # Given something to match but no match found
        if ((number and number > len(alarms)) or
                (recur and not recurrence_matches) or
                (when and not time_matches)):
            return (status[2], None)
        # If number of alarms filtered were the same, assume user asked for
        # All alarms
        if (len(alarms) == orig_count and max_results > 1 and
                not number and not when and not recur):
            return (status[0], alarms)
        # Return immediately if there is ordinal
        if number and number <= len(alarms):
            return (status[1], [alarms[number - 1]])
        # Return immediately if within maximum results
        elif alarms and len(alarms) <= max_results:
            return (status[1], alarms)
        # Ask for reply from user and iterate the function
        elif alarms and len(alarms) > max_results:
            desc = []
            for alarm in alarms:
                desc.append(self._describe(alarm))

            items_string = ''
            if desc:
                items_string = join_list(desc, self.translate('and'))

            reply = self.get_response(dialog, data={
                                                    'number': len(alarms),
                                                    'list': items_string,
                                                    }, num_retries=1)
            if reply:
                return self._get_alarm_matches(reply,
                                               alarm=alarms,
                                               max_results=max_results,
                                               dialog=dialog,
                                               is_response=True)
            else:
                return (status[3], None)

        # No matches found
        return (status[2], None)
Ejemplo n.º 12
0
    def handle_set_alarm(self, message):
        """Handler for "set an alarm for..."""
        utt = message.data.get('utterance').lower()
        recur = None

        if message.data.get('Recurring'):
            # Just ignoring the 'Recurrence' now, we support more complex stuff
            # recurrence = message.data.get('Recurrence')
            recur = self._create_day_set(utt)
            # TODO: remove days following an "except" in the utt

            while not recur:
                r = self.get_response('query.recurrence', num_retries=1)
                if not r:
                    return
                recur = self._create_day_set(r)

            if self.voc_match(utt, "Except"):
                # TODO: Support exceptions
                self.speak_dialog("no.exceptions.yet")
                return

        # Get the time
        when, utt_no_datetime = extract_datetime(utt, lang=self.lang) or (None, utt)

        # Get name from leftover string from extract_datetime
        name = self._get_alarm_name(utt_no_datetime)

        # Will return dt of unmatched string
        today = extract_datetime(self.texts.get('today'), lang=self.lang)
        today = today[0]

        # Check the time if it's midnight. This is to check if the user
        # said a recurring alarm with only the Day or if the user did
        # specify to set an alarm on midnight. If it's confirmed that
        # it's for a day only, then get another response from the user
        # to clarify what time on that day the recurring alarm is.
        is_midnight = self._check_if_utt_has_midnight(utt,
                                                      when,
                                                      self.threshold)

        if (when is None or when.time() == today.time()) and not is_midnight:
            r = self.get_response('query.for.when', validator=extract_datetime)
            if not r:
                self.speak_dialog("alarm.schedule.cancelled")
                return
            when_temp = extract_datetime(r, lang=self.lang)
            if when_temp is not None:
                when_temp = when_temp[0]
                # TODO add check for midnight
                # is_midnight = self._check_if_utt_has_midnight(r, when_temp,
                #                                               self.threshold)
                when = when_temp if when is None \
                                 else datetime(tzinfo=when.tzinfo,
                                               year=when.year,
                                               month=when.month,
                                               day=when.day,
                                               hour=when_temp.hour,
                                               minute=when_temp.minute)
            else:
                when = None

        # Verify time
        alarm_time = when
        confirmed_time = False
        while (not when or when == today) and not confirmed_time:
            if recur:
                t = nice_time(alarm_time, use_ampm=not self.use_24hour, use_24hour = self.use_24hour)
                conf = self.ask_yesno('confirm.recurring.alarm',
                                      data={
                                          'time': t,
                                          'recurrence': self._recur_desc(recur)
                                      })
            else:
                t = nice_date_time(alarm_time, now=today, use_ampm= not self.use_24hour, use_24hour = self.use_24hour, lang=self.lang)
                conf = self.ask_yesno('confirm.alarm', data={'time': t})
            if not conf:
                return
            if conf == 'yes':
                when = [alarm_time]
                confirmed_time = True
            else:
                # check if a new (corrected) time was given
                when = extract_datetime(conf, lang=self.lang)
                if when is not None:
                    when = when[0]
                if not when or when == today:
                    # Not a confirmation and no date/time in statement, quit
                    return
                alarm_time = when
                when = None  # reverify

        alarm = {}
        if not recur:
            alarm_time_ts = to_utc(alarm_time).timestamp()
            now_ts = now_utc().timestamp()
            if alarm_time_ts > now_ts:
                alarm = self.set_alarm(alarm_time, name)
            else:
                if (self.texts.get('today') in utt) or (self.texts.get('tonight') in utt):
                    self.speak_dialog('alarm.past')
                    return
                else:
                    # Set the alarm to find the next 24 hour time slot
                    while alarm_time_ts < now_ts:
                        alarm_time_ts += 86400.0
                    alarm_time = datetime.utcfromtimestamp(alarm_time_ts)
                    alarm = self.set_alarm(alarm_time, name)
        else:
            alarm = self.set_alarm(alarm_time, name, repeat=recur)

        if not alarm:
            # none set, it was a duplicate
            return

        # Don't want to hide the animation
        self.enclosure.deactivate_mouth_events()
        if confirmed_time:
            self.speak_dialog("alarm.scheduled")
        else:
            t = self._describe(alarm)
            reltime = nice_relative_time(self.get_alarm_local(alarm), lang=self.lang)
            if recur:
                self.speak_dialog("recurring.alarm.scheduled.for.time",
                                  data={"time": t, "rel": reltime})
            else:
                self.speak_dialog("alarm.scheduled.for.time",
                                  data={"time": t, "rel": reltime})

        self._show_alarm_anim(alarm_time)
        self.enclosure.activate_mouth_events()