Pebble: добавляем в своё приложение возможности конфигурирования и сохранения настроек

в 8:08, , рубрики: Pebble, pebble watch, tutorial, Программирование

С наращиванием функциональности и увеличением сложности приложения возникает необходимость дать пользователю возможность самому настраивать необходимые ему параметры. Приложение в свою очередь должно уметь сохранять эти настройки и предоставить пользователю интерфейс для управления ими. Какие средства и возможности для этого дали нам разработчики Pebble?

Документация у проекта Pebble хорошая и целью изложенного не является её дублирования. Это попытка собрать то, что касается хранения данных и возможностей по конфигурированию приложения в одном месте. Далее присутствуют краткие выдержки из документации и немного кода в виде примера проекта.

Хранение данных

Разработчики предусмотрели следующие варианты хранить данные приложения:

  1. Хранение данных приложения на самом устройстве.
    Для этого есть отдельное для каждого приложения key-value хранилище. Размер хранилища до 256 байт, поддерживает хранение целых чисел, строк и массивов байт. Отличительная особенность — данные сохраняются на самих часах, не требуется сопряжение с приложением-компаньоном, как следствие — скорость работы и энергосбережение. [1]
  2. Хранение данных на телефоне в приложении-компаньоне.
    В официальном мобильном приложении PebbleKit JavaScript реализует API на основе объекта localStorage. [2]

Настройка приложения

PebbleKit JavaScript позволяет отобразить на экране смартфона окно конфигурации (web-приложение) размещенное и доступное по определенному разработчиком URL'у. [3]
После завершения работы пользователя с web-приложением, оно вызывает особый URL «pebblejs://close» и передает в мобильное приложение необходимые данные.

Реализация

Для полноценной работы приложения с настройками необходимо реализовать следующее:

  • persistent storage — организовать «локальное» хранилище настроек на часах, необходимо для автономной работы приложения;
  • local storage — организовать «локальное» хранилище настроек в официальном мобильном приложении, понадобится для связи окна конфигурации с приложением на часах;
  • сделать web-страничку с интерфейсом управления настройками;
  • организовать передачу новых настроек из мобильного приложения для сохранения на часах.

Обо всем по порядку, добавим в простое приложение, которое показывает на экране время, следующие опции:

  • «отбивать» каждую минуту вибросигналом;
  • сигнализировать вибросигналом о потере и восстановлении bluetooth-соединения.

Persistent storage

Настройки будем хранить не отдельными параметрами, а в структуре.
Данные, по рекомендации из документации, будем упаковывать, так как компилятор каждое поле структуры выравнивает по границе 32 bit.
Также зададим значения по умолчанию — вибрировать при смене статуса bluetooth соединения и молчать при смене минут:

#define STORAGE_KEY 99

typedef struct persist {
    bool vibe_bt;
    bool vibe_min;
} __attribute__((__packed__)) persist;

persist settings = {
    .vibe_bt = true,
    .vibe_min = false
};

Чтение и запись данных будет выглядеть следующим образом:

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

Задаем обработчики событий, подписываемся и отписываемся от событий, добавляем чтение из хранилища при старте приложения и запись при завершении его работы:

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();
}

Теперь, когда приложение умеет сохранять состояние своих настроек, переходим к организации хранилища на телефоне.

Local storage и web-интерфейс

Для отображения значка шестеренки по клику на который вызывается окошко с настройками необходимо включить соотвествующую опцию в appinfo.json:

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

Нажатие на шестеренку генерирует событие showConfiguration, в WebView загружается страничка указанная в обработчике pebble-js-app.js:

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

Для примера простой интерфейс из двух чекбоксов и кнопки «Submit», их даже не принципиально «оборачивать» в форму.
Сначала считываем значения из localStorage и в соответствии с этим расставляем галочки на чекбоксах, если параметр отсутствует в хранилище инициализируем его значением по умолчанию:

<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>

Отслеживаем нажатие кнопки и отправляем в приложение выбор пользователя, попутно сохраняя новые значения в хранилище:

<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>

Полный код страничики, не обошлось без jquery для выполнения кода при открытии страницы:

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>

Закрытие окна-странички порождает событие webviewclosed обработчик которого необходимо прописать в pebble-js-app.js:

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

Подошли к тому моменту, когда показали пользователю всё что хотели, он сделал свой выбор и дело за малым, сохранить этот выбор в приложении на часах.

AppMessage API

Как сообщает нам документация, AppMessage API реализует push-ориентированный протокол обмена. Либо часы, либо смартфон может инициировать передачу сообщения. Обработка принимающей стороной осуществляется callback'ами.
Каждое сообщение — словарь, который содержит список пар key-value.

Чтобы и часы и смартфон «понимали», чем они обмениваются, необходимо описать параметры этого словаря минимум в двух местах, в основном Си файле:

#define VIBE_BT 1
#define VIBE_MIN 2

и в appinfo.json:

{
    /* ... */
    "appKeys": {
        "VIBE_BT": 1,
        "VIBE_MIN": 2,
    }
    /* ... */
}

Регистрируем обратный вызов и инициализируем 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);
    /*...*/
}

И завершающий штрих, описываем callback для входящих сообщений:

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;
}

В итоге, приложение при первом запуске/установке инициализирует persistent storage, умеет читать и сохранять данные, имеет собственное страничку для настройки параметров.

Полный код проекта на 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(); }

Автор: tmnhy

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js