Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/issue 74 #75

Open
wants to merge 5 commits into
base: 20.02
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)

Expand Down
113 changes: 113 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -667,6 +675,111 @@ def _check_duplicate_timer_name(self, name):
return True
return False

@intent_handler('start.interval.timer.intent')
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.interval',
validator=validate_duration)
secs, _ = self._extract_duration(req_duration)
if secs is None:
return # user cancelled

#~~ GET TIMER NAME
# 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?
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.interval.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')
Expand Down
1 change: 1 addition & 0 deletions dialog/en-us/ask.how.long.interval.dialog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
How long of an interval timer?
3 changes: 3 additions & 0 deletions dialog/en-us/started.interval.timer.dialog
Original file line number Diff line number Diff line change
@@ -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}
3 changes: 3 additions & 0 deletions dialog/en-us/started.ordinal.interval.timer.dialog
Original file line number Diff line number Diff line change
@@ -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}
3 changes: 3 additions & 0 deletions vocab/en-us/interval.timer.status.intent
Original file line number Diff line number Diff line change
@@ -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}
5 changes: 5 additions & 0 deletions vocab/en-us/start.interval.timer.intent
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(another|one more|second|third|fourth|fifth|) interval timer ((for every |){duration}|)
ping me every {duration}
(set|start|create) (an|) interval timer
(set|start|create) (an|) interval timer for every {duration}
(set|start|create) (a|) {duration} interval timer
7 changes: 7 additions & 0 deletions vocab/en-us/stop.interval.timer.intent
Original file line number Diff line number Diff line change
@@ -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