From 1b0475f896c97e75bdc2e0c9ef03f45fd865397c Mon Sep 17 00:00:00 2001 From: softwaretest Date: Mon, 6 Jul 2020 18:56:02 -0600 Subject: [PATCH 1/4] WIP Adding interval feature to mycroft-timer --- __init__.py | 112 +++++++++++++++++++++ dialog/en-us/started.interval.timer.dialog | 3 + vocab/en-us/interval.timer.status.intent | 3 + vocab/en-us/start.interval.timer.intent | 5 + vocab/en-us/stop.interval.timer.intent | 7 ++ 5 files changed, 130 insertions(+) create mode 100644 dialog/en-us/started.interval.timer.dialog create mode 100644 vocab/en-us/interval.timer.status.intent create mode 100644 vocab/en-us/start.interval.timer.intent create mode 100644 vocab/en-us/stop.interval.timer.intent diff --git a/__init__.py b/__init__.py index 1fdf7da..0f14f6b 100644 --- a/__init__.py +++ b/__init__.py @@ -412,6 +412,14 @@ def update_display(self, message): # Timer still running remaining = (timer["expires"] - now).seconds self.render_timer(idx, remaining) + elif timer.get("is_interval"): + # Timer has expired but it is an interval + self._play_beep() + # reset the timer expiration + remaining = timer["duration"] + time_expires = datetime.now() + timedelta(seconds=remaining) + timer["expires"] = time_expires + self.render_timer(idx, remaining) else: # Timer has expired but not been cleared, flash eyes overtime = (now - timer["expires"]).seconds @@ -667,6 +675,110 @@ def _check_duplicate_timer_name(self, name): return True return False + @intent_handler(IntentBuilder("start.interval.timer").require("Timer") + .require("Start").optionally("Connector")) + def handle_start_interval_timer(self, message): + """ Common handler for start_interval_timer intents + """ + def validate_duration(string): + """Check that extract_duration returns a valid duration.""" + res = extract_duration(string, self.lang) + return res and res[0] + + utt = message.data["utterance"] + #~~ GET TIMER DURATION + secs, utt_remaining = self._extract_duration(utt) + if secs and secs == 1: # prevent "set one timer" doing 1 sec timer + utt_remaining = message.data["utterance"] + + if secs == None: # no duration found, request from user + req_duration = self.get_response('ask.how.long', + validator=validate_duration) + secs, _ = self._extract_duration(req_duration) + if secs is None: + return # user cancelled + + #~~ GET TIMER NAME + if utt_remaining is not None and len(utt_remaining) > 0: + timer_name = self._get_timer_name(utt_remaining) + if timer_name: + if self._check_duplicate_timer_name(timer_name): + return # make another timer with a different name + else: + timer_name = None + + #~~ SHOULD IT BE AN ALARM? + # TODO: add name of alarm if available? + if secs >= 60*60*24: # 24 hours in seconds + if self.ask_yesno("timer.too.long.alarm.instead") == 'yes': + alarm_time = now_local() + timedelta(seconds=secs) + phrase = self.translate('set.alarm', + {'date': alarm_time.strftime('%B %d %Y'), + 'time': alarm_time.strftime('%I:%M%p')}) + self.bus.emit(Message("recognizer_loop:utterance", + {"utterances": [phrase], "lang": "en-us"})) + return + + #~~ CREATE TIMER + self.timer_index += 1 + time_expires = datetime.now() + timedelta(seconds=secs) + timer = {"name": timer_name, + "index": self.timer_index, + # keep track of ordinal until all timers of that name expire + "ordinal": self._get_ordinal_of_new_timer(secs), + "duration": secs, + "expires": time_expires, + "announced": False, + "is_interval":True} + self.active_timers.append(timer) + self.log.debug("-------------TIMER-CREATED-------------") + for key in timer: + self.log.debug('creating inverval timer: {}: {}'.format(key, timer[key])) + self.log.debug("---------------------------------------") + + #~~ INFORM USER + if timer['ordinal'] > 1: + dialog = 'started.ordinal.timer' + else: + dialog = 'started.interval.timer' + if timer['name'] is not None: + dialog += '.with.name' + + self.speak_dialog(dialog, + data={"duration": nice_duration(timer["duration"]), + "name": timer["name"], + "ordinal": self._get_speakable_ordinal(timer)}) + + #~~ CLEANUP + self.pickle() + wait_while_speaking() + self.enable_intent("handle_mute_timer") + # Start showing the remaining time on the faceplate + self.update_display(None) + # reset the mute flag with a new timer + self.mute = False + + # Handles custom start phrases eg "ping me in 5 minutes" + # Also over matches Common Play for "start timer" utterances + @intent_file_handler('start.interval.timer.intent') + def handle_start_interval_timer_padatious(self, message): + self.handle_start_interval_timer(message) + + @intent_file_handler('stop.interval.timer.intent') + def handle_stop_interval_timer(self, message): + timer = self._get_next_timer() + if timer and timer["expires"] < datetime.now(): + # Timer is beeping requiring no confirmation reaction, + # treat it like a stop button press + self.stop() + elif message and message.data.get('utterance') == "cancel": + # No expired timers to clear + # Don't cancel active timers with only "cancel" as utterance + return + else: + self.handle_cancel_timer(message) + + # Handles custom start phrases eg "ping me in 5 minutes" # Also over matches Common Play for "start timer" utterances @intent_file_handler('start.timer.intent') diff --git a/dialog/en-us/started.interval.timer.dialog b/dialog/en-us/started.interval.timer.dialog new file mode 100644 index 0000000..bd9607a --- /dev/null +++ b/dialog/en-us/started.interval.timer.dialog @@ -0,0 +1,3 @@ +An interval timer has started for every {duration} +Alright, I've set an interval timer for every {duration} +I'm starting an interval timer for every {duration} diff --git a/vocab/en-us/interval.timer.status.intent b/vocab/en-us/interval.timer.status.intent new file mode 100644 index 0000000..bd9607a --- /dev/null +++ b/vocab/en-us/interval.timer.status.intent @@ -0,0 +1,3 @@ +An interval timer has started for every {duration} +Alright, I've set an interval timer for every {duration} +I'm starting an interval timer for every {duration} diff --git a/vocab/en-us/start.interval.timer.intent b/vocab/en-us/start.interval.timer.intent new file mode 100644 index 0000000..52fb2ea --- /dev/null +++ b/vocab/en-us/start.interval.timer.intent @@ -0,0 +1,5 @@ +(another|one more|second|third|fourth|fifth|) timer ((for every |){duration}|) +ping me every {duration} +(set|start|create) (a|) interval timer +(set|start|create) (a|) interval timer for every {duration} +(set|start|create) (a|) {duration} interval timer diff --git a/vocab/en-us/stop.interval.timer.intent b/vocab/en-us/stop.interval.timer.intent new file mode 100644 index 0000000..e14b07e --- /dev/null +++ b/vocab/en-us/stop.interval.timer.intent @@ -0,0 +1,7 @@ +(stop|end|cancel|abort|delete|kill|turn off) (all|the|) (interval timer|interval timers) +turn it off +silence +shutup +shut up +I got it +end timer From 1048af4c72d6d8d908f677a9ea0285db058d6acb Mon Sep 17 00:00:00 2001 From: wamsachel Date: Tue, 7 Jul 2020 03:38:32 +0100 Subject: [PATCH 2/4] changes after testing --- __init__.py | 5 ++--- vocab/en-us/start.interval.timer.intent | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 0f14f6b..f1d0e30 100644 --- a/__init__.py +++ b/__init__.py @@ -735,14 +735,13 @@ def validate_duration(string): for key in timer: self.log.debug('creating inverval timer: {}: {}'.format(key, timer[key])) self.log.debug("---------------------------------------") - #~~ INFORM USER if timer['ordinal'] > 1: dialog = 'started.ordinal.timer' else: dialog = 'started.interval.timer' - if timer['name'] is not None: - dialog += '.with.name' + # if timer['name'] is not None: + # dialog += '.with.name' self.speak_dialog(dialog, data={"duration": nice_duration(timer["duration"]), diff --git a/vocab/en-us/start.interval.timer.intent b/vocab/en-us/start.interval.timer.intent index 52fb2ea..bcc96ae 100644 --- a/vocab/en-us/start.interval.timer.intent +++ b/vocab/en-us/start.interval.timer.intent @@ -1,4 +1,5 @@ (another|one more|second|third|fourth|fifth|) timer ((for every |){duration}|) +(set|start|create) (a|) timer ((for every |){duration}|) ping me every {duration} (set|start|create) (a|) interval timer (set|start|create) (a|) interval timer for every {duration} From 40eb32a2298f895b1dc35629105be6a27c0f0163 Mon Sep 17 00:00:00 2001 From: wamsachel Date: Wed, 7 Oct 2020 12:00:00 -0600 Subject: [PATCH 3/4] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e32a2b4..cfcf4c6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Set named timers for cooking, watering plants, brewing tea and more. ## About -Use Mycroft when your hands are messy or you need more that the one timer in your kitchen. Multiple timers are easy to set and track with conversational interactions. +Use Mycroft when your hands are messy or you need more that the one timer in your kitchen. Multiple timers are easy to set and track with conversational interactions. Interval timers will play a single beep before restarting the timer again. On a Mark 1 you'll see visual feedback as the timer runs, and you can use the top button to stop the beeping once a timer expires. @@ -17,6 +17,10 @@ the top button to stop the beeping once a timer expires. * "How long is left on the turkey timer?" * "Mute the timer" (once triggered) +* "Start an interval timer for every 30 seconds" +* "Set a 10 minute interval timer" +* "Ping me every minute" + ## Credits Mycroft AI (@MycroftAI) From e33f51a5c386fc9d84c84003ba88816dff44248a Mon Sep 17 00:00:00 2001 From: wamsachel Date: Thu, 8 Oct 2020 17:39:38 +0100 Subject: [PATCH 4/4] adding interval specific dialog --- __init__.py | 24 ++++++++++--------- dialog/en-us/ask.how.long.interval.dialog | 1 + .../started.ordinal.interval.timer.dialog | 3 +++ vocab/en-us/start.interval.timer.intent | 7 +++--- 4 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 dialog/en-us/ask.how.long.interval.dialog create mode 100644 dialog/en-us/started.ordinal.interval.timer.dialog diff --git a/__init__.py b/__init__.py index f1d0e30..df67ed5 100644 --- a/__init__.py +++ b/__init__.py @@ -675,8 +675,7 @@ def _check_duplicate_timer_name(self, name): return True return False - @intent_handler(IntentBuilder("start.interval.timer").require("Timer") - .require("Start").optionally("Connector")) + @intent_handler('start.interval.timer.intent') def handle_start_interval_timer(self, message): """ Common handler for start_interval_timer intents """ @@ -692,20 +691,23 @@ def validate_duration(string): utt_remaining = message.data["utterance"] if secs == None: # no duration found, request from user - req_duration = self.get_response('ask.how.long', + req_duration = self.get_response('ask.how.long.interval', validator=validate_duration) secs, _ = self._extract_duration(req_duration) if secs is None: return # user cancelled #~~ GET TIMER NAME - if utt_remaining is not None and len(utt_remaining) > 0: - timer_name = self._get_timer_name(utt_remaining) - if timer_name: - if self._check_duplicate_timer_name(timer_name): - return # make another timer with a different name - else: - timer_name = None + # START WIP - Not worried about timer names for now + #if utt_remaining is not None and len(utt_remaining) > 0: + # timer_name = self._get_timer_name(utt_remaining) + # if timer_name: + # if self._check_duplicate_timer_name(timer_name): + # return # make another timer with a different name + #else: + # timer_name = None + timer_name = None + # END WIP #~~ SHOULD IT BE AN ALARM? # TODO: add name of alarm if available? @@ -737,7 +739,7 @@ def validate_duration(string): self.log.debug("---------------------------------------") #~~ INFORM USER if timer['ordinal'] > 1: - dialog = 'started.ordinal.timer' + dialog = 'started.ordinal.interval.timer' else: dialog = 'started.interval.timer' # if timer['name'] is not None: diff --git a/dialog/en-us/ask.how.long.interval.dialog b/dialog/en-us/ask.how.long.interval.dialog new file mode 100644 index 0000000..2979807 --- /dev/null +++ b/dialog/en-us/ask.how.long.interval.dialog @@ -0,0 +1 @@ +How long of an interval timer? diff --git a/dialog/en-us/started.ordinal.interval.timer.dialog b/dialog/en-us/started.ordinal.interval.timer.dialog new file mode 100644 index 0000000..7b74569 --- /dev/null +++ b/dialog/en-us/started.ordinal.interval.timer.dialog @@ -0,0 +1,3 @@ +{ordinal} interval timer started for {duration} +Alright, I've set a {ordinal} interval timer for {duration} +I'm starting a {ordinal} interval timer for {duration} diff --git a/vocab/en-us/start.interval.timer.intent b/vocab/en-us/start.interval.timer.intent index bcc96ae..fd6d818 100644 --- a/vocab/en-us/start.interval.timer.intent +++ b/vocab/en-us/start.interval.timer.intent @@ -1,6 +1,5 @@ -(another|one more|second|third|fourth|fifth|) timer ((for every |){duration}|) -(set|start|create) (a|) timer ((for every |){duration}|) +(another|one more|second|third|fourth|fifth|) interval timer ((for every |){duration}|) ping me every {duration} -(set|start|create) (a|) interval timer -(set|start|create) (a|) interval timer for every {duration} +(set|start|create) (an|) interval timer +(set|start|create) (an|) interval timer for every {duration} (set|start|create) (a|) {duration} interval timer