こんにちは。
開発チームの高木です。

今回はコイツ↓を使ったネタを一つ。
20170331_145159
Apple Watch?Android Wear?違います。Pebbleです。
さて、今回はこの、Pebble Time Steelを使用して少し遊んでみたいと思います。

Pebbleってナニ?


Pebbleとは、 2013年頃に発売したスマートウォッチです。
kickstarter」 というクラウドファンディングサイトで約12億円の資金を調達し誕生しました。
スマートウォッチというカテゴリですが、あくまで「腕時計」であることを重視しているデバイスです。

Pebbleシリーズの特徴を挙げていくと、
 ・昼夜場所を問わず見やすい画面
 ・時計部分が常に表示されている
 ・長持ちするバッテリー(Pebble Timeで7日間、Time Steelだと10日間(!))
 ・価格の安さ(Pebble Timeで1万円程度、Time SteelやTime Roundだと1万5千円ぐらい)
 ・iOS、Androidのどちらでも使用可能
 ・完全防水 
 ・普通の腕時計の22mm用バンドと交換が可能

…と、Apple WatchやAndroid Wearとは違う方面に力を入れているのがわかると思います。
当然スマートウォッチとしての最低限の役割も果たしており、通話やスマホの通知をしっかり教えてくれます。
その他にも運動記録や睡眠の質など、最近流行りのヘルスケア方面の記録も○。
バッテリーが切れてしまうとこれらの機能は使用することは出来ませんが、時計だけは意地でも表示するという健気さ。 
良さを挙げたらキリがありません。
しかし…
ご存知の方もいらっしゃるかと思いますが、PebbleはFitbitに買収されてしまいました。
開発、販売は終了してしまいますが、今後もサポートは継続していくとのこと。
今後は手元にあるPebbleを愛でつつ、FitbitがPebbleのような素晴らしいデバイスを制作してくれることを期待しましょう。

WatchFaceとは



ここからが本編。
今日はチュートリアル に沿ってWatchFaceを作ってみます。
WatchFaceとは、一言で言ってしまうと文字盤です。
Pebbleのサイト内にWatchFaceをまとめているページがあり、ここから文字盤をダウンロード出来ます。

そしてこのWatchFace、自由に作って自由に使うことが出来ます。
なので今回は自由に作ってみることにします。

開発環境の準備



さて、開発するには環境が必要です。
PebbleのWatchFaceとアプリはC言語かJSを使って開発をします。
JSを使ってもいいのですが、情報があまりにも少ないため、今回はCを使います。

というわけでCの開発環境を準備しないといけないのですが、今更PCに作るのもなんかなー…という感じ。
なので、Pebble側が用意してくれている、Cloud Pebbleを使用して開発します。
monaca のPebble版と考えて頂ければ良いと思います。

プロジェクトを作る



早速CloudPebbleでプロジェクトを作りましょう。
Pebbleのアカウントを作成~ログインまでは割愛します。

ログインしたら、ヘッダーメニューから「PROJECTS」を開きます。
pebble2
プロジェクトの一覧ページに移動したら、「CREATE」でプロジェクトを作成。
pebble3
「PROJECT NAME」にプロジェクトの名前を入力し、他はそのままで「CREATE」。
すると、プロジェクトの開発画面へ移動すると思います。
pebble4
左メニューの「APP SOURCE」から「ADD NEW」を選択し、Cファイルを作ります。
pebble5
「FILE TYPE」を「C file」に、「FILENAME」は適当な名前を、
「TARGET」は「App / Watchface」にします。
「CREATE HEADER」のチェックはここでは外しておきましょう。
pebble6
「wfSample.c」が出来ました。ゴリゴリ書いていきましょう。

エミュレータを起動してみる



準備が出来たので、「wfSample.c」にソースを書いて、時間を表示させてみましょう。
下記のソースを「wfSample.c」にコピペして、エミュレータで確認してみましょう。
 
#include <pebble.h>

static Window *s_main_window;
static TextLayer *s_time_layer;

static void update_time() {
  // Get a tm structure
  time_t temp = time(NULL);
  struct tm *tick_time = localtime(&temp);

  // Write the current hours and minutes into a buffer
  static char s_buffer[8];
  strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ?
                                          "%H:%M" : "%I:%M", tick_time);

  // Display this time on the TextLayer
  text_layer_set_text(s_time_layer, s_buffer);
}

static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
  update_time();
}

static void main_window_load(Window *window) {
  // Get information about the Window
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  // Create the TextLayer with specific bounds
  s_time_layer = text_layer_create(
      GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50));

  // Improve the layout to be more like a watchface
  text_layer_set_background_color(s_time_layer, GColorClear);
  text_layer_set_text_color(s_time_layer, GColorBlack);
  text_layer_set_text(s_time_layer, "00:00");
  text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
  text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);

  // Add it as a child layer to the Window's root layer
  layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
}

static void main_window_unload(Window *window) {
  // Destroy TextLayer
  text_layer_destroy(s_time_layer);
}

static void init() {
  // Create main Window element and assign to pointer
  s_main_window = window_create();

  // Set handlers to manage the elements inside the Window
  window_set_window_handlers(s_main_window, (WindowHandlers) {
    .load = main_window_load,
    .unload = main_window_unload
  });

  // Show the Window on the watch, with animated=true
  window_stack_push(s_main_window, true);
  
  // Make sure the time is displayed from the start
  update_time();
  
  // Register with TickTimerService
  tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
}

static void deinit() {
  // Destroy Window
  window_destroy(s_main_window);
}

int main(void) {
  init();
  app_event_loop();
  deinit();
}


ソースを保存したら、右上にある緑色のボタンをクリック。
pebble7
すると、左上にあるエミュレータが起動します。
pebble8

エミュレータ上の端末を変えたい時は、左メニューの「COMPILATION」から設定します。
pebble9
私が持っている実機はPebble Time Steelなので、「BASALT」をクリック。 
※参考
Pebble : Aplite
Pebble 2 : Diorite
Pebble Time, Time Steel : Basalt
Pebble Time Round : CHALK
Pebble Time 2 : Emery(開発中止)
ソースをコピペしてエミュレータで確認すると、下記のような表示になると思います。
pebble10
時間が表示されていますね。
静止画なので伝わりにくいですが、1分おきに表示が更新されます。

ソースの解説

さて、とりあえず時間が表示出来ました。
しかしこの時間、どうやって表示させているのでしょう?
先ほどのソースを解説しながら、処理を追うことにしましょう。

#include <pebble.h>

こちらは説明不要かと思います。pebble.hをincludeすることで、pebble SDKを使用可能にしています。

main関数

こちらも説明はいらなさそうですね。
「wfSample」を実行したときに、このmain関数が呼び出され処理が進みます。

init()関数

main関数が呼び出されると、最初にこのinit()を実行します。このinitで時間の表示を行っています。
s_main_window = window_create();

まず、window_createというものを呼び出して、アプリのメインウィンドウを作成しています。
  window_set_window_handlers(s_main_window, (WindowHandlers) {
    .load = main_window_load,
    .unload = main_window_unload
  });

ここで、画面のイベントハンドラを登録して、画面の作成か破棄かを振り分けているようです。
画面を作成する場合は、main_window_loadを実行し、破棄する場合はmain_window_unloadが実行されます。

main_window_load

画面の内容を作成し、表示する関数です。
簡単に説明すると、

 ・Pebbleの画面サイズに合わせて、画面のレイヤーを作成する
 ・「text_layer_create」でテキストレイヤーを作成
 ・「text_layer_set_~」というメソッドで、背景色、文字色、フォント、表示するテキストの内容を設定
 ・「layer_add_child」で、画面にレイヤーを設定する
…と、画面に表示する内容を一式設定します。
ただし、実際にmain_window_loadが実行されるのは、ハンドラの登録後にある
window_stack_pushという関数です。
テキストレイヤーなどの設定を行った後、window_stack_pushを実行することでPebbleの実画面に反映されます。

update_time

update_timeでは、現在の時刻を取得し、「hh:MM」の書式にした後、先ほど作成したテキストレイヤーに反映させています。
この処理が動かないと、テキストレイヤーの表示は「00:00」のままになってしまうことになります。

tick_timer_service_subscribe

これは、一定間隔で特定の関数を実行させるハンドラです。
第1引数に間隔を設定し、第2引数で実行させる関数を設定します。
ここでは、1分毎(MINUTE_UNIT)に、tick_handlerというものを呼び出すようにしています。

tick_handler

tick_timer_service_subscribeで呼び出される先です。この関数が1分毎に動くことになります。
中身はというと…update_timeを呼び出しているだけですね。
update_timeは前述の通り時間を取得して表示しているだけなので、1分毎に時間を取得して、テキストレイヤーに表示する…ということになります。

以上がinit()での初期化処理です。
ではアプリを終了した時はどういう動作をするのでしょうか?
アプリの終了がハンドリングされたときは、main_window_unloadという関数が動きます。

static void main_window_unload(Window *window) {
  // Destroy TextLayer
  text_layer_destroy(s_time_layer);
}

text_layer_destroyというものを実行しています。これは名前の通りで、テキストレイヤーを削除します。
ちなみにアプリを終了させた時は、main_window_unloadが実行された後、deinit()が呼び出され、アプリが終了します。
static void deinit() {
  // Destroy Window
  window_destroy(s_main_window);
}


initでcreateしたwindowを、deinitでdestroyしていることがわかります。
実はこのdestroyは重要で、createで確保したメモリを開放する役割も持っています。
つまり、ちゃんとdestroyして終了しないと、Pebbleのメモリは大変なことになってしまうので気をつけましょう。
app_event_loop

名前の通り、この部分でアプリの処理をずーと続けるようにしています。

つまり、
mainが動く

init()が動いて画面を生成

app_event_loop()で処理をループさせる

その間1分毎にtick_timer_service_subscribeが実行され、時間の更新がされる

アプリの終了イベントが動いたら、init内でハンドラーとして登録された、main_window_unloadを実行

app_event_loop()を抜けて、deinit()が呼び出され終了

ということです。

背景を変えてみる

時間の表示が出来ましたが、これだけだとなんだか味気ない…
そこで、背景に可愛いキャラクターを表示してみましょう。
キャラクターじゃなくてもなんでも良いのですが、弊社には素敵なキャラクターがいるので表示させてみましょう。
左メニューから「RESOUECES」の右にある「ADD NEW」をクリックし、下記画面を開きます。
pebble11
「FILE NAME」と「IDENTIFIER」に適当な値を設定し、さらに下の方にある「UPLOAD NEW FILE」からファイルをアップロードします。
SAVEすると、各デバイスでのプレビューが表示されるので念のため確認しておきましょう。
さらに、text_layer_set_background_colorという記述をコメントアウトし、下記記述を追加します。
 
  s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_fumi);
  s_bitmap_layer = bitmap_layer_create(bounds);
  bitmap_layer_set_background_color(s_bitmap_layer, GColorVividCerulean);
  bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap);
  bitmap_layer_set_compositing_mode(s_bitmap_layer, GCompOpSet);
  layer_add_child(window_layer, bitmap_layer_get_layer(s_bitmap_layer));

忘れないように変数の定義も追加します。
 
  static GBitmap *s_bitmap;
  static BitmapLayer *s_bitmap_layer;

メモリの開放も忘れないように。main_window_unloadに書きましょう。
 
  bitmap_layer_destroy(s_bitmap_layer);
  gbitmap_destroy(s_bitmap);

これでエミュレータで動かしてみると…
pebble12
追加した画像がWatchFaceに表示されましたね!
でも時計としては微妙なので、日付やバッテリー残量など表示したいですね。
また、今回はデジタル時計でしたが、アナログ時計を作るのも楽しそう。

実機で確認する

実機がある場合はそっちにインストールして確認することが出来ます。
「COMPILATION」から「PHONE」を選択し、しばらく待てばインストールが出来ます。
※スマホ側のPebbleアプリで「Settings→Enable Developer Mode」にチェック。
 その後に、「Developer Connection」のDisabledをConnectedにすればインストールされます。
表示するとこんな感じに。うーん、地味だ。
pebble13

終わりに

Pebble、いかがでしょうか?時計の文字盤をかなり自由に自分で作れるというのは楽しいですね。
C言語での開発がなんとも言えませんが、WatchFaceだけでなくアプリも作れるので、スマートウォッチデビューには最適かと思います。
1万円弱で購入出来るので、興味があれば是非買って遊んでみてください。