A while ago I created a simple Alert Library, which provides functions to show and hide a set of layers to show an ‘alert window’ for a time that tells the user of an event without changing Window.

screenshot

Continuing this theme, today I created another similar library called the ToastLayer, which has two advantages over the Alert Library:

1. The toast notification animates up from the bottom, instead of covering the majority of the Window.

2. It is designed as an object, like any of the other Layers in the Pebble SDK. This means there can be more than one!

Feel free to use it to show notifications without changing Window! I will probably look into including this in some of my existing apps.

Not a lot has been happening on this blog for the last few weeks, and the reason for that is that I have been busy beginning my internship at Pebble! It’s been a great experience so far (We released SDK 2.5 today!) and I can’t wait to work on more awesome things to help make Pebble even more useful smartwatch.

That being said, I’ve been able to find time to maintain and update my public Pebble apps at weekends, and hope to continue this pattern whenever I can, because I have some ideas I want to implement even just for myself.

A result of this is a new application of my Spark Core driven LCD project in collaboration with a new colleague to display social media trends from WhatTheTrend on animated cards on Pebble, and as an added bonus show the same information on the LCD display.

After some teething issues, it was eventually presentable and works pretty well – but for some reason only on Wi-Fi. Here’s a photo of the whole thing in action. (Eduardo Sasha is imprinted on my brain now from all the testing…)

image

Stay tuned for coming updates to Dashboard and Wristponder, as well as bringing Watch Trigger up to date as well!

Just released Dashboard 1.3!

The major new feature in this version is the ability to dynamically re-order the toggles to suit your preference. The way this works involves selecting each position in an Android Spinner in the ‘Configure’ tab:

Screenshot_2014-09-07-19-46-17

 

Each time a user makes a selection in one of the positions, the rest of the Spinner array is checked to look for a duplicate of the toggle the user has just chosen, and switches the two around. For example, if the user changes the Wi-Fi toggle to Autosync, the first toggle becomes Autosync and then the existing Autosync Spinner duplicate is changed to the only other missing toggle type – Wi-Fi!

This means that the toggles can be any order possible, such as the examples below:

toggle-config

That’s a total of P(8,8) = 40320!

 

A not insignificant amount of time after starting work on this update, it is finally here!

New features include:

- All data to be synchronised is now done in a streaming manner after each Window appears, meaning no more waiting for sync!
– Favourite contacts are now chosen using the Android Contact Picker, and not from five (potentially very large) spinners.
– The debug log can now be read and reported from the Settings screen. This makes reporting bugs to me much simpler.

Here’s a before and after shot of the Favourites selection Activity:

favourite-selection

There is only really one major comment to make that arose during development for this version, and that’s on the subject of an Android Activity in KitKat. I came across a a strange behaviour when launching the Pebble Android app to install the watchapp. It appears that in the event that the user chooses ‘Install Watchapp’ from Wristponder Settings, completes the installation and returns to the Settings Activity, they will not be able to launch any new ones such as Favourites or Import for anywhere between 10 to 45 seconds. On top of this any spamming of buttons done by a bemused user results in just as many launches when the Activitys requested are eventually delivered.

The only evidence of this in a single logcat output something along the lines of “Waited long enough for ServiceRecord” (unable to reproduce it at the time of writing, which is a good thing!) and this is the only link I can find on the subject. Apparently its to do with serializing launches from background services, although none of my launches are Services.

Anyway, that is the only problem with this release, and I feel that Wristponder is now a pretty solid and smooth piece of software. For this I am proud!

Download
Get it on Google Play

Edit: 100th post!

Quick post to share  a ‘new’ Layer type I created for an upcoming project: ColorLayer. It’s supposed to be a convenience for adding a simple layer of colour. Problem is, unless I’m missing something very obvious, the two options are to declare a standard Layer and assign it a basic _fill_rect() LayerUpdateProc, or use a TextLayer and modifying the background colours.

I normally choose the latter, so for the umpteenth time of doing so I decided to wrap it up to make it a bit simpler to use. Here’s the result!

ColorLayer.h

/**
 * Layer on top of TextLayer used just for coloring areas without using LayerUpdateProc
 * Author: Chris Lewis (@Chris_DL)
 * Version 1.0.0
 */
#include <pebble.h>

#ifndef COLOR_LAYER_H
#define COLOR_LAYER_H

typedef struct {
	TextLayer *layer;
} ColorLayer;

ColorLayer* color_layer_create(GRect bounds, GColor fill_color);
void color_layer_destroy(ColorLayer *this);
void color_layer_set_color(ColorLayer *this, GColor fill_color);
void color_layer_set_frame(ColorLayer *this, GRect bounds);
Layer* color_layer_get_layer(ColorLayer *this);

#endif

ColorLayer.c

#include "color_layer.h"

ColorLayer* color_layer_create(GRect bounds, GColor fill_color)
{
	ColorLayer *this = malloc(sizeof(ColorLayer));
	this->layer = text_layer_create(bounds);
	text_layer_set_background_color(this->layer, fill_color);

	return this;
}

void color_layer_destroy(ColorLayer *this)
{
	text_layer_destroy(this->layer);
	free(this);
}

void color_layer_set_color(ColorLayer *this, GColor fill_color)
{
	text_layer_set_background_color(this->layer, fill_color);
}

void color_layer_set_frame(ColorLayer *this, GRect bounds)
{
	layer_set_frame(text_layer_get_layer(this->layer), bounds);
}

Layer* color_layer_get_layer(ColorLayer *this)
{
	return text_layer_get_layer(this->layer);
}

It could be argued that it’s such a thin layer you may as well not bother, but I find it to be sufficiently easier to setup and read (as well as avoiding confusion with TextLayers that actually show text), so once again I’m glad coding allows a degree of personal preference and style!

CL Pebble Apps (formerly Watch App Selector) now has a third tab; SDK Tutorial. It’s been an idea I’ve had for a while, but only just got around to implementing.

This means that as well as being able to install all my published watch apps and watchfaces, users can now also see a list and be linked to my Pebble SDK tutorials.

Screenshot_2014-07-26-15-10-48

Neat! If I write more parts to the tutorial, this app will be updated as it will for new versions of the other watch apps and watchfaces.

Get it on Google Play

Required Reading

Pebble SDK 2.0 Tutorial #1: Your First Watchapp

Pebble SDK 2.0 Tutorial #2: Telling the Time

Pebble SDK 2.0 Tutorial #3: Images and Fonts

Pebble SDK 2.0 Tutorial #4: Animations and Timers

Pebble SDK 2.0 Tutorial #5: Buttons and Vibrations

Pebble SDK 2.0 Tutorial #6: AppMessage for PebbleKit JS

Pebble SDK 2.0 Tutorial #7: MenuLayers

Pebble SDK 2.0 Tutorial #8: Android App Integration

Pebble SDK 2.0 Tutorial #9: App Configuration

Introduction

In this section of the tutorial series, I will be covering some of the Event Services introduced in the 2.0 version of the Pebble SDK which have been oft requested due to their popularity in newer, interactive watchfaces. Namely:

  • Bluetooth Connection Service
  • Battery State Service
  • Accelerometer Service (tap and raw)
  • App Focus Service (not covered, but works identically to the Bluetooth Connection Service)

Setup

To begin with, we will be using the blank template from before, shown below for convenience. Create a new CloudPebble project and start a new C file with the template as its contents:

#include <pebble.h>

static Window* window;

static void window_load(Window *window)
{

}

static void window_unload(Window *window)
{

}

static void init()
{
  window = window_create();
  WindowHandlers handlers = {
    .load = window_load,
    .unload = window_unload
  };
  window_set_window_handlers(window, (WindowHandlers) handlers);
  window_stack_push(window, true);
}

static void deinit()
{
  window_destroy(window);
}

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

Bluetooth Connection Service
The first Event Service we will be using is the Bluetooth Connection Service, which allows us to see the current connection status as well as subscribe to updates (only while Bluetooth is actually connected, so be careful with debug logs), much in the same way as with the TickTimerService. Firstly, we will create a TextLayer in window_load() to use for showing the events happening. First is the global pointer:

static TextLayer *bt_layer;

then creation proper in window_load(). Note the use of bluetooth_connection_service_peek() to show the state of the connection at the time of creation. As always we also add the corresponding destruction function call to free memory:

static void window_load(Window *window)
{
  //Setup BT Layer
  bt_layer = text_layer_create(GRect(5, 5, 144, 30));
  text_layer_set_font(bt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
  if(bluetooth_connection_service_peek() == true)
  {
    text_layer_set_text(bt_layer, "BT: CONNECTED");
  }
  else
  {
    text_layer_set_text(bt_layer, "BT: DISCONNECTED");
  }
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(bt_layer));
}

static void window_unload(Window *window)
{
  text_layer_destroy(bt_layer);
}

Next we will subscribe to the BluetoothConnectionService to update this TextLayer whenever the status of the Bluetooth connection to the phone changes. Like a TickHandler, we start by creating a function to use as a handler with the correct signature, and fill it with logic to change the text displayed. This should be placed before init(), where it will be registered:

static void bt_handler(bool connected)
{
  if(connected == true)
  {
    text_layer_set_text(bt_layer, "BT: CONNECTED");
  }
  else
  {
    text_layer_set_text(bt_layer, "BT: DISCONNECTED");
  }
}

The final step is to perform the actual subscription, which is very easy to do, and happens in init():

//Subscribe to BluetoothConnectionService
bluetooth_connection_service_subscribe(bt_handler);

After compiling and installing the project, try disconnecting and re-connecting your phone’s Bluetooth radio a few times and observe the result.

Battery State Service
The next Event Service we will be adding will be the Battery State Service, which provides information on the Pebble’s battery. It provides more detail than a simple bool, including charging status and whether the cable is plugged in or not. As before, we will create a new TextLayer to show the output. Add the pointer to the last one in the declaration:

static TextLayer *bt_layer, *batt_layer;

Then, perform the proper creation in window_load(). This time, the information provided by the Battery State Service comes in the form on the BatteryChargeState data structure, with fields as shown in the documentation. It is worth noting that the Service only returns the battery charge in increments of 10. The setup of the new TextLayer is shown below:

//Setup Battery Layer
batt_layer = text_layer_create(GRect(5, 25, 144, 30));
text_layer_set_font(batt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(batt_layer));

//Get info, copy to long-lived buffer and display
BatteryChargeState state = battery_state_service_peek();
static char buffer[] = "Battery: 100/100";
snprintf(buffer, sizeof("Battery: 100/100"), "Battery: %d/100", state.charge_percent);
text_layer_set_text(batt_layer, buffer);

After re-compiling, the battery charge percentage should be shown below the Bluetooth status.

Accelerometer Service (tap)
The Accelerometer Service operates in a very similar manner to the previous two Event Services, but can operate in two modes: tap and raw data. The tap mode will call a handler that we subscribe when the Pebble is tapped (or wrist is shaken), whereas the raw data mode will supply X, Y and Z values at an rate we select. I’ll show both of these for the sake of completeness. An application of the latter mode can be seen here.

First, we create a further TextLayer to show the output data:

static TextLayer *bt_layer, *batt_layer, *accel_layer;

The first mode we will use is the tap mode. Let’s create the TextLayer proper in window_load():

//Setup Accel Layer
accel_layer = text_layer_create(GRect(5, 45, 144, 30));
text_layer_set_font(accel_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
text_layer_set_text(accel_layer, "Accel tap: N/A");
layer_add_child(window_get_root_layer(window), text_layer_get_layer(accel_layer));

Next, we will create the handler function to be called whenever a tap is detected, and furnish it with logic to show what kind of tap was detected:

static void accel_tap_handler(AccelAxisType axis, int32_t direction)
{
  switch(axis)
  {
  case ACCEL_AXIS_X:
    if(direction > 0)
    {
      text_layer_set_text(accel_layer, "Accel tap: X (+)");
    }
    else
    {
      text_layer_set_text(accel_layer, "Accel tap: X (-)");
    }
    break;
  case ACCEL_AXIS_Y:
    if(direction > 0)
    {
      text_layer_set_text(accel_layer, "Accel tap: Y (+)");
    }
    else
    {
      text_layer_set_text(accel_layer, "Accel tap: Y (-)");
    }
    break;
  case ACCEL_AXIS_Z:
    if(direction > 0)
    {
      text_layer_set_text(accel_layer, "Accel tap: Z (+)");
    }
    else
    {
      text_layer_set_text(accel_layer, "Accel tap: Z (-)");
    }
    break;
  }
}

Finally, we subscribe our handler function to the Accelerometer Event Service in init():

//Subscribe to AccelerometerService
accel_tap_service_subscribe(accel_tap_handler);

You should now be able to see the result of tapping the watch. Personally I’ve found that shaking the wrist is a more reliable way of triggering events (such as showing more information on a watchface), but taps can still be used as an option.

Accelerometer Service (raw data)
Finally, we will use the raw data mode of the Accelerometer Service. To do this, we will first remove the existing Accelerometer Service subscription (but still keep the handler for reference).

In the raw data mode, the data values arrive at a specific interval chosen with a call to accel_service_set_sampling_rate(), and the number of samples in a batch can be chosen using accel_service_set_samples_per_update(). We will stick with the default rate and update size for simplicity. Be aware that this mode will drain the battery significantly faster than the tap mode.

Next, we will create a new handler function to let us access the data that arrives from the Event Service. Accessing the data is as simple as reading the fields in the data parameter in the handler, as shown below:

static void accel_raw_handler(AccelData *data, uint32_t num_samples)
{
  static char buffer[] = "XYZ: 9999 / 9999 / 9999";
  snprintf(buffer, sizeof("XYZ: 9999 / 9999 / 9999"), "XYZ: %d / %d / %d", data[0].x, data[0].y, data[0].z);
  text_layer_set_text(accel_layer, buffer);
}

Finally, we add the new subscription, making sure we have disabled the one one in init():

//Subscribe to AccelerometerService (uncomment one to choose)
//accel_tap_service_subscribe(accel_tap_handler);
accel_data_service_subscribe(1, accel_raw_handler);

Now this is all in place, re-compile and re-install the watch app to see the live values. Try tilting the watch in each axis to see the constant g acceleration act on each in turn.

The final result should look like this:
final

Conclusion
So, that’s the new Event Services. As I mentioned, there is another called the App Focus Service which tells you when your app is covered by a notification, but it works in a very similar way to the Bluetooth Connection Service, so you should be able to figure it out!

The full source code can be found here on GitHub.

Follow

Get every new post delivered to your Inbox.

Join 179 other followers