├── .gitignore ├── .jshintrc ├── .travis.yml ├── README.md ├── appinfo.json ├── resources └── img │ ├── cloud~bw.png │ ├── cloud~color.png │ ├── rain~bw.png │ ├── rain~color.png │ ├── snow~bw.png │ ├── snow~color.png │ ├── sun~bw.png │ └── sun~color.png ├── src ├── js │ └── pebble-js-app.js └── weather.c ├── weather-screenshot~bw.png ├── weather-screenshot~color.png └── wscript /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .lock-waf* 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { "Pebble" : true }, 3 | "browser": true, 4 | "node": true, 5 | "devel": true, 6 | "freeze": true, 7 | "evil": false, 8 | "curly": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": "nofunc", 12 | "undef": true, 13 | "unused": true, 14 | "strict": false, 15 | "trailing": true, 16 | "eqeqeq": true, 17 | "quotmark": "single", 18 | "eqeqeq": true, 19 | "eqnull": true, 20 | "plusplus": true 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: "wget https://s3.amazonaws.com/developer.getpebble.com/examples/install-script.sh && chmod +x install-script.sh && ./install-script.sh" 5 | script: "wget https://s3.amazonaws.com/developer.getpebble.com/examples/run-script.sh && chmod +x run-script.sh && ./run-script.sh" 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pebblekit-js-weather 2 | 3 | [![Build Status](https://travis-ci.org/pebble-examples/pebblekit-js-weather.svg)](https://travis-ci.org/pebble-examples/pebblekit-js-weather) 4 | 5 | ![screenshot](weather-screenshot~color.png) 6 | ![screenshot](weather-screenshot~bw.png) 7 | 8 | This watchapp uses PebbleKit JS to fetch weather data from the 9 | [openweathermap.org](http://openweathermap.org/) and display it on the watch. 10 | It uses the location provided by the phone to look up the nearest location, and 11 | sends temperature data along with that location's name. A weather icon is also 12 | displayed. 13 | 14 | **Important** 15 | 16 | To obtain weather information, you must supply a value for `myAPIKey` in 17 | `src/pebble-js-app.js`. These are freely available from 18 | [OpenWeatherMap.org](https://openweathermap.org/appid). 19 | -------------------------------------------------------------------------------- /appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "74460383-8a0f-4bb6-971f-8937c2ed4441", 3 | "shortName": "PebbleJS Weather", 4 | "longName": "PebbleJS Weather", 5 | "companyName": "Pebble Technology", 6 | "versionCode": 2, 7 | "versionLabel": "2.0", 8 | "watchapp": { 9 | "watchface": false 10 | }, 11 | "appKeys": { 12 | "WEATHER_CITY_KEY": 2, 13 | "WEATHER_TEMPERATURE_KEY": 1, 14 | "WEATHER_ICON_KEY": 0 15 | }, 16 | "resources": { 17 | "media": [ 18 | { 19 | "type": "bitmap", 20 | "name": "IMAGE_SUN", 21 | "file": "img/sun.png" 22 | }, 23 | { 24 | "type": "bitmap", 25 | "name": "IMAGE_RAIN", 26 | "file": "img/rain.png" 27 | }, 28 | { 29 | "type": "bitmap", 30 | "name": "IMAGE_CLOUD", 31 | "file": "img/cloud.png" 32 | }, 33 | { 34 | "type": "bitmap", 35 | "name": "IMAGE_SNOW", 36 | "file": "img/snow.png" 37 | } 38 | ] 39 | }, 40 | "targetPlatforms": [ 41 | "aplite", 42 | "basalt", 43 | "chalk" 44 | ], 45 | "sdkVersion": "3" 46 | } -------------------------------------------------------------------------------- /resources/img/cloud~bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/cloud~bw.png -------------------------------------------------------------------------------- /resources/img/cloud~color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/cloud~color.png -------------------------------------------------------------------------------- /resources/img/rain~bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/rain~bw.png -------------------------------------------------------------------------------- /resources/img/rain~color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/rain~color.png -------------------------------------------------------------------------------- /resources/img/snow~bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/snow~bw.png -------------------------------------------------------------------------------- /resources/img/snow~color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/snow~color.png -------------------------------------------------------------------------------- /resources/img/sun~bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/sun~bw.png -------------------------------------------------------------------------------- /resources/img/sun~color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/resources/img/sun~color.png -------------------------------------------------------------------------------- /src/js/pebble-js-app.js: -------------------------------------------------------------------------------- 1 | var myAPIKey = ''; 2 | 3 | function iconFromWeatherId(weatherId) { 4 | if (weatherId < 600) { 5 | return 2; 6 | } else if (weatherId < 700) { 7 | return 3; 8 | } else if (weatherId > 800) { 9 | return 1; 10 | } else { 11 | return 0; 12 | } 13 | } 14 | 15 | function fetchWeather(latitude, longitude) { 16 | var req = new XMLHttpRequest(); 17 | req.open('GET', 'http://api.openweathermap.org/data/2.5/weather?' + 18 | 'lat=' + latitude + '&lon=' + longitude + '&cnt=1&appid=' + myAPIKey, true); 19 | req.onload = function () { 20 | if (req.readyState === 4) { 21 | if (req.status === 200) { 22 | console.log(req.responseText); 23 | var response = JSON.parse(req.responseText); 24 | var temperature = Math.round(response.main.temp - 273.15); 25 | var icon = iconFromWeatherId(response.weather[0].id); 26 | var city = response.name; 27 | console.log(temperature); 28 | console.log(icon); 29 | console.log(city); 30 | Pebble.sendAppMessage({ 31 | 'WEATHER_ICON_KEY': icon, 32 | 'WEATHER_TEMPERATURE_KEY': temperature + '\xB0C', 33 | 'WEATHER_CITY_KEY': city 34 | }); 35 | } else { 36 | console.log('Error'); 37 | } 38 | } 39 | }; 40 | req.send(null); 41 | } 42 | 43 | function locationSuccess(pos) { 44 | var coordinates = pos.coords; 45 | fetchWeather(coordinates.latitude, coordinates.longitude); 46 | } 47 | 48 | function locationError(err) { 49 | console.warn('location error (' + err.code + '): ' + err.message); 50 | Pebble.sendAppMessage({ 51 | 'WEATHER_CITY_KEY': 'Loc Unavailable', 52 | 'WEATHER_TEMPERATURE_KEY': 'N/A' 53 | }); 54 | } 55 | 56 | var locationOptions = { 57 | 'timeout': 15000, 58 | 'maximumAge': 60000 59 | }; 60 | 61 | Pebble.addEventListener('ready', function (e) { 62 | console.log('connect!' + e.ready); 63 | window.navigator.geolocation.getCurrentPosition(locationSuccess, locationError, 64 | locationOptions); 65 | console.log(e.type); 66 | }); 67 | 68 | Pebble.addEventListener('appmessage', function (e) { 69 | window.navigator.geolocation.getCurrentPosition(locationSuccess, locationError, 70 | locationOptions); 71 | console.log(e.type); 72 | console.log(e.payload.temperature); 73 | console.log('message!'); 74 | }); 75 | 76 | Pebble.addEventListener('webviewclosed', function (e) { 77 | console.log('webview closed'); 78 | console.log(e.type); 79 | console.log(e.response); 80 | }); 81 | -------------------------------------------------------------------------------- /src/weather.c: -------------------------------------------------------------------------------- 1 | #include "pebble.h" 2 | 3 | static Window *s_main_window; 4 | 5 | static TextLayer *s_temperature_layer; 6 | static TextLayer *s_city_layer; 7 | static BitmapLayer *s_icon_layer; 8 | static GBitmap *s_icon_bitmap = NULL; 9 | 10 | static AppSync s_sync; 11 | static uint8_t s_sync_buffer[64]; 12 | 13 | enum WeatherKey { 14 | WEATHER_ICON_KEY = 0x0, // TUPLE_INT 15 | WEATHER_TEMPERATURE_KEY = 0x1, // TUPLE_CSTRING 16 | WEATHER_CITY_KEY = 0x2, // TUPLE_CSTRING 17 | }; 18 | 19 | static const uint32_t WEATHER_ICONS[] = { 20 | RESOURCE_ID_IMAGE_SUN, // 0 21 | RESOURCE_ID_IMAGE_CLOUD, // 1 22 | RESOURCE_ID_IMAGE_RAIN, // 2 23 | RESOURCE_ID_IMAGE_SNOW // 3 24 | }; 25 | 26 | static void sync_error_callback(DictionaryResult dict_error, AppMessageResult app_message_error, void *context) { 27 | APP_LOG(APP_LOG_LEVEL_DEBUG, "App Message Sync Error: %d", app_message_error); 28 | } 29 | 30 | static void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) { 31 | switch (key) { 32 | case WEATHER_ICON_KEY: 33 | if (s_icon_bitmap) { 34 | gbitmap_destroy(s_icon_bitmap); 35 | } 36 | 37 | s_icon_bitmap = gbitmap_create_with_resource(WEATHER_ICONS[new_tuple->value->uint8]); 38 | bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet); 39 | bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap); 40 | break; 41 | 42 | case WEATHER_TEMPERATURE_KEY: 43 | // App Sync keeps new_tuple in s_sync_buffer, so we may use it directly 44 | text_layer_set_text(s_temperature_layer, new_tuple->value->cstring); 45 | break; 46 | 47 | case WEATHER_CITY_KEY: 48 | text_layer_set_text(s_city_layer, new_tuple->value->cstring); 49 | break; 50 | } 51 | } 52 | 53 | static void request_weather(void) { 54 | DictionaryIterator *iter; 55 | app_message_outbox_begin(&iter); 56 | 57 | if (!iter) { 58 | // Error creating outbound message 59 | return; 60 | } 61 | 62 | int value = 1; 63 | dict_write_int(iter, 1, &value, sizeof(int), true); 64 | dict_write_end(iter); 65 | 66 | app_message_outbox_send(); 67 | } 68 | 69 | static void window_load(Window *window) { 70 | Layer *window_layer = window_get_root_layer(window); 71 | GRect bounds = layer_get_bounds(window_layer); 72 | 73 | s_icon_layer = bitmap_layer_create(GRect(0, 10, bounds.size.w, 80)); 74 | layer_add_child(window_layer, bitmap_layer_get_layer(s_icon_layer)); 75 | 76 | s_temperature_layer = text_layer_create(GRect(0, 90, bounds.size.w, 32)); 77 | text_layer_set_text_color(s_temperature_layer, GColorWhite); 78 | text_layer_set_background_color(s_temperature_layer, GColorClear); 79 | text_layer_set_font(s_temperature_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)); 80 | text_layer_set_text_alignment(s_temperature_layer, GTextAlignmentCenter); 81 | layer_add_child(window_layer, text_layer_get_layer(s_temperature_layer)); 82 | 83 | s_city_layer = text_layer_create(GRect(0, 122, bounds.size.w, 32)); 84 | text_layer_set_text_color(s_city_layer, GColorWhite); 85 | text_layer_set_background_color(s_city_layer, GColorClear); 86 | text_layer_set_font(s_city_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)); 87 | text_layer_set_text_alignment(s_city_layer, GTextAlignmentCenter); 88 | layer_add_child(window_layer, text_layer_get_layer(s_city_layer)); 89 | 90 | Tuplet initial_values[] = { 91 | TupletInteger(WEATHER_ICON_KEY, (uint8_t) 1), 92 | TupletCString(WEATHER_TEMPERATURE_KEY, "1234\u00B0C"), 93 | TupletCString(WEATHER_CITY_KEY, "St Pebblesburg"), 94 | }; 95 | 96 | app_sync_init(&s_sync, s_sync_buffer, sizeof(s_sync_buffer), 97 | initial_values, ARRAY_LENGTH(initial_values), 98 | sync_tuple_changed_callback, sync_error_callback, NULL); 99 | 100 | request_weather(); 101 | } 102 | 103 | static void window_unload(Window *window) { 104 | if (s_icon_bitmap) { 105 | gbitmap_destroy(s_icon_bitmap); 106 | } 107 | 108 | text_layer_destroy(s_city_layer); 109 | text_layer_destroy(s_temperature_layer); 110 | bitmap_layer_destroy(s_icon_layer); 111 | } 112 | 113 | static void init(void) { 114 | s_main_window = window_create(); 115 | window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorIndigo, GColorBlack)); 116 | window_set_window_handlers(s_main_window, (WindowHandlers) { 117 | .load = window_load, 118 | .unload = window_unload 119 | }); 120 | window_stack_push(s_main_window, true); 121 | 122 | app_message_open(64, 64); 123 | } 124 | 125 | static void deinit(void) { 126 | window_destroy(s_main_window); 127 | 128 | app_sync_deinit(&s_sync); 129 | } 130 | 131 | int main(void) { 132 | init(); 133 | app_event_loop(); 134 | deinit(); 135 | } 136 | -------------------------------------------------------------------------------- /weather-screenshot~bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/weather-screenshot~bw.png -------------------------------------------------------------------------------- /weather-screenshot~color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/pebblekit-js-weather/588544f57de55fbc87ea5cbe77ae0135551d263d/weather-screenshot~color.png -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------