Persistent configs in Rocky.js watchfaces

Rocky.JS is the first step in Pebble journey to run JavaScript directly on the watches (unlike Pebble.JS which runs on your phone). Previously I described how to convert a simple watchface from C to Rocky.js. But that was a static watchface with unchangeable settings.

Here I will show how to create a configurable watchface in Rocky.js similarly how classic SDK faces can be configured. You will be able to reuse your existing config page – and if it was set to work with Pebble emulator as well as real watch – you will reuse it without any changes at all.

First let’s review how classic Pebble SDK calls config page. In PKJS (JavaScript) portion of Pebble code usually there’s a piece like this:

Pebble.addEventListener("showConfiguration",
  function(e) {
    Pebble.openURL("http://my.cool.server/pebble/my_cool_config.html");
  }
);

If user requests config of face/app – this event fires and opens page with configurable options from specified URL. After user modifies settings usually “Save” button is clicked on that page and code similar to this executes:

$('#xbtnSave').click(function () {
   var location = (decodeURIComponent(getURLVariable('return_to')) || 
                   "pebblejs://close#") + 
                   encodeURIComponent(JSON.stringify(settings));
   document.location = location;
})

Here, first we determine which location to redirect config page to. If parameter "return_to” is passed in query string (here custom function getURLVariable() is used to extract individual parameters – look it up), so if this parameter is passed – it means config page is called form the emulator and we use it for redirection. Otherwise we use standard "pebblejs://close#" URL to save settings into real watch. We also take settings object which has our collective options combined, convert it to string and add to the URL as a parameter. Page then is redirected to resulting URL and Pebble emulator or real watch takes care of processing parameters.

So, how can we (re)use it in a Rocky.js watchface?

Assuming you already have a running watchface similar to described in previous post let’s add a config button (it can be an image) – add it right after your canvas declaration:

<img src="configure.png" style="cursor:pointer" onclick="open_config();" />

And add actual function open_config() to your JS code:

function open_config() {
    window.open("http://my.cool.server/pebble/my_cool_config.html?return_to=" + 
                encodeURIComponent(location.href + "?config=true&json="), 
                "config", "width=350, height=600")
}

Similarly to the original approach this code opens your config page in a separate window of predefined size, but in this case it explicitly adds "return_to" parameter which is current page’s own URL plus 2 additional parameters – “config=true” – to indicate that page is called to save config settings and “json=” which is placeholder for returned settings.

Let’s go back to HTML and add following code before your CANVAS declaration:

<script type="text/javascript">
   if (getURLVariable('config') == 'true') {
      var json_string = getURLVariable('json');
      if (json_string != '') opener.save_config(json_string);
      window.close();
   }
</script>

Using getURLVariable() function (did you look it up?) this code determines whether config=true parameter is passed in query string. If it isn’t – that means it’s a normal page load and we skip to the rest of watchface code. But if config=true is passed – that means we’re currently in a popup window and returning from the external config page. In that case we check if any payload was passed in the “json=” parameter (remember placeholder we added before?). If it’s empty that means user clicked “Cancel” on the config page and we simple close the popup window. Otherwise the popup calls save_settings() function of the main parent page, passes the received string with settings and then closes the window.

Let’s go back to your main JS code and add the save_settings() function:

var flag_showSeconds;
var flag_invertColors

function save_config(json_string) {

    //getting settings
    var settings = JSON.parse(decodeURIComponent(json_string));

    //storing them
    flag_showSeconds = settings.showSeconds; 
    localStorage.setItem("flag_showSeconds", flag_showSeconds);

    flag_invertColors = settings.invertColors; 
    localStorage.setItem("flag_invertColors", flag_invertColors);

    rocky.mark_dirty();

}

Here we restoring objects with settings from its stringified version and store settings (in this case .showSeconds and .invertColors) both into variables (for use in code) and localStorage for persistent save. Traditional C SDK uses Pebble persistent storage, but in JavaScript world we use localStorage. We then mark canvas a dirty so it can refresh with new settings

We’re done with retrieving and persisting settings. Now to use them. Before your main code runs – you need to load settings from localStorage, should saved settings already exist. Or fill them with default values otherwise:

flag_showSeconds = localStorage.getItem("flag_showSecondse") === null ? '1' : localStorage.getItem("flag_showSeconds");
flag_invertColors = localStorage.getItem("flag_invertColors") === null ? '1' : localStorage.getItem("flag_invertColors");

Now that you have your settings – either read from localStorage on initial run or returned from config page – you can use them:

rocky.update_proc = function (ctx, bounds) {
   
    //.... some important stuff

    if (flag_showSeconds == '1') {
       //.... run code to display seconds
    }
    
    //.... more important stuff

    if (flag_invertColors == '1') {
       //.... run code to invert colors
    }
 
     //.... some really important stuff

}

See wasn’t that easy? To see this shenanigan in action take a look at Vortex watchface in at the beginning of this article. Tap or click gear icon next to it. Change any settings (besides battery and Bluetooth because – come on!) and hit “Save” button. Close and reopen the page. It’s a miracle.

2 replies on “Persistent configs in Rocky.js watchfaces”

  1. Hi, gadgetbridge developer here (https://github.com/Freeyourgadget/Gadgetbridge ).
    I am trying to find the best on adding support for watchapps configuration, but we are not willing to get Internet access permission.
    My best bet at the moment is to interact with the local javascript contained in the pebble app and invoke the standard browser for the configuration page itself.
    I believe this example will be helpful for me, and just wanted to thank you!

  2. @Daniele glad it was helpful! Yes, the fact that Pebble app requires Internet connection just to load config page from external server sometimes is a major inconvenience. Would be interesting to see the workaround.

Leave a Reply

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