0

cloudBitのHTTP APIをNode.jsで使う

littleBitsにはcloudBitという組み込みLinuxが入っててWiFiを積んでるブロックがある。最近日本でも発売された。

Amazon.co.jp | littleBits Cloud Starter Bundle | ホビー 通販


回路

こんな感じで左から
電源→sound trigger→dimmer→cloudBit→LEDやサーボ
のように接続すると、音に反応してcloudBitがwebhookを送ってくれる。また、こっちからHTTP POSTするとcloudBitから出力できて、LEDやサーボを動かしたりもできる。ようするにwebから使えるアナログ入出力ブロックだ。

sound triggerとcloudBitの間にdimmerを挟むと音の反応しきい値が調整しやすい。



tokenとdevice_id取得

littleBits Cloud Controlで新規プロジェクト登録して、tokenとdevice_idを取得する。

export LB_TOKEN=a1b2cdefghjiklasdf
export LB_DEVICE=98d76reb


出力


POSTするとcloudBitから電圧を0〜100で指定して出力できる。一定時間経つと出力は0に戻る。
request = require 'request'

little_out = (percent, duration, callback = ->) ->
request
method: 'POST'
url: "https://api-http.littlebitscloud.cc/devices/#{process.env.LB_DEVICE}/output"
headers:
Authorization: "Bearer #{process.env.LB_TOKEN}"
Accept: 'application/vnd.littlebits.v2+json'
postData:
percent: percent
duration_ms: duration
, callback

# 出力90で3000ミリ秒
little_out 90, 3000, (err, res, body) ->
console.error err
console.log body

littlebits-cloud-httpというnpmがあるけど勝手に標準出力になんか出してきたり、APIがなんか変だし実装も妙にまわりくどかったのでrequestでやった。


webhook設定


curl -i -XPOST \
-H "Authorization: Bearer $LB_TOKEN" \
-H "Accept: application/vnd.littlebits.v2+json" \
-d subscriber_id=http://example.com/littlebits \
-d publisher_id=$LB_DEVICE \
-d publisher_events=amplitude:delta:ignite \
https://api-http.littlebitscloud.cc/subscriptions
http://example.com/littlebitsにwebhookしてくる。subscriber_idに他のcloudBitのdevice_idを指定すると直接通信させる事もできるらしい。

amplitude:delta:igniteは電圧が上がった時に反応する。
BitCloud API Documentation

amplitude –––– when there is any voltage (catch-all, default)
amplitude:delta:sustain –––– when high voltage is constant (eg button being held)
amplitude:delta:ignite –––– when there is significant voltage jump (eg button press)
amplitude:delta:release –––– when there is significant voltage drop (eg button release)
amplitude:delta:nap –––– when low voltage is constant (eg idle bitSnap system)
amplitude:level:active –––– generic, when there is high voltage (eg during a sustain or maybe just ignited)
amplitude:level:idle –––– generic, when there is low voltage (eg during a long nap or maybe just released)



webhook受信


express = require 'express'
bodyParser = require 'body-parser'

router = express()
router.use bodyParser.json()

http = require('http').Server(router)

router.post '/littlebits', (req, res) ->
res.end 'ok'
console.log req.body
percent = req.body.payload.percent
console.log "percent: #{percent}"

http.listen 3000

1秒もかからずwebhookが来る。POSTのbodyはjsonで、こんな感じ
{ type: 'amplitude',
timestamp: 1429543559474,
user_id: 58158,
bit_id: '98d76reb',
payload: { percent: 85, delta: 'ignite' } }


hubotで受信して、部屋のわいわい感をチャットに流すとかできる。


webhook解除


DELETEで解除する
curl -i -XDELETE \
-H "Authorization: Bearer $LB_TOKEN" \
-H "Accept: application/vnd.littlebits.v2+json" \
-d subscriber_id=http://example.com/littlebits \
-d publisher_id=$LB_DEVICE \
https://api-http.littlebitscloud.cc/subscriptions


全デバイスの設定確認
curl -i -XGET \
-H "Authorization: Bearer $LB_TOKEN" \
-H "Accept: application/vnd.littlebits.v2+json" \
https://api-http.littlebitscloud.cc/devices

0

MacMini(2013)のHDDが死んだのでHDDコピーして復活させた

HDDを取り出して新しいHDDに取り替えて、古いHDDをUSB接続して、リカバリディスク(USBメモリ)から起動してディスクユーティリティで丸ごとコピーして復活させた。
OS再インストールにならなくてよかった。

症状

Mac OSX Marvericksは起動するけど、ディスクへの書き込みができない。ファイルを作成しようとするとエラーが起きて失敗する。


新しいHDDを買った

元々MacMiniに入っていたのは東芝の2.5インチ500GB SATA HDDだった。

SSD買うお金がないので、東芝の1TBを買った。
ヨドバシ.com – 東芝 TOSHIBA MQ01ABD100 [2.5インチ 5400rpm SATA 1TB バルクハードディスク ノートパソコン用]【無料配達】

朝注文したら夕方届いた。ヨドバシすごい


SATA-USB変換アダプタを買った

Macminiには1つしかHDDが入らないので、古いHDDを外付けHDD化してコピーするために買った。
もう一つ別のメーカーのも買ったんだけど、そっちはドライバが必要で、後でやるリカバリディスクからのディスクユーティリティでのコピーでは使えなかった。

GREEN HOUSE SATA/IDE-USB2.0変換アダプタ 2.5インチHD対応 GH-USHD-IDESA
グリーンハウス (2007-05-31)
売り上げランキング: 261



HDDの交換

ここが参考になった。
Mac miniのHDDをSSDに変更する方法 | 男子ハック

裏のフタを回し開けて、ファン→WiFiマークの金網→HDDと外して入れ替えた。


リカバリディスク(USBメモリ)を作る


別の無事なMacにUSBメモリをさした状態で
OS X 復元ディスクアシスタント
を起動すれば作れる


リカバリディスクから起動する

Macminiにリカバリディスクと古いHDDを接続して、option(alt)キーを押しながら電源を入れて、ディスクユーティリティを起動する。
先に新しいHDDにパーティションを作ってから、復元する


3時間ぐらい待ったら完了してた。
リカバリディスクと古いHDDをはずして、再起動したらMacmini復活した。

0

CircleCIでAndroidのプロジェクトをビルドする

Androidのtestまだよくわからないのでとりあえずビルドだけする。
Travis CIでは色々細かく設定しなければならないみたいなので、適当にSDKバージョンとか見つけてくれるCircle CIを使う事にした。

JavaとXMLの世界なので、コンパイルが通るかをプルリク・マージの時に自動的にCircleCIがやってくれて結果がGitHub表示されているだけでも安心感がある(という事をテストがあるとプルリクしやすいにも書いた)


手順


手元でビルド

自分のプロジェクトが手元で
./gradlew assembleDebug
でデバッグビルドできるのを確認しておく


Github → Circle CIの連携設定

GitHubの各リポジトリ内の設定から、Webhooks&ServicesでCircle CIを追加する。
githubにpushする毎に自動的にCircle CIにhookが飛ぶ。

Add projects – CircleCIでもプロジェクトを追加する。


circle.yml

リポジトリルートにcircle.ymlを置く。中身はこれだけでいい
test:
override:
- ./gradlew assembleDebug


テストする

githubにpushしたら https://circleci.com/gh/(ユーザ名)/(リポジトリ名) でテストが走ってるのが見える


Status Badge

https://circleci.com/gh/(ユーザ名)/(リポジトリ名).png に画像が生成されるので、README.mdに埋め込んでおく


circle.ymlの書き方

Configuring CircleCI – CircleCIに設定方法が書いてある

VoiceTweetではTwitterのconsumer keyをリポジトリにコミットしたくなかったので、testのpreでサンプルファイルから事前にコピーしてからコンパイルするようにした。
test:
pre:
- cd mobile/src/main/java/org/shokai/voicetweet/ && cp TwitterConfig.java.sample TwitterConfig.java
override:
- ./gradlew assembleDebug

0

DataItemでAndroidWearと本体のデータを共有する

Wearと本体でデータを共有するにはDataItemを使う。

DataItemはkey-valueストアで、1キーに100KBまで保存できる。文字列・数値だけでなく画像などのバイナリデータも保存できて、Bluetoothの帯域を適当に考慮して同期してくれるらしい。
具体的な処理は隠蔽されているがデータはGoogleのサーバーにも保存されているっぽい。
そのためGoogle API Clientとインターネットへの接続が必要。

key-valueは wear://<node_id>/<path> のようなURIの下に保存されるが、node_idは普通意識しないのでpathだけ指定して使う。


保存期限

いつまでデータが生きているのかは、ドキュメントに書かれていないのでわからない。
期限を設定するプロパティが無い事と、DataItemに書き込んでからスマホとWearを両方とも再起動してもデータは残っていたことから、まあずっと残ってるんじゃないかと思う。
SharedPreferenceみたいにも使えそう。

片方が生きてない時にDataItemに書き込んで、もう片方だけ起動したら読めるか?を試そうとしたけどWearはそもそもスマホがないとネットワーク接続できなかったから試せなかった。


使い方

“/testapp”というpathの中で、名前とウェブサイトをWearと本体間で共有する例


書き込み

GoogleApiClientを接続してからkey-valueを保存する
PutDataMapRequest mapReq = PutDataMapRequest.create("/testapp");
mapReq.getDataMap().putString("name", "shokai");
mapReq.getDataMap().putString("url", "http://shokai.org");
Wearable.DataApi.putDataItem(mGoogleApiClient, mapReq.asPutDataRequest());

読み出し(イベント)

Wearable.DataApiにDataApi.DataListenerを登録しておけばデータの変更が通知されてくる。

// Activity自身をlistenerとして登録
Wearable.DataApi.addListener(mGoogleApiClient, this);
// 解除
Wearable.DataApi.removeListener(mGoogleApiClient, this);

public class MainActivity extends Activity implements DataApi.DataListener {

// (略)

@Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
DataItem item = event.getDataItem();
if (item.getUri().getPath().equals("/testapp")) {
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
String name = dataMap.getString("name"); // "shokai"
String url = dataMap.getString("url"); // "http://shokai.org"
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
// 削除イベント
}
}
}

1回書き込んだだけでも、なぜか連続で2回イベントが来る事があった。1回目は古いデータで、2回目は更新されたデータがくる。


読み出し

イベントではなく、現在の値をこっちから読みに行く方法は
Android WearのData Layer APIを試してみた – bati11's diary
に書いてあった。GoogleAPIClientを接続してから
Wearable.DataApi.getDataItems(googleClient)
.setResultCallback(new ResultCallback() {
@Override
public void onResult(DataItemBuffer dataItems) {
String name = null;
String url = null;
for(DataItem dataItem : dataItems){
if(dataItem.getUri().getPath().equals("/testapp")) {
DataMap dataMap = DataMap.fromByteArray(dataItem.getData());
name = dataMap.getString("name");
url = dataMap.getString("url");
}
}
if(name != null && url != null) {
Log.i("userinfo", "found");
}
else{
Log.i("userinfo", "not found");
}
dataItems.release(); // releaseしないとリークするという警告がでる
}
});
この方法で他のアプリケーションのデータも読めてしまうかと思ったけど、読めなかった。よかった。


削除

DataMapまるごとしか削除できない。保存する時はpath指定なのに削除はURI指定しなければならない。

Uriのnode_idは省略できるので、/testappに保存してるDataMapはwear:/testappを指定したら削除できた。
削除するとonDataChangedイベントのTYPE_DELETEDが発火する。

Wearable.DataApi.deleteDataItems(mGoogleApiClient, Uri.parse("wear:/testapp"))
.setResultCallback(new ResultCallback() {
@Override
public void onResult(DataApi.DeleteDataItemsResult deleteDataItemsResult) {
Log.i("deleteDataItems", deleteDataItemsResult.getNumDeleted() + "個削除した");
}
});
}

DataItemにはnullも書き込めるので、DataMapまるごと削除したくない時はnull埋めでもいいかもしれない。

0

Android WearからHTTPリクエストできない

Android Wearから音声入力でツイートできるTwitterクライアントをTwitter4jで作っているんだけど、Android Wearから直接はHTTPリクエストできなかった。

エミュレータからだとホスト名が解決できないし

Unable to resolve host “api.twitter.com”: No address associated with hostname


実機(Moto360)からだとホスト名は解決できてるけど接続ができない。

failed to connect to api.twitter.com/199.16.158.169 (port 443) after 20000ms


Twitter4jじゃなくて普通にHttpURLConnectionを使って他のサーバーにリクエストしても同じ結果になる。


調べてもコレぐらいしか困ってる人がいない。全員Wearから他のサーバーへは接続できなくて未解決
Does Android Wear support HttpURLConnection – getting EOFException – Stack Overflow


ドキュメントにはネットワーク使えないって書いてないけど、無理なものは無理なので次は設計時に気をつける
Creating Wearable Apps | Android Developers

Wearable apps can access much of the standard Android APIs, but don’t support the following APIs:
android.webkit
android.print
android.app.backup
android.appwidget
android.hardware.usb



解決方法

スマホ側をproxyにするしかない。

もともとWearからsendMessageしてスマホ側のWearableListenerServiceで受け取ってtweetを中継していたので、その設計に戻した。
Android Wearからスマホ本体側にメッセージを送る