📜 ⬆️ ⬇️

Pebble: add configuration and save options to your application

With increasing functionality and increasing the complexity of the application, it becomes necessary to give the user the ability to customize the parameters he needs. The application in turn should be able to save these settings and provide the user with an interface for managing them. What tools and opportunities did Pebble developers give us for this?

The documentation for the Pebble project is good and the goal of the above is not to duplicate it. This is an attempt to collect data storage and application configuration capabilities in one place. Further, there are brief excerpts from the documentation and some code as an example project.

Data storage


The developers have provided the following options to store application data:
  1. Storing application data on the device itself.
    For this, there is a separate key-value storage for each application. Storage size up to 256 bytes, supports storing integers, strings and arrays of bytes. A distinctive feature is that the data is stored on the watch itself, no pairing with the companion application is required, as a result, the speed of work and energy saving. [one]
  2. Data storage on the phone in the companion application.
    In the official mobile application, PebbleKit JavaScript implements the API based on the localStorage object. [2]


Application setup


PebbleKit JavaScript allows you to display on the smartphone screen a configuration window (web application) hosted and accessible at a URL specified by the developer. [3]
After the user's work with the web application is completed, it calls the special URL “pebblejs: // close” and sends the necessary data to the mobile application.
')

Implementation


For the application to work properly with the settings, you need to implement the following:

About everything in order, we add in a simple application that shows the time on the screen, the following options:


Persistent storage


The settings will not be stored as separate parameters, but in a structure.
The data, on the recommendation of the documentation, will be packaged, since the compiler aligns each field of the structure along the 32 bit border.
We will also set default values ​​- to vibrate when changing the status of a bluetooth connection and to be silent when changing minutes:

#define STORAGE_KEY 99 typedef struct persist { bool vibe_bt; bool vibe_min; } __attribute__((__packed__)) persist; persist settings = { .vibe_bt = true, .vibe_min = false }; 

Reading and writing data will look like this:

  /*...*/ persist_read_data(STORAGE_KEY, &settings, sizeof(settings)); /*...*/ persist_write_data(STORAGE_KEY, &settings, sizeof(settings)); 

We set event handlers, subscribe and unsubscribe from events, add a read from the repository at the start of the application and write at the completion of its work:

 static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { /*...*/ if ((units_changed & MINUTE_UNIT) && (settings.vibe_min)) { vibes_double_pulse(); }; } void bt_handler(bool connected) { if (settings.vibe_bt) { vibes_long_pulse(); }; } static void init(void) { if (persist_exists(STORAGE_KEY)) { persist_read_data(STORAGE_KEY, &settings, sizeof(settings)); } else { persist_write_data(STORAGE_KEY, &settings, sizeof(settings)); }; /*...*/ window_stack_push(window, animated); tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); bluetooth_connection_service_subscribe(bt_handler); } static void deinit(void) { /*...*/ persist_write_data(STORAGE_KEY, &settings, sizeof(settings)); tick_timer_service_unsubscribe(); bluetooth_connection_service_unsubscribe(); } 

Now, when the application is able to save the state of its settings, go to the storage organization on the phone.

Local storage and web interface


To display the gear icon by clicking on it, a window with settings is called up, you must enable the corresponding option in appinfo.json :

 { /* ... */ "capabilities": [ "configurable" ], /* ... */ } 

Clicking on the gear generates a showConfiguration event, the page specified in the pebble-js-app.js handler is loaded into WebView :

 Pebble.addEventListener('showConfiguration', function(e) {Pebble.openURL("http://domain.tld/index.html"); }) 

For example, a simple interface of the two checkboxes and the “Submit” button, they are not even fundamentally “wrapped” in a form.
First, we read the values ​​from localStorage and in accordance with this we tick the checkboxes, if the parameter is absent in the storage, initialize it with the default value:

 <script> $().ready(function() { var vibe_bt = parseInt(localStorage.getItem("vibe_bt")); var vibe_min = parseInt(localStorage.getItem("vibe_min")); if (isNaN(vibe_bt)) { vibe_bt = 1; }; if (vibe_bt) { $('#vibe_bt').prop('checked', true); } else { $('#vibe_bt').prop('checked', false); }; if (isNaN(vibe_min)) { vibe_min = 0; }; if (vibe_min) { $('#vibe_min').prop('checked', true); } else { $('#vibe_min').prop('checked', false); }; }); /*...*/ </script> 

We follow the button press and send the user a choice to the application, storing new values ​​in the repository along the way:

 <script> /*...*/ var submitButton = document.getElementById("b_submit"); submitButton.addEventListener("click", function() { localStorage.setItem("vibe_bt", $('#vibe_bt').prop('checked') ? 1 : 0); localStorage.setItem("vibe_min", $('#vibe_min').prop('checked') ? 1 : 0); var result = { vibe_bt: $('#vibe_bt').prop('checked') ? 1 : 0, vibe_min: $('#vibe_min').prop('checked') ? 1 : 0, }; var location = "pebblejs://close#"+JSON.stringify(result); document.location = location; }, false); </script> 

Full code pages, it was not done without jquery for executing code when opening the page:

index.html
 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="js/jquery/jquery.min.js"></script> <title>Configuration window</title> <style> </style> </head> <body> <div> <div> <input id="vibe_bt" type="checkbox" checked /> <span>vibe bluetooth on/off</span> </div> <div> <input id="vibe_min" type="checkbox" checked /> <span>vibe every minute</span> </div> <div> <input id="b_submit" type="submit" value="Save" /> </div> </div> <script> $().ready(function() { var vibe_bt = parseInt(localStorage.getItem("vibe_bt")); var vibe_min = parseInt(localStorage.getItem("vibe_min")); if (isNaN(vibe_bt)) { vibe_bt = 1; }; if (vibe_bt) { $('#vibe_bt').prop('checked', true); } else { $('#vibe_bt').prop('checked', false); }; if (isNaN(vibe_min)) { vibe_min = 0; }; if (vibe_min) { $('#vibe_min').prop('checked', true); } else { $('#vibe_min').prop('checked', false); }; }); var submitButton = document.getElementById("b_submit"); submitButton.addEventListener("click", function() { localStorage.setItem("vibe_bt", $('#vibe_bt').prop('checked') ? 1 : 0); localStorage.setItem("vibe_min", $('#vibe_min').prop('checked') ? 1 : 0); var result = { VIBE_BT: $('#vibe_bt').prop('checked') ? 1 : 0, VIBE_MIN: $('#vibe_min').prop('checked') ? 1 : 0, }; var location = "pebblejs://close#"+JSON.stringify(result); document.location = location; }, false); </script> </body> </html> 


Closing the page window raises the webviewclosed event, the handler of which must be registered in pebble-js-app.js :

 Pebble.addEventListener('webviewclosed', function(e) { Pebble.sendAppMessage(JSON.parse(e.response), function(e) {}, function(e) {}); }) 

They came to the moment when they showed the user everything they wanted, he made his choice and the matter is small, to keep this choice in the application on the clock.

AppMessage API


According to the documentation, the AppMessage API implements a push-oriented exchange protocol. Either the watch or smartphone can initiate the message transfer. Handling is handled by callbacks.
Each message is a dictionary that contains a list of key-value pairs.

In order for both the clock and the smartphone to “understand” what they exchange, it is necessary to describe the parameters of this dictionary in at least two places, mainly in the C file:

 #define VIBE_BT 1 #define VIBE_MIN 2 

and in appinfo.json :
 { /* ... */ "appKeys": { "VIBE_BT": 1, "VIBE_MIN": 2, } /* ... */ } 

Register the callback and initialize the AppMessage:

 static void init(void) { /*...*/ app_message_register_inbox_received(in_received_handler); const uint32_t inbound_size = 128; const uint32_t outbound_size = 128; app_message_open(inbound_size, outbound_size); /*...*/ } 

And the final touch, we describe the callback for incoming messages:

 void in_received_handler(DictionaryIterator *received, void *context) { Tuple *vibe_bt_tuple = dict_find(received, VIBE_BT); Tuple *vibe_min_tuple = dict_find(received, VIBE_MIN); settings.vibe_bt = (bool)vibe_bt_tuple->value->int16; settings.vibe_min = (bool)vibe_min_tuple->value->int16; } 


As a result, when the application is first launched / installed, the application initializes persistent storage , can read and save data, has its own page for setting parameters.

Full code of the project on Bitbucket .

[1] Pebble Developers // Persisting App Data
[2] Pebble Developers // Extending App Capabilities // Storing Data
[3] Pebble Developers // App Configuration
[4] Pebble SDK 2.0 Tutorial # 8: Android App Integration | try {work (); } finally {code (); }
[5] Pebble SDK 2.0 Tutorial # 9: App Configuration | try {work (); } finally {code (); }

Source: https://habr.com/ru/post/245247/


All Articles