Node.js
電子工作
RaspberryPi
Swift

(初心者向けiOSアプリ開発)raspberry pi2+温度センサで部屋の温度を取得する + iOSアプリ上で見られるようにする話

More than 3 years have passed since last update.

前記事( http://qiita.com/CST_negi/items/13ded1e2118d52be7418 )でBASIC認証がかかったコンテンツを見るみたいな話を突然したと思うのですが、なぜかというとこの記事の前提を説明したかったからです。
記事を書こうとした矢先に普通にNode.jsで温度センサから温度を取得するAPIサーバー作ってやったほうがいいでしょということになって、やってみたら普通にできたので、以降はそんな感じの説明をしてゆきます。

やりたかったこと:
・温度センサを使って部屋の温度を取得する
・Apache2でWebサーバが立っているのでそれを介して見られるようにする。
・BASIC認証がかかっているディレクトリ上に部屋の温度を示すファイルを置いてiOS側でそれを参照する。

・Node.jsを使って温度を表すjsonをレスポンスするサーバを作る
・iOSアプリでそこからjsonを取得してパースしてDouble型の温度の値を取得する。

用意するもの
・raspberry pi2(※)
・温度センサ(DS18B20)
・ブレッドボード、抵抗器(1kΩを2個)、ジャンパワイヤ(オスーメス)を3本
・node.js
・DS18B20からのセンサーデータをJavaScriptで扱えるようにするもの(https://github.com/chamerling/ds18b20)

※実際の僕のサーバ周りの環境構築は
http://qiita.com/CST_negi/items/a329cc98fb1aa33f33d3
http://qiita.com/CST_negi/items/13ded1e2118d52be7418
に書いてあります。
簡単に説明すると、僕の環境では、Apache2によってサーバが立っており、一部のコンテンツにはBASIC認証がかかっています。そこに新しくNode.jsによって温度を返すAPIサーバを立てます。


・温度センサを使って部屋の温度を取得する


(参考 http://deviceplus.jp/hobby/raspberrypi_entry_018/)
この方のページにDS18B20を用いた場合のかなり詳しい解説が載っていますので、そちらをなぞっていくと取得することができると思います(※)。

---------------------------以下補足---------------------------
※僕の場合だけかもしれないのですが、このブログにあるような回路では、温度センサが認識されませんでした。
具体的な症状としては
ls /sys/bus/w1/devices/すると
00-008000000b00 00-000000000000 w1_bus_master1
みたいな感じの謎フォルダができていまして、このフォルダにアクセスしても温度は取得できませんでした。

結局これも解決法がちゃんとあって、回路を変えるという解決法です。
(参考 http://blog.livedoor.jp/victory7com/archives/33399310.html )
このページに書いてある回路に変更したら完全に解決しました。

root@raspberrypi ~ $ cd /sys/bus/w1/devices/
root@raspberrypi /sys/bus/w1/devices $ ls
28-00000723bfe0 w1_bus_master1

という感じでめでたく解決できました。本当はプルアップ抵抗のほうが回路が安定しそうですけど、できないなら仕方ないです…。
---------------------------補足終わり---------------------------


・Node.jsを使って温度を表すjsonをレスポンスするサーバを作る


(1) Node.jsを使って外部にページを公開する準備を行う
(1-1) nodebrewをインストール
$ curl -L git.io/nodebrew | perl - setup
でインストール。(これが1時間とか普通にかかる勢いだったので適当に他のことして待ってるとよいです)
インストールが終わると、

========================================
Add path:

export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================

最後にこんなことを言われるので、
.bashrcに
export PATH=$HOME/.nodebrew/current/bin:$PATH
を追記する。
で、source ~/.bashrcで読み込み直すとnodebrewが使えるはず。(この段階ではnode.jsはインストールされていません。)

(1-2) node.jsをインストールする。
$nodebrew ls-remoteで一覧がずらっとでるので、僕はそこから最新版である0.12.7を
$nodebrew install-binary v0.12.7で、インストールしました。

(1-3) 外部公開するページを作ってみる。
これ自体はものすごく簡単です。

example.js
//httpモジュールをインポート
var http = require('http');

//Webサーバーの設定
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(8180);
//ポート番号はお好みで 
console.log('Server running at http://localhost:8180/');

あとはこれをターミナルで
$ node example.js
で、サーバーを立てるだけ。
これだけでlocalhost:8180にアクセスするとHello Worldと書かれたページが帰ってきます。

これを外部公開するには
ルーターの方で、外部からの8180番アクセスをraspberrypiのIPアドレスの8180番アクセスとしてマッピングしてやれば外部公開できるはずです。
(詳しくは、 http://qiita.com/CST_negi/items/a329cc98fb1aa33f33d3
の「DDNSの取得と設定」と「ルータのポートの設定」をみてください)

(1-ex) node.js express4を使う(失敗例)
やろうと思ったのですが、僕がnode.jsに不慣れだったのと資料がまだ少なくてわかりませんでした。(参考: http://yutapon.hatenablog.com/entry/2014/04/29/124657 )
ただ、ちゃんと作るならexpress使った方がいいかもですね。
今回の用途では(1-3)で書いたレベルで事足りたので以降は(1-3)を元に書いていきます。


(2) 温度センサからのデータをレスポンスするサーバを作る
(2-1) ds18b20( https://github.com/chamerling/ds18b20 )の使いかた
ファイル構成は解説に必要なもののみ列挙すると
ds18b20/lib
ds18b20/samples/index.js
ds18b20/index.js
となっています。

まずは/samples/index.jsを見ると、

/samples/index.js
var sensor = require('..');

〜〜(中略)

sensor.sensors(function(err, ids) {
  if (err) {
    console.log('Can not get sensor IDs', err);
  } else {
    console.log(ids);
    for (var id in ids) {
        console.log('Sensor ' + ids[id] + ' :' + sensor.temperatureSync(ids[id]));
    }
  }
});

まず、var sensor = require('..')によってセンサーからのデータを読み取るモジュールを呼び出しています。

それによって
sensor.sensors(function ~~~によって、温度を読みこむことができます。
コードをみてわかる通り、エラーが起きた場合とセンサーが読み込めた場合で場合分けされています。
エラーが出なかった場合は、全てのセンサーを読み込みます。

これで、
root@raspberrypi ~/tmp $ cd ds18b20
root@raspberrypi ~/tmp/ds18b20 $ cd samples/
root@raspberrypi ~/tmp/ds18b20/samples $ node index.js

のように実行すると、温度センサがちゃんと認識されていれば
[ '28-00000723bfe0' ]
[ '28-00000723bfe0' ]
Sensor 28-00000723bfe0 :27.5
Sensor 28-00000723bfe0 : 27.5625

のようにいい感じに温度が取得できるはずです。

---------------------------以下補足---------------------------
※ここが少し注意が必要なところで、本来は require(../lib/ds18b20)としなくてはなりません。
なぜこれが動作しているかというと、 ds18b20/index.jsのおかげで、

ds18b20/index.js
exports = module.exports = require('./lib/ds18b20');

としていて、index.jsのモジュール指定を/lib/ds18b20に参照しているためです。
でも、require('..')ってindex.js呼び出してなくない?って思いませんでしたか?

公式のドキュメント(http://nodejs.jp/nodejs.org_ja/docs/v0.4/api/modules.html#folders_as_Modules )を読むと、
1つ目は、 package.json というファイルをフォルダ直下に作成し、 main モジュールを指定するという方法
ですが、このリポジトリにはpackage.jsonはありますが、mainを指定していなかったのでこのファイルはあまり意味がないものと推測しています。

そうなると、package.jsonがない場合の扱いと同じになるのですが、
例えば、もし上の例で package.json がいるが存在しないとすると、 require('./some-library') は以下のファイルを読み込もうとします:
./some-library/index.js
./some-library/index.node
とあります。
つまり
・require('..')によって上の階層(ds18b20/)を読みこむ
・package.jsonによるmainモジュールの指定がないため、ds18b20/index.jsを読み込む。
・index.jsには「require('./lib/ds18b20')を読みこむ」機能を持ったモジュールが指定されているため、それを読みこむ
・実質的にrequire('..')はrequire('./lib/ds18b20')と同等のモジュール読み込みとなる。
(実際にソースをrequire('../lib/ds18b20')に変更しても同様の動作をするはずです。)

---------------------------補足終わり---------------------------

(2-2) (1)を改造して、温度センサの値をレスポンスするサーバーを構築する
以下のようなコードを書いて実行します。

temprature.js
var HTTP = require('http');
var sensor = require('./ds18b20');

HTTP.createServer(function(req, res){
    var temperature = 0;

    sensor.sensors(function(err, ids) {
    if (err) {
            console.log('Can not get sensor IDs', err);
    } else {
            for (var id in ids) {
                temperature =  sensor.temperatureSync(ids[id]);
        }
        var sendData = new Object();
        sendData.temprature = temperature;
            res.end(JSON.stringify(sendData));
        return;
    }
    })

}).listen(8180);
console.log('Server running http://localhost:8180/')

何をしているかというと、
・まずrequireによってHTTPモジュールと温度センサモジュールを読み込む。
・次にサーバーを立てる
・このサーバーの処理として、センサーデータが読み込まれたら
・温度に関するJSONを文字列化して返す
ということです。
require('./ds18b20')となっていることからお分かりだと思いますが、temprature.jsファイルとds18b20ディレクトリは同じ階層にあります。

実際に
$ node temprature.jsして
8180番ポートにアクセスすると
{"temprature":27.5625}
とだけ書かれたWebページがこんにちはすると思います。

ここまでで、
温度センサを取得して、外部から値を取得できるようにする。
ということはできてると思います。


・iOSアプリでサーバーからjsonを取得してパースしてDouble型の温度の値を取得する。

僕は以下のようなコードを書いています。
(参考:http://qiita.com/tsumekoara/items/7293c54762afeeb10ed5)

例によって公開サーバのURLを http://example.com とします。

ViewController.swift
func ValueChange(){
        var urlString = "http://example.com:8180"
        var url = NSURL(string: urlString)


        var task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler:{data, response, error in
            //jSONを受け取ってパース(※)
            var json = JSON(data: data!)
            var t = json["temprature"]
            var tv = t.asDouble!

            //Double型としてPrintする。
            print(tv)


            //UILabelにこれを書く。
            dispatch_async(dispatch_get_main_queue(), {
                self.tempratureLabel.text = "\(tv)"
            })

        })
            //これを呼ぶと生成したNSURLSessionを終了させる
            task.resume()
    }

JSONを受け取ってパースするのがSwiftだけだと苦行なのでSwiftyJSON使った方が良いという印象です。
(参考:http://dev.classmethod.jp/smartphone/iphone/swiftyjson/ )
(SwiftyJSON:https://github.com/SwiftyJSON/SwiftyJSON )
データを受け取ったら.asDouble!によってDouble型に変換しましょう。

あとはこのDouble型の温度の値をどうするかは自由です。
UIを更新する場合は
dispatch_async(dispatch_get_main_queue(), {
self.tempratureLabel.text = "\(tv)"
})
のようにして、dispatch_asyncの中に書くと安全に処理を行えます。

以上です。
Node.jsを理解するのが大事で、それさえわかってしまえばあとはiOSの方はそんなにコード書くわけでもないですし、たぶんこれが一番楽だと思います。

 
 
Node.jsはあまりいろんなコンテンツを乗っけるよりは、この記事みたいに簡単なレスポンスをする用途の方が向いている印象です。今回は温度のJSONのみを返していますが、湿度とか照度とかそういうのも一緒に返してもいいかもしれません。
iOSの方でそれぞれパースしてその値をそれぞれ、どうのこうのすればいい話ですし、これができると応用がかなり効きそうですね。

今回も長かった。

ちなみに気温の意味を表す英単語はtempratureではなくtemperatureなのでくれぐれも皆さんは間違えないように!