Category: New Stuff

Pushing pins to Pebble Time timeline from .NET code

By , May 12, 2015 1:27 pm

Timeline on Pebble Time Pebble Time timeline is a very cool user interface allowing you to see future and past events and act upon them right on your watch. Right out of the box Pebble Time supports calendar pins that shows your future and past appointments in the timeline as well as weather alerts. But the real power comes from 3rd party apps using timeline – they can add anything from sports scores to latest news to TV showtimes – limit is just your imagination.
Pebble has always had open SDK – this is one of its major strengths, and Timeline is not an exception. Timeline API is a very straightforward way to push your own pins to users of your app. There’re various examples and libraries including PHP and node.js on how to deal with the timeline, but I, being mostly a Microsoft developer by trade, decided to bring Timeline into .NET. This particular example is in ASP.NET – pin is pushed from Webpage when user clicks a button, but it’s just one of the possible scenarios.

In order to push timeline pins successfully you will need 2 pieces:

  1. A watchapp that runs on Pebble. In fact after first run, that subscribes user to timeline, the app doesn’t have to be running on the watch anymore. It doesn’t even have to be on the watch. As long as it simple remains in your locker on the phone – you will continue to receive its pins
  2. Your own server that sends calls to Pebble public Timeline API to control pins

Continue reading 'Pushing pins to Pebble Time timeline from .NET code'»

Mask Effect for EffectLayer for Pebble

By , April 15, 2015 9:42 am

I’ve written before about EffectLayer library for Pebble smartwatch I’ve been working on. The idea is – user places the layer over screen and that layer applies an effect to screen content.

I’ve started with a few basic effects (invert, mirror) but since then several more developers joined the project adding more cool features. Ron added 90┬░ rotation, zoom and lens effect. Gregoire Sage added cool blur effect. LeFauve not only added FPS effect, but also optimized the library to run the effects in a very efficient way: now effect can be defined as a function (even user defined function!) and that function passed as a parameter to effect_layer_add_effect method along with parameters for that effect.

I, for my part, contributed “mask” effect. What it does is essentially lets you show parts of background image thru user defined mask, creating a feel of transparency. Continue reading 'Mask Effect for EffectLayer for Pebble'»

Universal access to Pebble framebuffer on Basalt and Aplite via coordinates

By , April 4, 2015 11:27 pm

Rotate Effect on Aplite In my previous post I described how you can access framebuffer of Pebble screen via familiar X,Y coordinates. To reiterate: you capture framebuffer as a bitmap, and access bitmap as 2-dimentional matrix:

#define WINDOW_WIDTH 144
GBitmap *fb = graphics_capture_frame_buffer(ctx);
uint8_t (*fb_matrix)[WINDOW_WIDTH] = (uint8_t (*)[WINDOW_WIDTH]) gbitmap_get_data(fb);

After that you can access specific pixel on the screen via coordinates, e.g. fb_matrix[120][60] will represent pixel and coordinates Y = 120, X = 60

This works fine on Pebble Time (Basalt platform) where every pixel represented by a byte. But what about classic Pebble (I think this term is becoming quite popular, but to avoid confusion I will call it Aplite platform). On Aplite every byte in the framebuffer represent 8 pixels and the above approach doesn’t work.

Fortunately there’s a universal solution.
Continue reading 'Universal access to Pebble framebuffer on Basalt and Aplite via coordinates'»

Simplify access to Framebuffer on Pebble Time

By , April 3, 2015 4:43 pm

Pebble smartwatch SDK offers a very extensive graphics library. And if that is not enough – you can access graphics memory directly for pixel-precision manipulation. For example this code

GBitmap *fb = graphics_capture_frame_buffer_format(ctx, GBitmapFormat8Bit);
uint8_t *fb_data = gbitmap_get_data(fb);

captures Pebble screen as a bitmap and consequently as raw uint8_t data you can manipulate. But the problem with this approach – you access the data as 1-dimensional array, which is very inconvinient when you’re dealing with 2-dimensional screen.

Fortunately due to magic of casting this problem can be addressed. Consider following addition to previous code:

#define WINDOW_WIDTH 144  
uint8_t (*fb_matrix)[WINDOW_WIDTH] = (uint8_t (*)[WINDOW_WIDTH]) fb_data;

Using this, you can access screen data via familiar coordinates. For example if you need to set pixel at coordinates Y=120, X=60 to black color, all you have to do is

fb_matrix[120][60] = 0;

And now we can rewrite InverterLayer from previous post to a simpler form. First in layer callback we create bitmap matrix and call effect function:

static void effect_layer_update_proc(Layer *me, GContext* ctx) {
  // getting layer coordinates
  GRect layer_frame = layer_get_frame(me);  
  
  //capturing framebuffer bitmap into 2-d matix
  GBitmap *fb = graphics_capture_frame_buffer_format(ctx, GBitmapFormat8Bit);
  uint8_t (*fb_matrix)[WINDOW_WIDTH] = (uint8_t (*)[WINDOW_WIDTH]) gbitmap_get_data(fb);
  
  //callig effect function
  effect_invert(fb_matrix, layer_frame);

  //releasing framebuffer
  graphics_release_frame_buffer(ctx, fb);
  
}

And actual inverting function becomes much simpler as well, simple loop thru layer pixel, inverting colors:

// position: x,y,h,w of the layer  
void effect_invert(uint8_t (*fb_a)[WINDOW_WIDTH], GRect position) {
  
  for (int y = 0; y < position.size.h; y++)
     for (int x = 0; x < position.size.w; x++)
        fb_a[y + position.origin.y][x + position.origin.x] = ~fb_a[y + position.origin.y][x + position.origin.x];
  
}

You can see this approach in EffectLayer library.

InverterLayer (and other effects) for Pebble Time

By , April 3, 2015 11:48 am

Inverter EffectMirror Effect
InverterLayer is a pretty cool feature of Pebble smartwath SDK, its simple purpose to invert colors of everything it’s placed over (black becomes white and vice versa).

Unfortunately it no longer works in SDK 3 (Basalt, Pebble Time) and will be depreciated. Fortunately it’s pretty straightforward to create your own InverterLayer. Continue reading 'InverterLayer (and other effects) for Pebble Time'»

Original Pebbles are getting TimeLine! (with special offers)

By , April 1, 2015 12:01 am

Original Pebble Steel with TimeLine Pebble Corporation is very well know for its visionary ideas. Ideas that kept its incredible smartwatches Pebble and Pebble Steel relevant on the market years after their release. They key to such longevity is regular software updates that bring new features to existing hardware. Multilanguage support, actionable notifications and many other fetures delighted Pebble users to no end.

This worked amazingly well so far. But now Pebble is finally on the verge of releasing new hardware as well. Color Pebble Time is scheduled (as of this writing) to be released in May and a shinier version – Pebble Time Steel is to be shipped in July. Both are getting brand new OS with time-oriented Timeline interface that will streamline the way you interact with the watch.

But what about users of existing Pebbles? Are they doomed to use obsolete devices? Continue reading 'Original Pebbles are getting TimeLine! (with special offers)'»

SliderLayer for Pebble

By , March 27, 2015 4:59 pm

Pebble SDK provides very cool features for programming watchapps and watchfaces. And combining them can yield very useful results. For example “out of the box” Pebble doesn’t have a slider layer – a layer that would slide in over existing information to display new one. But we have all the basic ingredients to create one:

This example uses BitmapLayer to display images, but it just as well maybe text or other items.
Continue reading 'SliderLayer for Pebble'»

Pebble Time: Draw transparent text over color bitmap background

By , March 20, 2015 4:10 pm

Pebble SDK has had a feature allowing to combine 2 images with transparency for a while via different composition modes. Unfortunately this doesn’t apply to texts, so, for example, if you need to display transparent time over bitmap background – you had to draw time digits as custom bitmaps. What’s worse – composition modes do not apply to colors, the result is always black-and white.

But there is a way. Pebble allows you to capture graphics context of a layer as a standard bitmap in the layer update callback. You can access that bitmap as raw data. So if you have another bitmap with the background you want to show through current layer, all you have to do is copy it byte by byte into captured bitmap, but only if pixel of specified color, that you use as a mask is encountered.

For example if your current layer needs to show background bitmap “s_bitmap” thru whatever is drawn in white color (255) you can use following snippet:

GBitmap *fb = graphics_capture_frame_buffer_format(ctx, GBitmapFormat8Bit);
  
uint8_t *fb_data =  gbitmap_get_data(fb);
uint8_t *background_data =  gbitmap_get_data(s_bitmap);
  
for (int i=0; i < 144*168; i++) {
  if (fb_data[i] == 255) {
    fb_data[i] = background_data[i];
  }
}
    
graphics_release_frame_buffer(ctx, fb);

Here Line 1 captures context as a bitmap, Lines 3-4 access captured bitmap and background bitmap as raw data. Lines 6-10 loop thru the captured data (each pixel takes a byte and in this example we’re going thru full 144×168 screen). If pixel of a white color is encountered – it is replaced with pixel from the same address of the background bitmap. And finally Line 12 releases captured framebuffer.

And if you combine this approach with APNG support that Pebble Time features, you can even create animated texture for your texts.

UPDATE: Thanks to @Jnm for this tip: If your masking layer is black-and white (00 & FF) that entire IF block can be replaced with bitwise AND:

fb_data[i] &= background_data[i];

And effect will be the same.

UPDATE 2 You can further optimize this code in 2 ways: First, let’s say your mask begins at Y = 50 and ends at Y = 100 – you don’t have to loop thru entire screen – you can use the loop below. It also demonstrates how you can jump 8 pixels/bytes at a time by utilizing 64-bit type:

uint64_t *fb_data = (uint64_t *)gbitmap_get_data(fb);
uint64_t *background_data = (uint64_t *)gbitmap_get_data(s_bitmap);
  
for (int i=50*144/8; i < 100*144/8; i++) {
  fb_data[i] &= background_data[i];
}

Thanks rajrdajr for the tip!

Useful Links:

Pebble: How to autoscroll large text

By , February 17, 2015 8:17 pm

Last time i described how to load random string from resource. If you recall the code ended up with the line

text_layer_set_text(s_textlayer_quote, (char *)quote);

to display loaded text on the screen. But what if text is too large to fit on the screen? If you’re building a watchface, there’s no scrolllayer with user interaction available. But what we can do is automatically scroll the text for user convenience to gradually reveal entire content.

The trick is to create text layer larger than it’s container. In my case I am displaying text full screen on Pebble window which is 144×168 pixels, but I will create text layer with the height of 2000:

#define WINDOW_HEIGHT 168
#define WINDOW_WIDTH 144  
#define TEXTBOX_HEIGHT 2000
...
s_textlayer_quote = text_layer_create(GRect(0, 0, WINDOW_WIDTH, TEXTBOX_HEIGHT));

If the loaded text is larger then container window we now can dynamically detect the difference:

text_layer_set_text(s_textlayer_quote, (char *)quote);
  
// if height of quote > height of window, initiate animation to scroll
GSize text_size = text_layer_get_content_size(s_textlayer_quote);
int number_of_pixels = WINDOW_HEIGHT - text_size.h;
if (number_of_pixels < 0) {
    animate_quote(number_of_pixels);
}

Here we get dynamic size of loaded text content and if the difference between window height and actual text height is negative – call animation function, passing number of pixel we need to shift by.

Now to animation function. Continue reading 'Pebble: How to autoscroll large text'»

Pebble: How to load random string from resource

By , February 15, 2015 10:10 pm

There’s no doubt that Firefly is a greatest TV series in the history of all creation. That is why when I was learning resource handling in Pebble SDK I have decided to create a watchface that would display a random quote from Firefly. And thanks to Bill Hatcher of http://cubemonkey.net/quotes/ I obtained a plain TXT file with almost 500 quotes.

The file is in the format "quote1%quote2%quote3..." e.g. there is a “%” separator between the quotes, so I quickly wrote a small script that gives me a position of each percentage sign within the file, so I can create an array of the positions in my C code for Pebble:

#define NO_OF_QUOTES 472
int aQuotePointers[NO_OF_QUOTES] = {0, 206, 354, 417, 480, 554, 662, 695,... 88825}

which basically gave me position of each quote in the file and which I prepended with 0 and appended with filesize. Then I added the resource to my project (in CloudPeble environment it’s as easy as loading a BLOB resource and giving it a name). A Pebble watchface or watchapp can handle resources of up to 96K, fortunately file with quotes was less, otherwise some kind of string compression would have to be implemented.

After that it’s a trivial matter to generate random position, retrieve quote from that position and display it on a text layer:

// determining number of quote (that will give us address of begining and end)
srand(time(NULL));
int number_of_quote = rand() % NO_OF_QUOTES;
  
//determining size of quote and allocating memory
int size_of_quote = aQuotePointers[number_of_quote + 1] - aQuotePointers[number_of_quote] - 1;
uint8_t *quote = malloc(size_of_quote);

//loading quote, displaying and freeing memory
ResHandle rh = resource_get_handle(RESOURCE_ID_FIREFLY_QUOTES);
resource_load_byte_range(rh, aQuotePointers[number_of_quote] + 1, quote, size_of_quote);
quote[size_of_quote] = 0; //null terminating string
text_layer_set_text(s_textlayer_quote, (char *)quote);

Lines 2-3 generate random index for the array of quote pointers
Lines 6-7 calculate size of the quote (based on position of current and next quote) and allocate memory for the quote
Lines 10-11 load range from the resource based on index and size
Lines 12-13 0-terminate the loaded range and display data on the text layer.

It’s all pretty straightforward and works like a magic and the result you can see in published watchface: Blue Sun Quotes.

Some useful links:

Next time I will describe how I handled situation when loaded quote is too long to display on a text layer

Panorama Theme by Themocracy

Bitnami