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℃
...





2010年2月23日火曜日

mp3データをブログ上で再生する


mp3データをブログ上で再生させる方法。
ものすごく簡単にできてしまいますが、著作権に注意するべし。
引用する場合は、きちんと引用元を明記しましょう。

まず、目的のmp3ファイルを任意のwebサーバにアップします。

例)私の場合 Yahoo Geocities をやっているので、そこにmp3データをアップしたいと思います。
まず、ドキュメントルート直下に "data" というディレクトリを作成します。

http://www.○○○.net/data

そして、この "data" ディレクトリ内に "test.mp3" をアップします。

http://www.○○○.net/data/test.mp3

この時、mp3ファイルを設置する場所は、セキュリティがかかっていないディレクトリに置かなくてはなりません。
(上記のURLをたたくと直接mp3データにアクセスできてしまうので、URLの公開はしません。)
例えば、Yahoo Geocities では、ドキュメントルート直下に "geo_cgi_private" というディレクトリを作成するとセキュリティがかかり、
geo_cgi_privateディレクトリ以下に設置したファイルにはアクセスできません。
ですので、このようなディレクトリ内にmp3ファイルを置いてしまうと音楽が再生されません。

次に、以下のスクリプトをブログに貼り付けます。

<embed
    allowscriptaccess="never"
    bgcolor="#ffffff"
    flashvars="playerMode=embedded"
    height="27"
    quality="best"
    src="http://www.google.com/reader/ui/3247397568-audio-player.swf?audioUrl=○○○"
    type="application/x-shockwave-flash"
    width="400"
    wmode="window">
</embed>


「○○○」の個所に、上記のmp3ファイルへの絶対パスを記述します。
こんな感じになります。

<embed
    allowscriptaccess="never"
    bgcolor="#ffffff"
    flashvars="playerMode=embedded"
    height="27"
    quality="best"
    src="http://www.google.com/reader/ui/3247397568-audio-player.swf?audioUrl=http://www.○○○.net/data/test.mp3"
    type="application/x-shockwave-flash"
    width="400"
    wmode="window">
</embed>


これで、ブログでmp3データが公開できます。




高木正勝タイ・レイ・タイ・リオ【文庫本付き】 / Tai Rei Tei Rio』

2010年2月21日日曜日

Arduinoで湿度・温度を計測する SHT-71使用

この投稿は、建築発明工作ゼミ2009 『温・湿度センサ SHT-71』をもとに、新しく書き直したものです。

図1 温度・湿度センサ SHT-71 データシート

SHTシリーズのセンサをArduinoに接続し、温度と湿度を両方計測してみます。

センサはSHT-71を使いました。SHTシリーズではSHT-11が有名なのですが、小型の基盤にセットしなければ使いづらそうなので、ちょっと値段は高くなりますが4本のピンが並んでいるこちらを使う事にします。
センサとしての性能は全く同じですが、SHT-11とはピンの配置が異なるので注意が必要です。

このセンサの利点はまずその小ささです。センサ部分が4mm×5mmしかありません。
あと、Arduinoへの入力はデジタルインピンを使うので貴重なアナログインピンを節約できます。この小ささで正確な温度と湿度が計測できます。

まずここからライブラリをダウンロードします。 解凍後、「sht1x」というフォルダを以下に設置します。

(Macの場合)
○○○(ユーザディレクトリ)/書類/Arduino/library/

これでライブラリのインストールは完了です。


回路図
VDDからプルアップ抵抗をSCKとDATAに挟まないと正しく計測できません。
SHT-11センサは、参考リンク先のエレキジャックさんがとても詳しく書かれているので、大変参考になります。
SCKピンとDATAピンを差し込む箇所はデジタル2ピン、デジタルD3ピンと決まっているらしいです。
ダウンロードしたライブラリの sht1x_hw.h というファイルのline 26,27 に以下のような記述があります。

# define SHT1X_CLOCK_LINE PD2
# define SHT1X_DATA_LINE PD3

Arduino Duemilanove 168(Atmega168)の場合、'PD2'と'PD3'という定数は読み込まないらしいので、
これを適当なエディタで開き、以下のように書き換えて保存します。

# define SHT1X_CLOCK_LINE PORTD2
# define SHT1X_DATA_LINE PORTD3

これで、Arduino (Atmega328)以降でも読み込むようになります。
ちなみにArduino MEGAの場合、

SCKピン -> デジタル19ピン
DATAピン -> デジタル18ピン

に指す必要があります。

Board sht1x_hw.h line 26,27の記述 ピン番号
Arduino Duemilanove 168 # define SHT1X_CLOCK_LINE PD2
# define SHT1X_DATA_LINE PD3
SCK -> デジタル2ピン
DATA -> デジタル3ピン
Arduino Duemilanove 328 # define SHT1X_CLOCK_LINE PORTD2
# define SHT1X_DATA_LINE PORTD3
SCK -> デジタル2ピン
DATA -> デジタル3ピン
Arduino MEGA # define SHT1X_CLOCK_LINE PORTD2
# define SHT1X_DATA_LINE PORTD3
SCK -> デジタル19ピン
DATA -> デジタル18ピン


SHT-71センサを導線にハンダ付けし、防水用にエキポシ樹脂で固めます。


スケッチ
#include <sht1x.h>
#include <sht1x_hw.h>

void setup()
{
  int timeout = 0;

  Serial.begin(9600);
  delay(11);
  sht1x_init();

  if (sht1x_read_humidity() < 0) {
    Serial.println("soft reset");
    sht1x_reset();
  }
}

void loop()
{
  unsigned int h = sht1x_read_humidity();
  unsigned int t = sht1x_read_temp();

  while (millis() % 1000);

  Serial.print("hum : ");
  Serial.print(-4.0 + 0.0405 * h - 2.8e-6 * h * h, DEC);
  Serial.print(" ");
  Serial.print("temp : ");
  Serial.println(-40.0 + 0.01 * t, DEC);
}


スケッチを実行し、Arduino IDE のシリアルモニタで表示させます。左の値が湿度、右が温度になっています。

追記
Arduino Duemilanove 328だと正常に動かないみたいです。
Atmega168に乗せ変えて、ボードの指定をAtmega168に設定すると動きます。
謎。

Arduino 328、Arduino MEGAでの動かし方は、KOUSAKUさんのアドバイスによりできるようになりました。ありがとうございます。
上記参照。

Arduinoで紫外線を計測する

図1 UVセンサ G5842 データシート

UVセンサ G5842 を使用し、Arduinoで紫外線を計測してみます。
紫外線は太陽光以外ではほとんど反応しません。データシート「短絡電流の直進性」グラフによると、lx(ルクス)による出力電流が pA(ピコアンペア) から uA(マイクロアンペア)です。
つまり、紫外線によって変化する微少電流をオペアンプで電圧変換して、増幅してからArduinoにアナログ入力しなければなりません。
下記回路は、参考書籍に載っている回路をそのまま使いました。


回路図
今回もオペアンプはLMC6032INを使っています。


スケッチ
int val=0; //入力値の変数を用意し、0に設定

void setup(){
  // シリアル通信速度
  Serial.begin(9600);
}

void loop(){

  //ANALOG INの0番ピンを読み取りvalに代入
  val=analogRead(0);
  
  Serial.println(val,DEC);

  //1秒ループにする
  delay(1000);

  val = 0;
}


『センサ活用の素』

オペアンプについてはけっこうわかりやすく書いてあるのでおすすめ。PICで作る事を前提としていますが、Arduinoを使った回路に応用が効く記述が多いです。光・温度・湿度・超音波・ガス・圧力センサの実装回路が載っています。
今回は、紫外線センサの箇所を参考にさせていただきました。