Pebble Timeでバッテリー残量を表示するWatch Faceを作ってみた
|あむちょです。
実際に作ったのは半年以上前の話です。
目次
Pebble Timeってなんですかって人はこちら。丸型スマートウォッチPebble Time Roundのレビュー
今回(半年前に)作ったのは、旧型Pebbleで割りとダウンロード数の多かったバッテリー残量を表示するWatch Faceです。旧型のPebbleはこちら。Pebbleのバッテリー残量を表示するWatch Face
今回も角形と丸型の両方に対応。
Watch Face 「Battery Info」
外側の円がバッテリー残量で、内側が秒をあらわしてます。
周りの赤色の角は、余白をごまかすためのただの飾りです。
オープニングでアニメーションするけど、gifを作るのが大変なので動画で
こちらからダウンロードできます。Pebble Time Watch Face 「Battery Info」
[amazonjs asin=”B0166NBSFK” locale=”JP” title=”Pebble Time Round 極薄かつ超軽量の丸型スマートウォッチ「ペッブルタイム・ラウンド」Black 並行輸入品”]
ソースコード
バッテリー残量と秒はパスで直接描画してます。毎秒再描画させているのでバッテリーの消費がちょっと心配。
Pebbleは、textだったり描画レイヤー以外でも変更すると、システムから再描画がかかるので、バッテリーを気にするなら秒の表示はやめておく方がいいです。
#include static Window *window; static TextLayer *text_layer; static TextLayer *time_layer; static Layer *round_layer; static GPath *round_path; static GPath *round_rect_path; static GPath *round_sec_path; static GBitmap *image; static BitmapLayer *image_layer; #define EMPTY_SLOT -1 int image_state = EMPTY_SLOT; static int sec = 0; static int battery = 0; static AppTimer *timer; static int animationCnt = 0; static float delSec = 0.0; static float delBt = 0.0; static int line_x = 0.0; #define STOP_CNT 30 #define COLOR_BACK GColorBlack #define COLOR_BATTERY GColorCyan #define COLOR_SEC GColorElectricBlue #define COLOR_TIME GColorElectricBlue #define COLOR_TEXT_BT GColorCyan #if defined(PBL_RECT) #define WIDTH 144 #define HEIGHT 168 #elif defined(PBL_ROUND) #define WIDTH 180 #define HEIGHT 180 #endif static const GPathInfo ROUND_POINTS = { 4, (GPoint []){ {-5, 70}, {5, 70}, {5, 55}, {-5,55} } }; static const GPathInfo ROUND_POINTS_RECT = { 4, (GPoint []){ {-20, 70}, {20, 70}, {20, 55}, {-20,55} } }; static const GPathInfo ROUND_POINTS_SEC = { 4, (GPoint []){ {-10, 58}, {10, 58}, {10, 45}, {-10,45} } }; static void handle_second_tick(struct tm* tick_time, TimeUnits units_changed); static void handle_battery(BatteryChargeState charge_state); static void round_update_proc(Layer *layer, GContext *ctx) { if(animationCnt == 0)return; //battery graphics_context_set_fill_color(ctx, COLOR_BATTERY); graphics_fill_circle(ctx,GPoint(WIDTH/2,HEIGHT/2),65); graphics_context_set_fill_color(ctx, COLOR_BACK); graphics_fill_circle(ctx,GPoint(WIDTH/2,HEIGHT/2),57); int n=0; for(n=0;n<10;n++){ gpath_rotate_to(round_path, (TRIG_MAX_ANGLE*n/10)); gpath_draw_filled(ctx, round_path); if(n < battery){ gpath_rotate_to(round_rect_path, (TRIG_MAX_ANGLE*(n+0.5+5)/10)); gpath_draw_filled(ctx, round_rect_path); } } //sec graphics_context_set_fill_color(ctx, COLOR_SEC); graphics_fill_circle(ctx,GPoint(WIDTH/2,HEIGHT/2),53); graphics_context_set_fill_color(ctx, COLOR_BACK); graphics_fill_circle(ctx,GPoint(WIDTH/2,HEIGHT/2),48); gpath_rotate_to(round_sec_path, (TRIG_MAX_ANGLE*(sec+30)/60)); gpath_draw_filled(ctx, round_sec_path); graphics_context_set_stroke_color(ctx, GColorRed); graphics_context_set_stroke_width(ctx,2); //line length 64 graphics_draw_line(ctx, GPoint((WIDTH-64)/2,HEIGHT/2-30+10),GPoint((WIDTH-64)/2+line_x,HEIGHT/2-30+10)); graphics_draw_line(ctx, GPoint((WIDTH-64)/2+64-line_x,HEIGHT/2-30+55),GPoint((WIDTH-64)/2+64,HEIGHT/2-30+55)); } static void update_layer(){ animationCnt++; sec = delSec*animationCnt; battery = 10-delBt*animationCnt; line_x = 64*animationCnt/STOP_CNT; layer_mark_dirty(round_layer); if(animationCnt < STOP_CNT){ timer = app_timer_register(13 /* milli */, update_layer, NULL); } else{ tick_timer_service_subscribe(SECOND_UNIT, &handle_second_tick); battery_state_service_subscribe(&handle_battery); } } static void handle_battery(BatteryChargeState charge_state) { static char battery_text[] = "100% charged"; battery = 10-charge_state.charge_percent/10; if (charge_state.is_charging) { snprintf(battery_text, sizeof(battery_text), "charging"); } else { snprintf(battery_text, sizeof(battery_text), "%d%% charged", charge_state.charge_percent); } text_layer_set_text(text_layer, battery_text); } static void handle_second_tick(struct tm* tick_time, TimeUnits units_changed) { static char time_text[] = "08:22"; //strftime(time_text, sizeof(time_text), "%T", tick_time); text_layer_set_text(time_layer, time_text); handle_battery(battery_state_service_peek()); sec = tick_time->tm_sec; layer_mark_dirty(window_get_root_layer(window)); } static void handle_battery(BatteryChargeState charge_state) { static char battery_text[] = "100% charged"; battery = 10-charge_state.charge_percent/10; if (charge_state.is_charging) { snprintf(battery_text, sizeof(battery_text), "charging"); } else { snprintf(battery_text, sizeof(battery_text), "%d%% charged", charge_state.charge_percent); } text_layer_set_text(text_layer, battery_text); } static void handle_second_tick(struct tm* tick_time, TimeUnits units_changed) { static char time_text[] = "08:22"; strftime(time_text, sizeof(time_text), "%T", tick_time); text_layer_set_text(time_layer, time_text); handle_battery(battery_state_service_peek()); sec = tick_time->tm_sec; layer_mark_dirty(window_get_root_layer(window)); } static void window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); //image image_state=1; image = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BACK); image_layer = bitmap_layer_create(bounds); bitmap_layer_set_bitmap(image_layer, image); layer_add_child(window_layer, bitmap_layer_get_layer(image_layer)); //path round_path = gpath_create(&ROUND_POINTS); round_rect_path = gpath_create(&ROUND_POINTS_RECT); round_sec_path = gpath_create(&ROUND_POINTS_SEC); GRect centerRect =(GRect){.origin = {0,0}, .size = {bounds.size.w,bounds.size.h}}; const GPoint center = grect_center_point(¢erRect); gpath_move_to(round_path, center); gpath_move_to(round_rect_path, center); gpath_move_to(round_sec_path, center); round_layer = layer_create(bounds); layer_set_update_proc(round_layer, round_update_proc); layer_add_child(window_layer, round_layer); //text time_layer = text_layer_create((GRect) { .origin = { 0, HEIGHT/2-30+15+5 }, .size = { bounds.size.w, 40 } }); //text_layer_set_text(text_layer, "Press a button"); text_layer_set_text_color(time_layer,COLOR_TIME); text_layer_set_background_color(time_layer,GColorClear); text_layer_set_font(time_layer, fonts_get_system_font(FONT_KEY_BITHAM_30_BLACK)); text_layer_set_text_alignment(time_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(time_layer)); text_layer = text_layer_create((GRect) { .origin = { 0, HEIGHT/2-40+15+6 }, .size = { bounds.size.w, 20 } }); //text_layer_set_text(text_layer, "Press a button"); text_layer_set_text_color(text_layer,COLOR_TEXT_BT); text_layer_set_background_color(text_layer,GColorClear); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer)); } static void window_unload(Window *window) { text_layer_destroy(text_layer); text_layer_destroy(time_layer); layer_destroy(round_layer); } static void init(void) { window = window_create(); window_set_background_color(window, COLOR_BACK); window_set_window_handlers(window, (WindowHandlers) { .load = window_load, .unload = window_unload, }); const bool animated = true; window_stack_push(window, animated); time_t now = time(NULL); struct tm *current_time = localtime(&now); delSec = (float)(current_time->tm_sec+1)/STOP_CNT; BatteryChargeState charge_state = battery_state_service_peek(); delBt = (float)(charge_state.charge_percent/10)/STOP_CNT; timer = app_timer_register(500 /* milli */, update_layer, NULL); } static void deinit(void) { tick_timer_service_unsubscribe(); battery_state_service_unsubscribe(); //path gpath_destroy(round_path); gpath_destroy(round_rect_path); gpath_destroy(round_sec_path); //image if (image_state != EMPTY_SLOT) { layer_remove_from_parent(bitmap_layer_get_layer(image_layer)); bitmap_layer_destroy(image_layer); gbitmap_destroy(image); image_state = EMPTY_SLOT; } window_destroy(window); } int main(void) { init(); app_event_loop(); deinit(); }
オープニングアニメーションは13ミリ秒のタイマーを30フレーム分でまわしてますが、実際そこまで速度が出せないので、とりあえず早めにしてあります。
バッテリー残量の取り方は
変化があった時のイベント登録は
//init()で登録 battery_state_service_subscribe(&handle_battery); //バッテリー残量や充電状態の変化で呼ばれる static void handle_battery(BatteryChargeState charge_state) { //バッテリー残量 0~100で10、刻み int battery = charge_state.charge_percent; //充電中かどうか bool isCharging = charge_state.is_charging }
イベントではなくバッテリー状態をすぐに知りたい時は
BatteryChargeState charge_state = battery_state_service_peek(); int battery = charge_state.charge_percent;
で求めれます。
自分のため
これでようやく3個リリースできました。
アプリ内のWatch Face管理画面はストアからダウンロードしないと画像が表示されない(gifだとちゃんと動く)のでちょっとさみしいですね。
自分の管理画面のサムネイルを表示したいので、
そのためだけに公開してます。
[ad][ad]