ラベル Json の投稿を表示しています。 すべての投稿を表示
ラベル Json の投稿を表示しています。 すべての投稿を表示

2016年6月27日月曜日

ArduinoでJSON


javaでJacksonなんかをいじっていると、 "{\"name\" …なんて書いていくのは面倒くさいというか、どうせミスするのがオチなんだよ俺なんかー(血の叫び

と、つい人は不良化してしまいます。

というわけで、また楽な道を探しましたところ、良いライブラリ見つけました。

これ使いやすいですよ。ちょっとバッファを食うので、nano / unoあたりだと大きなデータは作りにくいかもしれないですが、例えばAmbient(thingspeak.comのようなクラウドサービス)に送るデータをserializeする場合なんか、これだけで書けてしまいます。dictionaryもarrayもかなり直感的に書くことができてとてもラクです。

//
//  send to Ambient
//
const int kMaxJsonBuffer = 2048;
StaticJsonBuffer<kMaxJsonBuffer> jsonBuffer;
char jsonPrintBuffer[kMaxJsonBuffer];
bool sendStorageToAmbient() {
  int n = storage.uni.datas.counter;
  JsonObject& root = jsonBuffer.createObject();
  root["writeKey"] = "*****************";    // ambientのapi write key
  JsonArray& dataArray = root.createNestedArray("data");
  for (int i = 0; i < n; i++) {
    OneData_t *pSrc = &storage.uni.datas.blocks[i];
    JsonObject& one = jsonBuffer.createObject();
    one["created"] = pSrc->created;
    one["d1"] = pSrc->temperature;
    one["d2"] = pSrc->humidity;
    one["d3"] = pSrc->pressure;
    one["d4"] = pSrc->airQuality;
    dataArray.add(one);
  }
  root["data"] = dataArray;
  Serial.print("text size = ");
  Serial.println(root.measureLength());
  root.printTo(jsonPrintBuffer, sizeof(jsonPrintBuffer));
  Serial.println(jsonPrintBuffer);
  int sent = ambient.bulk_send(jsonPrintBuffer);
  return true;
}

お勧め。

上の例ではバッファサイズを2048バイトにしているのでArduinoだと一発アウトですが、一度に送るレコード数を制限する等でカバーしてみてください。

なお、写真は特にこの記事とは関係ありませんが、次期モデルのプロトタイプ。LEDの色をちょっくら変えてみました。緑色LEDが明るすぎるので少し抵抗を大きくしないといけないの図。

2015年8月16日日曜日

合計1000円以下のArduino+Etherからサーバへデータを送る:サーバ編

エラーメッセージがベタっと出てきて頭を抱える

■初めに■

前の記事でArduinoなどから簡単に使える400円のEthernetインタフェースを使ってみたもののhttpsに対応していなくて挫折、というネタを書きました。httpsに対応していないと外のサービスは使えないことが多いけど、でも400円でEtherがつながるのはやっぱり捨てがたい。ということで、どこのご家庭にもあるBeagleboneやRaspberry piを中継サーバとし、Arduinoからhttpで送ったデータを一度Beagleboneで受け、あらためてhttpsで外のサービスに送る…という工作をしてみます。

■サーバ側■

ごく単純にRESTを受けるだけのサーバアプリを作ります。

Beagleboneを使いましたが、Raspberry pi / pi2でも同じです。というかnodeが入っていればLinuxはもちろんMacOS Xでも同じ。

参考にしたのはこの辺(ありがとうございます)
http://qiita.com/hoshi-takanori/items/2128a6cf1dbb533379a2
http://yutapon.hatenablog.com/entry/2014/04/29/124657

まず作業ディレクトリを作り、その中に移動します。
mkdir restRelay
cd restRelay
次に以下のソースファイルを作ります。
app.js:
var express    = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.get('/', function (req, res) {
  res.send('Hello, World!');
});
app.post('/weather', function(req, res) {
  console.log(req.body);
  res.send('ok');
});
app.listen(2999);

package.json:
{
  "name": "restRelay",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "body-parser": "^1.12.2",
    "express": "^4.12.3"
  }
}

パッケージをインストールします。
npm install
サーバ起動
node app

まず簡単にブラウザから試してみます。ブラウザに サーバ名:2999 と入力して、以下のような表示が出ればOKです。
JSONのテストは別のコンソールからcurlで。
curl -H "Content-Type: application/json" \-d '{"temperature":30.0, "humidity":52.3, "pressure":1023.4}' \http://localhost:2999/weather
これで「ok」が返ってきて、サーバ側に
{ temperature: 30, humidity: 52.3, pressure: 1023.4 }
が表示されていればOKです。

いやー、簡単ですね。こんなに簡単なRESTサーバ見たことないです。Play! Framework良い勝負。

で。

…こう書くと10分ぐらいで終わったみたいだけど、curlのパラメータを
-d '{temperature:30.0, humidity:52.3, pressure:1023.4}' \
って書いたらsyntax errorが出てしまい、悩みまくりました。
SyntaxError: Unexpected token t
    at Object.parse (native)
    at parse (/root/restRelay/node_modules/body-parser/lib/types/json.js:88:17)
    at /root/restRelay/node_modules/body-parser/lib/read.js:108:18
    at done (/root/restRelay/node_modules/body-parser/node_modules/raw-body/index.js:239:14)
    at IncomingMessage.onEnd (/root/restRelay/node_modules/body-parser/node_modules/raw-body/index.js:285:7)
    at IncomingMessage.g (events.js:180:16)
    at IncomingMessage.emit (events.js:92:17)
    at _stream_readable.js:944:16
    at process._tickCallback (node.js:419:13)
こんな表示だったからライブラリの問題かと思って、バージョンを入れ替えたりnpm installをやり直したり。

結局JSON書式の問題でした。エラーメッセージの「token t」ってのは { の直後のtemperatureのt。普通tokenというとtemperatureって切り出されるのが普通なので、気づくのに時間がかかりました…。

合計1000円以下のArduino+Etherからサーバへデータを送る:Arduino側



今回は1個約400円の激安パチモノArduino nanoと、同じく約350円のEtherアダプタENC28J60をSPIで接続しました。

サーバ側の記事とクライアントの記事(この記事)に分けました。サーバ側についてはこちらの記事をご覧下さい。

最初に注意事項です。ENC28J60は3.3v電源で動作しますが、Arduinoから外部に供給可能な3.3vの電流容量は50mAまでです。ENC28J60は最大で250mA程度消費するとのことなので、ぜんぜん足りません。最初、それに気づかなくて、「なんでEtherのランプが点灯しないんだろ…壊れたか?」と思ってました。

というわけで、9VのACアダプタからArduinoへ電源を供給しましたが、手持ちで250mAを供給できる3.3vレギュレータは秋月で買った超高能率三端子レギュレータ、380円。本体よりも電源の方が高くなって1000円を超えてしまいました。ただ、普通のシリーズレギュレータなら3.3v1Aで100円ぐらいなので…。

電源はACアダプタの9vをブレッドボード手前のレールに接続し、Arduino nanoのVINとGNDに供給しています。同じく手前の9vレールから三端子レギュレータのVin/GNDに接続し、GND/Voutをブレッドボード奥のレールに接続しています。Arduino nanoの5vと3.3v出力はどこにも接続していません。

■接続など■

Arduinoとの接続はSPIです。

ENC28J60ピンArduino nano/unoピン
VCC3.3v電源
GNDGND
CS8
RSTReset(省略可)
SI11
SCK13
SO12

別のArduinoと接続する場合には、SO=MISO, SI=MOSIと読み替えてください。また、前述の通り、3.3vはArduino出力ではなく電源アダプタ〜三端子レギュレータからの出力です。三端子レギュレータの出力側には手持ちの関係で1000μF 35vの電解コンデンサと0.1μFの積層セラミックコンデンサを入れましたが、電解コンデンサは100μF 25v程度で問題ないかと思います。

今回はArduinoに温湿度センサーHDC1000と気圧センサーMPL115A2をI2cで接続しました。

HDC1000(秋月)MPL115A2(秋月)Arduino nano/uno
VCC(1)VDD(1)3.3v
SDA(2)SDA(7)A4
SCL(3)SCL(8)A5
RDY(4)2
GND(5)GND(3)GND
CAP(2)とGNDの間に1μFを入れる

上記HDC1000/MPL115A2のピン番号は秋月版モジュールのピン番号です。HDC1000のRDY端子はArduinoの2につないでいますが、これは今回使用したライブラリのデフォルトが2だからです。HDC1000 mySensor(0x40, 4); の2つめのパラメータを変更すれば他の端子でも使えます。

MPL115A2のCAP端子とGNDの間には1μFのコンデンサを接続します。これを入れないと動作(測定値)がものすごく不安定になります。その辺の経緯はこの辺の記事をご参照くださいませ(涙)。I2cのプルアップ抵抗はHDC1000に入っているので不要です。

■ライブラリ■

まず最初に。各ライブラリ制作者の貢献に敬意と感謝を表します。使用したENC26J60, HDC1000, MPL115A2用ライブラリは下記の通りです。

モジュールライブラリ
HDC1000https://github.com/ftruzzi/HDC1000-Arduino
MPL115A2https://github.com/adafruit/Adafruit_MPL115A2
ENC28J60https://github.com/jcw/ethercard

ENC26J60用としてはいくつか公開されています。一応一通り試してみたのですが、今回は「EtherCard」を用います。機能が多いのにRAMのフットプリントが小さくてArduino nanoでも余裕があるからです。ただ、ライブラリにありがちなことですが、必要最小限の資料しかないので「サンプルは動くけど、自前のコードが動かなくて頭を抱える」パターンでしばらく悩みました。

Adafruitさん、MPL1115A2を秋月で買ったのにライブラリだけ拝借させていただいてすみません。送料がかかるので、もうちょっとショッピングカートに欲しいものが貯まったときに注文します。

HDC1000はググって落として一発で動いたがこれということで他は試していません。

■ソース■

EtherCard収録のサンプルwebClientをベースにhttp postで動くようにしてHDC1000とMPL115の処理を追加したものです。なお、行番号69/70にdhcpSetupを呼び出しています。本来ならばこれはそのあとの#if USE_DHCPの中に入れるべきなのですが、ここで何らかの初期化が行われているようで、非DHCPで使いたい場合もこれを呼び出さないと動いてくれません。接続相手はポートとIPアドレスで指定します。IPアドレスは24〜29あたり、ポートは85のようにether.hisportに指定します。

今回、JSON形式でBeaglebone black上のアプリにPOSTしています。サーバ側についてはこちらの記事をご参照ください。

以下のソースについては、権利も義務もすべて放棄します。ただ、各ライブラリのLicenseについてはご注意ください。特にethercardはGPLです。



■余談■

今回のハマりポイントは以下の通り

  • ethercardのetherPostなどを用いる場合は、call backだけでなくloop上にpacketLoopを忘れないこと。普通call backを入れておけばresponseの処理をしてくれる処理系/ライブラリが多いのですが、このライブラリはpacketLoopで実際の送受信処理を行っているようで、入れないと動きません。
  • 上記の通り、DHCPを使わない場合でもdhcpSetupを呼び出さないと通信できません。
  • 通信相手の指定はあくまでもhisIpとhisport。POSTのパラメータとは関係ありません…って当たり前なんですが、以前使った別のライブラリはGETのパラメータを読み取って自動的に接続してくれていたので、ハマりました。
  • 今回サーバ側がわりとシビアにJSON形式を処理するタイプで、フォーマットが違うとokが返ってこないため、POSTの扱いに問題があるのではないかとしばらく悩みました。結果として正しいJSON形式に変更したら、ちゃんとokが返ってきました。

2015年1月12日月曜日

Play!アプリとBoneScriptの通信 - 3.BoneScript

「BBBとは?」みたいな余計な説明がなくて中身が濃い。

というわけで、Play!アプリとBoneScriptの通信、完結編です。

  1. 構成
  2. Playアプリ
  3. BoneScript

■BoneScript■

以前、BoneScriptで7セグメントLEDを駆動したら、毎秒170桁しかダイナミック点灯させることができずにちらつきました。その際には各セグメントのon/offもループしていたのでおよそ毎秒7x170回はループしていた、ということになります。ダイナミック点灯は無理でも毎秒10回程度のサンプリングなら余裕ですね。8bit時代のBASICマイコンで空のFOR〜NEXTループを回すと250回ぐらいだったかなぁ(遠い目)。

ということで、以下ソースです。


特に目新しいことはしていません(できません)。line 11で100m秒ごとのインターバルを設定してchechPIRを呼び出し、digitalReadのコールバックとしてprintStatusを呼び出しています。

printStatusでは100m秒ごとにサンプリングして入力がHIGHならcountDetectedをインクリメントし、「1分間のうち何秒間、PIRセンサがHIGHを返してくるか(なおPIRセンサは負論理で動きを検出するとLOWになるのですが、トランジスタでレベル変換した時に正論理に変わってます)」を求めています。結果はCSVとしてファイルに書き出し(line no 56 - 62)、JsonとしてWeb APIに送っています(line no 64 - 94)。

その処理自体は簡単なのですが、起動直後の中途半端な「分」を無視したり(prevMinute==99)、「睡眠記録」として作ったので昼の12時でファイル名が切り替わるようにしたりで(line no 44 - 54)、ちょっと面倒になっています。

なお、日付書式の変換には下記で公開されている「formatDate」を使わせていただきました。ありがとうございます。上記ソースを試す場合には末尾にformatDateをコピペしてください。
Qiita : 日付フォーマットなど 日付系処理

■今後の予定■

現状ではセンサーを追加したらDBの定義からやり直さなければいけないですが…それはなんぼ何でもIoTっぽくないので、定義を抽象化するなりシリアライズして「思いついたらすぐセンサーを追加してグラフ化」という路線を目指してみましょうか。

そういえばXivery申し込んだんだけど、返事が来ない…まぁXiveryを使っていたら、Play!でグラフ化するなんてことをやろうとは思わなかったでしょうから、むしろ感謝すべきかなw