├── LICENSE.md ├── README.md ├── appinfo.json ├── resources ├── background.png └── perfect-dos-vga.ttf ├── screenshots └── screenshot.png ├── src ├── js │ └── app.js └── watchface-tutorial.c └── wscript /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2014 Pebble Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Watchface Tutorial 2 | 3 | Sample project from the Getting Started watchface tutorials. 4 | 5 | This is the result of all three parts and is a customized watchface with weather information included. 6 | 7 | **Important** 8 | 9 | To obtain weather information, you must supply a value for `myAPIKey` in 10 | `src/js/app.js`. These are freely available from 11 | [OpenWeatherMap.org](https://openweathermap.org/appid). 12 | 13 | ![screenshot](screenshots/screenshot.png) 14 | -------------------------------------------------------------------------------- /appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "ed70b15c-3c48-4828-8e8c-e39a28efb38d", 3 | "shortName": "Tutorial-3-3", 4 | "longName": "Tutorial-3-3", 5 | "companyName": "Pebble", 6 | "versionCode": 1, 7 | "versionLabel": "1.0", 8 | "enableMultiJS": true, 9 | "watchapp": { 10 | "watchface": true 11 | }, 12 | "appKeys": { 13 | "KEY_TEMPERATURE": 0, 14 | "KEY_CONDITIONS": 1 15 | }, 16 | "capabilities": [ 17 | "location" 18 | ], 19 | "resources": { 20 | "media": [ 21 | { 22 | "type": "font", 23 | "name": "FONT_PERFECT_DOS_48", 24 | "file": "perfect-dos-vga.ttf" 25 | }, 26 | { 27 | "type": "font", 28 | "name": "FONT_PERFECT_DOS_20", 29 | "file": "perfect-dos-vga.ttf" 30 | }, 31 | { 32 | "type": "bitmap", 33 | "name": "IMAGE_BACKGROUND", 34 | "file": "background.png" 35 | } 36 | ] 37 | }, 38 | "targetPlatforms": [ 39 | "aplite", 40 | "basalt", 41 | "chalk" 42 | ], 43 | "sdkVersion": "3" 44 | } 45 | -------------------------------------------------------------------------------- /resources/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/watchface-tutorial/0d0a19f9d4c4c11111c84018c28ba6b17cbfda30/resources/background.png -------------------------------------------------------------------------------- /resources/perfect-dos-vga.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/watchface-tutorial/0d0a19f9d4c4c11111c84018c28ba6b17cbfda30/resources/perfect-dos-vga.ttf -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/watchface-tutorial/0d0a19f9d4c4c11111c84018c28ba6b17cbfda30/screenshots/screenshot.png -------------------------------------------------------------------------------- /src/js/app.js: -------------------------------------------------------------------------------- 1 | var myAPIKey = ''; 2 | 3 | var xhrRequest = function (url, type, callback) { 4 | var xhr = new XMLHttpRequest(); 5 | xhr.onload = function () { 6 | callback(this.responseText); 7 | }; 8 | xhr.open(type, url); 9 | xhr.send(); 10 | }; 11 | 12 | function locationSuccess(pos) { 13 | // Construct URL 14 | var url = "http://api.openweathermap.org/data/2.5/weather?lat=" + 15 | pos.coords.latitude + "&lon=" + pos.coords.longitude + 16 | '&appid=' + myAPIKey; 17 | 18 | // Send request to OpenWeatherMap 19 | xhrRequest(url, 'GET', 20 | function(responseText) { 21 | // responseText contains a JSON object with weather info 22 | var json = JSON.parse(responseText); 23 | 24 | // Temperature in Kelvin requires adjustment 25 | var temperature = Math.round(json.main.temp - 273.15); 26 | console.log("Temperature is " + temperature); 27 | 28 | // Conditions 29 | var conditions = json.weather[0].main; 30 | console.log("Conditions are " + conditions); 31 | 32 | // Assemble dictionary using our keys 33 | var dictionary = { 34 | "KEY_TEMPERATURE": temperature, 35 | "KEY_CONDITIONS": conditions 36 | }; 37 | 38 | // Send to Pebble 39 | Pebble.sendAppMessage(dictionary, 40 | function(e) { 41 | console.log("Weather info sent to Pebble successfully!"); 42 | }, 43 | function(e) { 44 | console.log("Error sending weather info to Pebble!"); 45 | } 46 | ); 47 | } 48 | ); 49 | } 50 | 51 | function locationError(err) { 52 | console.log("Error requesting location!"); 53 | } 54 | 55 | function getWeather() { 56 | navigator.geolocation.getCurrentPosition( 57 | locationSuccess, 58 | locationError, 59 | {timeout: 15000, maximumAge: 60000} 60 | ); 61 | } 62 | 63 | // Listen for when the watchface is opened 64 | Pebble.addEventListener('ready', 65 | function(e) { 66 | console.log("PebbleKit JS ready!"); 67 | 68 | // Get the initial weather 69 | getWeather(); 70 | } 71 | ); 72 | 73 | // Listen for when an AppMessage is received 74 | Pebble.addEventListener('appmessage', 75 | function(e) { 76 | console.log("AppMessage received!"); 77 | getWeather(); 78 | } 79 | ); 80 | -------------------------------------------------------------------------------- /src/watchface-tutorial.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define KEY_TEMPERATURE 0 4 | #define KEY_CONDITIONS 1 5 | 6 | static Window *s_main_window; 7 | static TextLayer *s_time_layer; 8 | static TextLayer *s_weather_layer; 9 | 10 | static GFont s_time_font; 11 | static GFont s_weather_font; 12 | 13 | static BitmapLayer *s_background_layer; 14 | static GBitmap *s_background_bitmap; 15 | 16 | static void update_time() { 17 | // Get a tm structure 18 | time_t temp = time(NULL); 19 | struct tm *tick_time = localtime(&temp); 20 | 21 | // Write the current hours and minutes into a buffer 22 | static char s_buffer[8]; 23 | strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ? "%H:%M" : "%I:%M", tick_time); 24 | 25 | // Display this time on the TextLayer 26 | text_layer_set_text(s_time_layer, s_buffer); 27 | } 28 | 29 | static void main_window_load(Window *window) { 30 | Layer *window_layer = window_get_root_layer(window); 31 | GRect bounds = layer_get_bounds(window_layer); 32 | 33 | // Create GBitmap 34 | s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BACKGROUND); 35 | 36 | // Create BitmapLayer to display the GBitmap 37 | s_background_layer = bitmap_layer_create(bounds); 38 | bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap); 39 | layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer)); 40 | 41 | // Create time TextLayer 42 | s_time_layer = text_layer_create( 43 | GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50)); 44 | text_layer_set_background_color(s_time_layer, GColorClear); 45 | text_layer_set_text_color(s_time_layer, GColorBlack); 46 | text_layer_set_text(s_time_layer, "00:00"); 47 | 48 | // Create GFont 49 | s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_48)); 50 | 51 | // Apply to TextLayer 52 | text_layer_set_font(s_time_layer, s_time_font); 53 | text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); 54 | layer_add_child(window_layer, text_layer_get_layer(s_time_layer)); 55 | 56 | // Create temperature Layer 57 | s_weather_layer = text_layer_create( 58 | GRect(0, PBL_IF_ROUND_ELSE(125, 120), bounds.size.w, 25)); 59 | text_layer_set_background_color(s_weather_layer, GColorClear); 60 | text_layer_set_text_color(s_weather_layer, GColorWhite); 61 | text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter); 62 | text_layer_set_text(s_weather_layer, "Loading..."); 63 | 64 | // Create second custom font, apply it and add to Window 65 | s_weather_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_20)); 66 | text_layer_set_font(s_weather_layer, s_weather_font); 67 | layer_add_child(window_layer, text_layer_get_layer(s_weather_layer)); 68 | 69 | // Make sure the time is displayed from the start 70 | update_time(); 71 | } 72 | 73 | static void main_window_unload(Window *window) { 74 | fonts_unload_custom_font(s_time_font); 75 | fonts_unload_custom_font(s_weather_font); 76 | 77 | gbitmap_destroy(s_background_bitmap); 78 | 79 | bitmap_layer_destroy(s_background_layer); 80 | text_layer_destroy(s_time_layer); 81 | text_layer_destroy(s_weather_layer); 82 | } 83 | 84 | static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { 85 | update_time(); 86 | 87 | // Get weather update every 30 minutes 88 | if(tick_time->tm_min % 30 == 0) { 89 | // Begin dictionary 90 | DictionaryIterator *iter; 91 | app_message_outbox_begin(&iter); 92 | 93 | // Add a key-value pair 94 | dict_write_uint8(iter, 0, 0); 95 | 96 | // Send the message! 97 | app_message_outbox_send(); 98 | } 99 | } 100 | 101 | static void inbox_received_callback(DictionaryIterator *iterator, void *context) { 102 | // Store incoming information 103 | static char temperature_buffer[8]; 104 | static char conditions_buffer[32]; 105 | static char weather_layer_buffer[32]; 106 | 107 | // Read tuples for data 108 | Tuple *temp_tuple = dict_find(iterator, KEY_TEMPERATURE); 109 | Tuple *conditions_tuple = dict_find(iterator, KEY_CONDITIONS); 110 | 111 | // If all data is available, use it 112 | if(temp_tuple && conditions_tuple) { 113 | snprintf(temperature_buffer, sizeof(temperature_buffer), "%dC", (int)temp_tuple->value->int32); 114 | snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", conditions_tuple->value->cstring); 115 | 116 | // Assemble full string and display 117 | snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", temperature_buffer, conditions_buffer); 118 | text_layer_set_text(s_weather_layer, weather_layer_buffer); 119 | } 120 | } 121 | 122 | static void inbox_dropped_callback(AppMessageResult reason, void *context) { 123 | APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!"); 124 | } 125 | 126 | static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) { 127 | APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!"); 128 | } 129 | 130 | static void outbox_sent_callback(DictionaryIterator *iterator, void *context) { 131 | APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!"); 132 | } 133 | 134 | static void init() { 135 | // Create main Window element and assign to pointer 136 | s_main_window = window_create(); 137 | window_set_background_color(s_main_window, GColorBlack); 138 | window_set_window_handlers(s_main_window, (WindowHandlers) { 139 | .load = main_window_load, 140 | .unload = main_window_unload 141 | }); 142 | window_stack_push(s_main_window, true); 143 | 144 | // Register with TickTimerService 145 | tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); 146 | 147 | // Register callbacks 148 | app_message_register_inbox_received(inbox_received_callback); 149 | app_message_register_inbox_dropped(inbox_dropped_callback); 150 | app_message_register_outbox_failed(outbox_failed_callback); 151 | app_message_register_outbox_sent(outbox_sent_callback); 152 | 153 | // Open AppMessage 154 | const int inbox_size = 128; 155 | const int outbox_size = 128; 156 | app_message_open(inbox_size, outbox_size); 157 | } 158 | 159 | static void deinit() { 160 | // Destroy Window 161 | window_destroy(s_main_window); 162 | } 163 | 164 | int main(void) { 165 | init(); 166 | app_event_loop(); 167 | deinit(); 168 | } -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This file is the default set of rules to compile a Pebble project. 4 | # 5 | # Feel free to customize this to your needs. 6 | # 7 | 8 | import os.path 9 | 10 | top = '.' 11 | out = 'build' 12 | 13 | def options(ctx): 14 | ctx.load('pebble_sdk') 15 | 16 | def configure(ctx): 17 | ctx.load('pebble_sdk') 18 | 19 | def build(ctx): 20 | ctx.load('pebble_sdk') 21 | 22 | build_worker = os.path.exists('worker_src') 23 | binaries = [] 24 | 25 | for p in ctx.env.TARGET_PLATFORMS: 26 | ctx.set_env(ctx.all_envs[p]) 27 | ctx.set_group(ctx.env.PLATFORM_NAME) 28 | app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) 29 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), 30 | target=app_elf) 31 | 32 | if build_worker: 33 | worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) 34 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) 35 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), 36 | target=worker_elf) 37 | else: 38 | binaries.append({'platform': p, 'app_elf': app_elf}) 39 | 40 | ctx.set_group('bundle') 41 | ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js')) 42 | --------------------------------------------------------------------------------