commit a8cb56b85cba3efbc531a9306b98b7de56469527 Author: tmeissner Date: Wed Oct 14 00:00:00 2015 +0200 Initial commit of C watchface tutorial diff --git a/create_a_c_watchface/.gitignore b/create_a_c_watchface/.gitignore new file mode 100644 index 0000000..9eaef5d --- /dev/null +++ b/create_a_c_watchface/.gitignore @@ -0,0 +1,6 @@ + +# Ignore build generated files +build + +# Ignore waf lock file +.lock-waf* diff --git a/create_a_c_watchface/appinfo.json b/create_a_c_watchface/appinfo.json new file mode 100644 index 0000000..dd2caeb --- /dev/null +++ b/create_a_c_watchface/appinfo.json @@ -0,0 +1,54 @@ +{ + "uuid": "3c6e0682-a917-4283-a27e-349f2de629fa", + "shortName": "create_a_c_watchface", + "longName": "create_a_c_watchface", + "companyName": "goodcleanfun", + "versionLabel": "1.0", + "sdkVersion": "3", + "targetPlatforms": ["aplite", "basalt"], + "watchapp": { + "watchface": true + }, + "appKeys": { + "dummy": 0 + }, + "capabilities": + [ "location" ], + "appKeys": { + "KEY_TEMPERATURE": 0, + "KEY_CONDITIONS": 1 + }, + "resources": { + "media": [ + { + "type": "font", + "name": "FONT_PERFECT_DOS_16", + "file": "fonts/perfect-dos-vga.ttf", + "compatibility":"2.7" + }, + { + "type": "font", + "name": "FONT_PERFECT_DOS_20", + "file": "fonts/perfect-dos-vga.ttf", + "compatibility":"2.7" + }, + { + "type": "font", + "name": "FONT_PERFECT_DOS_24", + "file": "fonts/perfect-dos-vga.ttf", + "compatibility":"2.7" + }, + { + "type": "font", + "name": "FONT_PERFECT_DOS_48", + "file": "fonts/perfect-dos-vga.ttf", + "compatibility":"2.7" + }, + { + "type": "png", + "name": "TUTORIAL_BACKGROUND", + "file": "images/background.png" + } + ] + } +} diff --git a/create_a_c_watchface/resources/fonts/perfect-dos-vga.ttf b/create_a_c_watchface/resources/fonts/perfect-dos-vga.ttf new file mode 100644 index 0000000..d03b1c5 Binary files /dev/null and b/create_a_c_watchface/resources/fonts/perfect-dos-vga.ttf differ diff --git a/create_a_c_watchface/resources/images/background.png b/create_a_c_watchface/resources/images/background.png new file mode 100644 index 0000000..1b5e78c Binary files /dev/null and b/create_a_c_watchface/resources/images/background.png differ diff --git a/create_a_c_watchface/src/js/pebble-js-app.js b/create_a_c_watchface/src/js/pebble-js-app.js new file mode 100644 index 0000000..697f036 --- /dev/null +++ b/create_a_c_watchface/src/js/pebble-js-app.js @@ -0,0 +1,94 @@ +var xhrRequest = function (url, type, callback) { + + var xhr = new XMLHttpRequest(); + + xhr.onload = function () { + callback(this.responseText); + }; + + xhr.open(type, url); + xhr.send(); + +}; + + +function locationSuccess(pos) { + + // generic API key, has to be replaced by developers one + var api_key = 'bd82977b86bf27fb59a04b61b657fb6f'; + + // Construct URL + var url = 'http://api.openweathermap.org/data/2.5/weather?lat=' + + pos.coords.latitude + '&lon=' + pos.coords.longitude + + '&APPID=' + api_key; + + // Send request to OpenWeatherMap + xhrRequest(url, 'GET', + function(responseText) { + // responseText contains a JSON object with weather info + var json = JSON.parse(responseText); + + // Temperature in Kelvin requires adjustment + var temperature = Math.round(json.main.temp - 273.15); + console.log('Temperature is ' + temperature); + + // Conditions + var conditions = json.weather[0].main; + console.log('Conditions are ' + conditions); + + // Assemble dictionary using our keys + var dictionary = { + 'KEY_TEMPERATURE': temperature, + 'KEY_CONDITIONS': conditions + }; + + // Send to Pebble + Pebble.sendAppMessage(dictionary, + function(e) { + console.log('Weather info sent to Pebble successfully!'); + }, + function(e) { + console.log('Error sending weather info to Pebble!'); + } + ); + } + ); + +} + + +function locationError(err) { + + console.log('Error requesting location!'); + +} + + +function getWeather() { + + navigator.geolocation.getCurrentPosition( + locationSuccess, + locationError, + {timeout: 15000, maximumAge: 60000} + ); + +} + + +// Listen for when the watchface is opened +Pebble.addEventListener('ready', + function(e) { + console.log('PebbleKit JS ready!'); + // Get the initial weather + getWeather(); + } +); + + +// Listen for when an AppMessage is received +Pebble.addEventListener('appmessage', + function(e) { + console.log('AppMessage received!'); + getWeather(); + } +); diff --git a/create_a_c_watchface/src/main.c b/create_a_c_watchface/src/main.c new file mode 100644 index 0000000..b56366b --- /dev/null +++ b/create_a_c_watchface/src/main.c @@ -0,0 +1,277 @@ +#include + + +#define KEY_TEMPERATURE 0 +#define KEY_CONDITIONS 1 + + +// Function signatures +static void update_time(); +static void tick_handler(struct tm *tick_time, TimeUnits units_changed); +static void main_window_load(Window *window); +static void main_window_unload(Window *window); +static void init(); +static void deinit(); +static void inbox_received_callback(DictionaryIterator *iterator, void *context); +static void inbox_dropped_callback(AppMessageResult reason, void *context); +static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context); +static void outbox_sent_callback(DictionaryIterator *iterator, void *context); + + +// Global variables +static Window *s_main_window; +static TextLayer *s_time_layer; +static TextLayer *s_weather_layer; +static TextLayer *s_daymonth_layer; +//static TextLayer *s_year_layer; +static GFont s_time_font; +static GFont s_weather_font; +static GFont s_date_font; +static BitmapLayer *s_background_layer; +static GBitmap *s_background_bitmap; + + + +int main(void) { + + init(); + app_event_loop(); + deinit(); + +} + + +static void update_time() { + + // Get a tm structure + time_t temp = time(NULL); + struct tm *tick_time = localtime(&temp); + + // Create a long-lived buffer + static char buffer[] = "00:00"; + + // Write the current hours and minutes into the buffer + if (clock_is_24h_style()) { + // Use 24 hour format + strftime(buffer, sizeof("00:00"), "%H:%M", tick_time); + } else { + // Use 12 hour format + strftime(buffer, sizeof("00:00"), "%I:%M", tick_time); + } + + // Display this time on the TextLayer + text_layer_set_text(s_time_layer, buffer); + +} + + +static void update_date() { + + // Get a tm structure + time_t temp = time(NULL); + struct tm *tick_time = localtime(&temp); + + // Create a long-lived buffer + static char buffer_daymonth[] = "00.000"; + + // Write the current hours and minutes into the buffer + strftime(buffer_daymonth, sizeof(buffer_daymonth), "%e.%b", tick_time); + + // Display this time on the TextLayer + text_layer_set_text(s_daymonth_layer, buffer_daymonth); + +} + + +static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { + + update_time(); + if (units_changed & DAY_UNIT) { + update_date(); + } + + // Get weather update every 30 minutes + if (tick_time->tm_min % 30 == 0) { + // Begin dictionary + DictionaryIterator *iter; + app_message_outbox_begin(&iter); + + // Add a key-value pair + dict_write_uint8(iter, 0, 0); + + // Send the message! + app_message_outbox_send(); + } + +} + + +static void inbox_received_callback(DictionaryIterator *iterator, void *context) { + + // Store incoming information + static char temperature_buffer[8]; + static char conditions_buffer[32]; + static char weather_layer_buffer[32]; + + // Read first item + Tuple *t = dict_read_first(iterator); + + // For all items + while(t != NULL) { + + // Which key was received? + switch(t->key) { + case KEY_TEMPERATURE: + snprintf(temperature_buffer, sizeof(temperature_buffer), "%d°C", (int)t->value->int32); + break; + + case KEY_CONDITIONS: + snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", t->value->cstring); + break; + + default: + APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key); + break; + } + + // Look for next item + t = dict_read_next(iterator); + } + + // Assemble full string and display + snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", temperature_buffer, conditions_buffer); + text_layer_set_text(s_weather_layer, weather_layer_buffer); + +} + + +static void inbox_dropped_callback(AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!"); +} + + +static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!"); +} + + +static void outbox_sent_callback(DictionaryIterator *iterator, void *context) { + APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!"); +} + + +static void main_window_load(Window *window) { + + // Create GBitmap, then set to created BitmapLayer + s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TUTORIAL_BACKGROUND); + s_background_layer = bitmap_layer_create(GRect(0, 0, 144, 168)); + bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap); + + // Create time TextLayer + s_time_layer = text_layer_create(GRect(5, 52, 139, 50)); + text_layer_set_background_color(s_time_layer, GColorClear); + text_layer_set_text_color(s_time_layer, GColorBlack); + + // Create temperature Layer + s_weather_layer = text_layer_create(GRect(0, 130, 144, 25)); + text_layer_set_background_color(s_weather_layer, GColorClear); + text_layer_set_text_color(s_weather_layer, GColorWhite); + text_layer_set_text(s_weather_layer, "Loading..."); + + // Create day-month TextLayer + s_daymonth_layer = text_layer_create(GRect(5, 12, 139, 30)); + text_layer_set_background_color(s_daymonth_layer, GColorBlack); + text_layer_set_text_color(s_daymonth_layer, GColorWhite); + + // Create year TextLayer +// s_year_layer = text_layer_create(GRect(5, 123, 139, 30)); +// text_layer_set_background_color(s_year_layer, GColorBlack); +// text_layer_set_text_color(s_year_layer, GColorWhite); + + // Create GFont + s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_48)); + s_weather_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_16)); + s_date_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_24)); + + // Apply to TextLayer + text_layer_set_font(s_time_layer, s_time_font); + text_layer_set_font(s_weather_layer, s_weather_font); + text_layer_set_font(s_daymonth_layer, s_date_font); +// text_layer_set_font(s_year_layer, s_date_font); + + // Improve the layout to be more like a watchface + text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); + text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter); + text_layer_set_text_alignment(s_daymonth_layer, GTextAlignmentCenter); +// text_layer_set_text_alignment(s_year_layer, GTextAlignmentCenter); + + // Add it as a child layer to the Window's root layer + layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_background_layer)); + layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_time_layer)); + layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer)); + layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_daymonth_layer)); + // layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_year_layer)); + +} + + +static void main_window_unload(Window *window) { + + // Destroy TextLayer + text_layer_destroy(s_time_layer); + text_layer_destroy(s_weather_layer); + text_layer_destroy(s_daymonth_layer); +// text_layer_destroy(s_year_layer); + + // Unload GFont + fonts_unload_custom_font(s_time_font); + fonts_unload_custom_font(s_weather_font); + fonts_unload_custom_font(s_date_font); + + // Destroy GBitmap + gbitmap_destroy(s_background_bitmap); + + // Destroy BitmapLayer + bitmap_layer_destroy(s_background_layer); + +} + + +static void init() { + + // Create main Window element and assign to pointer + s_main_window = window_create(); + + // Set handlers to manage the elements inside the Window + window_set_window_handlers(s_main_window, (WindowHandlers){ + .load = main_window_load, + .unload = main_window_unload + }); + + // Show the Window on the watch, with animated=true + window_stack_push(s_main_window, true); + + // Register with TickTimerService + tick_timer_service_subscribe((MINUTE_UNIT | DAY_UNIT), tick_handler); + + // Register AppMessage callbacks + app_message_register_inbox_received(inbox_received_callback); + app_message_register_inbox_dropped(inbox_dropped_callback); + app_message_register_outbox_failed(outbox_failed_callback); + app_message_register_outbox_sent(outbox_sent_callback); + + // Open AppMessage + app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); + + update_time(); + update_date(); + +} + + +static void deinit() { + + // Destroy Window + window_destroy(s_main_window); + +} diff --git a/create_a_c_watchface/wscript b/create_a_c_watchface/wscript new file mode 100644 index 0000000..040ab7c --- /dev/null +++ b/create_a_c_watchface/wscript @@ -0,0 +1,41 @@ + +# +# This file is the default set of rules to compile a Pebble project. +# +# Feel free to customize this to your needs. +# + +import os.path + +top = '.' +out = 'build' + +def options(ctx): + ctx.load('pebble_sdk') + +def configure(ctx): + ctx.load('pebble_sdk') + +def build(ctx): + ctx.load('pebble_sdk') + + build_worker = os.path.exists('worker_src') + binaries = [] + + for p in ctx.env.TARGET_PLATFORMS: + ctx.set_env(ctx.all_envs[p]) + ctx.set_group(ctx.env.PLATFORM_NAME) + app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), + target=app_elf) + + if build_worker: + worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) + ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), + target=worker_elf) + else: + binaries.append({'platform': p, 'app_elf': app_elf}) + + ctx.set_group('bundle') + ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))