2017年4月3日月曜日

FlashAir+Arduino+Raspberry Piで温湿度計(3):Arduino から Raspberry Pi に測定値を送る

[This report is also available in English.]

もくじ
  1. 準備〜センサから値を読む
  2. iSDIOを使いながらSDカードを読む
  3. Arduino から Raspberry Pi に測定値を送る
  4. Raspberry Pi で見える化する〜完成

前回までで Arduino から FlashAir を使う準備が整いました。
今回はついに FlashAir から Raspberry Pi にデータを送ります。
今回作成したソースコードは Github に置いてあります。

Arduino と FlashAir を接続する

図のように接続します。


SD カードの各ピンの意味は次の通りです。
今回はチップセレクトを4番ピンにしました。
シルク意味
CD未使用
CSチップセレクト
CLK同期用クロック
SDOデータ出力
SDIデータ入力
GNDグランド
3.3V3.3V 電源
5V5V電源


FlashAir から設定値を読み込む

せっかく SD 読み書きと iSDIO の両方を使えるようにしたので、SD カード領域に保存した設定値を参照できるようにします。

今回は測定場所のIDを FlashAir に保存しておきます。
「id.txt」というファイル名でファイル本文には数字を1個(測定場所ID)だけ書いておきます。

今回、Raspberry Pi 側では測定場所IDと温湿度を紐付けで管理させます。
温湿度計(Arduino)を置いた場所を FlashAir に書き込んでおけば、1台の温湿度計を全ての場所に使いまわすことができます。

こうしておくと、いちいち Arduino IDE を開いてコードを書き換えなくても、FlashAir のファイルを書き換えるだけで場所を変えられて楽です。

ちなみに、測定場所IDだけでなく、無線関係の設定値も全て SD カードに保存したかったのですが、あまりたくさん実装するとメモリを使い尽くして動作が不安定になってしまったのでやめました。

// 改造した SD カードライブラリを include
#include 
#include 

// SD カードのチップセレクトピン番号
#define CHIP_SELECT_PIN 4

// 設定値を保存してあるファイルパス
#define ID_FILE "/id.txt"

int ID = 0;

// SD カードからファイルを読み込む関数
char* SD_read( char* path, char* buf, size_t s ) {

  // FlashAir に連続アクセスすると不安定になることがあるので適当に delay
  delay(1000);

  // ファイルを開く
  File file = SD.open(path,FILE_READ);
  if ( !file ) { return ""; }

  // ファイルの中身を読み込む
  String str = "";
  while (file.available()) { str += char(file.read()); }
  file.close();

  // 文字配列にして返す
  str.toCharArray(buf, s);

  return buf;
}

// SD カードのセットアップ
void setup_SD() {
  // Initialize SD card.
  Serial.println(F("Initializing SD card..."));  

  // SS ピンを出力にしておかないと SD カードライブラリが動作しないらしい
  pinMode(SS, OUTPUT);
  if (!SD.begin(CHIP_SELECT_PIN)) {
    Serial.println(F("Card failed, or not present"));
    abort();
  }

    : (中略)

  // SD カードから読み込んだデータを数値に変換
  char buf[32];
  ID = atoi(SD_read(ID_FILE,buf,32));
  Serial.print(F("ID="));
  Serial.println(ID);

}

void setup() {
  // Initialize UART for message print.
  Serial.begin(9600);
  while (!Serial) {
    ;
  }

  setup_SD();
}


送信するときだけ WiFi を起動して測定値を送信

WiFi を起動したままだと電力の消費が激しいので、使用しないときは切っておきます。

#include 
#include 

// 計測間隔
#define LOG_INTERVAL 600000

char SSID[] = "{WiFiのSSIDを指定}";
char NETWORKKEY[] = "{WiFiのパスワードを指定}";
char API_HOST[] = "{Raspberry PiのIPアドレス}";
char API_PATH[] = "/api.php";

uint8_t buffer[512];
uint32_t nextSequenceId = 0;

// FlashAir Developers のチュートリアルにある
// iSDIO_waitResponse を流用
boolean iSDIO_waitResponse(uint32_t sequenceId) {
  // 記載略
}

// FlashAir Developers のチュートリアルにある
// iSDIO_disconnect を流用
boolean iSDIO_disconnect(uint32_t sequenceId) {
   // 記載略
}

// FlashAir Developers のチュートリアルにある
// iSDIO_connect を流用
boolean iSDIO_connect(uint32_t sequenceId, const char* ssid, const char* networkKey) {
  // 記載略
}

// FlashAir Developers のチュートリアルにある
// iSDIO_http をベースに、接続先を引数で指定できるよう改造
boolean iSDIO_http(uint32_t sequenceId, char* host, char* path, char* param) {
  Serial.println(F("http command: "));
  memset(buffer, 0, 512);
  uint8_t* p = buffer;
  p = put_command_header(p, 1, 0);

  // SSL 無しで接続するため「0x21」
  p = put_command_info_header(p, 0x21, sequenceId, 2);

  // 接続先ホストは Raspberry Pi の IP アドレス
  p = put_str_arg(p, host);  // Argument #1.

  // HTTP ヘッダを生成
  char getParam[128];
  sprintf(getParam,
    "GET %s?%s HTTP/1.1\r\n"
    "Host: %s\r\n"
    "User-Agent: FlashAir\r\n"
    "\r\n", path, param, host);
  Serial.println(getParam);
  p = put_str_arg(p, getParam);
  put_command_header(buffer, 1, (p - buffer));
  return SD.card.writeExtDataPort(1, 1, 0x000, buffer) ? true : false;
}

// SD セットアップで iSDIO を設定
void setup_SD() {
  // Initialize SD card.
  Serial.println(F("Initializing SD card..."));  

  pinMode(SS, OUTPUT);
  if (!SD.begin(CHIP_SELECT_PIN)) {
    Serial.println(F("Card failed, or not present"));
    abort();
  }
 
  // Read the previous sequence ID.
  if (SD.card.readExtMemory(1, 1, 0x420, 0x34, buffer)) {
    if (buffer[0x20] == 0x01) {
      nextSequenceId = get_u32(buffer + 0x24);
      iSDIO_waitResponse(nextSequenceId);
      nextSequenceId++;
    } else {
      nextSequenceId = 0; 
    }
  } else {
    Serial.println(F("Failed to read status."));
    nextSequenceId = 0; 
  }

  char buf[32];
  ID = atoi(SD_read(ID_FILE,buf,32));
  Serial.print(F("ID="));
  Serial.println(ID);
  }
}

void setup() {
  // Initialize UART for message print.
  Serial.begin(9600);
  while (!Serial) {
    ;
  }

  setup_SD();
}

void loop() {

  // 時間を測る
  unsigned long start = millis();
  digitalWrite(13,HIGH);

  //温湿度を測定
  int chk = DHT.read22(DHT_PIN);
  if ( chk != 0 ){
    Serial.println(F("Fail to read humidity."));
    delay(1000);
    return;
  }

  // HTTP パラメータを生成
  char humid[8];
  char temp[8];
  char param[32];
  sprintf(param,"ID=%d&H=%s&T=%s",
    ID,dtostrf(DHT.humidity,5,2,humid),dtostrf(DHT.temperature,5,2,temp));
  Serial.print(F("PARAM:"));
  Serial.println(param);

  // WiFi を起動する
  // 繋がらない場合は間隔をあけてリトライ
  for ( int retry = 3; retry > 0; retry -- ) {
    delay(1000);
    if( iSDIO_connect(nextSequenceId, SSID, NETWORKKEY) &&
      iSDIO_waitResponse(nextSequenceId) ) {
      Serial.println(F("Connected"));
      retry = 0;
    } else {
      Serial.print(F("Failed or waiting. errorCode="));
      Serial.println(SD.card.errorCode(), HEX);
    }
    nextSequenceId++;
  }

  // HTTP GET で送信。うまくいかないならリトライ
  for ( int retry = 3; retry > 0; retry -- ) {
    delay(1000);
    if ( iSDIO_http(nextSequenceId,API_HOST,API_PATH,param) &&
      iSDIO_waitResponse(nextSequenceId)) {
        Serial.println(F("OK"));
        retry = 0;
    } else {
      Serial.print(F("Failed or waiting. errorCode="));
      Serial.println(SD.card.errorCode(), HEX);
    }
    nextSequenceId++;
  }

  //用が済んだらWiFiを停止
  if (iSDIO_disconnect(nextSequenceId) &&
      iSDIO_waitResponse(nextSequenceId)) {
    Serial.println(F("Success."));
  } else {
    Serial.print(F("Failed or waiting. errorCode="));
    Serial.println(SD.card.errorCode(), HEX);
  }
  nextSequenceId++;

  digitalWrite(13,LOW);

  //次の送信まで停止
  unsigned long span = millis() - start;
  if ( span > LOG_INTERVAL ) { return; }
  unsigned long sleep_time = LOG_INTERVAL - span;
  Serial.print(F("SLEEP => "));
  Serial.println(sleep_time,DEC);

  // 省電力停止
  delaySleep(sleep_time);
}


省電力停止させる

測定と送信は数秒で終わるため、大半の時間は待ち時間です。

真面目に省電力対応をするにはリレーで電源を分離し、Arduino 停止中は FlashAir, DHT11 も停止させる必要がありそうです。
その上で WDT (Watch Dog) を使って Arduino を停止させます。

とは言え、少々面倒なので今回は気休め程度に delay だけ省電力させたなんちゃって対応です。
盛大に電源を消費するので、長時間の測定にはスマホ用のバッテリーなどが必要です。

delay の省電力化についてはこちらのサイトを参考にしました。
ラジオペンチ:Arduinoでdelay関数を実行時の消費電流を減らす

void delaySleep(unsigned long t) {
  //
  // 注意:millis関数を使っているので50日以上の連続動作は出来ない。
  //
  unsigned long t0;
  if( t <= 16 ) {                       // 16ms以下なら普通のdelayで処理
    delay(t);
  }
  else{                                 // 17ms以上ならスリープ入れたdelayで実行
    t0 = millis();                      // 開始時のmillisの値を記録しておき
    set_sleep_mode (SLEEP_MODE_IDLE);   // アイドルのモード指定
    while( millis() - t0 < t ) {        // 設定値になるまでループ
      sleep_mode();                     // スリープに入れる(自動復帰するので何度も指定)
    }
  }
}


これで Arduino 側は完成です。
動作させるとエラーになります、、、え?

送信先が無いので当たり前ですね。
次回は母艦となる Raspberry Pi を作っていきます。

0 件のコメント:

コメントを投稿