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)
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, }
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'))
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()
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
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)
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
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')
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"], }
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
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)
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()