2010年2月25日木曜日

Arduinoで計測した値を指定のwebサーバに送信、保存する

この投稿は、建築発明工作ゼミ2008を参考に、建築発明工作ゼミ2009 『Arduinoで計測した値を指定のwebサーバに送信、保存する』を新しく書き直したものです。
この投稿内容については、上記ブログ管理人のkousakuさんの許可を得ております。

上にのっているのが Ethernet Shield、下にあるのがArduino Duemilanove 328


ArduinoとEthernet Shieldを使用し、計測した温度を指定のwebサーバ上のファイルに記録してみます。
Ethernet Shieldは、Arduinoをネットに接続してデータの送受信を行えるようにするシールドで、上の写真のようにArduinoに装着します。
データのログをとっていく時、通常はPCに常時接続してPCにデータを記録して行きますが、今回はArduinoをスタンドアロンで動かすので常時接続しなくてもよくなります。
この方法の利点は、ネット接続できる環境ならばどこでも取得したデータをほぼリアルタイムで確認できることでしょう。

基本的な流れです。
  1. まずここでやっている回路で温度を計測
  2. ルータに接続したEthernet Shield から、ArduinoのEthernetライブラリを使って指定のサーバにデータを送信
  3. 指定のwebサーバに設置したスクリプトで温度値を受け取り、ファイルに書き込む

回路図


Ethernetライブラリを使用する場合の基本的な入力情報として、
  1. MACアドレス
  2. サーバのIPアドレス
  3. ポート番号
が必要となります。
各値の調べ方はこちらに詳しく明記されています。

加えて、データを保存するwebサーバのIPアドレスが必要です。
それにwebサーバ側で値を受け取ってデータ保存するスクリプトを書く必要があります。
これらは、サーバサイド言語であるPHPを使って実装します。
PHPはサーバ側で動作する言語ですから、データを保存するwebサーバに設置します。

webサーバのディレクトリ構成

http://www.○○○.net/
        + projectsbiotope
        |       + ip.php
        |       + temp_log.php
        + public_data
                + projectsbiotope
                        + text
                                + temp_log.txt


webサーバは私が契約している Yahoo Geocities を使います。
まず、ドキュメントルート直下に、任意のディレクトリを設置します(今回は 'projectsbiotope' という名前にします)。

http://www.○○○.net/projectsbiotope

そして、projectsbiotopeディレクトリ直下に以下のスクリプトを設置します。

データ保存するwebサーバのIPアドレス表示PHPスクリプト(ip.php)

<?php
    $ipAddress = gethostbyname($_SERVER['SERVER_NAME']);
    print $ipAddress;
?>


ブラウザのURL欄に

http://www.○○○.net/projectsbiotope/ip.php

と打ち込めば、IPアドレスがわかります。

Arduinoのスケッチ

/*
  1秒に一回温度を計測し、
  10秒ごとの平均値を取得
  30秒ごとに指定のwebサーバに送信
*/

// Ethernetライブラリ
#include <Ethernet.h>

/* 基本設定 */

// サーバのポート番号を定義
#define PORT 80

// 温度計測のターム(1秒ごとに計測する)
#define MEASURE_TERM 1

// 計測結果出力ターム(10秒ごとに平均摂氏値を出力する)
#define OUTPUT_TERM 10

// 送信のターム(30秒ごとに送信する)
#define LOG_TERM 30

// Ethernet Shild MACアドレス
byte mac[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

// Ethernet Shild IPアドレス
byte ip[] = { 10 , 0 , 0 , 177 };

// 接続先のIPアドレス
byte server[] = { 000 , 00 , 00 , 000 };

// アナログ入力ピン番号
int A_inPin = 0;

// アナログ入力値(0~1023)
int A_val;

// 摂氏値( ℃ )
float tempC = 0;

// 10秒ごとの合計摂氏値( ℃ )
float tempCPlus = 0;

// 10秒ごとの最大摂氏( ℃ )
float maxTempC = 0;

// 10秒ごとの最小摂氏( ℃ )
float minTempC = 0;

// 指定したIPアドレスとポートに接続するクライアントを生成
Client client = Client(server, PORT);

void setup()
{
    // Ethernetライブラリとネットワーク設定を初期化
    Ethernet.begin(mac, ip);

    delay(1000);
}

void loop()
{
    // 1秒に一回、10秒間分温度計測
    for (int i = 0; i < OUTPUT_TERM; i++) {

        // アナログピンから計測値を取得(0~1023)
        A_val = analogRead( A_inPin );

        // 摂氏に換算
        tempC = (100 * A_val) / 1024;

        // 平均値を取得するために1秒ごとの値を10秒分合計しておく
        tempCPlus += tempC;

        // 現for文ループ内で使わなくなった変数は解放
        A_val = 0;
        tempC = 0;

        // 1秒間ストップ
        delay(MEASURE_TERM * 1000);
    }

    // 10秒間の平均摂氏値
    tempC = tempCPlus / OUTPUT_TERM;

    // 指定したwebサーバへの接続開始
    // 成功
    if (client.connect()) {

        // 指定のwebサーバのPHPスクリプトにGET送信
        // 'temp'という名前で計測した温度値を送信
        client.write("GET /projectsbiotope/temp_log.php?temp=");
        client.print(tempC, DEC);
        client.write(" HTTP/1.1\n");
        client.write("HOST: www.○○○.net\n\n");
    
    } else {
        
        // 接続終了
        client.stop();
    }

    // 現loop()ループ内で使わなくなった変数は解放
    tempCPlus = 0;
    tempC = 0;

    // 20秒停止
    delay((LOG_TERM - OUTPUT_TERM) * 1000);
}



// 指定のwebサーバのPHPスクリプトにGET送信
// 'temp'という名前で計測した温度値を送信
client.write("GET /projectsbiotope/temp_log.php?temp=");
client.print(tempC, DEC);
client.write(" HTTP/1.1\n");
client.write("HOST: www.○○○.net\n\n");

の箇所がwebサーバに温度値を送信している記述です。
上記は4列になっていますが、つなげると以下のような文字列を送信していることになります。

GET /projectsbiotope/temp_log.php?temp=○○○(温度値) HTTP/1.1 HOST: www.○○○.net

これは、

http://www.○○○.net/projectsbiotope/temp_log.php

のスクリプトに 'temp' という名前の温度値を送っているということです。
正式には以下のようなリクエストをサーバに投げています。

http://www.○○○.net/projectsbiotope/temp_log.php?temp=○○○(温度値)

注意しなければならないのは、write()メソッドは byte型か char型のデータしか正しく送信しないので、int型の変数値を送信するときは print()メソッドを使うことです。
URLの○○○の箇所に不正な値を書き込まれるとロギングされる値もおかしくなるために、値のチェックもします。
かりに温度値だけではなくて湿度など他の値も送りたいときは、

temp_log.php?temp=○○○(温度値)&humidity=△△△(湿度値)&uv=□□□(紫外線値)

のように、&(アンド)でつなげてゆけばいいのです。


PHPスクリプト(temp_log.php)

<?php

/*** 設定 ***/

    // データ保存ファイルの相対パス
    define('TEMP_FILE_PATH', '../public_data/projectsbiotope/text/temp_log.txt');

    // 限度値下限(0℃まで)
    define('TEMP_LIMIT_MIN', 0);

    // 限度値上限(100℃まで)
    define('TEMP_LIMIT_MAX', 100);

    // 日時情報(YYYY/MM/DD HH:ii:ss形式)
    $strDate = date("Y/m/d H:i:s");

    // 書き込む文字列
    $strLog = '';



/*** 値チェック ***/

    // 'temp'という名前の変数に格納された値を受け取る
    $strTempVal = $_GET['temp'];

    // 条件
    // 一桁の数字もしくは一桁以上の数字から始まって0~1個のドットを含み一桁以上の数字
    // かつ0以上100以下の値のみOK
    if (preg_match("/^\d$|^\d+\.?\d+$/", $strTempVal)
     && $strTempVal >= TEMP_LIMIT_MIN
     && $strTempVal <= TEMP_LIMIT_MAX
    ) {
        // 値をフォーマット(小数点以下の数値を1桁まで切り詰める)
        $strTempVal = number_format($strTempVal, 1);
        
    // 不正な値の場合、0を格納
    } else {
        $strTempVal = 0;
    }



/*** 値を書き込み ***/

    // 書き込み形式
    // 日時情報(YYYY/MM/DD HH:ii:ss) + 温度値(00.0℃) + 改行コード
    $strLog = $strDate . ' | ' . $strTempVal . "℃\n";

    // データを保存するテキストファイルを追記モードでオープン
    $fp = fopen(TEMP_FILE_PATH, "a");

    // ファイルを排他ロック
    flock($fp, LOCK_EX);

    // 送信された値をテキストファイルに書き込み
    fwrite($fp, $strLog);

    // ロックを開放
    flock($fp, LOCK_UN);

    // ファイルポインタをクローズ
    fclose($fp);
?>


セキュリティに配慮して送信される値のチェックを行っています。
データ保存する temp_log.txt は、なければ自動的に作成されます。

上記の準備がすべて終わったら、スケッチをArduino にアップロードしてみてください。

http://www.○○○.net/public_data/projectsbiotope/text/temp_log.txt

に、以下の形式でデータが保存され続けるはずです。

2010/03/01 01:19:32 | 18.0℃
2010/03/01 01:20:33 | 18.0℃
2010/03/01 01:21:32 | 19.5℃
2010/03/01 01:22:33 | 18.0℃
2010/03/01 01:23:33 | 18.0℃
2010/03/01 01:24:33 | 17.0℃
2010/03/01 01:25:33 | 17.7℃
...





5 コメント:

Unknown さんのコメント...

参考にさせてもらっています。
この例の場合PHPのスクリプトにtempを送っていますが
これが.jspや.javaといったものでも同様に送れるのでしょうか?
送れるのならやってみたいのですが・・・

Daisuke En さんのコメント...

duoさん

こんにちは。
JSPは触ったことがないのですが、おそらくできると思います。以下のリンクが参考になるのではないでしょうか。

http://www.fk.urban.ne.jp/home/kishida/kouza/kishou/jsp03.html

.jspのファイルに、HTTPリクエストで test という名前の値を送ると、

<%=request.getParameter("test")%>

で受け取れるみたいですね。
拡張子が.jspでも、サーバで動作する言語ならば値の受け渡しはできるはずなので、
試してみてください。

Unknown さんのコメント...
このコメントは投稿者によって削除されました。
きくやん さんのコメント...

Arduinoのプログラムがコンパイルできないのですが・・・
invalid cast to abstract class type 'Client'
とエラーになります。
バージョンは1.6.12です。
よろしく教えてください。

福が多い さんのコメント...

スケッチをコンパイルするとClient client = Client(server, PORT);
でinvalid cast to abstract class type ("Client")
とエラーになります。
何が問題でしょうか?
よろしくお願いします。

コメントを投稿