A nice Pebble countdown watchface [Tutorial]

Posted on Posted in Development, Mobile.SMART
  • Time to read: about 20 minutes.

date countdown for pebble watchYesterday, my wife just called me and asked me if I saw the last picture posted online by a friend of mine. I wasn’t aware of which post, because I was on going back home from the office, so she told me that he has a new watchface for his Vector smartwatch: a very nice countdown with total amount of days until he’s moving in his new house.

So, her request was very natural: can you make me a similar watchface? Yeah… we just bought our first house, but she is still counting the days until we’re moving in. That’s why she was very interested in having a such countdown watchface.

This is how I started writing this post during the way to my office.

 

If you want to see how the final result might look like, here is a screenshot.

date countdown watchface result

However, in this article, we will cover only the first steps of how you can make your own date countdown watchface with no settings screen for it: the countdown date of the event you’re waiting for will be hard-coded. I have to admit that you might find a very good project on GitHub that can be configurable for different kind of events: if you have already experience in creating watchapps or watchfaces for Pebble here you have to go – https://github.com/mcongrove/PebbleDateCountdown.

 

Environment. Tools. CloudPebble

In order to achieve our goal, we will make use of Pebble cloud platform for developers – cloudpebble.net. To get started, please go to CloudPebble platform and sign in using your own pebble account if you have one, or just create  new one. If you don’t feel comfortable with the platform, I recommend you to start some introductive tutorials from Pebble Developers Portal to get familiar with CloudPebble platform.

 

The Application. Steps.

There are few necessary steps we have to make.

  1. Create a new empty C project (watchface configuration)
  2. Setup basic application structure
  3. Display the current time
  4. Display the countdown for the specific event
  5. Add an icon / image to our watchface

 

1. Create a new empty C project

Go to Projects section and click the Create button (a red one).

create new pebble c countdown project

Give your project a name, make sure you select Pebble C SDK as project type, SDK 3 as SDK version and instead of an existing template, make sure you select Empty Project.

Now, from the left panel menu, go to Settings and let’s change the APP KIND property to be Watchface instead of Wactchapp.

pebble-project-settings-watchface

Well, from this point, our project is almost ready. We don’t have any file associated to this project so let’s do that by using the “Add New” button from “Source files” section – left panel menu.

pebble project add new source file

The file type is “C file”, the filename I chose to be is “main.c” and I selected also the “Create header” option. Click the Create button and wait until the new file is created.

 

2. Setup basic application structure

As any application created on any platform, there is a structure we should implement. So, first things first.

Let’s edit our “main.c” file and add the following code.

#include <pebble.h>
#include "main.h"

static void init() {

}

static void deinit() {

}

int main(void) {
  init();
  app_event_loop();
  deinit();
}

Being a standard C application, our app is implementing the main function. Then we call for an “init()” function where we initialize our app,  call of “app_event_loop()” which makes the system to wait until the application is closed and “deinit()” used to clean up our app to free the memory.

Let’s fill the content with some application logic. Firstly, we have to add two new pointers just below our header and then we add 2 new functions above the init function, just like below:

#include <pebble.h>
#include "main.h"

// We have to render a main window
static Window *s_main_window;
// The Time layer (text)
static TextLayer *s_time_layer;

// UI. Everything we load is here. Texts, layers, graphics etc.
static void main_window_load(Window *window) {

}

// UI. Everything we created earlier, we destroy here.
static void main_window_unload(Window *window) {

}

// CORE. Application init. Create the window, handlers, subscribe to timer sercice...
static void init() {
  // Create main Window element and assign to pointer
  s_main_window = window_create();
  // Set Background color
  // window_set_background_color(s_main_window, GColorBlack);
  // Set handlers to manage the elements inside the Window
  window_set_window_handlers(s_main_window, (WindowHandlers) {
    .load = main_window_load,
    .unload = main_window_unload
  });
  
  // Show the Window on the watch, with animated=true
  window_stack_push(s_main_window, true);
}

// CORE. Destroy the window
static void deinit() {
  // Destroy Window
  window_destroy(s_main_window);
}

The application has an empty window now. If you run the application using the emulator, you should see an empty white screen. This is because we have no text and no graphics yet.

Let’s prepare our window to display some text layers.

// UI. Everything we load is here. Texts, layers, graphics etc.
static void main_window_load(Window *window) {
  // Get information about the Window
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);
  
  // Create the TextLayer with specific bounds
  s_time_layer = text_layer_create(
      GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50));

  // Improve the layout to be more like a watchface
  text_layer_set_background_color(s_time_layer, GColorClear);
  text_layer_set_text_color(s_time_layer, GColorBlack);
  text_layer_set_text(s_time_layer, "00:00");
  text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
  text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);


  // Add it as a child layer to the Window's root layer
  layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
}

 

At this point, our application is displaying a black text in the middle of the screen using a specific font called  Bitham 42 Bold.

 

3. Display the current time

Now, let’s make this text to be the current time instead, and updated every minuted. In order to do this, we will create a function “update_time()” that will have the logic to get the current time and update the text layer, and also we will have to register to a specific event that will trigger our update_time() function every minute.

Firstly, please include the time header in your file:

#include <pebble.h>
#include <time.h>
#include "main.h"

Just above the main_window_load() function we will add the following code:

// Function used to update the current time
static void update_time() {
  // Get a tm structure
  time_t temp = time(NULL);
  struct tm *tick_time = localtime(&temp);

  // Write the current hours and minutes into a buffer
  static char s_buffer[8];
  strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ?
                                          "%H:%M" : "%I:%M", tick_time);

  // Display this time on the TextLayer
  text_layer_set_text(s_time_layer, s_buffer);
}

// Tick Handler -> event called on every tick: used to update Time and CountDown
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
  update_time();
}

And now, let’s update our init() function to register our tick_handler like bellow:

  // Show the Window on the watch, with animated=true
  window_stack_push(s_main_window, true);
  
  // Register with TickTimerService
  tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
  
  // Make sure the time is displayed from the start
  update_time();

Also, we have to make sure we cleanup our code. Let’s update our main_window_unload():

// UI. Everything we created earlier, we destroy here.
static void main_window_unload(Window *window) {
  // Destroy TextLayer
  text_layer_destroy(s_time_layer);
}

Great! This is a real watch face that shows the correct time!

 

4. Display the countdown for the specific event

Well, here I had to use some pieces of code from other good projects. This is the part were I was inspired by the project I mentioned in the beginning of the article. However, I decided is not the case to import some libraries in our code, instead we can just use some utilities functions.

So, here you have the entire block of utilities functions I decided to import in our project (make sure you put them on the top of the file, just after header and declaration area).

/* START of Utilities functions */
/*
  This code is derived from PDPCLIB, the public domain C runtime
  library by Paul Edwards. http://pdos.sourceforge.net/
  This code is released to the public domain.
*/
/* scalar date routines    --    public domain by Ray Gardner
** These will work over the range 1-01-01 thru 14699-12-31
** The functions written by Ray are isleap, months_to_days,
** years_to_days, ymd_to_scalar, scalar_to_ymd.
** modified slightly by Paul Edwards
*/
static int isleap (unsigned yr) {
  return yr % 400 == 0 || (yr % 4 == 0 && yr % 100 != 0);
}

static unsigned months_to_days (unsigned month) {
  return (month * 3057 - 3007) / 100;
}

static unsigned years_to_days (unsigned yr) {
  return yr * 365L + yr / 4 - yr / 100 + yr / 400;
}

static long ymd_to_scalar (unsigned yr, unsigned mo, unsigned day) {
  long scalar;

  scalar = day + months_to_days(mo);
  if (mo > 2) // adjust if past February
    scalar -= isleap(yr) ? 1 : 2;
  yr--;
  scalar += years_to_days(yr);
  return scalar;
}

time_t p_mktime (struct tm *timeptr) {
  time_t tt;

  if ((timeptr->tm_year < 70) || (timeptr->tm_year > 120)) {
    tt = (time_t)-1;
  } else {
    tt = ymd_to_scalar(timeptr->tm_year + 1900,
                       timeptr->tm_mon + 1,
                       timeptr->tm_mday)
      - ymd_to_scalar(1970, 1, 1);
    tt = tt * 24 + timeptr->tm_hour;
    tt = tt * 60 + timeptr->tm_min;
    tt = tt * 60 + timeptr->tm_sec;
  }
  return tt;
}
/* END of Utilities functions */

It’s not the time to focus on these functions now. Just be aware that we need those later on.

To make the counter work, we have to add few more characters  into the scene:

#include <pebble.h>
#include <time.h>
#include "main.h"

// We have to render a main window
static Window *s_main_window;
// The Time layer (text)
static TextLayer *s_time_layer;
// The Text layer used for countdown
static TextLayer *s_countdown_layer;

/* START EVENT SETTINGS */
// Result of concatenation should be like: Christmas in XX days
static char PRE_TEXT[] = "Christmas in ";  // used before the number of days
static char POST_TEXT[] = " days";         // used after the number of days
// Setup Your Future Event Here
static int EVENT_YEAR = 2016;  // Year
static int EVENT_MONTH = 12; // 1 (January) - 12 (December)
static int EVENT_DAY = 25;   // Day number 25 - Christmas
static int EVENT_HOUR = 0;   // Start of the day (00:00)
static int EVENT_MINUTE = 0; // minute 0
/* END EVENT SETTINGS */

As you can see here, we added s_countdown_layer which will be used to display the countdown text, and a separate block containing a configuration section of properties for our countdown. I decided to have 2 strings: one will precede the days counter, and the other will follow. This will help me to write something like “Christmas  in XX days”, where XX will be the days counter. The other EVENT_* properties will help us to setup our future event that we want to create the countdown for.

And  here is the countdown function.

// Function used to update the countdown
static void calculate_countdown() {
	time_t t = time(NULL);
	struct tm *now = localtime(&t);
	static char countText[] = "";
	

	// Set the current time
	time_t seconds_now = p_mktime(now);
	
	now->tm_year = EVENT_YEAR - 1900;
	now->tm_mon = EVENT_MONTH - 1;
	now->tm_mday = EVENT_DAY;
	now->tm_hour = EVENT_HOUR;
	now->tm_min = EVENT_MINUTE;
	now->tm_sec = 0;
	
	time_t seconds_event = p_mktime(now);
	
	// Determine the time difference
	int difference = ((((seconds_event - seconds_now) / 60) / 60) / 24);
	
	if(difference < 0) {
		difference = 0;
	}
	
	// Set the countdown display
	snprintf(countText, 200, "%d", difference);
  
  char *result = malloc(strlen(PRE_TEXT) + strlen(countText) + strlen(POST_TEXT) + 1);
  strcpy(result, PRE_TEXT);
  strcat(result, countText);
  strcat(result, POST_TEXT);
	
	text_layer_set_text(s_countdown_layer, result);
}

Then, we update our tick_handler to use it every time when the time is being updated. This is how we make sure we have our countdown updated.

// Tick Handler -> event called on every tick: used to update Time and CountDown
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
  update_time();
  calculate_countdown();
}

We have one missing puzzle piece: the text layer used to show the countdown is not displayed yet. Let’s update our main_window_load() function.

  // Add days remaining layer
	s_countdown_layer = text_layer_create(GRect(0, 130, bounds.size.w, 23));
    text_layer_set_text(s_countdown_layer, "");
	text_layer_set_text_color(s_countdown_layer, GColorBlack);
	text_layer_set_background_color(s_countdown_layer, GColorClear);
	text_layer_set_text_alignment(s_countdown_layer, GTextAlignmentCenter);
	text_layer_set_font(s_countdown_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
	layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_countdown_layer));

Cool! We have the countdown ready!

Not yet! Let’s update main_window_unload() function to destroy our s_countdown_layer.

// UI. Everything we created earlier, we destroy here.
static void main_window_unload(Window *window) {
  // Destroy TextLayer
  text_layer_destroy(s_time_layer);
  
  // Destroy CountDown
  text_layer_destroy(s_countdown_layer);
}

Add an icon / image to our watchface

Now, to reach my objective, I have to add a bitmap. In the left panel menu, we have a section called Resources. This is the place you can add images to your project. My recommendation is to use a 2 bit or 8 bit png file format. Let’s add a new one.

pebble project add resource part 1

pebble project add resource part 2

pebble project add resource part 3

This is the PNG file I used for this example.pebble project christmas tree 8bit

Now, exactly like in our previous steps, we need to create some new properties: a bitmap layer and a bitmap object.

// The Background Layer -> Display an image/icon
static BitmapLayer *s_background_layer;
// The Bitmap used to load our image -> then display it as background
static GBitmap *s_background_bitmap;

And inside our main_window_load() function we add the block that will render our bitmap resource. Notice that we load the bitmap resource using the Identifier we provided when we added our resource into the project.

  // Create GBitmap
  s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_CHRISTMAS_TREE);
  
  // Create BitmapLayer to display the GBitmap
  s_background_layer = bitmap_layer_create(bounds);
  
  // Set the bitmap onto the layer and add to the window
  bitmap_layer_set_compositing_mode(s_background_layer, GCompOpSet);
  bitmap_layer_set_alignment(s_background_layer, GAlignTop);
  bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
  layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer));

And as you already learned, we have to destroy our bitmap resource and bitmap layer.

// UI. Everything we created earlier, we destroy here.
static void main_window_unload(Window *window) {
  // Destroy GBitmap
  gbitmap_destroy(s_background_bitmap);
  
  // Destroy BitmapLayer
  bitmap_layer_destroy(s_background_layer);
  
  // Destroy TextLayer
  text_layer_destroy(s_time_layer);
  
  // Destroy CountDown
  text_layer_destroy(s_countdown_layer);
}

Awesome! Now you have your project done!

pebble countdown result

 

If you want to see the final project, please check it out on GitHub: https://github.com/unneuron/countdown-watchface

Leave a Reply

Your email address will not be published. Required fields are marked *