Pebble Timeで天気を表示するWatch Faceを作ってみた(Pip-boyを添えて)
あむちょです。
そろそろネタがきれそうですが、またWatch Faceを公開
目次
Pebble Timeってなんですかって人はこちら。丸型スマートウォッチPebble Time Roundのレビュー
今回作ったのは、位置情報から天気予報APIを使って天気予報を表示してみました。
![]() |
![]() |
FalloutってゲームのPip boyってキャラクターが無駄に歩きます。

充電中は別のアニメーションに切り替わります。
スマホの位置情報がOnの時は、現在地の天気予報を取得し、Offの時は設定画面で入力した都市の天気予報を表示します。
こちらからダウンロードできます。Pebble Time Watch Face 「PiP-Boy」
[amazonjs asin=”B00Y2CYYQ8″ locale=”JP” title=”Pebble Time スマートウォッチ 腕時計 (ブラック) 並行輸入品”]
ソースコード
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]

