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]

