Showing posts with label parkrun. Show all posts
Showing posts with label parkrun. Show all posts

Friday, 2 December 2016

Garmin ConnectIQ - First Attempt at Using the SDK

The Garmin Forerunner 910XT that I have previously blogged about using died a death a couple of weeks ago so I decided to buy a Garmin Fenix 3 which has roughly the same feature set.  The big selling point for a geek like me was the Connect IQ capability that means you can write apps for the watch.  Geektastic!

Overall ConnectIQ let's you write:

  • Fully blown apps
  • Watch faces
  • Widgets
  • Data fields

...so really customising your Garmin product.

Some resources I used to get myself setup:


In particular I spent a long time going through the Getting Started section of the Programmer's Guide.  I'm generally pretty gung ho and like to go it alone but it was worth going through this slowly step-by-step.  I chose to use Eclipse (Luna) and followed the tutorial to produce a simple watch face and load it into the emulator. I won't repeat the steps here as the ones Garmin provide are extremely good.

I then decided to modify the watch face in some way to learn more about ConnectIQ.  My idea was to provide a "countdown until parkrun" watch face.  Keen readers will know I like a bit of parkrun so I decided to do a watch face that both shows you the current time and, as time passes, counts down the days, minutes, hours and seconds until parkrun.

To modify the watch face I just had to change two files within the Eclipse project.  These are selected on the Project Explorer view below.

In simple terms, layout.xml defines the layout of the watch face and the xxxView.mc file contains the code required to modify aspects of the watch face.

For the layout I decided to have a simple one of:

  • The current time at the top
  • Then a count down until parkrun
  • Then some text to say "until parkrun"
  • Then some form of "motivational" slogan

The layout.xml for this looks like:

<layout id="WatchFace">
    <label id="TimeLabel" x="center" y="50" font="Gfx.FONT_LARGE" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_BLUE" />
    <label id="TimeToParkrunLabel" x="center" y ="100" font="Gfx.FONT_SMALL" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_RED" />
    <label id="ParkrunTextLabel" x="center" y ="125" font="Gfx.FONT_SMALL" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_RED" /> 
    <label id="SloganTextLabel" x="center" y ="160" font="Gfx.FONT_SMALL" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_GREEN" />
</layout>

Here we see four labels with their name, position on screen, font size and colour defined.

Without changing anything in the view.mc file, this function references the layout file and tells ConnectIQ what to draw on screen:

function initialize() {
        WatchFace.initialize();
    }

    // Load your resources here
    function onLayout(dc) {
        setLayout(Rez.Layouts.WatchFace(dc));
    } 

Where the WatchFace reference links to the layout.xml contents.

The first function I changed was the "onUpdate" one.  Here it is:

    // Update the view
    function onUpdate(dc) {
        // Get and show the current time
        var clockTime = Sys.getClockTime();
        var timeString = Lang.format("$1$:$2$:$3$", [clockTime.hour, clockTime.min.format("%02d"),clockTime.sec.format("%02d")]);
        var view = View.findDrawableById("TimeLabel");
        view.setText(timeString);
        
        //Time to park run
        var TimeToParkrunStr = CalcTimeToParkrun();
        var ParkrunTimeView = View.findDrawableById("TimeToParkrunLabel");
        ParkrunTimeView.setText(TimeToParkrunStr);
        
        //Added by Paul Weeks
        var ParkrunLabelStr = "...until next parkrun!";
        var ParkrunLabelView = View.findDrawableById("ParkrunTextLabel");
        ParkrunLabelView.setText(ParkrunLabelStr);
        
        //This is a slogan at the bottom.  Could change dynamically in future
        var SloganStr = "#DFYB";
        var SloganLabelView = View.findDrawableById("SloganTextLabel");
        SloganLabelView.setText(SloganStr);
        
        // Call the parent onUpdate function to redraw the layout
        View.onUpdate(dc);
    }

Here we can see some simple concepts that show how to update the screen and show the time.  Breaking it down:

1)Create a variable to hold the current time:
var clockTime = Sys.getClockTime();

2)Turn this into a string.  The Lang.format method can be used for this.  Here we see it used to create a string with three components (referenced as $1$:$2$:$3$) formed from the hour, second and min part of the time.  The %02d part simply puts a leading zero on to pad numbers less than 10:

var timeString = Lang.format("$1$:$2$:$3$", [clockTime.hour, clockTime.min.format("%02d"),clockTime.sec.format("%02d")]);

3)Creating a "view variable" that links to layout.xml and then updating this with the time text:
var view = View.findDrawableById("TimeLabel");
view.setText(timeString);

But all the heavy lifting I did was related to the "countdown until parkrun" part.  Here you can see I call out to another function called "CalcTimeToParkrun".  It was really slow going writing this!

Unlike with other languages like Python there's not a mass of examples an tutorials on the internet or entries on Stack Overflow.  Instead it was a case of using the API reference and trial and error to get things working.  I learnt a lot!

Here's the function:

//Calculate time until parkrun
//Algorithm may be a little clunky.  Refine over time
function CalcTimeToParkrun() {
  //Some constants
  var parkrunDay = 7;     //Parkrun on Saturday.  Could be setting in future
  var parkrunTime = 9;    //Parkrun at 0900.  Could be setting in future
        
  //Need to calculate the next parkrun day at parkrun time and then work out the difference between then and now.  
  var now = Time.now();
  var info = Calendar.info(now, Time.FORMAT_SHORT);
        
  //Format_short means day of week is a number.  1 for Sunday, 2 for Monday etc.
  var dayStr = Lang.format("$1$", [info.day_of_week]);   
  var hourStr = Lang.format("$1$", [info.hour]);
        
  //Might be useful, shows how to format a date
  //var dateStr = Lang.format("$1$ $2$ $3$", [info.day_of_week, info.month, info.day]);
        
  //Have a look at the day of week.  The actual day is a special day as parkrun might either be that day or next week.  
  //Saturday is day 7.
  var dayNum = dayStr.toNumber();   //Turn day to an actual number
  var hourNum = hourStr.toNumber(); //Turn hour into actual number
        
  //What we need to do is calculate how many days to parkrun.  Saturday is the key case, i.e. assessing before or after
  //parkrun time.
  var daysToPR = -1;
  if ((dayNum < parkrunDay) || ((dayNum == parkrunDay) && (hourNum < parkrunTime))){
     daysToPR = 7 - dayNum;
     }
  else {
     daysToPR = 7;
     }
          
  //Create a moment that represents midnight today
  var todayDict = {:day => info.day.toNumber(), :month => info.month.toNumber(), :year => info.year.toNumber()};
  var todayMoment = Calendar.moment(todayDict);
          
  //Create a duration of the number of days until parkrun + hours until parkrun
  var durDict = {:days => daysToPR, :hours => parkrunTime};          
  var myDuration = Calendar.duration(durDict);
          

  //Add the number of days and hours to midnight today to get a moment that represents parkrun start time
  var parkrunMoment = myDuration.add(todayMoment);
         
  //Subtract now from when parkrun is to get a duration until parkrun.  .value turns it into seconds
  var durationTillParkrun = parkrunMoment.subtract(now).value(); 
          
  //So now we have seconds until parkrun. Need to turn into days, hours and mins
  //This seems like hard yards but can't find a better way...
  var daysTillParkrun = durationTillParkrun / 86400;
  var hoursTillParkrun = (durationTillParkrun - (daysTillParkrun * 86400)) / 3600; 
  var minsTillParkrun = (durationTillParkrun - ((daysTillParkrun * 86400)+(hoursTillParkrun * 3600))) / 60;
  var secsTillParkrun = (durationTillParkrun - ((daysTillParkrun * 86400)+(hoursTillParkrun * 3600)+(minsTillParkrun * 60)));
                                        
  //Return 
  return daysTillParkrun.toString() + " days " + hoursTillParkrun.toString() + ":" + minsTillParkrun.toString() + ":" + secsTillParkrun.toString();  
}

The algorithm is pretty simple.  It's as follows:

  • Get a number associated with the day of week.  So Sunday = 1, Monday = 2 etc.
  • Calculate "days until parkrun".  In general this is 7 - Day of week number.  However there's an extra decision to make on parkrun day as to whether it's before or after parkrun (and so mere minutes to go or several days).
  • Calculate a duration* which is from midnight today plus the number of full days until parkrun plus the number of hours to wait on parkrun day.
  • Calculate a moment** which is the actual date and time of parkrun.
  • Calculate the difference between now and the date and time of parkrun.

*A duration is a period of time in Garmin Connect IQ.  So these two lines of code define a duration:
var durDict = {:days => daysToPR, :hours => parkrunTime};          
var myDuration = Calendar.duration(durDict);

So the dictionary defines the number of days and hours for the duration, then the duration is calculated.

**A moment is a moment in time in Garmin Connect IQ.  These two lines of code define a moment:
  var todayDict = {:day => info.day.toNumber(), :month => info.month.toNumber(), :year => info.year.toNumber()};
  var todayMoment = Calendar.moment(todayDict); 

Again the dictionary defines the parameters for the moment and then the moment is created.

You can then do calculations based upon durations and moments:
var parkrunMoment = myDuration.add(todayMoment);

..and so the big reveal, how does it look in the Eclipse simulator?


...hmmm, yes you're right, somewhere between awful and terrible!

Playing with the layout file I changed it to be:

<layout id="WatchFace">
  <label id="TimeLabel" x="center" y="15" font="Gfx.FONT_NUMBER_THAI_HOT" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_WHITE" />
  <label id="TimeToParkrunLabel" x="center" y ="115" font="Gfx.FONT_MEDIUM" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_WHITE" />
  <label id="ParkrunTextLabel" x="center" y ="140" font="Gfx.FONT_MEDIUM" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_WHITE" /> 
  <label id="SloganTextLabel" x="center" y ="175" font="Gfx.FONT_SMALL" justification="Gfx.TEXT_JUSTIFY_CENTER" color="Gfx.COLOR_WHITE" />
</layout>

So all white text and bigger fonts.  The "THAI_HOT" font is a built in Garmin font.  I also played with the simulator settings to specify that a Fenix 3 simulator be used:


...and now it looks a lot better in the simulator:


...and it looks vaguely OK on my wrist after side-loading the application!


So what have I learnt:
  • Real estate is at a premium on a Garmin watch.  Just 218 x 218 pixels to play with!
  • You need to think very carefully as to what to put on the very small screen.
  • I need to explore sleep mode as the watch counts down the seconds for about 10 seconds then stops and just updates once a minute.  Lots of wrist waggling is required to get it to start counting down again.  I know this is to save battery but the default watch faces both have a seconds component so it must be possible to get a continuous countdown.





Saturday, 29 October 2016

Resting Heart Rate and Fitness

Previously I've done plenty of posts on using Fitbit Heart Rate data, Strava data and suchlike to assess my fitness.

Two things I've spotted recently:

  1. My resting heart rate seems to be decreasing, as shown on my Fitbit Charge HR.
  2. I seem to be running consistently faster at parkrun.

Conventional wisdom is that a lower heart rate represents improved fitness.  So to find out whether the two are linked...

The analysis was pretty simple.  First I just scraped monthly average resting heart rate data from my Fitbit app and noted it in Excel (I didn't think I'd need the power or R for this analysis).  This nicely smooths out day-to-day variations in heart rate and shows some decent trends.  Example:


I also scraped all parkrun results from the parkrun website.  I chose to use parkrun for this analysis as it's the same distance run at the same time every week in (almost) the same place.  There are some variables that could affect my time (e.g. if it's muddy underfoot, the odd bit of tourism) but these things should cancel themselves out if you take enough data points and allow trends to be spotted.  An example of the data:

With some Excel jiggery-pokery I managed to get resting heart rate (blue line) and park run times (orange dots) on the same chart.  Here it is:



I like this chart as it tells a real story of the correlation between heart rate and fitness (or low parkrun time).

  • On the left hand side you can see when I first got my Fitbit, my resting heart rate was between 65 and 70 BPM and my parkrun time was ~21 minutes.  
  • I then got injured in summer 2015 and there was a gap when I didn't run at all.  At the end of this my resting heart rate was over 70 BPM so I was relatively unfit.
  • I then made a comeback in Autumn 2015, started running regularly and by Spring 2016 had a sub 60 resting heart rate and was running sub 20 minute parkruns.
  • Then the summer 2016 came and for various reasons (holidays, kids' activities, doing cycling) and my heart rate crept up to nearly 60 BPM and my parkrun time went back to the ~21 minute realm.
  • Then most recently I've done a strong block of focused running training, my heart rate is at 55 BPM (lowest ever recorded) and I'm back to 20 minute and sub 20 minute parkruns.

I love graphs like this!  My view is that running is an "honest" sport, the more you put in the more you get out, and this graph underlines this point.