IRKitのセキュリティアップデート

IRKitの脆弱性とファームウェアアップデートのお願いを今更ながら対応。

バージョンが3未満だとアップデートしないといけない。バージョンはAPIを呼び出すとレスポンスヘッダに入っているのでcurlを使って以下のように確認。

$ curl -i "http://192.168.1.1/messages" -H "X-Requested-With: curl"
HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Server: IRKit/2.0.1
Content-Type: text/plain

2.0.1なのでアップデートする。発売直後に買った人などは古いバージョンになっているはず、自分は一度動作がおかしかったときにArduino IDEでファームウェアを更新したので2.0.1になっている。

Electronアプリと思われるIRKit Updater.appをダウンロードして実行。GateKeeperに制限されて起動しなかったので、右クリックから開くで起動。無事3.0.0.0.g85190b1へアップデートできた。

$ curl -i "http://192.168.1.1/messages" -H "X-Requested-With: curl"
HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Server: IRKit/3.0.0.0.g85190b1
Content-Type: text/plain

今回の修正でX-Requested-Withが必要になるなどIRKit Device HTTP APIの仕様が一部変更になったのだが、自作のPebble WatchappであるIRKit RemoteではIRKit Internet HTTP APIを使っているのでファームウェア更新後も問題なく動いた。オプションとしてIRKit Device HTTP APIを使うモードも用意しているけど使っている人はいないと思うので次にWatchappを更新するタイミングで対応しようと思う。

IRKit Remote watchappのPebble Time Round対応

PebbleからIRKitにリモコンコマンドを送信するIRKit Remote watchappをPebble Time Roundに対応して公開した。アプリの検索でirkitで検索すると見つかるはず。まだ暖房をつけずに頑張っているので冬の季節には間に合った。

やったこと

  • Pebble SDK 3.7をインストール
  • PBL_ROUNDの時、ステータスバーの残り容量を表示しない
  • PBL_ROUNDの時、メニューのヘッダを追加しない
  • PBL_ROUNDの時、ローディング表示を中央に
GitHubにソースコードを置いてある
https://github.com/makotokw/pebble-irkit-remote

menu_cell_basic_header_draw

メインのリスト表示はPebble SDKのMenuLayerを使っていたのでPebble SDKが良きに計らってくれる、と思っていた。実際に良きに計らってくれた。

セルの文字列が自動でセンター寄せになって、フォーカスのあるセルが画面の中央に来るようになる。ただし menu_cell_basic_header_draw で描画したヘッダは良きに計らってくれなかた。

微妙な左寄せ。

テキストのCommandsに情報量がないのでPBL_RECTのときだけヘッダをつけるように。

  menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks){
    .get_num_sections = menu_get_num_sections_callback,
    .get_num_rows = menu_get_num_rows_callback,
#ifdef PBL_RECT
    .get_header_height = menu_get_header_height_callback,
    .draw_header = menu_draw_header_callback,
#endif
    .draw_row = menu_draw_row_callback,
    .select_click = menu_select_callback,
  });

ヘッダ、フッタの調整

Pebble Timeからステータスバーを自前で描画していた。(Pebble Time向けのwatchappビルド)

その時の実装が絶対位置指定で残りバッテリーの表示がはみ出てしまうのでこれもPBL_RECTの時だけ表示するように。

またローディング中などの状態表示に使っていた TextLayer も丸型だとはみ出てしまうのでPBL_ROUNDの場合は中央に持ってくるようにした。

丸型UI

Pebble Time Roundの対応、思ったより時間がかかった。丸型UIを舐めていた。もともと既存のPebble向けの実装でも144×168の解像度でハードコーディングするのではなく layer_get_bounds でWindowの領域を取得してUIコントロールをレイアウトしていたのでそれほど修正は必要ないだろうと考えいた。大分甘かった。

Pebble Time Round、仕様上は180×180の解像度になっているけど実際面積は180×180ではない。

スマホのUIだとメニューアイコンなどは四隅に配置しがち。隅でなくても上部のステータスバーや、下部のボトムバーを使ったりする。しかし丸型の場合は四隅がない、そして上部と下部も領域が非常に狭く、中央の小さなスペースにアイコンか文字を出すしか無い。結果的に必要なコントロールは画面中央に持ってこざるを得ない。

今まで丸型のUIをデザインしたことが無かったからそういった難しさをわかっていなかった。シンプルなUIのWatchappだったが、それでもいろいろ勉強になった。

エミュレーター

しかしながら、Pebble SDKのエミュレータは便利すぎる。今ではPebble向けのアプリを作ろうとすると最大3つのプラットフォームに対応する必要がある。

機種 Pebble(Steel) Pebble Time(Steel) Pebble Time Round
プラットフォーム名 aplite basalt chalk
解像度 144×168 144×168 180×180
PBL_RECT PBL_RECT PBL_ROUND
PBL_BW PBL_COLOR PBL_COLOR

考え方としては(PBL_RECT,PBL_ROUND) x (PBL_BW, PBL_COLOR)でPBL_ROUNDxPBL_BW(白黒の丸型)がないマトリクスになる。もう少し細かい機種ごとの違いはHardware Comparisonを参照。

一応、3つのプラットフォームのPebbleを持っているけど1台のスマホから3つのPebbleを管理してデバッグするのは辛い。そこでエミュレータの出番になる。画面が小さいので並べて比較することができて、UIの確認は非常に楽だった。

アプリの公開にアセットが必要

日曜日にビルドしてPebble appstoreにアップロードしたけどなかなか公開されなかった。それもそのはずで、Pebble appstoreではプラットフォームごとにアプリの説明、スクリーンショットを登録しなければならず、それができていないとアップロードしたアプリは公開されない。(Publishボタンが押せない)

iOSの癖で「なかなか審査終わらねーな」と思いつつも待っていたんだけど、「Pebble watchappって審査無いよな?」と気づいたのが今日。前回、Pebble Time(basalt)に対応した時も同じことに遭遇していたのにこの学習能力の無さ。

Pebble Time向けのwatchappビルド

IRKit Remote watchappをPebble Time向けにビルドした。

やったこと

  • HomebrewでSDK3をインストール
  • Pebble Timeプロジェクト変換
  • エミュレータ用に設定ページを変更
  • ステータスバー対応
  • メニューの色を水色に
  • App Storeアイコンの変更

基本的に SDK 3.x Migration Guide を参考にした。

GitHubにソースコードを置いてある
https://github.com/makotokw/pebble-irkit-remote

HomebrewでSDK3をインストール

OS XではPebble SDKをHomebrewでインストールできるようになった。依存関係も解決してくれるので楽になった。

brew install pebble/pebble-sdk/pebble-sdk

Pebble Timeプロジェクト変換

Pebble Time用のプロジェクト変換は以下のコマンドでできる。 appinfo.jsonwscript が変換される。

pebble convert-project

Pebble SDK 3からはPebbleとPebble Time向けの両方のバイナリをビルドしてpbwに両方含めるようになった。Pebble向けの白黒画像やPebble Time向けのカラー画像を同居できる。iOSの Universal Binary の感じ。

エミュレータ用に設定ページを変更

Pebble SDKにエミュレータが入った。PebbleとPebble Timeの両方で確認しないといけなくなったのでエミュレータを活用することにした。エミュレータは以下のようなコマンドで起動できる。

# Pebble Time
pebble install --emulator basalt
# Pebble
pebble install --emulator aplite

エミュレータ用に設定ページを変更する必要があった。実機では設定ページはiOS/Android側のPebbleアプリ内のブラウザで pebblejs://close# に結果を渡せたけどエミュレータの場合は普通に開発PCのブラウザで開くので以下のような技を使う必要があるらしい。
参考: Pebble Smartwatch Emulator in CloudPebble!

 // Get query variables
  function getQueryParam(variable, defaultValue) {
    // Find all URL parameters
    var query = location.search.substring(1);
    var vars = query.split('&');
    for (var i = 0; i < vars.length; i++) {
      var pair = vars[i].split('=');

      // If the query variable parameter is found, decode it to use and return it for use
      if (pair[0] === variable) {
        return decodeURIComponent(pair[1]);
      }
    }
    return defaultValue || false;
  }

  $(document).ready(function () {
    $('#saveButton').click(function () {
      var settings = validateSettings();
      if (settings) {
        var returnTo = getQueryParam('return_to', 'pebblejs://close#');
        document.location = returnTo + encodeURIComponent(JSON.stringify(settings));
      }
    });
  });

ステータスバー対応

Pebble Timeではステータスバーが表示されなくなり、完全なフルスクリーンアプリになった。ステータスバーを表示したい場合は自力で描画する必要がある。
SDK 3.x Migration Guide#Using the Status Bar

しかし自力でステータスバーを描画するとその高さ16を考慮して他のレイヤーを配置しないといけないのでiOS7のステータスバー対応と似たようなことが起きた。

メニューの色を水色に

実際のところPebble Time対応として何もすることがないのでとりあえずハイライトカラーを水色にしてみた。

#ifdef PBL_COLOR
  menu_layer_set_highlight_colors(s_menu_layer, GColorCyan, GColorBlack);
#endif

GColorCyan のような色の定義は Color Picker から選べる。

Pebble

Pebble Time

Pebble TimeではMenu(ListView)の区切り線が表示されなくなった。dividerを描画するAPIは存在するので対応しようかと思ったが、Pebble TimeのTimelineを見ても区切りを配置しないデザインを使っているので特に対応せずPebble Timeのしきたりに合わせることにした。

App Storeアイコンの変更

iOS/Android側のアプリでアイコンが見にくかったのは白黒で背景透過の画像を登録していたせいで、Pebble Timeアプリは背景が黒ベースなので見難くなっていた。そもそもApp Storeに登録するアイコンはPebble側で表示されないので普通にフルカラーのpngで作り直して登録した。

Pebble2IRKit iOS版

Pebble2IRKitがiOSでも使えるようになった。Watchappの状態は以下のように変更していった。

  • Public on Android, Waiting for JavaScript bundling on iOS
  • → Public on Android, Waiting for release of iOS app 2.6.4
  • → Feb 04, 2015

WatchappにJavascriptのコードを埋め込んでいる場合は、Appleの規約を守るためWatchappから抜き出さずにPebbleのiOSアプリに含めて配信するという方法が取られているのでiOSアプリの更新を待たないといけない。

残念ながら、Watchappのステータスの変更はメール通知してくれないようだ。しかしよく考えたら、PebbleのiOSアプリの更新が通知代わりになると思ってiPhoneのApp Storeの更新状態を見てみた。

するとPebbleのiOSアプリ 2.6.4は2/17に公開されていた。Watchappを2/4に公開したあと、すぐにPebbleのiOSアプリが審査に入ってくれたんだけど、どうも最近Appleの審査の期間が長いらしく10日くらい待たされた模様。

というわけでiPhoneユーザの人はPebbleアプリがApp Storeで更新されていたら何か使えるWatchappが増えたかチェックしてみると良いと思う。

Pebble2IRKit公開した

需要があるか考えるなPushせよ。

PebbleからIRKitにメッセージ投げて、IoTができるWatchappをPebbleのAppstoreで公開してみた。
Pebble2IRKit

もともと自分で使える分には夏頃できていたけど、冬になってまたエアコンの季節になったので公開できるようにハードコーディングしていた部分を設定画面などを作って対応した。PebbleとIRKit両方持っている人がどれだけ存在するのかとか、IRKit日本でしか売ってなくてPebbleのAppstoreには地域の概念が無いんだけどとか、いろいろ考えると面倒なので勢いで作って、勢いで公開した。Appstoreに無いアプリはMY PEBBLEのところでスクリーンショットが砂嵐で悲しかったし。

Pebble2IRKitはJavascriptのコードが含んであってiOSの場合は、インターネットで取得したコードを実行してはいけないという規約があってそれを回避するためPebbleアプリ本体にWatchAppのJavascriptコードを含めてアップしているため公開まで時間がかかる。今のところWatchAppは Public on Android, Waiting for JavaScript bundling on iOS という状態になっている。Androidはすぐに公開されたのでXperia Tablet Z3 Compactから使えることを確認した。Pebble2IRKitは Remote カテゴリに入っている。

事前にIRKitに送るメッセージを含めたJsonファイルを作ってもらって、それをインターネットに公開してもらうという設定をしてもらわないといけない。ファイルの作り方はここに書いた。これをDropBoxの公開リンクとかで公開してURLをなんとかコピペして設定画面で入力してもらえば動くはず。

Pebble2IRkitはGitHubにソースコードも置いてある
https://github.com/makotokw/pebble-irkit-remote

Watchappの設定画面はWebページを用意しないといけないので若干敷居が高い。今回は面倒なのでGitHub Pageを使った。設定画面のページもGitHubの同じリポジトリに入れてある。

とりあえず自己満足。

Pebbleでエアコンの電源入れた

暑くなって耐えかねたのでPebbleアプリ開発3日目で外出先からの操作に対応した。前回までのバージョンからはIRKitのDevice HTTP APIからInternet HTTP APIに切り替えただけ。

http://getirkit.com/ にある説明の通りに clientkeydeviceid を取得した。スマホのネイティブアプリ内で動いているからCross-Originの制約にも引っかからないし、エスケープする必要なさそうなデータなので単純にXHRでPOSTするだけでうまくいった。ほとんどタイムラグもなくIRKitに届いているようだ。

var irkit = {
  internetHttpApi: 'https://api.getirkit.com',
  clientKey: '',
  deviceId: ''
};
var xhr = new XMLHttpRequest();
xhr.open('POST', irkit.internetHttpApi + '/1/messages', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('clientkey=' + irkit.clientKey + '&deviceid=' + irkit.deviceId + '&message=' + message);

一つ問題があって ++_JS_LIFECYCLE_++:KILLED みたいなログがでてJS側のプロセスが死んでしまうことがある。作ったアプリにバグがあるのか、プロセス管理で殺されることがあるのか、Javascript側のライフタイムについてちゃんと調べていないので調査と対応が必要かも。

JS側が死んでる場合はPebbleでボタンを押しても何も起きないので、JS側でXHRのPOSTが成功したらそこからPebbleにメッセージを飛ばしてPebbleをブルっとさせるようにした。これで送ったつもりが送れていないという問題を回避する。とりあえずJS側が死んでるときはPebble側のアプリを起動しなおせば良さげ。

まぁ普段はPebbleでは時計を表示して、使う時だけwatchappを起動するような形になるから今のデバッグ中にwatchappを起動しっぱなしにしている方がイレギュラーなのかも。

これで今日は帰宅前にPebbleからエアコンを付けて帰ってちゃんと付いてた。Pebbleたっのしぃ(・∀・)

自分用にはこれで満足しているけど、clientkeydeviceid や赤外線コマンドはJS側にハードコーディングしているのでちゃんとアプリにするには設定画面に逃さないといけない。htmlで作った設定画面をJavascrypt Frameworkで表示して値をPebbleに送ったりとかできるらしいので次回はそれを調べる予定。しかし俺はもう今のバージョンで満足してしまっている。

Pebbleでエアコンの電源入れる

ただしまだ室内からのみ。

Pebbleアプリ製作開始から実装を進めた。前回はJavascript(iPhone)からPebbleへメニューの項目を送ってPebble側でメニュー表示するところまでやった。今回は逆にPebbleでメニューを選択したら、選択したメニューのインデックスをJavascript(iPhone)へメッセージを投げるところを実装した。

Javascriptでは以下のようにイベントハンドラを実装できるんだけど、どうやって受け取ったパラメータを取り出すのか良くわからなかった。

Pebble.addEventListener("appmessage",
  function(e) {
    console.log("Received message: " + e.payload);
  }
);

結局、ドキュメントはWorking with the PebbleKit JavaScript Frameworkしかなくてわかりにくかったんだけど送信する時と仕様は同じようだ。

まずappinfo.jsonにキーと対応する数字を設定して(値はてきとー)

"appKeys": {
  "command": 100
},

app_message_outbox_* APIで数値をキーにした値を送る。

pebble_irkit.c
enum {
    MSG_SEND_COMMAND_KEY = 100
};
void send_selected_command(int index) {
  DictionaryIterator *iter;
  app_message_outbox_begin(&iter);
  if (iter == NULL) {
    return;
  }
  dict_write_uint8(iter, MSG_SEND_COMMAND_KEY, index);
  dict_write_end(iter);
  app_message_outbox_send();
}

んで、Javascript側で受け取る。e.payloadappinfo.jsonで指定した名前でパラメータが入る。

Pebble.addEventListener("appmessage",
  function(e) {
    var commandIndex = e.payload.command;
    postMessageToIrkitByDeviceAPI(irkit.commands[commandIndex].message);
  });

ここから先はIRKitのIRKit Device HTTP APIを使っているだけなんだけど、これがLAN内で使うAPIなので部屋の中からしか使えない。もともと外からはサーバとして使っているRaspberry Piを経由していたのでそれで良かったのだけど今回のPebbleアプリでは公開できるようにクラウド経由で使えるIRKit Internet HTTP APIを使うようにしたいと考えてる。くっそ暑くなってきたので早く家の外から操作できるようにしたい。

しかし、屋内だとリモコンを操作するのと等価なのに楽しくてPebbleから何回もエアコンをつけたり消したりしていた(電気の無駄遣い)。Pebble楽しい。(・∀・)

Pebbleアプリ製作開始

そろそろ本気出す。← 最初から出してください。

PebbleについてはCloudPebbleSimplyjsで少し勉強というか戯れていたけど本格的に自分が欲しいものを作り出すことにした。

結局やりたいことをいろいろ考えるとCで書く必要があって、コンパイルや実機での動作を頻繁に繰り返して動きを確認していきたいタイプなのでCloudPebbleは使わずにローカルに開発環境を構築した。こんなaliasを入れてビルドしまくっている。

~/.aliases
# pebble
alias pb='pebble'
alias pbb='pebble build'
alias pbi='pebble install'

いくつか作りたいものがあるけど、まずはPebbleから家の電化製品を操作したくてIRKitと連携するアプリをつくっている。Javascript側からリストデータを受け取って表示するところまではできた。

あとはボタンを押したらJavascript側にメッセージを送ってIRKitにコマンドを送る。XHRでIRKitにコマンドを送る部分は先に確認していたので自分が使う分だけのバージョンはもうすぐできそう。

PebbleとIRKitの両方を持っている人はあまりいなさそうだけど、設定画面を作って誰でも使えるところまで持って行きたいと思っている。

個人的にはPebble SDKはあまり難しくなさそうだった。リスト表示はiOSのTableViewのような形でCallbackを実装していけばいい。サンプルだとヘッダファイルを作らずソースファイルだけで書いているのでプロトタイプ宣言などCのことを知らない人にとってはハマるのかもしれない。難しくはないがlibcなんでも使えるわけじゃなさそうなので使える関数は制限されていて面倒っちゃ面倒だけどPebbleのような非力な環境での開発だからしょうがない。

IRCaptureをIRKit iOS SDK 2.0に対応した

IRKitで赤外線キャプチャするツールIRCaptureがビルドが通らなくなったり、動かなくなっていたので対応した。

バージョンv1.0.1。IRKit iOS SDKをv2.0.2に上げてビルドが通るようにした。

Frmwareを2.0に上げたせいなのか、IRCaptureからiRKitが発見できなくなっていたが、無事発見されるようになった、赤外線コマンドも取れるようになった。でも、試しにキャプチャしたコマンドを送信しても動かない。よく見てみるとキャプチャした赤外線のbyteデータが全て同じになっている。デバッグしていくとどうもResponseの時点でおかしい。

なので、iOS関係なく IRKit Device HTTP API を使ってもちゃんと取れない。コマンドによってバイト数は変わるが値が常に0。

$ curl -i http://192.168.1.43/messages
HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Server: IRKit/2.0.0.0.g992a4a5
Content-Type: text/plain

{"format":"raw","freq":38,"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}

IRKit本体側の問題のように思えたのでFirmwareの最新版をチェック。するとIRKit Deviceの2.0.1がリリースされていて、Macからの更新方法が書いてあった。久しぶりに Arduino IDE を使った。

Firmwareの更新はGitHubリポジトリにあるREADMEの手順に従ったがそのまま version.template を使うとバージョン番号が __VERSION__ になってわからないので version.c にリネームしたあとバージョンを書いた。

firmware/src/IRKit/version.c
const char version[] = "2.0.1";

どうやら製品版のバージョンにはGitのコミット番号が付いているようだ。

Firmware 2.0.1に上げた所、GET /messages が無事動くようになりました。ふー。IRCaptureは実機(x64)でも動くようになりました。でも赤外線コマンドをメモるにはXcode+シミュレータで動かす方が楽です。

Raspberry Pi、もしくはIRKitと何かを使って目覚ましや通知的なことをしたいと思っている。

Raspberry PiからIRKitを操作する

ようやくRaspberry PiからIRKit Device HTTP APIを使ってIRKitを操作するようにできた。(ほぼChef-SoloでRaspberry Piをセットアップする方に時間を割いていたわけなのだが)

Raspberry PiからIRKit

前回iOSアプリ(ツール)で調べた赤外線信号をRubyのSinatraプロジェクトからjsonでただPOSTするだけのシステム。超適当。

require 'net/http'

# Send to IRKit
get '/:command' do

  irkit_address = '192.168.1.43'
  ir_commands = {
       aircon_off: '{"format":"raw","data":[6881,...],"freq":38}',
       aircon_on: '{"format":"raw","data":[6881,...],"freq":38}'
  }
  command = params[:command].to_sym

  uri = URI.parse("http://#{irkit_address}/messages")
  request = Net::HTTP::Post.new(uri.request_uri, initheader = {'Content-Type' => 'application/json'})
  request.body = ir_commands[command]

  http = Net::HTTP.new(uri.host, uri.port)
  http.start do |h|
    response = h.request(request)
  end

  response.body if response
end

秘密のURLにアクセスするとエアコンを入れたり、切ったりすることができる。二人は同じUSBハブから給電していて一蓮托生。低電力で動くのもこの手のデバイスの魅力。