After integration of the Isometric and WebSocket modules (previously ‘additional’) into PGE, I took some time to do something I’d wanted to do for a while: make it a repo usable directly after git clone. Previously the repo was an example project which could be cloned and played around with, but to use the engine in a new game required knowing which files to copy into the new project.

After re-organization, the repo can now be directly git cloned into the new project’s src directory and requires no further manipulation to be compiled. The previous asteroids example has been moved to a new pge-examples repository on the asteroids branch, which also hosts a new example ‘game’ for the WebSockets module PGE WS, which aims to allow developers to send and receive multiplayer data with as few lines as possible. The example allows each player who installs the example to trigger a vibration on all other player’s watches while they are running the game, after hosting the server.

For an overview of how to use the new WebSockets module, check out the docs for PGE WS, which summarizes how to set up the server (which forwards all data both directions automatically by default), the JS client, and a C client, which needs only to connect, send and receive data.

On May 29th, 2014 I released ‘BBC News Headlines’, an app I had used personally for a while to read BBC News stories on my wrist to keep up on current affairs with minimal effort. With the config page, I added some settings (category selection, font size, etc.) and it worked well.

When I learned about the concept behind the timeline, one of my first thoughts was “I can use this!”. I had the idea to add timeline pins to the app, as well as update it for Pebble Time to use colors, pins, as well as a new ‘cards’ design (as recommended by Pebble’s new Design and Interaction guides, which you should check out!) to replace the unnecessary menu screen. I did this, which you can read about when the app was half-way through redesign.

Now the re-design is finished, the timeline integration improved (reduced push interval, custom colors, aggressive de-duplication and status reporting to the watchapp), and config page moved into the app itself, removing a need for an external page entirely. I also added a whole bunch of polish behind the scenes, with persistent storage of the last downloaded news stories, timeout and disconnection handling, adaptive scrolling and subtle animations etc.

1.5-flow

With a new name ‘News Headlines’, this version is now available as a straight update to ‘BBC News Headlines’ for existing and new users, and is fully compatible with Aplite (Pebble & Pebble Steel).

As a stretch goal, I have implemented all the necessary code to download the (conveniently sized 144×81) thumbnails for each news story for display in a sort of ‘viewer pane’ within the app, but discovered too late that neither the HTML5 Canvas object (which could be used to get JPEG pixel data once rendered to the object), or on-board JPEG de-compression is an option, so that feature, while exciting, will have to wait for now.

In the meantime, you can find it on the Pebble appstore!

A feverish evening spent with little else to do resulted in a quick port of my isometric Pebble library to Java Canvas with Graphics2D. Might prove useful for an isometric tile game or such if the mood takes me. There’s something distinctly satisfying about seeing the same results on a different platform.

screenshot

To use, create a context where a Graphics2D object is available, then use static methods of the Isometric class to draw stuff.

public void program(Graphics2D g2d) {
  // Black background
  g2d.setColor(Color.BLACK);
  g2d.fillRect(0, 0, Build.WINDOW_SIZE.width, Build.WINDOW_SIZE.height);

  Isometric.drawRect(g2d, new Vec3(100, 100, 100), new Dimension(100, 100), Color.BLUE);
  Isometric.fillRect(g2d, new Vec3(50, 50, 50), new Dimension(50, 50), Color.RED);

  Isometric.fillBox(g2d, new Vec3(150, 150, 150), new Dimension(25, 25), 25, Color.YELLOW);
  Isometric.drawBox(g2d, new Vec3(150, 150, 150), new Dimension(25, 25), 25, Color.BLACK);
}

You can see all the applicable code on GitHub.

According to the Pebble dev portal, the last version of Beam Up to be released was December 14th, 2014. If I remember correctly, that version was the much required update that rolled all the different versions (inverted, with date, with battery etc.) into one version. This was a move away from the SDK 1.x hangover where app config pages did not exist, so multiple apps were required.

The SDK has come a long way since then, with the latest release (3.0-dp9) released today, after much testing. The biggest change this time around is deprecating the InverterLayer element, popularly used to add a quick black-on-white effect to many watchfaces, as it no longer makes sense in a world of 64 colors.

How do you invert a color? As I found out during my degree course, techniques applied to colors (and even grayscale) cannot be easily applied to color in the same way. A prime example from that work was the techniques involved in Mathematical Morphology and utilized ordering of pixels heavily. A white pixel can be defined as ‘greater’ than a black pixel, a grayscale pixel with value 128 can be ‘lesser’ than one with a value of 238, and so on. This enabled the implementation of various filters that went on to enable the automatic counting of tree canopies. But the last part of the work was adapting these techniques for color images. The crucial question was this: how can you decide whether one color is ‘greater’ than another? There were several options, with no clear leader in terms of logic. Was the ‘greater’ color:

  • The one with the largest single component?
  • The one with the largest averaged value?
  • The one with the largest total component value?

The answer I chose was borne out of a piece I read arguing that the human eye is more sensitive to green light (perhaps a hangover from living in the verdant wilderness?), and so proposed that the colors be ordered with a preference for the green component, and this gave good results.

The same problem exists when placing a Pebble InverterLayer over another color in a Layer beneath it: what’s the inverse of yellow? Is it:

  • The inverted color according to classic opposites?
  • The inverted bits in the byte representation?
  • The inverted values of the RGB channels?

So it was removed, and APIs added to MenuLayer (the chief user of the InverterLayer in the system) to allows developers to specify their own choice of colors for menu items when each is selected. A more general approach I adopted to enable me to continue to develop Beam Up (a handy coincidence when I wanted to add color themes) is to have the developer specify two colors, and use the frame buffer API to invert then to each other wherever they appear. This approach worked really well, and enables any color combination desired as an artificial ‘inversion’; especially useful for adding themes to Beam Up. These take the form of pairs of colors, selectable in presets (for now!) in the app config page.

The image below shows three of the new themes in action: blue, green and midnight. Classic, Inverted Classic, Red, and Yellow are also initially available.

themes

The new version is available on the Pebble appstore. On Aplite (Pebble, Pebble Steel) it behaves as it ever did, except now the config page remembers your choices from last time you saved. On Basalt, the new themes are available, using the new pseudo-InverterLayer, called InverterLayerCompat in the code. This is still available in full on GitHub!

A few weeks ago I wrote about transmitting images to Pebble Time from Android, as a quick and convenient way to open any image via an Intent and display it on the watch. At that time, I had problems sending some types of images, where only the blue channel appeared to make it across correctly. The only exception appeared to be the image bundled in the Android app’s resources, which always displayed correctly.

After a getting and re-write of the flaky transmission system (and a bonus first-time working solution!), I now have the app in a state where any image opened with the Android app from a file manager will be resized to fit the Pebble screen and transmitted for display.

Now, the focus will be to enable cropping and scaling of the image in the Android app to let the user choose which part of the image to display (as a photo shown on the watch loses detail somewhat!). In the meantime, here are some images!

Original Image (credit to gmiezis!)

11034411_954803447872213_3799211289901104533_o

Imported to the Android app

Screenshot_2015-04-19-17-45-46

Image on Pebble Time

IMG_20150419_174608

Introduction

As a side-effect of being involved with the development and testing of the new Pebble timeline experience, I’ve been eager to try my hand at exploiting this new mechanism to bring timely updates to users of BBC News (which as with most of my apps, began as an app for personal use) with timeline pins.

The Concept

As you may know from reading the Pebble Developers timeline guides, Pebble watchapps can now incorporate a purely web-based element to display data from web data sources (such as the BBC News feeds) on the timeline. This is ideal for chronological events such as news stories, as each pin is shown on the timeline according to date.

Pins can also include notifications that appear when they are first created or updated, keeping the user informed as details change. This functionality is used to show a notification each time a new news story is available. The pin itself includes only the icon (standard timeline pin icon) and the title, which is the main title of the story. In addition, timeline pins can have actions associated with them, enabling the user to launch the associated watchapp (whose API key is used to push the pins) and pass a single integer as a context argument. My idea was to use this action ability to enable the pin to open the BBC News watchapp to show the full story details, since a very long timeline pin body was a poor scrolling experience.

The Problem

The fundamental question was this; How does one tell the watchapp which story to show from the pin using a single integer? The first guess was to specify the launchCode (the argument) as the position in the array of downloaded news stories. This would work for a very recent pin only, as the order of news stories in the feed changes frequently as new stories are added and removed from the headlines. After an hour or two, the order would change and an existing pin would possess a launchCode pointing to the same array location, but no longer guaranteed to be the same story.

Implementation

The solution I ended up choosing came from Wristponder, which uses a checksum of the persisted list of responses on the Android and Pebble sides, prompting a re-download to the watch if the two do not match (i.e.: The user has modified their list of responses). Thus the procedure to open a news story from its pin is this:

1. Backend server running on DigitalOcean downloads the RSS feed from the BBC every half an hour, using a function to create a list of story objects from the XML;

var parseFeed = function(responseText) {
  var items = [];
  var longestTitle = 0;
  var longestDesc = 0;
  while(responseText.indexOf('<title>') > 0 && items.length < MAX_ITEMS) {
    //Title
    var title = responseText.substring(responseText.indexOf('<title>') + '<title>'.length);
    title = title.substring(0, title.indexOf('</title>'));
    responseText = responseText.substring(responseText.indexOf('</title>') + '</title>'.length);

    //Desc
    var desc = responseText.substring(responseText.indexOf('<description>') + '<description>'.length);
    desc = desc.substring(0, desc.indexOf('</description>'));

    // Date
    var date = responseText.substring(responseText.indexOf('<pubDate>') + '<pubDate>'.length);
    date = date.substring(0, date.indexOf('</pubDate>'));

    //Add
    var s = { 'title': title, 'description': desc, 'date': date };
    items.push(s);

    // Metrics
    Log('Story '+ items.length + ': ' + s.title + ' // ' + s.description);
    Log('(' + s.title.length + 'x' + s.description.length + ')');
    if(s.title.length > longestTitle) {
      longestTitle = s.title.length;
    }
    if(s.description.length > longestDesc) {
      longestDesc = s.description.length;
    }

    // Next
    responseText = responseText.substring(responseText.indexOf('</description>') + '</description>'.length);
  }

  Log('parseFeed(): Extracted ' + items.length + ' items.');
  Log('parseFeed(): Longest title/description: ' + longestTitle + '/' + longestDesc);
  return items;
};

2. Backend uses story titles, publish dates and checksums of the titles to push a pin for each story. The id field of each pin is a prefix followed by the Unix timestamp of the pubDate RSS field (as an easy solution of a fairly unique number from each story). The checksum is generated by simply adding all the character codes in each title, then specifying this number as the ‘Open Story’ pin action’s lanchCode;

var pin = {
  'id': 'bbcnews-story-' + pubDate.unix(),
  'time': pubDate.toDate(),
  'layout': {
    'type': 'genericPin',
    'tinyIcon': 'system://images/TIMELINE_PIN_TINY',
    'title': gStories[i].title,
    'subtitle': 'BBC News Headline'
  },
  'createNotification': {
    'layout': {
    'type': 'genericPin',
    'tinyIcon': 'system://images/TIMELINE_PIN_TINY',
    'title': gStories[i].title,
    'subtitle': 'BBC News Headline'
    }
  },
  'actions': [
    {
      'title': 'Open Story',
      'type': 'openWatchApp',
      'launchCode': checksum(gStories[i].title)
    }
  ]
};

3. These pins are filtered for duplicates and staleness by the Pebble timeline public API, then pushed through the Pebble mobile app to the user’s watch. This is demanded by a subscription to the ‘headlines’ topic that all users are subscribed to when they first launch the BBC News watchapp (this will be optional in the first release).

4. The user selects a BBC News story from their timeline and chooses the ‘Open Story’ pin action. This opens the watchapp with the launch_get_args() value set as the checksum stored in the pin when it was originally generated. If the launchCode is not zero, a pin was specified;

#ifdef PBL_PLATFORM_APLITE

// Timeline not available, give me ALL the stories!
comm_request(COMM_MODE_LIST, 0);

#elif PBL_PLATFORM_BASALT

// Is launchCode specified from a pin?
int launch_code = launch_get_args();

if(launch_code == 0) {
  // Get all stories
  comm_request(COMM_MODE_LIST, 0);

  stories_window_set_desc_text("Updating...");
} else if(launch_reason() == APP_LAUNCH_TIMELINE_ACTION) {
  // Check this checksum, JS!
  comm_request(COMM_MODE_PIN, launch_code);

  stories_window_set_desc_text("Getting pin story...");
}

#endif

5. The watchapp sends the checksum for the pin in question to PebbleKit JS, which downloads the latest feed from the BBC News site and checks the query checksum against checksums of all the stories downloaded. First, the checksum is obtained from the watch’s AppMessage;

if(hasKey(dict, 'KEY_ACTION')) {
  gLaunchCode = getValue(dict, 'KEY_ACTION');
  Log('TIMELINE PIN LAUNCH CODE: ' + gLaunchCode + '\n\n\n');

  // Download stories, and match the titles to the pin
  download(persistRead('category', 'headlines'), findPinWithHash);
}

Next, the matching story is found;

function findPinWithHash(responseText) {
  //Strip metadata
  var spool = responseText.substring(responseText.indexOf('<item>') + '<item>'.length);
  gQuantity = 30; // Get all

  var stories = parseFeed(spool);
  Log('Finding title with launchCode=' + gLaunchCode + ' in list of ' + stories.length + ' stories');

  var found = false;
  for(var i = 0; i < stories.length; i += 1) {
    var check = checksum(stories[i].title);
    if('' + check == '' + gLaunchCode) {
      Log('Found! check=' + check + ', gLaunchCode=' + gLaunchCode);

      // Send to phone
      var dict = {
        'KEY_ACTION': 0,
        'KEY_TITLE': stories[i].title,
        'KEY_DESCRIPTION': stories[i].description
      };
      Pebble.sendAppMessage(dict, function() {
        Log('Sent pin data to watch!');
        found = true;
      });
    }
  }

  // Not found?
  if(found == false) {
    var dict = {
      'KEY_FAILED': 1
    };
    Pebble.sendAppMessage(dict, function() {
      Log('Informed Pebble of failure to find story.');
    },
    function(err) {
      Log('Failed to inform of failure!');
    });
  }
}

6. If the story is still relatively recent (this can vary) and a checksum match is found, the story’s full title and body are sent to the watchapp for display to the user. The RSS feed also contains links to thumbnails already formatted to 144 pixels in width, which is ideally placed to be a possible future feature.

Results

The resulting flow looks something like the image below, which is still a work in progress:

pin

This new layout will be available soon (once bugs are worked out) for Aplite users of the existing BBC News Headlines watchapp, in color on Basalt for those with Pebble Time watches (admittedly few right now!). Stay tuned!

Long time no blog! There hasn’t been much time for experimentation (besides creating a couple of color apps for Pebble Time) in the months leading up the Pebble Time Kickstarter. Totally worth the effort though, after seeing the response to the new material on the Pebble Developers site.

This weekend, however, I found some time to bring to reality an idea I have had since I first learned of the ability to make color apps for Pebble. Being a big fan of PebbleKit Android (used in Dashboard, Wristponder, Watch Trigger etc) to remotely control/access the connected phone. The idea is this: create an app for Android that registers as a receptor of opening image files. This app then resizes and reduces the color palette of the file before piping it to an automatically opening watchapp for viewing. This stemmed partly from a desire to avoid constantly changing resource files and recompiling a simple app for viewing PNG files every time I wanted to see how a new image looked on Pebble Time.

The implementation was pretty straightforward: Make a simple Android app that included an Intent filter for image files, and a watchapp that simply accepted data packets and stored them in the image data allocated for a GBitmap of the right size. The difficulty came in the signaling between the two. I’m no stranger to establishing communication schemes between Android and Pebble apps, but I’m only human. For some reason I was trying to use a mix of PebbleDataReceivers and PebbleAckReceivers. The former is for processing messages from the watch, and the latter is for reacting to the event that the watch acknowledges a message from the phone. By sending the next data packet in an ‘ACK’ handler, you can ensure maximum transmission speed as no time is wasted between the watch processing one packet and being ready for the next. This is the same method that apps like Wristponder use for transferring lots of data (try 30 canned responses!). If you’re not careful, mixing these two modes (one of which can be considered manual, the other semi-automatic when set up in a continuous data transmission loop) can result in puzzling behavior that is difficult to debug.

Once debugged, however, the result is an app that sends a complete uncompressed image (save that for another day) 24k image to the watch in about 11 – 16 seconds (approx 2 kB/s) and then displays it on the watch. In the meantime, both the Android and Pebble apps show the progress of the transfer:

Screenshot_2015-03-08-18-41-59Screenshot_2015-03-08-18-19-10

transfer

 

 

 

 

 

 

 

 

 

 

 

The app sends a built-in image of a tree as a default option when it is launched by itself, or another image if presented one using ‘Open With’ actions, such as from file managers or emails. The result of sending the tree image is thus:

IMG_20150308_182032

For some reason I can’t quite fathom the color reduction process the app uses (admittedly brittle bit shifting) doesn’t handle all files as well, producing discolored results, which I will try and iron out with a better solution, perhaps in the Android SDK itself. When that day comes, I’ll polish both app components and hopefully make another Google Play app of it (free, of course, this doesn’t do anything particularly useful) and also a two-part library generalized to facilitating large data transfers between phone and watch.

Watch this space!

 

Follow

Get every new post delivered to your Inbox.

Join 308 other followers