凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Pebble SDK 2.0 Beta の JavaScript framework で Hello World してみた

はじめに

11 月末に $20 ディスカウントセールをやっていたので Pebble を買ってしまいました。Pebble は Kickstarter 発のスマートウォッチで、スマホBluetooth で連携してアプリが動いたり通知を垂れ流したりしてくれます(ただし日本語は現状は文字化けで豆腐になる)。

Eペーパーのお陰で 5 日間以上電池持ちますし、時計のスタイルも好みのものに切り替え可能で楽しいです。私は Pebblify という通知垂れ流しアプリを使って、Android に届く通知を全部垂れ流すのをメインに使っています。
さて、そんな Pebble ですが開発者としての一番の目玉はやっぱり SDK が提供されていることだと思います。そして先月、Pebble SDK 2.0 beta という 1.x 系から大幅に改良され、互換性がない形で更新が入った SDK が Beta の形で公開されました。

本エントリでは、この Pebble SDK 2.0 Beta で Hello World すること、また新たに導入された JavaScript framework を試してみることを目的に手順をまとめてみました。なお、本エントリの内容を実行される際は自己責任でお願いいたします。

環境

SDK の入手とファームウェアのアップデート

Getting Started があるのでその指示に従っていきます。

1. Pebble App のインストール

まず、Pebble アプリの Beta 版をインストールします。
予め入れてある Pebble はアンインストールし、ANDROID APP のボタンをクリックした apk を Android に入れます。

$ adb install PebbleApp-2.0-BETA4.apk

なお、adb は別途入れておくか、端末からブラウザ経由で apk をインストールして下さい。

2. Pebble SDK のダウンロード

最新版をダウンロードしておきます。

3. ファームウェアのダウンロード

Pebble 本体の Settings > About > Serial からシリアルナンバーを確認し、「a」で始まるか「Q」で始まるかに応じてダウンロードするファームウェアを選択して下さい。

4. ファームウェアのインストール

3 でダウンロードしたファームウェア(pbz ファイル)をブラウザからアクセスできる場所に置き、そこへ Android のブラウザからアクセスします。私は nginx で http サーバを立ち上げて、そこに pbz ファイルを置いてアクセスしました。アクセスできると Pebble アプリが自動で立ち上がり、以下の様な画面が表示されるので OK を押します。


しばらくすると終了し、Pebble 側に 2.0 がインストールされた旨が通知されます。

セットアップ

1. SDK の展開と配置

それぞれの OS でのセットアップ手順を参考にしてセットアップします。私は Mac なので以下を参考にしました。

先ほどダウンロードした SDK を展開、パスを通しておきます。

$ tar xzvf PebbleSDK-2.0-BETA4.tar.gz
$ mv PebbleSDK-2.0-BETA4 PATH_TO_YOUR_SDK_DIR

私は zsh を使っているので、以下のように .zshrc に追記しておきました。

# Pebble SDK
local PEBBLE_PATH="PATH_TO_YOUR_SDK_DIR/PebbleSDK-2.0-BETA4/bin"
export PATH="$PEBBLE_PATH:$PATH"

なお、bin の中には pebble という実行ファイルが入っていますが、中身は python スクリプトPebbleSDK-2.0-BETA4/tools/pebble.py に引数を渡すだけのものになっています。

2. Pebble ARM toolchain の導入

Pebble アプリは C 言語で作られるため、これを Pebble で動くように ARM 向けにビルドする必要があります。この toolchain には gcc などのコンパイラが入っている形になります。これをダウンロード、展開して先ほどの SDKディレクトリの中に放り込みます。

$ tar xzvf arm-cs-tools-macos-universal-static.tar.gz
$ mv arm-cs-tools PATH_TO_YOUR_SDK_DIR/PebbleSDK-2.0-BETA4/.
3. Python のライブラリのインストール

python のパッケージ管理マネージャ pip を利用して PebbleSDK-2.0-BETA4/requirements.txt を元に必要なライブラリをインストールします。

$ sudo easy_install pip
$ pip install --user -r PATH_TO_YOUR_SDK_DIR/PebbleSDK-2.0-BETA4/requirements.txt

なお、このままだとエラーが出ます。

Double requirement given: Pillow>=2.2.1 (from -r /Users/hecomi/Tools/PebbleSDK-2.0-BETA4/requirements.txt (line 2)) (already in Pillow==2.0.0 (from -r /Users/hecomi/Tools/PebbleSDK-2.0-BETA4/requirements.txt (line 1)), name='Pillow')
Storing complete log in /Users/hecomi/Library/Logs/pip.log

これは BETA4 SDK のバグのようです。

SDKディレクトリのルートにある requirements.txt の以下の場所を修正します。

Pillow==2.0.0
# Pillow>=2.2.1 <-- コメントアウト
autobahn==0.5.14
freetype-py==1.0
pyserial>=2.6
sh==1.08
twisted==12.0.0
websocket-client==0.11.0

これで再度 pip コマンドを叩くとインストールが完了します。

4. freetype のインストール

フォントを扱いたい場合は freetype が必要になるので入れておきましょう。

$ brew install freetype

Hello World

ファイルの生成

以下を参考にします。

Pebble SDK 2.0 からは「pebble」コマンドで色々と操作することが出来るようになったようです。
手順に従い以下のコマンドを実行します。

$ pebble new-project hello_world

これで以下のようなファイルが生成されます。

hello_world
├── appinfo.json
├── resources
├── src
│        └── hello_world.c
└── wscript

appinfo.json はアプリのメタ情報が書かれた JSON で、Pebble SDK 2.0 から導入されたようです。resources は画像やフォントを入れるディレクトリ(多分)、wscriptpython 製のビルドシステム waf 用のファイル、そして src/hello_world.c がメインのコードが書いてあるファイルになります。

ビルド

以下のコマンドを実行します。

$ pebble build

これでガーッとビルドが走って完了します。ビルドが完了すると build ディレクトリが生成されて install 可能になります。

インストール

以下のコマンドを実行します。

$ pebble install --phone IP_ADDRESS_OF_YOUR_PHONE

もしくは IP を環境変数に登録しても OK です。

$ export PEBBLE_PHONE=192.168.11.12
$ pebble install

PC とスマホを同一無線 LAN 下につないでおく必要があります。また、Pebble アプリの Pebble Settings > Developer Options > Enable Developer Connection にチェックを入れておく必要があります。IP は Pebble アプリのトップに表示されるようになります((スマホの設定 > 端末の状態 > IP アドレスからも確認できます)。これで Pebble 本体にアプリがインストールされます。

アプリではボタンをハンドルするサンプルになっており、各ボタンを押すとその名称が画面に表示されるものになっています。

サンプルを見る

Hello World が終わったら別のサンプルを見てみます。

PebbleSDK-2.0-BETA4/Examples 下に沢山サンプルが入っています。
同じ手順でビルド・インストール可能です。

pebble コマンドについて

SDK 2.0 からは pebble コマンドで色々と操作できるようになりました。

ログを見たり、スクリーンショットを撮ったりも出来ます。

JavaScript フレームワークについて

ようやくここまで来ました。PebbleKit JavaScript framework は Pebble SDK 2.0 から導入された機能で、XHR や window.localStoragenavigator.geolocation などが利用可能で、ネットアクセスや GPS 周り、スマホの画面、データにアクセスできたりと色々な機能が利用可能です。

また、プラットフォーム依存が無いことも利点です。ただし、JavaScript framework は UI を作る用途ではありません。イメージとしては JavaScriptAndroid 側で動き、C で書いたアプリが時計側で動いていて、JavaScript で XHR とかでサクサクデータを取ってきたものをメッセージのやり取りを介して時計側に伝える形になります。より詳細は上記ガイドをご覧ください。
まずは以下のコマンドで一連のファイルを生成します。

$ pebble new-project --javascript my_js_project

これで以下のようなファイル群が作成されます。

my_js_project
├── appinfo.json
├── resources
├── src
│        ├── js
│        │        └── pebble-js-app.js
│        └── my_js_project.c
└── wscript

新たに js/pebble-js-app.js が追加されています。中身はとてもシンプルで以下の様なコードです。

Pebble.addEventListener("ready",
    function(e) {
        console.log("Hello world! - Sent from your javascript application.");
    }
);

準備ができたら log を吐き出すだけのスクリプトですね。これを上記同様ビルドしてインストールします。

$ pebble build
$ pebble install --phone 192.168.11.12

ログを確認するために logs コマンドを実行してアプリの立ち上げをしてみます。

$ pebble logs --phone 192.168.11.12
[INFO    ] Enabling application logging...
[INFO    ] Displaying logs ... Ctrl-C to interrupt.
[INFO    ] I app_manager.c:134 Heap Usage for <my_js_project>: Available <23116B> Used <208B> Still allocated <0B>
[INFO    ] {'runhost client uuid' = 00000000-0000-0000-0000-000000000000}:{'webapp uuid' = b3e05e0c-ed98-4a97-bed7-ff645c5ee403}: ++_JS_LIFECYCLE_++:KILLED
[INFO    ] D my_js_project.c:56 Done initializing, pushed window: 0x2001a5b8
[INFO    ] {'runhost client uuid' = 00000000-0000-0000-0000-000000000000}:{'webapp uuid' = b3e05e0c-ed98-4a97-bed7-ff645c5ee403}: ++_JS_LIFECYCLE_++:LAUNCHING
[INFO    ] my_js_project__1/pebble-js-app.js:3 Hello world! - Sent from your javascript application.
[INFO    ] {'runhost client uuid' = 027faf5c-2ab0-4a6a-9253-ec68f010a833}:{'webapp uuid' = b3e05e0c-ed98-4a97-bed7-ff645c5ee403}: ++_JS_LIFECYCLE_++:READY-RUNNING

ダーッとログが流れます。その中に先ほど Hello world! を書いた console.log からの出力が見て取れると思います(上だと下から2番目の [INFO])。インストールと同時にアプリが立ち上がるので、そのタイミングでログを見ることも出来ます。

$ pebble install --phone 192.168.11.12 --logs

なお、動作しているアプリは先程の Hello world と同じく、キーの提示をしてくれるものです。理解を深めるためにこれをちょっと改造してみます。

Pebble.addEventListener("ready", function(e) {
	Pebble.showSimpleNotificationOnPebble("hoge", "hogehoge!!");
});

JS をこんなコードにしてみます。すると Pebble 側は以下のようになります。

通知がきました。さて、後は JavaScript と Pebble でメッセージの交換をしてみます。メッセージの交換は以下に詳しく紹介されています。

図を見るとよくわかると思うのですが、お互いに App Message を送り合って、OK なら ACK、ダメなら NACK で通知されるという仕組みです。このあたりは Examples/pebblekit-js/quoates/src/quoates.c を読むと書いてあります。まずは、先ほど作成した my_js_project の src/my_js_project.c を改造して、スマホ --> Pebble (JS --> C)のメッセージのやり取りを書いてみます。
まずは C 側でメッセージの受信部分を書いてみます。

my_js_project.c
...
// Pebble でメッセージ受信(成功)
void in_received_handler(DictionaryIterator *received, void *context) {
	APP_LOG(APP_LOG_LEVEL_DEBUG, "in received handler");
}

// Pebble でメッセージ受信(失敗)
void in_dropped_handler(AppMessageResult reason, void *context) {
	APP_LOG(APP_LOG_LEVEL_DEBUG, "in dropped handler");
}

static void init(void) {
	...
	// 追加
	app_message_register_inbox_received(in_received_handler);
	app_message_register_inbox_dropped(in_dropped_handler);

	const uint32_t inbound_size  = 64;
	const uint32_t outbound_size = 64;
	app_message_open(inbound_size, outbound_size);
	...
}
...

メッセージが来た時のハンドラをセットしています。app_message_open は以下をご覧ください。

次に、JS 側でメッセージの送信部分を書きます。

pebble-js-app.js
Pebble.addEventListener("ready", function(e) {
	console.log("Send App Message");
	Pebble.sendAppMessage({ "1": "String value" });
});

これでログを見ると以下のようになります。

$ pebble install --phone 192.168.11.12 --logs
[INFO    ] Enabling application logging...
[INFO    ] I app_manager.c:134 Heap Usage for <my_js_project>: Available <23116B> Used <208B> Still allocated <0B>
[INFO    ] {'runhost client uuid' = 00000000-0000-0000-0000-000000000000}:{'webapp uuid' = b3e05e0c-ed98-4a97-bed7-ff645c5ee403}: ++_JS_LIFECYCLE_++:KILLED
[INFO    ] Installation successful
[INFO    ] D my_js_project.c:87 Done initializing, pushed window: 0x2001a678
[INFO    ] {'runhost client uuid' = 00000000-0000-0000-0000-000000000000}:{'webapp uuid' = b3e05e0c-ed98-4a97-bed7-ff645c5ee403}: ++_JS_LIFECYCLE_++:LAUNCHING
[INFO    ] my_js_project__1/pebble-js-app.js:7 Send App Message
[INFO    ] {'runhost client uuid' = 371365f5-ecc3-44f3-9285-dc88f8faa9c1}:{'webapp uuid' = b3e05e0c-ed98-4a97-bed7-ff645c5ee403}: ++_JS_LIFECYCLE_++:READY-RUNNING
[INFO    ] D my_js_project.c:50 in received handler
[INFO    ] Displaying logs ... Ctrl-C to interrupt.

しっかり呼び出し側も呼び出され側もログが出ています。では次に、この送ったメッセージを取り出してみます。

void in_received_handler(DictionaryIterator *received, void *context) {
	Tuple *tuple = dict_find(received, 1);
	if (tuple) {
		APP_LOG(APP_LOG_LEVEL_DEBUG, "Text: %s", tuple->value->cstring);
	}
}

dict_find はそのキーが存在するかどうか調べています。

JS では { 1 : "String Value" } を送っていたので、ここでは「1」が存在するかどうか調べています(1 はベタ書きでなくて enum で名前をつけておくほうが好ましいです。サンプルもそうなってます)。なお、この Tuple 構造体の value は共用体になっています。

typedef struct __attribute__((__packed__)) {
  uint32_t  key;
  TupleType type:8;
  uint16_t  length;
  union {
    uint8_t  data[0];
    char     cstring[0];
    uint8_t  uint8;
    uint16_t uint16;
    uint32_t uint32;
    int8_t   int8;
    int16_t  int16;
    int32_t  int32;
  } value[];
} Tuple;

複数のメッセージを同時に送りたい場合については while で iterator を回せば良いのですが、そちらについては公式の説明をご覧ください。
では逆に Pebble 側からスマホ側(C から JS)にメッセージを送ってみます。

my_js_project.c
...

// 中央ボタンを押した時にメッセージを押すことにする
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
	text_layer_set_text(text_layer, "Select");

	Tuplet tuple1 = TupletCString(0, "Hoge");
	Tuplet tuple2 = TupletCString(1, "Fuga");
	Tuplet tuple3 = TupletCString(2, "Piyo");

	DictionaryIterator *iter;
	app_message_outbox_begin(&iter);
	if (iter == NULL) {
		return;
	}

	dict_write_tuplet(iter, &tuple1);
	dict_write_tuplet(iter, &tuple2);
	dict_write_tuplet(iter, &tuple3);
	dict_write_end(iter);

	app_message_outbox_send();
}

...

// Pebble からメッセージ送信(成功)
void out_sent_handler(DictionaryIterator *sent, void *context) {
	APP_LOG(APP_LOG_LEVEL_DEBUG, "out sent handler");
}

// Pebble からメッセージ送信(失敗)
void out_failed_handler(DictionaryIterator *failed, AppMessageResult reason, void *context) {
	APP_LOG(APP_LOG_LEVEL_DEBUG, "out failed handler");
}

static void init(void) {
	...
	// outbox も追加
	app_message_register_inbox_received(in_received_handler);
	app_message_register_inbox_dropped(in_dropped_handler);
	app_message_register_outbox_sent(out_sent_handler);
	app_message_register_outbox_failed(out_failed_handler);

	const uint32_t inbound_size  = 64;
	const uint32_t outbound_size = 64;
	app_message_open(inbound_size, outbound_size);
	...
}

これを JS 側で受信します。

Pebble.addEventListener("appmessage", function(e) {
	console.log("Received App Message");
	for (var x in e.payload) {
		console.log(e.payload[x]);
	}
});

ログを吐き出してみると以下のようになります。

[INFO    ] my_js_project__1/pebble-js-app.js:2 Received App Message
[INFO    ] my_js_project__1/pebble-js-app.js:4 Fuga
[INFO    ] my_js_project__1/pebble-js-app.js:4 Piyo
[INFO    ] my_js_project__1/pebble-js-app.js:4 Hoge
[INFO    ] D my_js_project.c:57 out sent handler

出ました!

おまけ

pebble のコードを見ていて1箇所良くわからない文法がありました。

window_set_window_handlers(window, (WindowHandlers) {
	.load = window_load,
	.unload = window_unload,
});

これは、構造体の初期化を gcc の拡張文法で行い、それを WindowHandlers にキャストしている形のようです。

なお、WindowHandlers は以下の様に宣言されています。

typedef void (*WindowHandler)(struct Window *window);

typedef struct WindowHandlers {
  WindowHandler load;
  WindowHandler appear;
  WindowHandler disappear;
  WindowHandler unload;
} WindowHandlers;

おわりに

色々なものが JavaScript で動かせるようになってきて面白いですね。Pebble もさすがに端末で JS 動かすのは難しそうですが、今回のような形でスマホ側で動く JS と連動するみたいな形式でも結構面白いです。C で書くのは色々とツラミもあるので、最終的には JS だけで簡単に色々書けるようになると楽しそうです。