Archive

Monthly Archives: March 2014

Updates
– 30/3/14 – Added links to source code

It’s been a long term aim of mine to try and speed up AppMessage as fast as I can, in order to transfer more than mere signal messages between the phone and the watch. An example of this is the long time it takes to send responses to the watch in Wristponder (although now that only applies when a change takes place, thanks to the Persistent Storage API).

An ideal use case for this is some sort of accelerometer data stream, so I set to it. I realised that the key to the fastest possible AppMessage speed is to send the next message as soon as possible, when the last one has been received on the other side. If a waiting period is not observed, there will be problems, such as APP_MSG_BUSY or APP_BSG_BUFFER_OVERFLOW. The solution I used uses the app_message_outbox_sent() callback to send the next message. This function is called as soon as the other side ACKnowledges the last message, signalling that it is ready for the next.

Gathering the accelerometer data asynchronously into a global storage array:

static void accel_new_data(AccelData *data, uint32_t num_samples)
{
	for(uint32_t i = 0; i < num_samples; i++)
	{
		latest_data[(i * 3) + 0] = (int)(0 + data[i].x);	//0, 3, 6
		latest_data[(i * 3) + 1] = (int)(0 + data[i].y);	//1, 4, 7
		latest_data[(i * 3) + 2] = (int)(0 + data[i].z);	//2, 5, 8
	}
}

And sending it when the previous message has been ACKnowledged:

static void send_next_data()
{
	DictionaryIterator *iter;
	app_message_outbox_begin(&iter);

	for(int i = 0; i < NUM_SAMPLES; i++)
	{
		for(int j = 0; j < 3; j++)
		{
			int value = 0 + latest_data[(3 * i) + j];
			Tuplet t = TupletInteger((3 * i) + j, value);
			dict_write_tuplet(iter, &t);
		}
	}

	app_message_outbox_send();
}

static void out_sent_handler(DictionaryIterator *iter, void *context)
{
	//CAUTION - INFINITE LOOP
	send_next_data();

	//Show on watch
	static char buffs[3][32];
	snprintf(buffs[0], sizeof("X: XXXXX"), "X: %d", latest_data[0]);
	snprintf(buffs[1], sizeof("Y: YYYYY"), "Y: %d", latest_data[1]);
	snprintf(buffs[2], sizeof("Z: ZZZZZ"), "Z: %d", latest_data[2]);
	text_layer_set_text(x_layer, buffs[0]);
	text_layer_set_text(y_layer, buffs[1]);
	text_layer_set_text(z_layer, buffs[2]);
}

An additional measure that helps speed things up is temporarily reducing the ‘sniff interval’ of the Bluetooth module to ‘SNIFF_INTERVAL_REDUCED‘:

app_comm_set_sniff_interval(SNIFF_INTERVAL_REDUCED);

And collecting accelerometer data at a faster rate than it is consumed, to avoid sending duplicate frames. This appears to be about 15 AppMessages per second, each packed with 30 ints representing 10 time-spliced samples from the accelerometer, with a total throughput of approximately 1.6 KBps.

The end result looks like this (using the excellent Android GraphView library):
Screenshot_2014-03-26-19-21-09The next step may be to implement some sort of gesture recognition to enable movements to control some external application. We shall see!

Source code
Android
Pebble

 

Advertisements

In a bit of downtime I thought I’d make myself a small app to give me local weather using PebbleKit JS to interpret a BBC Weather feed for my local town.

pebble-screenshot_2014-03-15_22-31-01

Quite stylish, I think! It’s for personal use, as I have neither the time or energy to generalise it enough, and I’m sure there are plenty of weather apps out there already!

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

Introduction

After a few requests, in this section we will look at using MenuLayers in a Pebble watchapp. If you pick up your Pebble now and press the select button from the watch face, what you see is a MenuLayer. It has rows, icons and actions. Let’s build one of those!

pebble-screenshot_2014-03-13_00-22-47

Setup

The first step as usual is to start a new CloudPebble project with the basic app template. Here’s that again, for convenience:

#include <pebble.h>

Window* window;

void window_load(Window *window)
{

}

void window_unload(Window *window)
{

}

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);
}

void deinit()
{
	window_destroy(window);
}

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

Now that’s out the way, declare a global pointer to a MenuLayer at the top of the file below the pre-processor directives.

MenuLayer *menu_layer;

This Layer type is a bit more complex to set up than the other Layers, in that it requires a large amount of information about how it will look and behave before it can be instantiated. This information is given to the MenuLayer via the use of a number of callbacks. When the MenuLayer is redrawn or reloaded, it calls these functions to get the relevant data. The advantage of this approach is that the MenuLayer rows can be filled with data that can be changed at any time, such as with Wristponder or Pebble Tube Status (shameless plugs!)

The API documentation describes all the possible MenuLayerCallbacks that can be associated with a MenuLayer, but the ones we will be using for a simple example will be:

  • .draw_row – This is used to draw the layout inside a menu item
  • .get_num_rows – This is used to feedback the total number of rows in the MenuLayer. This can be a #defined value, or an int, and so variable
  • .select_click – This is used to decide what happens when the select button is pressed, which will vary depending on which row is currently selected

Let’s define these callbacks using the signatures provided by the API documentation linked previously. These must be above window_load() as is now the norm (hopefully!):

void draw_row_callback(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index, void *callback_context)
{

}

uint16_t num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *callback_context)
{

}

void select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context)
{

}

Now those are in place, let’s add code to have them do something we’d find more useful than blank callbacks. The example we are going to use is a list of fruits (boring, I know!). The list will be of seven fruits, and brief descriptions. Thus, the num_rows_callback() function becomes simply:

uint16_t num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *callback_context)
{
	return 7;
}

For the draw_row_handler(), we will need to be able to alter what is drawn in the row depending on which row it is. This can be done by switching the cell_index->row property. You can use the presented GContext however you like for any of the SDK drawing functions, but to keep things simple we will use the pre-made drawing functions provided by the SDK. With these two last points combined, the draw_row_callback() function transforms into this beast:

void draw_row_callback(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index, void *callback_context)
{
	//Which row is it?
	switch(cell_index->row)
	{
	case 0:
		menu_cell_basic_draw(ctx, cell_layer, "1. Apple", "Green and crispy!", NULL);
		break;
	case 1:
		menu_cell_basic_draw(ctx, cell_layer, "2. Orange", "Peel first!", NULL);
		break;
	case 2:
		menu_cell_basic_draw(ctx, cell_layer, "3. Pear", "Teardrop shaped!", NULL);
		break;
	case 3:
		menu_cell_basic_draw(ctx, cell_layer, "4. Banana", "Can be a gun!", NULL);
		break;
	case 4:
		menu_cell_basic_draw(ctx, cell_layer, "5. Tomato", "Extremely versatile!", NULL);
		break;
	case 5:
		menu_cell_basic_draw(ctx, cell_layer, "6. Grape", "Bunches of 'em!", NULL);
		break;
	case 6:
		menu_cell_basic_draw(ctx, cell_layer, "7. Melon", "Only three left!", NULL);
		break;
	}
}

The NULL references are in the place that a row icon reference would be placed (if a GBitmap were to be shown). Thus, each layer will be drawn with its own unique message.

The final callback, select_click_callback() will do something different depending on which row is selected when the select button is pressed. To illustrate this, we will use a series of vibrations that signifies the numerical value of the row. Here’s how this is done (or Vibes 101!):

void select_click_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context)
{
	//Get which row
	int which = cell_index->row;

	//The array that will hold the on/off vibration times
	uint32_t segments[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

	//Build the pattern (milliseconds on and off in alternating positions)
	for(int i = 0; i < which + 1; i++)
	{
		segments[2 * i] = 200;
		segments[(2 * i) + 1] = 100;
	}

	//Create a VibePattern data structure
	VibePattern pattern = {
		.durations = segments,
		.num_segments = 16
	};

	//Do the vibration pattern!
	vibes_enqueue_custom_pattern(pattern);
}

With those three callbacks in place, we can actually create the MenuLayer and add it to the main Window. This is done in four stages:

  • Create the MenuLayer and assign it to the global pointer
  • Set it up to receive clicks from the Window
  • Set the callbacks we just wrote to give the MenuLayer the information it needs
  • Add the MenuLayer to the main Window

Here’s the code for that sequence, with annotations (Note the casts used in the MenuLayerCallbacks structure creation):

void window_load(Window *window)
{
	//Create it - 12 is approx height of the top bar
	menu_layer = menu_layer_create(GRect(0, 0, 144, 168 - 16));

	//Let it receive clicks
	menu_layer_set_click_config_onto_window(menu_layer, window);

	//Give it its callbacks
	MenuLayerCallbacks callbacks = {
		.draw_row = (MenuLayerDrawRowCallback) draw_row_callback,
		.get_num_rows = (MenuLayerGetNumberOfRowsInSectionsCallback) num_rows_callback,
		.select_click = (MenuLayerSelectCallback) select_click_callback
	};
	menu_layer_set_callbacks(menu_layer, NULL, callbacks);

	//Add to Window
	layer_add_child(window_get_root_layer(window), menu_layer_get_layer(menu_layer));
}

As always, de-init the MenuLayer:

void window_unload(Window *window)
{
	menu_layer_destroy(menu_layer);
}

If all has gone well, after compilation you should be greeted with the screen below, as well as the corresponding vibrations when each row is selected:

pebble-screenshot_2014-03-13_01-27-12

Conclusions
So that’s how to setup a basic MenuLayer. An extended application like those mentioned previously will use char[] buffers to store each row’s text, modified in a in_received signature AppMessage callback, and calling menu_layer_reload_data() in that AppMessage callback, thus updating the MenuLayer with the new data.

The source code can be found on GitHub HERE!

Let me know any queries you have. Enjoy!

UPDATE:

– v2.1.1 includes a much better fix for the animation bug. Sorry for the rapid-fire updates.

– Looks like I introduced another problem. Rolling back on the Pebble Appstore until I do my job properly.

Since the launch of the Pebble AppStore I have been receiving daily emails about the ‘stuck’ bug, which looks like this:

IMG_20131226_185234Not too pretty, eh? It seems to happen completely at random, and I have made attempts to fix this before. It seems to have been introduced by the 2.0 Animation system. I deduce it cannot be a programmatic bug, because all animations are treated the same way.

So my fix takes the form of manually _set_frame()ing all the layout items at time = 2 seconds past the minute tick. This is not ideal, but given the animation complexity is the best current option.

Another added feature is the fact that the bottom bar now animates to the correct position when the face is opened.

If successful, I’ll implement these changes to the other Beam Up variants soon.

The Data Toggle for Pebble Android app has been updated and is now version 1.1.0. The changes are as below:

  • Added an option to install from Pebble Appstore.
  • Added rating link to Google Play store page.

photo1

The main motivation behind this small update is to enable Pebble SDK 2.0 Beta 10 users work around the existing sideloading bug where my current install method of a .pbw from the bundled assets folder stops working. I’m not too concerned now the Pebble Appstore link is in place, and they have assured me a fix is in the works:

If you are not already a user, here’s a download link (it’s free!)


Get it on Google Play