あむちょです。
そろそろネタがきれそうですが、またWatch Faceを公開
目次
Pebble Timeってなんですかって人はこちら。丸型スマートウォッチPebble Time Roundのレビュー
今回作ったのは、位置情報から天気予報APIを使って天気予報を表示してみました。
FalloutってゲームのPip boyってキャラクターが無駄に歩きます。
充電中は別のアニメーションに切り替わります。
スマホの位置情報がOnの時は、現在地の天気予報を取得し、Offの時は設定画面で入力した都市の天気予報を表示します。
こちらからダウンロードできます。Pebble Time Watch Face 「PiP-Boy」
ソースコード
pebble.jsで位置情報、もしくは都市を天気予報APIに渡し、Jsonでデータを取得してPebble本体に送信してます。
まずはCファイル
#include #define IMG_MAXNUM 8 #define IMG_MAXNUM_GOOD 6 #define EMPTY_SLOT -1 //#define DEBAG static Window *window; static BitmapLayer *image_layer; static BitmapLayer *imageWeather_layer; static GBitmap *image_walk[IMG_MAXNUM]; static GBitmap *image_good[IMG_MAXNUM_GOOD]; static GBitmap *image_weather; static TextLayer *textTime_layer; static TextLayer *textYear_layer; static TextLayer *textBt_layer; static TextLayer *textTMP_layer; static TextLayer *textCTY_layer; static TextLayer *textHUM_layer; static int cnt = 0; static int image_state = EMPTY_SLOT; static int image_state_good = EMPTY_SLOT; static int image_state_weather = EMPTY_SLOT; static AppTimer *timer; static bool charge = false; static const int RESOURCE_IDS[] = { RESOURCE_ID_IMAGE0,RESOURCE_ID_IMAGE1,RESOURCE_ID_IMAGE2,RESOURCE_ID_IMAGE3,RESOURCE_ID_IMAGE4,RESOURCE_ID_IMAGE5,RESOURCE_ID_IMAGE6,RESOURCE_ID_IMAGE7 }; static const int RESOURCE_IDS_GOOD[] = { RESOURCE_ID_IMAGE10,RESOURCE_ID_IMAGE11,RESOURCE_ID_IMAGE12,RESOURCE_ID_IMAGE13,RESOURCE_ID_IMAGE14,RESOURCE_ID_IMAGE15 }; static bool incFlag = true; static int roadCnt = 0; static AppSync s_sync; static uint8_t s_sync_buffer[256]; enum WeatherKey { WEATHER_ICON_KEY = 0, // TUPLE_INT WEATHER_TEMPERATURE_KEY = 1, // TUPLE_CSTRING WEATHER_CITY_KEY = 2, // TUPLE_CSTRING WEATHER_HUM_KEY = 3 }; #define CITY_KEY 4 static const uint32_t WEATHER_ICONS[] = { RESOURCE_ID_IMAGE_SUN, //0 RESOURCE_ID_IMAGE_CLOUD, //1 RESOURCE_ID_IMAGE_RAIN, //2 RESOURCE_ID_IMAGE_SNOW //3 }; #define NUM_CITY_DEFAULT 1849846 static int cityId; #if defined(PBL_RECT) #define DEL_X (180-144)/2 #define DEL_Y (180-168)/2 #elif defined(PBL_ROUND) #define DEL_X 0 #define DEL_Y 0 #endif static void request_weather(); static void sync_error_callback(DictionaryResult dict_error, AppMessageResult app_message_error, void *context) { APP_LOG(APP_LOG_LEVEL_DEBUG, "App Message Sync Error: %d", app_message_error); } static void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) { static char text[20]; static char textCTY[20]; static char textHUM[20]; switch (key) { case WEATHER_ICON_KEY: if(new_tuple->value->uint8 == 4){ layer_set_hidden((Layer *)imageWeather_layer, true); return; } if(image_state_weather != EMPTY_SLOT)gbitmap_destroy(image_weather); image_weather = gbitmap_create_with_resource(WEATHER_ICONS[new_tuple->value->uint8]); bitmap_layer_set_bitmap(imageWeather_layer, image_weather); image_state_weather = 1; layer_set_hidden((Layer *)imageWeather_layer, false); break; case WEATHER_TEMPERATURE_KEY: snprintf(text, sizeof(text),"TMP:\n%s",new_tuple->value->cstring); text_layer_set_text(textTMP_layer, text); break; case WEATHER_CITY_KEY: snprintf(textCTY, sizeof(textCTY),"LOC:\n%s",new_tuple->value->cstring); text_layer_set_text(textCTY_layer, textCTY); break; case WEATHER_HUM_KEY: snprintf(textHUM, sizeof(textHUM),"PRS:\n%s",new_tuple->value->cstring); text_layer_set_text(textHUM_layer, textHUM); break; case CITY_KEY: if(cityId == (int)new_tuple->value->int32)return; cityId = (int)new_tuple->value->int32; persist_write_int(CITY_KEY, new_tuple->value->int32); request_weather(); break; } } static void request_weather() { APP_LOG(APP_LOG_LEVEL_DEBUG, "call"); DictionaryIterator *iter; app_message_outbox_begin(&iter); if (!iter) { // Error creating outbound message return; } int value = cityId; dict_write_int(iter, CITY_KEY, &value, sizeof(int), true); dict_write_end(iter); app_message_outbox_send(); } //image static void update_layer(){ if(charge){ if(roadCnt != IMG_MAXNUM_GOOD)return; if(incFlag){ cnt++; if(cnt > IMG_MAXNUM_GOOD-1){ incFlag = false; cnt = IMG_MAXNUM_GOOD-2; } } else{ cnt--; if(cnt < 0){ incFlag = true; cnt = 1; } } if(image_state_good != EMPTY_SLOT)bitmap_layer_set_bitmap(image_layer, image_good[cnt]); if(cnt == 0){ timer = app_timer_register(2000, update_layer, NULL); } else if(cnt == 5){ timer = app_timer_register(2000, update_layer, NULL); } else{ timer = app_timer_register(100, update_layer, NULL); } } else{ if(roadCnt != IMG_MAXNUM)return; cnt++; if(cnt > IMG_MAXNUM-1)cnt=0; if(image_state != EMPTY_SLOT)bitmap_layer_set_bitmap(image_layer, image_walk[cnt]); timer = app_timer_register(150, update_layer, NULL); } //layer_mark_dirty(bitmap_layer_get_layer(image_layer)); } static void changeImage(bool charge){ roadCnt = 0; cnt = 0; //good if(charge){ if(image_state != EMPTY_SLOT){ int n= 0; for(n=0;n<IMG_MAXNUM;n++){ gbitmap_destroy(image_walk[n]); } image_state = EMPTY_SLOT; } layer_set_frame(bitmap_layer_get_layer(image_layer),(GRect) { .origin = { 35-DEL_X, 23-DEL_Y }, .size = { 100, 139 } }); layer_set_frame(text_layer_get_layer(textYear_layer),(GRect) { .origin = { 5, 70+DEL_Y }, .size = { 50, 28 } }); int n=0; for(n=0;n<IMG_MAXNUM_GOOD;n++){ image_good[n] = gbitmap_create_with_resource(RESOURCE_IDS_GOOD[n]); roadCnt++; } image_state_good = 1; //layer_set_hidden((Layer *)imageWeather_layer, true); } //walk else{ if(image_state_good != EMPTY_SLOT){ int n= 0; for(n=0;n<IMG_MAXNUM_GOOD;n++){ gbitmap_destroy(image_good[n]); } image_state_good = EMPTY_SLOT; } layer_set_frame(bitmap_layer_get_layer(image_layer),(GRect) { .origin = { 45-DEL_X, 23-DEL_Y }, .size = { 90, 139 } }); layer_set_frame(text_layer_get_layer(textYear_layer),(GRect) { .origin = { 15-DEL_X/1.5, 50-DEL_Y }, .size = { 50, 28 } }); int n=0; for(n=0;n<IMG_MAXNUM;n++){ image_walk[n] = gbitmap_create_with_resource(RESOURCE_IDS[n]); roadCnt++; } image_state = 1; //layer_set_hidden((Layer *)imageWeather_layer, false); } } //text time static void handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) { static char timeText[] = "sun 00:00"; static char yearText[] = "2014\n12/30"; strftime(timeText, sizeof(timeText), "%a %T",tick_time); text_layer_set_text(textTime_layer, timeText); strftime(yearText, sizeof(yearText), "%G\n%m/%d",tick_time); text_layer_set_text(textYear_layer, yearText); if(tick_time->tm_min%30 == 0)request_weather(); } //text battery static void handle_battery(BatteryChargeState charge_state) { static char battery_text[12]; #ifdef DEBAG if(true){ #else if(charge_state.is_charging){ #endif if(!charge){ charge = true; changeImage(charge); } snprintf(battery_text, sizeof(battery_text),"Charging"); } else{ if(charge){ charge = false; changeImage(charge); } snprintf(battery_text, sizeof(battery_text), "BTR:%d/100",charge_state.charge_percent); } text_layer_set_text(textBt_layer, battery_text); } static void window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); //main img BatteryChargeState charge_state = battery_state_service_peek(); charge = charge_state.is_charging; int n=0; if(charge){ for(n=0;n<IMG_MAXNUM_GOOD;n++){ image_good[n] = gbitmap_create_with_resource(RESOURCE_IDS_GOOD[n]); roadCnt++; } image_state_good = 1; image_layer = bitmap_layer_create((GRect) { .origin = { 35-DEL_X, 23-DEL_Y }, .size = { 100, 139 } }); } else{ for(n=0;n<IMG_MAXNUM;n++){ image_walk[n] = gbitmap_create_with_resource(RESOURCE_IDS[n]); roadCnt++; } image_state = 1; image_layer = bitmap_layer_create((GRect) { .origin = { 45-DEL_X, 23-DEL_Y }, .size = { 90, 139 } }); } bitmap_layer_set_background_color(image_layer, GColorClear); layer_add_child(window_layer, bitmap_layer_get_layer(image_layer)); //weather icon imageWeather_layer = bitmap_layer_create((GRect) { .origin = { 125-DEL_X/2, 35+DEL_Y*7 }, .size = { 30, 30 } }); bitmap_layer_set_background_color(imageWeather_layer, GColorClear); layer_add_child(window_layer, bitmap_layer_get_layer(imageWeather_layer)); layer_set_hidden((Layer *)imageWeather_layer, true); //text //time textTime_layer = text_layer_create((GRect) { .origin = { 0, 5-DEL_Y }, .size = { 180-DEL_X*2, 18 } }); text_layer_set_text_alignment(textTime_layer, GTextAlignmentCenter); text_layer_set_background_color(textTime_layer, GColorClear); text_layer_set_text_color(textTime_layer, GColorGreen); text_layer_set_font(textTime_layer,fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); layer_add_child(window_layer, text_layer_get_layer(textTime_layer)); //year textYear_layer = text_layer_create((GRect) { .origin = { 15-DEL_X/1.5, 50-DEL_Y }, .size = { 50, 28 } }); if(charge)layer_set_frame(text_layer_get_layer(textYear_layer),(GRect) { .origin = { 5, 70 }, .size = { 50, 28 } }); text_layer_set_text_alignment(textYear_layer, GTextAlignmentLeft); text_layer_set_background_color(textYear_layer, GColorClear); text_layer_set_text_color(textYear_layer, GColorGreen); layer_add_child(window_layer, text_layer_get_layer(textYear_layer)); //battery textBt_layer = text_layer_create((GRect) { .origin = { 0, 155-DEL_Y }, .size = { 180-DEL_X*2, 18 } }); text_layer_set_text_alignment(textBt_layer, GTextAlignmentCenter); text_layer_set_background_color(textBt_layer, GColorClear); text_layer_set_text_color(textBt_layer, GColorGreen); //handle_battery(battery_state_service_peek()); layer_add_child(window_layer, text_layer_get_layer(textBt_layer)); //temp textTMP_layer = text_layer_create((GRect) { .origin = { 132-DEL_X*1.8, 67-DEL_Y*7 }, .size = { 60, 42 } }); text_layer_set_text_alignment(textTMP_layer, GTextAlignmentLeft); text_layer_set_background_color(textTMP_layer, GColorClear); text_layer_set_text_color(textTMP_layer, GColorGreen); text_layer_set_text(textTMP_layer, "TMP:NA"); layer_add_child(window_layer, text_layer_get_layer(textTMP_layer)); //hum textHUM_layer = text_layer_create((GRect) { .origin = { 130-DEL_X, 115 }, .size = { 70, 28 } }); text_layer_set_text_alignment(textHUM_layer, GTextAlignmentLeft); text_layer_set_background_color(textHUM_layer, GColorClear); text_layer_set_text_color(textHUM_layer, GColorGreen); text_layer_set_text(textHUM_layer, "PRS:NA"); layer_add_child(window_layer, text_layer_get_layer(textHUM_layer)); //city textCTY_layer = text_layer_create((GRect) { .origin = { 10-DEL_X/3, 100 }, .size = { 70-DEL_X, 28 } }); text_layer_set_text_alignment(textCTY_layer, GTextAlignmentLeft); text_layer_set_background_color(textCTY_layer, GColorClear); text_layer_set_text_color(textCTY_layer, GColorGreen); text_layer_set_text(textCTY_layer, "LOC:NA"); layer_add_child(window_layer, text_layer_get_layer(textCTY_layer)); handle_battery(battery_state_service_peek()); Tuplet initial_values[] = { TupletInteger(WEATHER_ICON_KEY, (uint8_t) 4), TupletCString(WEATHER_TEMPERATURE_KEY, "NA"),//"1234\u00B0C"), TupletCString(WEATHER_CITY_KEY, "NA"), TupletCString(WEATHER_HUM_KEY, "NA"), TupletInteger(CITY_KEY, (int32_t)cityId), }; app_sync_init(&s_sync, s_sync_buffer, sizeof(s_sync_buffer), initial_values, ARRAY_LENGTH(initial_values), sync_tuple_changed_callback, sync_error_callback, NULL ); //request_weather(); app_timer_register(500 /* milliseconds */, request_weather, NULL); } static void window_unload(Window *window) { int n= 0; if(image_state != EMPTY_SLOT){ for(n=0;n<IMG_MAXNUM;n++){ gbitmap_destroy(image_walk[n]); } } if(image_state_good != EMPTY_SLOT){ for(n=0;n<IMG_MAXNUM_GOOD;n++){ gbitmap_destroy(image_good[n]); } } if(image_state_weather != EMPTY_SLOT){ gbitmap_destroy(image_weather); } bitmap_layer_destroy(image_layer); bitmap_layer_destroy(imageWeather_layer); text_layer_destroy(textTime_layer); text_layer_destroy(textYear_layer); text_layer_destroy(textBt_layer); text_layer_destroy(textTMP_layer); text_layer_destroy(textCTY_layer); text_layer_destroy(textHUM_layer); } static void init(void) { cityId = persist_exists(CITY_KEY) ? persist_read_int(CITY_KEY) : NUM_CITY_DEFAULT; window = window_create(); window_set_background_color(window, GColorBlack); window_set_window_handlers(window, (WindowHandlers) { .load = window_load, .unload = window_unload, }); window_stack_push(window, true); battery_state_service_subscribe(&handle_battery); tick_timer_service_subscribe(MINUTE_UNIT, &handle_minute_tick); timer = app_timer_register(125 /* milliseconds */, update_layer, NULL); app_message_open(256, 256); } static void deinit(void) { tick_timer_service_unsubscribe(); battery_state_service_unsubscribe(); app_sync_deinit(&s_sync); window_destroy(window); } int main(void) { init(); app_event_loop(); deinit(); }
角形と丸型の画面サイズの違いを調整するために、各レイヤーの座標がめんどくさいことになってます。内容自体は前回とあまりかわりません。Pebble Timeで残りの寿命を表示するWatch Faceを作ってみた
お次はjsファイル
function iconFromWeatherId(weatherId) { if (weatherId < 600) { return 2; } else if (weatherId < 700) { return 3; } else if (weatherId < 800) { return 1; } else { return 0; } } var apiKey = "your key"; var cityID = null; function fetchWeather(latitude, longitude,id) { var req = new XMLHttpRequest(); if(id != null)req.open('GET', "http://api.openweathermap.org/data/2.5/weather?id=" + cityID + "&appid=" + apiKey + "&cnt=1", true); else{ req.open('GET', "http://api.openweathermap.org/data/2.5/weather?" + "lat=" + latitude + "&lon=" + longitude + "&appid=" + apiKey + "&cnt=1", true); } req.onload = function(e) { if (req.readyState == 4) { if(req.status == 200) { console.log(req.responseText); var response = JSON.parse(req.responseText); var temperature = ""+Math.round(response.main.temp-273.15)+"\u00B0C\n"+Math.round(response.main.temp_min-273.15)+"/"+Math.round(response.main.temp_max-273.15)+"\u00B0C"; var icon = iconFromWeatherId(response.weather.id); var city = response.name; var prs = Math.round(response.main.pressure); console.log(temperature); console.log(icon); console.log(city); console.log(prs); Pebble.sendAppMessage({ "WEATHER_ICON_KEY":icon, "WEATHER_TEMPERATURE_KEY":temperature, "WEATHER_CITY_KEY":city, "WEATHER_HUM_KEY":""+prs} ); } else { console.log("ErrorAjax"); } } } req.send(null); } var locationOptions = { "timeout": 15000, "maximumAge": 60000 }; function locationSuccess(pos) { var coordinates = pos.coords; fetchWeather(coordinates.latitude, coordinates.longitude,null); } function locationError(err) { console.warn('location error (' + err.code + '): ' + err.message); fetchWeather(null, null, cityID); } function getLocation(){ console.log("getLocation"); locationWatcher = window.navigator.geolocation.getCurrentPosition(locationSuccess, locationError, locationOptions); } Pebble.addEventListener("ready", function(e) { console.log("connect!" + e.ready); //getLocation(); console.log(e.type); }); Pebble.addEventListener("appmessage", function(e) { window.navigator.geolocation.getCurrentPosition(locationSuccess, locationError,locationOptions); getLocation(); console.log(e.type); console.log(e.payload.temperature); console.log("message!"); var dict = e.payload; if(dict['CITY_KEY']){ cityID = dict['CITY_KEY']; } getLocation(); }); Pebble.addEventListener("webviewclosed", function(e) { console.log("webview closed"); console.log(e.type); console.log(e.response); var configuration = JSON.parse(decodeURIComponent(e.response)); var dict = {}; dict['CITY_KEY'] = configuration.city-0; Pebble.sendAppMessage(dict, function() { console.log('Send successful: ' + JSON.stringify(dict)); }, function() { console.log('Send failed!'); }); cityID = configuration.city-0; getLocation(); }); Pebble.addEventListener('showConfiguration', function(e) { Pebble.openURL('http://fieldwalking.jp/pebble/pip-boy/'); });
天気予報APIはOpenWeatherMapってとこを使ってますが、登録してAPI Keyを取得する必要があります。
ネタぎれ
これで作っためぼしいWatch Faceはほとんど公開しました。
あとは特にこれといった機能もないようなやつか、自分用アプリと連携したりするような物ばかりなので完全にネタぎれです。
アィディアください
[ad][ad]