雑文発散

«前の日記(2015-06-11) 最新 次の日記(2015-06-13)» 編集
過去の日記

2015-06-12 [長年日記]

[Pebble] そろそろ Pebble の開発についても体験しておこうと思ってチュートリアルを学んでみる #4

昨日の日記の続き。今日は Pebble から「Pebble とリンクしたスマートフォン」を通じて Web API から情報取得する部分を作っていくところになる。

Web から取得した情報を追加する

チュートリアルの Part3PebbleKit JS を使って、Web 上のコンテンツから取得した情報を Pebble アプリ内に表示するチュートリアルになる。

Watchface アプリのレイアウト定義をしていく

このチュートリアルでは、JavaScript で OpenWeatherMap から現在の天気と気温を取得して表示していく。

まずは、JavaScript で情報を書き換える場所づくりとして、テキストレイヤーを追加していく。これは、時刻表示のときと同じ流れなので分かりやすい。

// Create temperature Layer
s_weather_layer = text_layer_create(GRect(0, 130, 144, 25));
text_layer_set_background_color(s_weather_layer, GColorClear);
text_layer_set_text_color(s_weather_layer, GColorWhite);
text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter);
text_layer_set_text(s_weather_layer, "Loading...");

ちょっと違うのは、初期状態で表示するテキストを「Loading...」にしているところかな。JavaScript で情報取得して書き換える前には、こういった表示にしておくほうがユーザにやさしいということだろう。

それから同じフォントでも違うフォントサイズを利用するには、appinfo.json で改めて定義が必要なようだ。

{
  "type": "font",
  "name": "FONT_PERFECT_DOS_48",
  "file": "fonts/perfect-dos-vga.ttf",
  "compatibility": "2.7"
},
{
  "type": "font",
  "name": "FONT_PERFECT_DOS_20",
  "file": "fonts/perfect-dos-vga.ttf",
  "compatibility": "2.7"
},

ここでチュートリアルの説明にちょっとワナがある。

チュートリアル Part2 では、フォントファイルの指定を "file": "fonts/perfect-dos-vga.ttf" のように fonts ディレクトリ以下にファイルがあるように書いているのに、チュートリアル Part3 では "file":"perfect-dos-vga.ttf" とディレクトリが存在していない。

上記の JSON は Part2 に合わせてディレクトリが入った状態で書いている。

その他、いつもの unload 処理を追加した後で、build & install すると、このような Loading 表示が出た。

Pebble app

ちなみにフォントサイズの指定は「_48」とか「_20」の部分になるようだ。どういうルールでそうなっているのかは、うまく把握できていないのだけど、「_20」の部分を「_10」に書き換えて build & install してみたら、小さいフォントで表示されたので、今はそういうものだと思っておこう。

AppMessage を使うための定義を作る

Pebble および Pebble と接続しているスマートフォンとの間の通信は、AppMessage API を使って行われるとのこと。

AppMessage の使い方の概要は次のようになるらしい。

  1. AppMessage 用のコールバック関数を作成する
  2. コールバック関数をシステムへ登録する
  3. アプリケーションで通信を始めるために AppMessage をオープンする

この手順で開始されて実際にメッセージが届くと AppMessageInboxReceived コールバックが呼ばれるそうだ。コールバック関数は、次の4つが紹介されている。カッコ内の関数名はチュートリアルに書かれていた名前であり、これに固定されている訳ではない。

  • 正常系: メッセージが届いたとき( inbox_received_callback()
  • 異常系: メッセージをロストしたとき( inbox_dropped_callback()
  • 正常系: メッセージ送信に成功したとき( outbox_failed_callback()
  • 異常系: メッセージ送信に失敗したとき( outbox_sent_callback()

コールバック関数を定義したら、それを init() の中でシステムへ登録する。

// Register callbacks
app_message_register_inbox_received(inbox_received_callback);
app_message_register_inbox_dropped(inbox_dropped_callback);
app_message_register_outbox_failed(outbox_failed_callback);
app_message_register_outbox_sent(outbox_sent_callback);

AppMessage を ON にするためには「app_message_open()」を実行してやる必要がある。

// Open AppMessage
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());

これを実行するのは、コールバック関数を定義した後に行なうのがベストプラクティスだとチュートリアルには書いてある。この順番で実行することで、メッセージの取りこぼしが無くなるみたいだ。

それから、Pebble とスマートフォンの間でデータのやりとりをするためのキーとして、次の定義をしておく。

#define KEY_TEMPERATURE 0
#define KEY_CONDITIONS 1

AppMessage でのメッセージのやりとりのキーは数値のようなのだが、数字でのアクセスだとマジックナンバーになってしまうし、何よりコードがリーダブルでは無くなるので、こうしておくのが普通だそうだ。更に複雑になった場合には enum を利用して整理する方法もあるよと紹介されている。

PebbleKit JS の準備

PebbleKit JS は、src/js/pebble-js-app.js ファイルに記述していく。どうもこのひとつのファイルに全てを記述していくようだ。

最小のひな形としてサンプルが掲載されている。そのインデントが好みではないので、自分ではこのようにした。最小構成では readyappmessage のイベントを LISTEN するだけのものだ。

/*global Pebble */
Pebble.addEventListener('ready', function(e) {
  console.log('PebbleKit JS ready !');
});

Pebble.addEventListener('appmessage', function(e) {
  console.log('AppMessage received !');
});

ここまでできたら、また build & install を実行する。そして console.log() の出力を見てみようというのだ。まず、インストール時に console.log() の実行結果が表示された。

% pebble install --phone 192.168.1.78
[INFO    ] Installation successful
[INFO    ] JS: starting app: 0000-0000-0000-0000-0000000000000 Tutorial1
[INFO    ] app is ready: 1
[INFO    ] JS: Tutorial1: PebbleKit JS ready !

それから、ログを見る方法として pebble logs コマンドが紹介されている。log じゃなくて logs ね。これを実行すると、ログの流し見ができるようになる。

% pebble logs --phone 192.168.1.78
[INFO    ] Enabling application logging...
[INFO    ] Displaying logs ... Ctrl-C to interrupt.
[INFO    ] I ocess_manager.c:297 Heap Usage for App <Tutorial1>: Total Size <22352B> Used <6536B> Still allocated <40B>
[INFO    ] JS: stopping app: 0000-0000-0000-0000-0000000000000 Tutorial1
[INFO    ] JS: starting app: 0000-0000-0000-0000-0000000000000 Tutorial1
[INFO    ] app is ready: 1
[INFO    ] JS: Tutorial1: PebbleKit JS ready !

これは既に起動していた Tutorial1 の Watchface を別の Watchface に切り替えてから、もう一度 Tutorial1 へ戻したときの様子。一度 Tutorial1 がストップし別の Watchface が起動(こっちは特にログを吐き出していない)、そして Tutorial1 が再び起動して、console.log() の内容が表示された。

つまり、Watchface アプリの起動時に pebble-js-app.js が読み込まれ、イベントリスナーに登録した ready イベントが発火されたという訳だ。

この logs サブコマンドでは、PebbleKit JS の console.log() だけでなく、C 言語での APP_LOG の内容も出力してくれるそうだ。チュートリアルにも「logs はデバッグに超便利だよ!」って書かれている。プリントデバッグ万歳!

天気の情報を取得する

OpenWeatherMap.org から天気情報を取得して、Watchface アプリに表示させるステップが説明されている。チュートリアルの説明そのままだけど、こんな手順になるそうだ。

  1. スマートフォンに対してユーザの位置情報をリクエスト
  2. 取得した位置情報を用いて OpenWeatherMap.org へ天気情報をリクエスト(XMLHttpRequest を使用する)
  3. 取得した天気情報を Wachface アプリへ送信する

この手順は JavaScript の範疇になる。コードを追加していくのは、先ほどと同じ src/js/pebble-js-app.js だ。

その前に、位置情報を取得するためには appinfo.jsoncapabillities の定義が必要になるそうなので記述する。

"capabillities": [
  "location"
],

JSON 内のどのレベルに書くのか?と思ったのだけど、他のフィールドの配下ではなくトップレベル(という言い方で良いのだろうか?)に書けばいいようだ。

そして改めて JavaScript へ戻る。先ほど作った ready イベントのリスナーに getWeather() の呼び出しを追加する。

Pebble.addEventListener('ready', function(e) {
  console.log('PebbleKit JS ready !');
  getWeather();
});

getWeather() の中身はこうなっている。

function locationSuccess(pos) {
  // We will request the weather here
}

function locationError(err) {
  console.log('Error requesting location!');
}

function getWeather() {
  navigator.geolocation.getCurrentPosition(
    locationSuccess,
    locationError,
    {timeout: 15000, maximumAge: 60000}
  );
}

まず getWeather() 内で navigator.geolocation.getCurrentPosition() を実行して位置情報を取得する。これは Pebble の独自メソッドではなく、標準の Geolocation API の書式だ。

Pebble がスマートフォンと接続している状態であれば、この API を使ってスマートフォンが持っている位置情報をこの JavaScript で取得できるってことみたいだ。

位置情報が取得できたら、locationSuccess() が呼ばれ、失敗したら locationError() が呼ばれる。locationSuccess() には位置情報のオブジェクトが渡される。

次はこの locationSuccess() の中身を実装していく。

function locationSuccess(pos) {
  var url = 'http://api.openweathermap.org/data/2.5/weather?lat='
          + pos.coords.latitude
          + '&lon='
          + pos.coords.longitude;

  xhrRequest(url, 'GET', function(responseText) {
    var json = JSON.parse(responseText);

    var temperature = Math.round(json.main.temp - 273.15);
    console.log('Temperature is ' + temperature);

    var conditions = json.weather[0].main;
    console.log('Conditions are ' + conditions);

  });
}

xhrRequest() はこのチュートリアルで独自に定義した XMLHttpRequest の処理(この日記には書いてないので、本家チュートリアルを参照すべし)だ。ここでは、getCurrentPosition() で取得した位置情報を使って OpenWeatherMap.org の API 呼び出し用の URL を作り、それを XMLHttpRequest(つまり Ajax)で呼び出している。レスポンスが返ってきたら、その中身を JSON にパースして、console.log() へ吐き出している。

ここまで作って build & install したら次のようなログが出てきた。

% pebble install --phone 192.168.1.78
[INFO    ] I ocess_manager.c:297 Heap Usage for App <Tutorial1>: Total Size <22352B> Used <6536B> Still allocated <40B>
[INFO    ] JS: starting app: 0000-0000-0000-0000-0000000000000 Tutorial1
[INFO    ] Installation successful
[INFO    ] JS: starting app: 0000-0000-0000-0000-0000000000000 Tutorial1
[INFO    ] app is ready: 1
[INFO    ] JS: Tutorial1: PebbleKit JS ready !
[INFO    ] JS: Tutorial1: Temperature is 18
[INFO    ] JS: Tutorial1: Conditions are Rain

気温が 18 度で、天気は雨だ。このとき OpenWeatherMap の Web サイトで東京の天気を見てみたら、18度で雨となっているので、正しく情報が取得できたようだ(これを書いていたときには東京にいた)。

Pebble app

OpenWeatherMap からのレスポンスのサンプルが、このチュートリアルの Gist で公開されている。日の出・日の入りや気圧とかも取得できるみたいだね。

天気を Pebble に表示する

JavaScript での最後のステップと書かれている。このステップで、JavaScript から Pebble アプリへのデータ送信のやり方が分かるはず。

まず、AppMessage で使うキーを appinfo.json に定義する。

"appKeys": {
  "KEY_TEMPERATURE": 0,
  "KEY_CONDITIONS": 1
}

これは最初のほうで Tutorial1.c へ定義した、この部分と一致させるってことだろうな。

#define KEY_TEMPERATURE 0
#define KEY_CONDITIONS 1

次に AppMessage 送信の本体部分を実装していく。その本体はこのような形で Pebble.sendAppMessage() を使う。temperatureconditions には、それぞれ温度と天気の値が入っているものとする。

var dictionary = {
  'KEY_TEMPERATURE': temperature,
  'KEY_CONDITIONS': conditions
};

Pebble.sendAppMessage(dictionary,
  function(e) {
    console.log('Weather info sent to Pebble successfully!');
  },
  function(e) {
    console.log('Error sending weather info to Pebble!');
  }
 );

それから、appmessage のイベントリスナーにも getWeather() を仕込んでおけとのこと。これは後から実装する「情報のアップデート」に必要な仕込みらしい。

あともうちょい。

今度は Tutorial1.c の修正。inbox_received_callback() の中を実装することになる。AppMessage で送信されたキーごとに文字列を作成し、TextLayer へ出力する流れ。

static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
  Tuple *t = dict_read_first(iterator);

  while (t != NULL) {
    switch (t->key) {
    case KEY_TEMERATURE:
      snprintf(temperature_buffer, sizeof(temperature_buffer), "%dC", (int)t->value->int32);
      break;
    case KEY_CONDITIONS:
      snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", t->value->cstring);
      break;
    default:
      APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
      break;
    }

    t = dict_read_next(iterator);
  }

  snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", temperature_buffer, conditions_buffer);
  text_layer_set_text(s_weather_layer, weather_layer_buffer);
}

最後に「一定時間で天気情報をアップデートする」という処理を tick_handler() の中に記述する。tick_handler() は「1分ごとに時計表示を書き換える」ところで使っていたルーチン。ここに「30分ごとに天気情報を取得して AppMessage を送信する」という処理を追加する。

if (tick_time->tm_min % 30 == 0) {
  DictionaryIterator *iter;
  app_message_outbox_begin(&iter);

  dict_write_uint8(iter, 0, 0);

  app_message_outbox_send();
}

ここまで書いて build & install をすると、Watchface に現在位置の天気情報が表示される。さっきログに出てきたように、「18C Rain」と表示された。

Pebble app

これでチュートリアルの全てが終了。なんとなく全体像は把握できたかな。でも JavaScript 部分はともかく、C 言語部分はだいぶ模写しただけ感があるので、もうちょっと知識を得ねばという感じだな。

なにはともあれ、自分で作った画面が Pebble 上に表示されるのは楽しいね、チュートリアルとは言え。