Tuesday, October 03, 2017

Making the Martian Notifier Watch More Useful - Implementing Step and Time Based Alerts

Despite the far more capable Zenwatch 3 on my desk, I do find myself turning to the relatively primitive Martian Notifier surprisingly often. It does a reasonable job at keeping me aware of notifications, and has far better battery life. If I know I'm going to be away from an outlet for more than 12 hours, I'll definitely learn towards using the Martian.

One feature I miss on my Martian is the ability to easily set a timer which will trigger a notification on my wrist. Thankfully, this is easy functionality to implement in Tasker. While I was at it, I made two different types of timers: one to support a number of minutes and another to support a number of steps. The latter I intend to use while hiking or jogging.

The strategy I for these timers was to build out two sets of tasks: one for registering events and one for triggering the events.

The registering side of things use the 'Get Voice' action to ask for a number of minutes, or steps, along with a label. It then adds this information into a global queue variable. For the time based reminders, I convert the minutes to seconds, and then add %TIMES (that's Unix time) to this value. The result is an absolute time in the future when the reminder needs to happen. In other words, if I want to trigger a reminder in 5 minutes, then I'll add 5 x 60 to the current unix time, which happens to be 1507059578.

Steps work off the same principle, but instead of using %TIMES, I rely on the global variable %STEPS_EVER. This variable is set to be constantly updated every 10 steps.

One last feature of the 'register' side actions is that I also add an entry for half the time or steps into the queue variable. This allows me to trigger a notification telling me I'm half way to my goal. (Who doesn't love a little computer generated positive feedback?)

Here's the code for the register side of things:

Register Minutes Notifier (203)
 A1: Say [ Text:How many minutes? Engine:Voice:default:default Stream:3 Pitch:5 Speed:5 Respect Audio Focus:On Network:Off Continue Task Immediately:Off ] 
 A2: Get Voice [ Title:How Many Minutes? Language Model:Free Form Language: Maximum Results:1 Timeout (Seconds):30 ] 
 A3: Variable Set [ Name:%nmins To:%VOICE Recurse Variables:Off Do Maths:On Append:Off ] 
 A4: Say [ Text:Label? Engine:Voice:default:default Stream:3 Pitch:5 Speed:5 Respect Audio Focus:On Network:Off Continue Task Immediately:Off ] 
 A5: Get Voice [ Title:Label? Language Model:Free Form Language: Maximum Results:1 Timeout (Seconds):30 ] 
 A6: Variable Set [ Name:%nsecs To:%nmins * 60 Recurse Variables:Off Do Maths:On Append:Off ] 
 A7: Variable Set [ Name:%label To:%VOICE Recurse Variables:Off Do Maths:Off Append:Off ] 
 A8: Variable Set [ Name:%half To:(%nsecs / 2) + %TIMES Recurse Variables:Off Do Maths:On Append:Off ] 
 A9: Variable Set [ Name:%full To:(%nsecs) + %TIMES Recurse Variables:Off Do Maths:On Append:Off ] 
 A10: Array Push [ Variable Array:%MINUTES_NOTIFICATION_QUEUE Position:1 Value:%full;%label Fill Spaces:Off ] 
 A11: Array Push [ Variable Array:%MINUTES_NOTIFICATION_QUEUE Position:1 Value:%half;half way to %label Fill Spaces:Off ] 
 A12: Say [ Text:Got it. Notifying you about %label in %nmins minutes. Engine:Voice:default:default Stream:3 Pitch:5 Speed:5 Respect Audio Focus:On Network:Off Continue Task Immediately:Off ] 

Register Steps Notifier (184)
 A1: Say [ Text:How many steps? Engine:Voice:default:default Stream:3 Pitch:5 Speed:5 Respect Audio Focus:On Network:Off Continue Task Immediately:Off ] 
 A2: Get Voice [ Title:How Many Steps? Language Model:Free Form Language: Maximum Results:1 Timeout (Seconds):30 ] 
 A3: Variable Set [ Name:%nsteps To:%VOICE Recurse Variables:Off Do Maths:On Append:Off ] 
 A4: Say [ Text:Label? Engine:Voice:default:default Stream:3 Pitch:5 Speed:5 Respect Audio Focus:On Network:Off Continue Task Immediately:Off ] 
 A5: Get Voice [ Title:Label? Language Model:Free Form Language: Maximum Results:1 Timeout (Seconds):30 ] 
 A6: Variable Set [ Name:%label To:%VOICE Recurse Variables:Off Do Maths:Off Append:Off ] 
 A7: Variable Set [ Name:%half To:(%nsteps / 2) + %STEPS_EVER Recurse Variables:Off Do Maths:On Append:Off ] 
 A8: Variable Set [ Name:%full To:(%nsteps) + %STEPS_EVER Recurse Variables:Off Do Maths:On Append:Off ] 
 A9: Array Push [ Variable Array:%STEP_NOTIFICATION_QUEUE Position:1 Value:%full;%label Fill Spaces:Off ] 
 A10: Array Push [ Variable Array:%STEP_NOTIFICATION_QUEUE Position:1 Value:%half;half way to %label Fill Spaces:Off ] 
 A11: Say [ Text:Got it. Notifying you about %label in %nsteps steps. Engine:Voice:default:default Stream:3 Pitch:5 Speed:5 Respect Audio Focus:On Network:Off Continue Task Immediately:Off ] 

Once the absolute step or time value is registered, I need another set of tasks to continually scan the queue, looking for entries that need to trigger a notification. I wrote two different 'tick' tacks which perform this loop through the relevant array once. The array and string handling of variables offered by Tasker is downright primitive. But, it also worked just fine for my needs.

It's hard to see it from the code below, but this is really a simple for loop that's checking to see if the current time or step count exceeds the absolute value that was previously registered.

Tick Minutes Notifier (204)
 A1: Variable Set [ Name:%pos To:1 Recurse Variables:Off Do Maths:Off Append:Off ] 
 A2: For [ Variable:%item Items:%MINUTES_NOTIFICATION_QUEUE() ] 
 A3: Variable Split [ Name:%item Splitter:; Delete Base:Off ] 
 A4: Variable Set [ Name:%when To:%item1 Recurse Variables:Off Do Maths:Off Append:Off ] 
 A5: Variable Set [ Name:%label To:%item2 Recurse Variables:Off Do Maths:Off Append:Off ] 
 A6: Variable Set [ Name:%offset To:%when - %TIMES Recurse Variables:Off Do Maths:On Append:Off ] 
 A7: If [ %offset = 0 | %offset < 0 ]
 A8: Notify [ Title:%label Text: Icon:ipack:crystalhd:cnr Number:0 Permanent:Off Priority:3 ] 
 A9: Array Pop [ Variable Array:%MINUTES_NOTIFICATION_QUEUE Position:%pos To Var: ] 
 A10: End If 
 A11: Variable Add [ Name:%pos Value:1 Wrap Around:0 ] 
 A12: End For 

Tick Steps Notifier (202)
 A1: Variable Set [ Name:%pos To:1 Recurse Variables:Off Do Maths:Off Append:Off ] 
 A2: For [ Variable:%item Items:%STEP_NOTIFICATION_QUEUE() ] 
 A3: Variable Split [ Name:%item Splitter:; Delete Base:Off ] 
 A4: Variable Set [ Name:%when To:%item1 Recurse Variables:Off Do Maths:Off Append:Off ] 
 A5: Variable Set [ Name:%label To:%item2 Recurse Variables:Off Do Maths:Off Append:Off ] 
 A6: Variable Set [ Name:%offset To:%when - %STEPS_EVER Recurse Variables:Off Do Maths:On Append:Off ] 
 A7: If [ %offset = 0 | %offset < 0 ]
 A8: Notify [ Title:%label Text: Icon:ipack:crystalhd:cnr Number:0 Permanent:Off Priority:3 ] 
 A9: Array Pop [ Variable Array:%STEP_NOTIFICATION_QUEUE Position:%pos To Var: ] 
 A10: End If 
 A11: Variable Add [ Name:%pos Value:1 Wrap Around:0 ] 
 A12: End For 

The final bit of magic is to invoke these tick functions on a regular basis. This is easily done by setting up two profiles, one that's time based and one that's step based:

I opted to use the Tasker widget to invoke the various register tasks from a home screen. This means that I'm only one button press away from queueing up a time or step based reminder. It's not as sexy as an AndroidWear solution, but so far, it's been working well. And besides, it's Tasker, so I'm sure I'll come up with some clever addons to this basic functionality.

No comments:

Post a Comment