Skip to content

gregoiresage/enamel

Repository files navigation

Enamel Build Status MIT License

Enamel is a python script that generates C helpers from a Clay configuration file to easily get the value of your settings.

Enamel will :

  • handle AppMessages automatically (app_message_open, handler registration, ...)
  • save/load the value of the settings in the persistant storage automatically
  • provide a getter for each of your settings
  • notify subscribers when settings are received from Clay

You can focus on your watchapp/face, Enamel will do the rest !


Getting Started (SDK 3.13+)

  1. You project must contain a valid configuration file in src/js/config.json (see https://github.com/pebble/clay)
  2. Install enamel with pebble package install enamel
  3. Copy and paste the following line into the top of your wscript :
import sys
sys.path.append('node_modules')
from enamel.enamel import enamel
  1. Change the build of your wscript from
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), target=app_elf)

to

ctx(rule = enamel, source='src/js/config.json', target=['enamel.c', 'enamel.h'])
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c') + ['enamel.c'], target=app_elf)
  1. Launch your Pebble build : 2 files (enamel.c and enamel.h) should be generated in build and compiled

⚠️
The first time you launch a build, you will get an error message because Jinja2 module is missing.
Just follow the instructions to fix your environment.

Getting Started (CloudPebble)

In the CloudPebble environment, you can't modify the wscript so you need to call directly the python script.

  1. Under Dependencies in the project navigation, enter enamel as the Package Name and ^1.0.0 for the Version. You may use any specific version you like, however using ^1.0.0 will ensure you receive all minor version updates.

  2. Copy the content of your Clay's config.js file into a local file (local_config.js)

  3. Install the python dependencies for the code generation

pip install -r requirements.txt
  1. Call the script to generate the enamel files
python enamel.py --config /path/to/local_config.js 
  1. Copy the 2 generated files in your CloudPebble project

Using Enamel

  1. Setup your project correctly for Clay : https://github.com/pebble/clay
  2. Include enamel.h in your c file :
#include "enamel.h"
#include <pebble-events/pebble-events.h>
  1. Initialize enamel in your init function and call events_app_message_open() after any other libraries you need to init.
static void init(void) {
  // Initialize Enamel to register App Message handlers and restores settings
  enamel_init();
  
  // call pebble-events app_message_open function
  events_app_message_open(); 
  
  ...
}
  1. Deinitialize enamel in your deinit function :
static void deinit(void) {
  ...

  // Deinit Enamel to unregister App Message handlers and save settings
  enamel_deinit();
}
  1. (Optional) Subscribe with a handler after enamel_init that will be automatically called when the settings are received. Multiple subscribers are supported. Do not forget to unsubscribe before calling enamel_deinit!
static EventHandle s_window_event_handle;
static EventHandle s_text_layer_event_handle;

...

static void enamel_settings_received_window_handler(void *context){
  APP_LOG(0, "Settings received %d", enamel_get_myinteger());
  Window *window = (Window *) context;
  window_set_background_color(window, enamel_get_background());
}

static void enamel_settings_received_text_layer_handler(void *context){
  APP_LOG(0, "Settings received %d", enamel_get_myinteger());
  TextLayer *text_layer = (TextLayer *) context;
  text_layer_set_text_color(text_layer, enamel_get_foreground());
}

...

static void init(void) {
  // Initialize Enamel to register App Message handlers and restores settings
  enamel_init();

  // Subscribe a handler for a window
  s_window_event_handle = enamel_settings_received_subscribe(enamel_settings_received_window_handler, window);

  // Subscribe a handler for a text layer
  s_text_layer_event_handle = enamel_settings_received_subscribe(enamel_settings_received_text_layer_handler, text_layer);
  
  // call pebble-events app_message_open function
  events_app_message_open(); 
  
  ...
}

static void deinit(void) {
    // Unsubscribe from Enamel events
    enamel_settings_received_unsubscribe(s_window_event_handle);
    enamel_settings_received_unsubscribe(s_text_layer_event_handle);

    enamel_deinit();

    ...
}
  1. Get the value of your setting with :
enamel_get_Mysetting(); // where 'Mysetting' is a messageKey in your configuration file

Enamel API

Methods

Method Description
void enamel_init() Initialize Enamel and read settings from persistant storage
void enamel_deinit() Deinitialize Enamel and save the settings in the persistant storage
<type> enamel_get_<messageKeyId>() Return the value for the setting messageKeyId
bool enamel_get_<messageKeyId>(uint16_t index_) Only relevant for checkboxgroup.
Return the value at given index for the setting messageKeyId

Type mapping

Clay Type Type returned by the getter
input char*
toggle bool
color GColor
select/radiogroup char* or enum
checkboxgroup bool
slider int32_t

Special case for select, radiogroup

If the value of the options are string in the config.json, Enamel will generate a char* getter

If the value of the options are integer, Enamel will generate an enum mapping all the possible values for this setting and the getter will return this enum

For the given setting :

{
  "type": "radiogroup",
  "messageKey": "favorite_food",
  "label": "Favorite Food",
  "defaultValue": "1",
  "options": [
    { 
      "label": "Sushi", 
      "value": 0 
    },
    { 
      "label": "Pizza", 
      "value": 1 
    },
    { 
      "label": "Burgers", 
      "value": 2 
    }
  ]
}

Enamel will generate

typedef enum {
	FAVORITE_FOOD_SUSHI = 0,
	FAVORITE_FOOD_PIZZA = 1,
	FAVORITE_FOOD_BURGER = 2,
} FAVORITE_FOODValue;
FAVORITE_FOODValue enamel_get_favorite_food();

You can then easily code switch case for this setting

switch(enamel_get_favorite_food()){
 case FAVORITE_FOOD_SUSHI : break; //do something
 case FAVORITE_FOOD_PIZZA : break; //do something
 case FAVORITE_FOOD_BURGER : break; //do something
}

Special case for slider

Enamel will also generate a constant for your slider containing the 'precision' of your slider, e.g.

#define MY_SLIDER_PRECISION 100