Creating Weekday-only iOS Reminders

Here’s a quick tip; have you ever wanted to create a daily, but weekday-only, repeating task in iOS Reminders? The UI is rather limited but the functionality does exist.

Siri to the rescue!

Simply tell Siri something like:

Remember to pack a lunch on weekdays

Siri will offer to create the reminder for you and it will have a custom repeat setting. Once created you wont be able to view/edit the repeat settings in Reminders, but thats not a huge deal since it’s simple enough to ask Siri to create you a new one!

Bacon-Apple-Cheddar Burgers

Because recipes are yet another form of data and good food makes the fleshy part of our being very happy, here’s a rough recipe I made up that I loosely follow when we need something easy-yet-special on the grill.

Ingredients

  • 1 medium apple, chopped to 1/4-inch cubes
  • 1 lb ground beef
  • 1 small onion - chopped
  • 1 egg
  • 5 slices bacon, chopped
  • 3/4 cup shredded cheddar, the sharper the better
  • parsley

Directions

The directions are wicked-easy: thoroughly mix all the indredients, and make into burger patties. I find that making the burgers concave (as opposed to perfectly level) helps make them come out normal on the grill instead of turning into convex UFOs.

Grill and enjoy!

Most of the time I’ll eat them as-is with no extra condiments. If you must have something extra, I find that a nice honey mustard compliments the apples well.

Implementing the QS Data Spec on iOS

As part of implementing the Quantified Self Data Spec (look here for an introduction), I’ve started implementing it on iOS so that I’m living with it every day. The implementation I’m about to outline leverages the following technologies:

Workflow Overview

Launch Center Pro

Launch Center Pro is the perfect dashboard for testing (and even long-term running) of custom QS data collection efforts. You can set up multiple 1-touch actions to rapidly log many data points and have it integrate with other apps on your iOS device via URL-schemes if necessary. Notably, this process I’m using passes off to Drafts 4.

While it is possible to save files directly to Dropbox from Launch Center Pro, in order to fully support/implement the QS Data Spec I needed to be able to have a little more dynamic control over the JSON data before it was saved. You will see that my Launch Center Pro actions are missing the required uuid and timestamp JSON parameters. These will be inserted with Drafts later in the process.

I am going to demonstrate two basic workflow types: single-step (one touch) and multi-step. I’ve set up a emotional logger as the multi-step example, and a bathroom logger as the single-step example. For the bathroom logger, there are actually multiple single-step actions that I’ve grouped in to a folder, to make it more organized. I’m using single-step actions for this since it happens multiple times a day and I wanted to ensure it was as efficient as possible.

Launch Center Pro

Single-step Action (Bathroom log)

This one is very simple. We need to pass a URL-encoded version of the base JSON object for this datatype to Drafts for further processing. Here’s what the QS Data Spec says the JSON object should look like (full schema):

{
  "dataType": "consumption-waste",
  "dataSource": "manual",
  "uuid": "2b0b8801-c5e2-4b2d-a61a-31681eb95093",
  "timestamp": 1413555528,
  "urine": true,
  "feces": false
}

(Note: while the spec is very medical, I labeled things a little more politely in Launch Center Pro. i.e. feces -> BM)

As I mentioned previously, we will add the uuid and timestamp values dynamically within Drafts, so our Launch Center Pro action simply looks like this:

drafts4://x-callback-url/runAction?text=&action=&x-success=

This action is basically saying do the following steps:

  1. Launch Drafts 4
  2. Get ready to run an action
  3. Pass in the URL encoded text for the JSON object
  4. Run the ‘Log QuantifiedSelf Entry’ action
  5. On success, come back to Launch Center Pro

Multi-step Action (Emotional log)

Launch Center Pro - Emotional

The multi-step example follows the identical process as the single-step example except that it prompts the user for a few inputs before sending the JSON object over to Drafts. Here is what the QS Data Spec says the emotional object should look like (full schema):

{
  "dataType":"biometric-emotional",
  "dataSource":"manual",
  "energyLevel":5,
  "mood":3,
  "stressLevel":1,
  "uuid":"64759c11-e432-45a9-ad1b-a49b791549e9",
  "timestamp":1413765928
}

Our Launch Center Pro action, complete with user prompts to select the values of the three inputs looks like this:

drafts4://x-callback-url/runAction?text=&action=&x-success=

Bonus! Medical Log

You get the picture, right? So as a bonus we have the QS Data Spec example for medicine (full schema):

{
  "dataType": "consumption-drug",
  "dataSource": "manual",
  "uuid": "2b0b8801-c5e2-4b2d-a61a-31681eb95093",
  "timestamp": 1413555528,
  "drug": "Ibuprofen",
  "amount": 200,
  "measurementScale": "mg"
}

And the corresponding Launch Center Pro action, customized with entries for Ibuprofen and Ranitidine:

drafts4://x-callback-url/runAction?text=&action=&x-success=

Drafts 4

Drafts Action Overview

The quickest way to get this custom action into Drafts is from the Drafts Action Directory. I’ve posted it here.

The Drafts action is pretty straightforward. To contain a JavaScript file and a save to Dropbox action. To keep the files more organized until I can process them at a later date, I’ve told drafts to save the files in a date-based file system hierarchy: Logs/[year]/[month]/[day]/[UUID].json

The JavaScript is fairly simple itself. It basically sets the timestamp and UUID, and then saves the file to Dropbox with the UUID as the filename. It ends up looking more complicated because I had to paste in support-methods for UUID and JSON.

// Create the JSON object
var jsonString = decodeURIComponent(draft.content);
var j = JSON.parse(jsonString)

// Generate and set a UUID
var uuid = makeUUID();
j.uuid = uuid;

// Set the timestamp
j.timestamp = Math.round((new Date()).getTime() / 1000);

// Prep the Draft for saving to Dropbox in the next action step
draft.content = JSON.stringify(j);

// Send the UUID to the clipboard, so the next action can use it as the filename.
setClipboard(uuid);

// UUID support
function makeUUID(){

  var dec2hex = [];
  for (var i=0; i<=15; i++) {
    dec2hex[i] = i.toString(16);
  }

    var uuid = '';
    for (var i=1; i<=36; i++) {
      if (i===9 || i===14 || i===19 || i===24) {
        uuid += '-';
      } else if (i===15) {
        uuid += 4;
      } else if (i===20) {
        uuid += dec2hex[(Math.random()*4|0 + 8)];
      } else {
        uuid += dec2hex[(Math.random()*15|0)];
      }
    }

    return uuid;
}

// JSON support
var JSON;if(!JSON){JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\b","\t":"\t","\n":"\n","\f":"\f","\r":"\r",'"':'\"',"\":"\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==="string"){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[],:{}\s]*$/.test(text.replace(/\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\n\r]*"|true|false|null|-?\d+(?:.\d*)?(?:[eE][+-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());

The Beginning of a QS Data Spec

One of the frustrations I’ve had in dabbling with the Quantified Self movement is that there are a plethora of incredible ideas being shared, but no way to combine and replicate them easily. We need a data specification!

Yesterday I began thinking through what this may look like. I wanted it to be simple, flexible, as platform-agnostic as possible, and as extensible as possible. My thoughts quickly started to coagulate around JSON as the format, and a few high-level concepts to build upon.

I created a public Github repo to share my efforts. I welcome (and am sincerely hoping to attract) public comment and participation in drafting this specification. Specifically I’ve already filed an issue requesting comments from anyone who is interested in becoming involved.

While I’m writing up this specification I am testing it out as well; I don’t want it drafted in isolation. I’ve got a simple workflow running on my iPhone which I’ll post about shortly, with full code of course! I’ll also be creating and sharing some scripts to test pulling data from various services and storing it in this format.

How to Get Involved

  1. Head over to Github and read the rather short intro to the QuantifiedSelf Data Spec
  2. Post comments, questions, and suggestions to the issue queue
  3. If you’re really motivated fork the repo, make some updates, and send me a pull request!

SublimeText and OSX Yosemite

Here’s a quick post with two links for increasing your Sublime Text enjoyment on OS X Yosemite.

Sublime Text on Yosemite

Making Sublime Text look a bit more at home in Yosemite is easy with:

A new theme: El Capitan

And a new icon:Sublime Text Yosemite Icon

Enjoy!