Статьи

Разработка Pebble Watch с использованием JavaScript

В последние месяцы на часах Pebble растет число технарей, таких как я, которые с восторгом носят их на запястьях. Они только что выпустили вторую версию своего API, которая не только приводит в исполнение магазин приложений Pebble, но и привносит в картину фреймворк JavaScript. Платформа PebbleKit JavaScript Framework позволяет использовать JavaScript для динамического чтения данных и событий из Интернета в приложение для просмотра Pebble, и наоборот. Это открывает новые возможности для приложений Pebble — теперь мы можем легко интегрировать часы Pebble с любым количеством классных веб-API.

Эта статья научит вас, как добавить JavaScript в приложение Pebble с помощью PebbleKit JavaScript Framework. Мы будем создавать watchface, который всегда отображает адрес ближайшего Starbucks. Чтобы получить эти данные, мы будем использовать API Foursquare, чтобы находить близлежащие места с помощью поиска по месту их проведения. Будет легко изменить функциональность для поиска другого типа местоположения, если вы не большой любитель Starbucks!

Эта статья написана с точки зрения разработчика JavaScript, стремящегося начать с самого начала. Будет немного программирования на C, так как само приложение Pebble watch работает на C. Если вы не знакомы с C, вы можете использовать пример кода C и настроить JavaScript, чтобы добавить новые функции и поэкспериментировать!

Предпосылки

Если вы хотите следовать этому уроку, вам понадобится следующее:

  • Телефон Android или iPhone с установленным и запущенным приложением Pebble.
  • Часы Pebble .
  • Учетные данные API для API Foursquare .
  • Сеть Wi-Fi для передачи вашего приложения на мобильное устройство и часы Pebble.
  • Храбрость, чтобы объединить JavaScript с некоторым C-кодом

Следует отметить один довольно важный момент: ваше устройство iPhone / Android и ваш компьютер должны будут находиться в одной сети Wi-Fi, чтобы вы могли загружать в них новые приложения.

Установка Pebble SDK

Нам нужно установить Pebble SDK локально. Инструкции по загрузке и установке различаются в зависимости от операционной системы, но в документации Pebble все ясно. Поэтому я не буду вдаваться в подробности здесь.

Ваш первый проект

Сначала создайте папку для всех ваших приложений Pebble и перейдите к ней в окне терминала:

mkdir /Users/patcat/pebble-dev/projects cd /Users/patcat/pebble-dev/projects 

Затем создайте новый проект с помощью следующей команды.

 pebble new-project --javascript find_me_starbucks 

Эта команда создает новый каталог с именем вашего проекта (например, find_me_starbucks ). Мы собираемся работать в этом каталоге, поэтому cd в него. Внутри каталога проекта вы заметите, что Pebble был достаточно любезен, чтобы настроить для нас кучу вещей:

  • appinfo.json — Это действительно похоже на файл package.json для разработчиков Node.
  • ресурсы — храните изображения и тому подобное здесь, если они нужны вашему приложению.
  • SRC — Весь ваш код живет здесь. Это где мы начнем.
  • wscript — этот файл сообщает Pebble SDK, как создать ваше приложение. Этот файл лучше оставить в покое, если вы не знаете, что делаете.

Запуск приложения на устройстве Pebble

Далее мы проверим, можем ли мы успешно загрузить наше примерное приложение на часы Pebble. Убедитесь, что вы все еще находитесь в папке вашего проекта, и выполните следующую команду сборки.

 pebble build 

Это запустится в терминале и подготовит все необходимое для загрузки вашего приложения на ваше устройство. Если вы впервые занимаетесь разработкой Pebble, нам нужно настроить часы Pebble для принятия нашего тестового приложения. Сделать это:

  • На Android откройте приложение Pebble> Настройки> Параметры разработчика и установите флажок Включить подключение к разработчику.
  • На iOS перейдите в приложение настроек iOS> Pebble> Включить режим разработчика.

Затем откройте приложение Pebble, откройте меню и нажмите на недавно включенный пункт меню «Разработчик». Вы должны увидеть экран, который отображает IP-адрес ваших часов Pebble. Этот экран показан на следующем рисунке.

Экран разработчика Pebble

Введите в терминале следующую команду, заменив IP_ADDRESS_OF_YOUR_PHONE на IP-адрес вашего телефона.

 pebble install --phone IP_ADDRESS_OF_YOUR_PHONE 

Ваши часы Pebble теперь должны иметь ваше тестовое приложение. Это должно быть приложение по умолчанию, которое просто обнаруживает, когда вы нажимаете каждую кнопку на ваших часах Pebble. Если все работает правильно, мы можем перейти к следующему этапу поиска ближайшего Starbucks. Если что-то пошло не так, вот несколько потенциальных вещей, которые нужно проверить:

  • Убедитесь, что ваш телефон и компьютер находятся в одной сети.
  • Убедитесь, что на ваших часах Pebble есть свободный слот для приложения. Если нет, вам нужно сначала удалить один!
  • Попробуйте перезагрузить телефон и снова выполнить команду pebble install . Обратите внимание, что ваш IP-адрес может измениться в процессе.

Файл appinfo.json

Этот файл является невероятно важной частью нашей головоломки в приложении Pebble. Команда, которую мы запускали ранее, настраивала самые базовые части, но мы хотим точно знать, что к чему и где нам нужно внести некоторые коррективы. Файл appinfo.json для «Find Me Starbucks» будет выглядеть так:

 { "uuid": "f614da96-1c67-4ab6-be80-903d63728080", "shortName": "Find Me Starbucks", "longName": "Find Me Starbucks", "companyName": , "versionCode": 1, "versionLabel": "1.0.0", "watchapp": { "watchface": true }, "appKeys": { "location": 0 }, "capabilities": ["location"], "resources": { "media": [] } } 

Вот разбивка того, что означают все эти поля:

  • uuid — это уникальный идентификатор, который Pebble сгенерировал для нашего проекта. Нет необходимости менять это из того, что сгенерировано для нас. Не используйте тот, который показан в списке выше.
  • shortName — имя появляется на панели запуска часов Pebble.
  • longName — имя появляется в мобильном приложении Pebble watch. Длинное имя «Найди меня Starbucks» достаточно короткое, чтобы поместиться на часах Pebble, поэтому в этом случае оба названия одинаковы.
  • companyName — название компании или разработчика.
  • versionCode — номер версии, который вы будете увеличивать с каждым выпуском.
  • versionLabel — метка версии. Pebble рекомендует формат Major.Minor.Bugfix .
  • watchapp — здесь можно добавить два варианта
    • watchface — является ли приложение сторожевым устройством (пользователь не может взаимодействовать с ним, и они ожидают, что оно сообщит ему время), или более полнофункциональным приложением, с которым пользователь может взаимодействовать. Мы создаем циферблат с помощью приложения «Найди меня Starbucks».
    • only_shown_on_communication — должно ли приложение отображаться только при получении данных из мобильного приложения. Это необязательно, и для нашего приложения оно нам не нужно.
  • appKeys — имена ключей, в которых мы будем хранить данные для передачи из нашего JavaScript в часы Pebble. Я объясню это более подробно в ближайшее время, но важно понять это правильно.
  • Возможности — в этом поле можно указать разрешения для приложения, например, потребуется ли ему доступ к местоположению пользователя и будет ли у приложения окно конфигурации в мобильном приложении (например, ["location", "configurable"] ). В версии 2 API указание возможности определения местоположения может быть необязательным, но я бы хотел включить ее только для безопасности.
  • resources — содержит только media объект, в котором перечислены все media файлы, которые должны быть связаны с приложением. Допустимые форматы: raw , png , png-trans и font . В этом руководстве мы не будем использовать какие-либо изображения, но если вы решите включить значок для своего приложения (или изображений в самом приложении), вам необходимо добавить изображения сюда.

Код «Найди меня Starbucks» C

Команда pebble new-project создала файл C в вашей папке src . Откройте этот файл (если вы уже следовали моим src/find_me_starbucks.c , это будет src/find_me_starbucks.c ). Код C для «Найди меня Starbucks» немного сложнее, чем исходный шаблон. Он использует понятия из исходного файла шаблона и несколько новых. Основное внимание в этой статье уделяется стороне JavaScript, поэтому мы не будем подробно останавливаться на этом. Объяснение кода C предоставляется в качестве бонуса в конце этой статьи. Это даст вам немного больше фона, чтобы вы могли внести небольшие изменения, чтобы дополнить ваш JS.

Сейчас мы скопируем и вставим следующий код C в файл src/find_me_starbucks.c . Он должен заменить все, что было раньше:

 #include <pebble.h> static Window *window; static TextLayer *text_layer; static TextLayer *label_layer; static TextLayer *time_layer; static AppSync sync; static uint8_t sync_buffer[64]; enum { OUR_LOCATION = 0x0 }; void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) { switch (key) { case OUR_LOCATION: text_layer_set_text(text_layer, new_tuple->value->cstring); break; } } // http://stackoverflow.com/questions/21150193/logging-enums-on-the-pebble-watch/21172222#21172222 char *translate_error(AppMessageResult result) { switch (result) { case APP_MSG_OK: return "APP_MSG_OK"; case APP_MSG_SEND_TIMEOUT: return "APP_MSG_SEND_TIMEOUT"; case APP_MSG_SEND_REJECTED: return "APP_MSG_SEND_REJECTED"; case APP_MSG_NOT_CONNECTED: return "APP_MSG_NOT_CONNECTED"; case APP_MSG_APP_NOT_RUNNING: return "APP_MSG_APP_NOT_RUNNING"; case APP_MSG_INVALID_ARGS: return "APP_MSG_INVALID_ARGS"; case APP_MSG_BUSY: return "APP_MSG_BUSY"; case APP_MSG_BUFFER_OVERFLOW: return "APP_MSG_BUFFER_OVERFLOW"; case APP_MSG_ALREADY_RELEASED: return "APP_MSG_ALREADY_RELEASED"; case APP_MSG_CALLBACK_ALREADY_REGISTERED: return "APP_MSG_CALLBACK_ALREADY_REGISTERED"; case APP_MSG_CALLBACK_NOT_REGISTERED: return "APP_MSG_CALLBACK_NOT_REGISTERED"; case APP_MSG_OUT_OF_MEMORY: return "APP_MSG_OUT_OF_MEMORY"; case APP_MSG_CLOSED: return "APP_MSG_CLOSED"; case APP_MSG_INTERNAL_ERROR: return "APP_MSG_INTERNAL_ERROR"; default: return "UNKNOWN ERROR"; } } void sync_error_callback(DictionaryResult dict_error, AppMessageResult app_message_error, void *context) { APP_LOG(APP_LOG_LEVEL_DEBUG, "... Sync Error: %s", translate_error(app_message_error)); } static void handle_second_tick(struct tm* tick_time, TimeUnits units_changed) { static char time_text[] = "00:00"; strftime(time_text, sizeof(time_text), "%I:%M", tick_time); text_layer_set_text(time_layer, time_text); } static void init_clock(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); time_layer = text_layer_create(GRect(0, 20, bounds.size.w, bounds.size.h-100)); text_layer_set_text_alignment(time_layer, GTextAlignmentCenter); text_layer_set_text_color(time_layer, GColorWhite); text_layer_set_background_color(time_layer, GColorClear); text_layer_set_font(time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_LIGHT)); time_t now = time(NULL); struct tm *current_time = localtime(&now); handle_second_tick(current_time, SECOND_UNIT); tick_timer_service_subscribe(SECOND_UNIT, &handle_second_tick); layer_add_child(window_get_root_layer(window), text_layer_get_layer(time_layer)); } static void init_location_search(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); label_layer = text_layer_create((GRect) { .origin = { 0, 90 }, .size = { bounds.size.w, 100 } }); text_layer_set_text(label_layer, "Nearest Starbucks:"); text_layer_set_text_color(label_layer, GColorWhite); text_layer_set_text_alignment(label_layer, GTextAlignmentCenter); text_layer_set_background_color(label_layer, GColorClear); text_layer_set_font(label_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD)); layer_add_child(window_layer, text_layer_get_layer(label_layer)); text_layer = text_layer_create((GRect) { .origin = { 0, 115 }, .size = { bounds.size.w, bounds.size.h } }); text_layer_set_text(text_layer, "Loading..."); text_layer_set_text_color(text_layer, GColorWhite); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); text_layer_set_background_color(text_layer, GColorClear); text_layer_set_overflow_mode(text_layer, GTextOverflowModeFill); text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14)); layer_add_child(window_layer, text_layer_get_layer(text_layer)); Tuplet initial_values[] = { TupletCString(OUR_LOCATION, "Loading...") }; app_sync_init(&sync, sync_buffer, sizeof(sync_buffer), initial_values, ARRAY_LENGTH(initial_values), sync_tuple_changed_callback, sync_error_callback, NULL); } static void window_load(Window *window) { init_location_search(window); init_clock(window); } static void window_unload(Window *window) { text_layer_destroy(text_layer); text_layer_destroy(label_layer); text_layer_destroy(time_layer); } static void init(void) { window = window_create(); window_set_window_handlers(window, (WindowHandlers) { .load = window_load, .unload = window_unload, }); app_message_open(64, 64); const bool animated = true; window_stack_push(window, animated); window_set_background_color(window, GColorBlack); } static void deinit(void) { window_destroy(window); } int main(void) { init(); APP_LOG(APP_LOG_LEVEL_DEBUG, "Done initializing, pushed window: %p", window); app_event_loop(); deinit(); } 

Код JavaScript

Большинство реальных данных и полезности нашего приложения будут получены из нашего JavaScript. Именно здесь мы получим приложение, чтобы поговорить со всем этим неиспользованным потенциалом сети. Весь наш JavaScript должен быть в одном файле, который уже должен существовать для вас в вашем проекте под src/js . Имя файла должно быть pebble-js-app.js чтобы оно могло быть выбрано вашим приложением Pebble. Начальное содержимое pebble-js-app.js , который просто отображает сообщение «Hello world», показано ниже.

 Pebble.addEventListener("ready", function(e) { console.log("Hello world! - Sent from your javascript application."); } ); 

Этот код ожидает события ready , которое запускается, когда приложение Pebble готово принимать команды. Это похоже на идею использования $(document).ready(function() {}); в jQuery. Поскольку мы хотим найти Starbucks, а не сказать «привет», мы собираемся перезаписать содержимое этого файла кодом, показанным ниже. Я сломаю код более подробно позже.

 var locationOptions = {timeout: 15000, maximumAge: 60000}; function fetch_location_data(pos) { var req = new XMLHttpRequest(), version = Date.now(), clientId = 'BSFRMG541RT1SJBWRZ4NPV1F5QQKJ2B1OSMQ0EDTU3NR0ZAX', clientSecret = '4VFLSBVYEQAN0M0XNGERON0LYMSMG1AJRSXXAQURK5GJQBNB', latitude = pos.coords.latitude, longitude = pos.coords.longitude; req.open('GET', 'https://api.foursquare.com/v2/venues/search?client_id=' + clientId + '&client_secret=' + clientSecret + '&v=' + version + '&ll=' + latitude + ',' + longitude + '&query=starbucks', true); req.onload = function(e) { if (req.readyState == 4 && req.status == 200) { if (req.status == 200) { var response = JSON.parse(req.responseText); if (response && response.meta.code == '200' && response.response) { var venue = response.response.venues[0]; Pebble.sendAppMessage({location: venue.location.address + ', ' + venue.location.city}); } } else { console.log('Error'); } } } req.send(null); } function fetch_location_error(err) { console.log(err); Pebble.sendAppMessage({location: 'Unable to retrieve location'}); } Pebble.addEventListener('ready', function(e) { locationWatcher = window.navigator.geolocation.watchPosition(fetch_location_data, fetch_location_error, locationOptions); }); 

Объект AppMessage

Мы отправляем и получаем сообщения в виде объектов JSON с очень конкретными ключами. По умолчанию мы можем отправлять сообщения в и из Pebble, используя индекс, начинающийся с 0 для каждого ключа, например:

 {"0": "Your first value", "1": "Your second value", "2": "Your third value"} 

Тем не менее, код легче читать, если мы назовем ключи. Мы делаем это в файле appinfo.json . У нас есть этот бит информации, хранящийся в этом файле:

 "appKeys": { "location": 0 } 

Это дает индексу 0 название location . Теперь мы можем написать наши сообщения так:

 {"location": "Your first value"} 

Отправка и получение сообщений

Чтобы отправить сообщение, мы используем Pebble.sendAppMessage() . Первый параметр — это сообщение JSON, которое вы отправляете. Второй и третий параметры — это обратные вызовы success и error соответственно. Пример sendAppMessage() показан ниже.

 Pebble.sendAppMessage({"0": "Your first value", "1": "Your second value", "2": "Your third value"}, function(e) { console.log("Successfully delivered message with transactionId=" + e.data); }, function(e) { console.log("Unable to deliver message with transactionId=" + e.data + ". Error is: " + e.error.message); } ); 

Geolocating

API определения местоположения Pebble использует window.navigator.geolocation.watchPosition() чтобы отслеживать наше местоположение. Он принимает три параметра: функцию обратного вызова успеха, функцию обратного вызова ошибки и переменную, которая ожидает объект параметров JSON. В нашем приложении «Найди меня Starbucks» мы определяем две опции для поиска геолокации, timeout и maximumAge :

 var locationOptions = {timeout: 15000, maximumAge: 60000}; 

timeout — это время в миллисекундах, в течение которого приложение будет ждать, прежде чем отказаться и вернуть ошибку. В нашем примере выше мы проверяем данные о местоположении в течение 15 секунд, а затем возвращаем ошибку, если ничего не возвращается. Параметр maximumAge представляет возраст данных в миллисекундах, которые мы готовы использовать в кэше приложения. Как только данные о местоположении устаревают, мы запрашиваем новые данные о местоположении. В нашем коде мы проверяем новые данные о местоположении каждую минуту (например, каждые 60000 миллисекунд).

Мы запускаем watchPosition() , устанавливая fetch_location_data() в качестве обратного вызова успеха и fetch_location_error() в качестве обратного вызова ошибки:

 Pebble.addEventListener('ready', function(e) { locationWatcher = window.navigator.geolocation.watchPosition(fetch_location_data, fetch_location_error, locationOptions); }); 

Функция fetch_location_error() возвращает сообщение, если Pebble не может определить наше местоположение, давая пользователю знать, что произошло:

 function fetch_location_error() { Pebble.sendAppMessage({location: 'Unable to retrieve location'}); } 

fetch_location_data() вызов fetch_location_data() объясняется в следующем разделе, и именно здесь происходит волшебство.

Выполнение Ajax-запросов

Ajax-запросы следуют стандартному формату XMLHttpRequest . В нашем приложении «Найди меня Starbucks» обратный вызов fetch_location_data() начинается с определения нескольких важных переменных:

 var req = new XMLHttpRequest(), version = Date.now(), clientId = 'BNBFMG541RT1SJBWRZ1NPS1F1QQKK2B19SMS0EDAU3DR7ZZA', clientSecret = '4AFKSBKJHQAKJHFBNGERON0LYMSMG1AKJ2KJHBWKS8KJHSDKHE', latitude = pos.coords.latitude, longitude = pos.coords.longitude; 

req хранит объект XMLHttpRequest . version — это временная метка, которая генерируется для предоставления уникального номера «версии» для нашего запроса API, как того требует API Foursquare. clientId и clientSecret выдаются вам при регистрации в API Foursquare. Они будут уникальными для вашего приложения, а те, что показаны в примере кода, нереальны. Посетите Foursquare для разработчиков , зарегистрируйте свое приложение и получите набор ключей для использования. Наконец, latitude и longitude — это физические координаты геолокации, возвращаемые Pebble API.

В документации Foursquare приведен пример поиска места. Наше приложение адаптирует этот пример, запрашивая следующий URL.

 https://api.foursquare.com/v2/venues/search?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&v=1396158354368&ll=40.7,-74&query=starbucks 

API Foursquare возвращает данные в следующем формате (сокращено, чтобы отображать только те данные, которые мы будем использовать):

 { "meta": { "code": 200 }, "response": { "venues": [ { "id": "4a7ae3f0f964a52095e91fe3", "name": "Starbucks", "contact": { "phone": "2124826530", "formattedPhone": "(212) 482-6530", "twitter": "starbucks" } ... 

Затем мы используем функцию req.onload для чтения возвращаемых значений. Если req.status равен 200 (указывает на успех), то мы интерпретируем ответ как JSON и проверяем, что Foursquare возвратил meta.code 200 (часть API Foursquare) и имеет значение response . Код, который делает это, показан ниже.

 req.onload = function(e) { if (req.readyState == 4 && req.status == 200) { if (req.status == 200) { var response = JSON.parse(req.responseText); if (response && response.meta.code == '200' && response.response) { 

Затем мы возвращаем JSON первого найденного места:

 var venue = response.response.venues[0]; 

Используя sendAppMessage() , мы отправляем сообщение нашим часам Pebble. Метка location совпадает с тем, что мы установили в файле appinfo.json ранее (она должна совпадать, чтобы связь работала):

 Pebble.sendAppMessage({location: venue.location.address + ', ' + venue.location.city}); 

Запустите приложение

Как только весь код JavaScript и C будет готов, запустите pebble build , а затем pebble install --phone IP_ADDRESS_OF_YOUR_PHONE . Если все идет хорошо, у вас должен быть установлен циферблат, который сообщит вам время и ваши ближайшие Starbucks. Пример показан на следующем рисунке.

Найди меня Starbucks Pebble приложение

Отладка приложений Pebble

Использование console.log() в качестве метода отладки является обычной практикой. Чтобы просмотреть журналы ваших часов Pebble, введите следующую команду:

 pebble logs --phone IP_ADDRESS_OF_YOUR_PHONE 

Журналы JavaScript можно отличить от журналов C, так как они имеют pebble-js-app.js качестве имени файла и номера строки. Пример журнала JavaScript показан ниже.

 [INFO ] find_me_starbucks__1/pebble-js-app.js:3 If this is coffee, please bring me some tea; but if this is tea, please bring me some coffee. Abraham Lincoln. 

Обратите внимание, что если вам нужно отладить некоторый код на C, вы можете использовать APP_LOG() . Это похоже на console.log() в JavaScript. Сообщение будет отображаться в журналах, но будет выглядеть примерно так:

 find_me_starbucks.c:37 You can never get a cup of tea large enough or a book long enough to suit me. CS Lewis. 

Вывод (вроде)

Поздравляем! Теперь у вас есть приложение, которое извлекает ближайшую локацию Starbucks из Foursquare и отображает ее на часах Pebble, чтобы тонко поощрить зависимость от кофеина.

Существует больше возможностей для этого приложения. Я планирую расширить приложение в будущем, чтобы учесть пользовательскую конфигурацию пользователя — например, что, если владелец часов Pebble вместо этого хочет получать уведомления о ближайшем «Toys R Us»? Используя JavaScript-фреймворк PebbleKit, вы можете настроить окно конфигурации на телефоне пользователя, которое позволит им указать свои предпочтения для приложения. Это может быть в будущей статье!

Надеюсь, нам удалось вдохновить вас попробовать Pebble SDK и в скором времени провести собственный эксперимент. С платформой JavaScript теперь она полностью открыта для любого API, который вы можете придумать! Если вам нужно больше вдохновения, взгляните на часы Pebble Mars Watchface — вы можете получать потоковые изображения с марсохода Curiosity на вашем циферблате Pebble. Невероятно блестящее использование Pebble SDK.

Весь код «Найди меня Starbucks» доступен на GitHub . Проверьте это там и, пожалуйста, дайте мне знать, если вы используете этот код в качестве отправной точки для еще более вдохновляющего приложения Pebble. Я хотел бы увидеть, что вы придумали!

Бонусный раздел: подробнее о коде C

Если вы хотите немного больше узнать о том, что происходит в коде find_me_starbucks.c , я написал краткое руководство. Мы все еще сосредоточимся на сравнительно высоком уровне обзора (я не буду объяснять конкретный синтаксис C или что-то в этом роде), но это может помочь разработчикам JavaScript, которые просто хотят немного узнать о коде, с которым они работают ,

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

 #include <pebble.h> static Window *window; static TextLayer *text_layer; static void window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); text_layer = text_layer_create((GRect) { .origin = { 0, 72 }, .size = { bounds.size.w, 20 } }); text_layer_set_text(text_layer, "Hello world"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer)); } static void window_unload(Window *window) { text_layer_destroy(text_layer); } static void init(void) { window = window_create(); window_set_window_handlers(window, (WindowHandlers) { .load = window_load, .unload = window_unload, }); const bool animated = true; window_stack_push(window, animated); } static void deinit(void) { window_destroy(window); } int main(void) { init(); APP_LOG(APP_LOG_LEVEL_DEBUG, "Done initializing, pushed window: %p", window); app_event_loop(); deinit(); } 

Когда приложение загружено, вызывается функция main() . Внутри main() вызов init() считается наилучшей практикой для разработки приложений Pebble. Мы установили большую часть основ нашего приложения в init() . Затем мы используем app_event_loop() чтобы остановить выполнение приложения больше кода в main() с этого момента, пока приложение не завершится. Следовательно, deinit() вызывается только после закрытия приложения.

Windows

Окна — это контейнеры, в которые мы помещаем элементы в приложение Pebble. Нам всегда нужен хотя бы один из них, чтобы приложение работало. В приложении может быть несколько окон, однако в любой момент времени будет видно только одно. Окна создаются так:

 window = window_create(); 

Обработчики окон запускаются, когда конкретное окно в данный момент видно и когда оно больше не отображается. Мы устанавливаем window_load() для запуска, когда окно показано, и window_unload() для запуска, когда оно находится вне поля зрения:

 window_set_window_handlers(window, (WindowHandlers) { .load = window_load, .unload = window_unload, }); 

Первые две строки нашей функции window.load() получают размер окна приложения Pebble. Мы используем это, чтобы определить размер и положение других элементов в приложении.

Мы используем window_stack_push(window, animated) чтобы прикрепить окно к приложению. Вторая переменная устанавливает, является ли переключение окон анимированным или нет.

text_layer_create() и другие связанные функции создают текстовый элемент, задают его размер, происхождение, текстовое содержимое и выравнивание, а затем добавляют его в окно.

text_layer_destroy(text_layer) делает то, что вы ожидаете, он удаляет слой, который мы создали ранее из памяти. Держать вещи красивыми и аккуратными, когда они нам больше не нужны.

Управление памятью

Функция deinit() используется для очистки после выполнения нашего приложения. В C вам нужно уничтожать созданные вами элементы. Если вы этого не сделаете, они сохраняются в памяти на часах Pebble. JavaScript прекрасен и обрабатывает это для нас, но когда вы пишете код на C, вы должны позаботиться об этом сами. Например, когда вам больше не нужно окно, вы должны уничтожить его:

 window_destroy(window); 

Аналогично, текстовые слои должны быть уничтожены после того, как они утратили свою полезность:

 text_layer_destroy(text_layer); 

Код «Найди меня Старбакс»

Теперь пришло время вернуться к коду C намного раньше в этой статье. Мы добавляем функциональность для получения сообщений из JavaScript, а также добавляем функциональность часов. Первое, на что стоит обратить внимание, это то, что мы добавили еще несколько текстовых слоев:

 static TextLayer *text_layer; static TextLayer *label_layer; static TextLayer *time_layer; 

Функциональность синхронизации — самое большое обновление. Мы используем модуль AppSync Pebble SDK для синхронизации данных между приложением телефона и часами Pebble. Эта реализация начинается с двух новых переменных в нашем C-коде. Мы определяем модуль AppSync в переменной sync и устанавливаем буфер длиной 64 байта для хранения сообщений:

 static AppSync sync; static uint8_t sync_buffer[64]; 

Мы также начинаем готовить приложение для пары ключ / значение, которую оно получит из нашего JavaScript. Мы ожидаем только одну пару ключ / значение, представляющую местоположение ближайшего Starbucks. Мы будем хранить эту пару ключ / значение в первой позиции (C называет ее позицией 0x0 ) как OUR_LOCATION . Это в основном способ именования первой пары ключ / значение как OUR_LOCATION вместо того, чтобы произносить 0x0 . Это больше для удобства чтения, чем функциональность:

 enum { OUR_LOCATION = 0x0 }; 

Мы также готовимся к отправке и получению сообщений в нашей функции init() . Чтобы использовать AppSync , нам нужно запустить app_message_open который принимает два параметра: максимальный размер app_message_open (сколько он может получить от нашего JS) и размер исходящей почты (сколько он может отправить):

 app_message_open(64, 64); 

Далее мы перейдем к функции init_location_search() . Все, что нужно сделать, чтобы найти наше местоположение и распечатать результаты, можно найти здесь. Большая часть этой функции — настройка текстовых слоев и их форматирование, чтобы они выглядели хорошо. Мы также устанавливаем массив initial_values который определяет начальную пару ключ / значение OUR_LOCATION как "Loading..." :

 Tuplet initial_values[] = { TupletCString(OUR_LOCATION, "Loading...") }; 

app_sync_init() передает наши начальные значения в буфер, затем запускает функцию обратного вызова sync_tuple_changed_callback() случае успеха или sync_error_callback() случае сбоя. В самых основных терминах наша sync_tuple_changed_callback() передает пару ключ / значение, полученную от app_sync_init() . Переменные, о которых мы заботимся, это key и new_tuple . key — позиция возвращаемой пары (например, 0x0 , 0x1 и т. д.), а new_tuple содержит ее содержимое (например, "L1, IMAX Cinema Complex, Darling Harbour" ). Мы дали нашей первой и единственной позиции 0x0 имя OUR_LOCATION , поэтому мы OUR_LOCATION за этим и устанавливаем текст в нашем приложении для отображения этого значения:

 void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) { switch (key) { case OUR_LOCATION: text_layer_set_text(text_layer, new_tuple->value->cstring); break; } } 

Отображение времени

Наше приложение по-прежнему часы в глубине души, поэтому оно должно показывать время. Я лично делаю все это в функции init_clock() (насколько я знаю, это не соглашение). Многое из этого — просто определение текстовых слоев для отображения информации, но есть и другие части, которые ничего не стоят.

Мы получаем текущее время в секундах как время Unix:

 time_t now = time(NULL); 

Далее мы подгоняем его под местный часовой пояс:

 struct tm *current_time = localtime(&now); 

Затем, запустите handle_second_tick() один раз, затем снова каждую секунду:

 handle_second_tick(current_time, SECOND_UNIT); tick_timer_service_subscribe(SECOND_UNIT, &handle_second_tick); 

Эта функция форматирует время с помощью стандартной strftime() и отображает его в тексте time_layer на часах:

 static void handle_second_tick(struct tm* tick_time, TimeUnits units_changed) { static char time_text[] = "00:00"; strftime(time_text, sizeof(time_text), "%I:%M", tick_time); text_layer_set_text(time_layer, time_text); } 

Вывод (серьезно)

С этим, мы надеемся, вы получили немного больше знаний о том, как код C играет свою роль в общей схеме вещей. Pebble предоставляет большое количество подробной документации по всему этому. Несколько отличных мест для начала: