#IoT
#WioLTE
#SORACOM
#DHT22
#SORACOMBeam

#WioLTE で温湿度センサーDHT22を動かす

どうもくろにゃんこたんです。

以前ブログに書いたネタなのですが、冬が終わる前に加筆修正したものQiitaにもアップさせていただきます。
次の冬季シーズンにご活用頂ければ幸いです。

僕は長野県在住なので、とりあえず外の気温がマイナスになったら洗濯物が凍らないように、アラートを通知するシステムを構築しようと思ったのです。
で、す、が。。。

動かない。

というスターターキットについているセンサーレベルで駄目というオチ。

実は温度センサー一つとってもかなーり深い話があるようでして。。。
「そんなデータで大丈夫か?」
https://soracomug-tokyo.connpass.com/event/70305/presentation/

なるほど負の電源というのを回路に埋め込まないといけないのですね。。
略して「負の回路」と呼びます。

センサーを購入

というわけで長い前書きになりましたが、スターターキットの温湿度センサーであるDHT11から吐き出されるバイトコードを氷点下で眺めていたのですが、データ自体微妙でした。。
結局
Grove - Temperature and Humidity Sensor Pro
略(してないけど)してDHT22です。こちらを買いました。

というわけで届いたので並べてみた写真がこちら(・ω・)ノ

データシート及びサンプルライブラリなどはwikiにあります。
http://wiki.seeed.cc/Grove-Temperature_and_Humidity_Sensor_Pro/

あんまり詳しくないので型番に惑わされるのですが、

センサー自体(白いやつ)→AM2302
基板に載せたやつ→Grove - Temperature&Humidity Sensor Pro
ライブラリ的な呼び方→DHT22
ということなんだと思っています。
ちなみにスターターキットについてくるのは「DHT11」です。

こちらのセンサーはなんと「 -40°C ~ 80°C」まで計れるとのことでまぁまぁ長野県でも使えそうです。
どちらかと言うと、-40℃とかで使うのであればWioLTEの方が凍りそう。そしてバッテリーも温めないといけません

設置します

今回はとりあえずベランダの気温が分かればいいので、持ち運びは気にせず、電源は家の中から直接持っていきます(・ω・)ノ

ほら!ちょうど良い感じの小窓がありまして、、、
3つの換気口が並んでるのですが、した2つは自動換気システムらしくあんまりケーブルが入るスペースがなかったので、一番上を使います。

バミリ!
とても雑ですがこれで良しとします。
隙間があると寒いですので割りと完璧に出来た方です( ・`ω・´)

ここからWioLTEに接続します。

温度センサーとアンテナをぶらり。

別角度から。

本当は百葉箱の様式に習って、直射日光や地上高を考える必要があるのですが、
今回はマイナスが分かればいいので一旦無視。

これで設置完了しました!
バッテリーの心配も無くとても平和です。

スケッチ

ここからArduinoのスケッチ例です。
Seeed wikiを参考に、ほぼWioLTEのスケッチ例である
grove-temperature-and-humidity-sensor.ino
を代替出来るように書きました。
DHT.hなどは読み込まなくてもなんとかなります。

また、関数名を「DHT11」→「DHT22」に変更している形で書いてあるので、センサーを買ったらまるっと書き換えだけで使えます。

こちらのコードはGithubで公開してみました。
https://github.com/kuronyankotan/wiolte/tree/master/DHT22

#include <WioLTEforArduino.h>

#define SENSOR_PIN    (WIOLTE_D38)

void setup()
{
  TemperatureAndHumidityBegin(SENSOR_PIN);
}

void loop()
{
  float temp;
  float humi;

  if (!TemperatureAndHumidityRead(&temp, &humi)) {
    SerialUSB.println("ERROR!");
    goto err;
  }

  SerialUSB.print("Current humidity = ");
  SerialUSB.print(humi);
  SerialUSB.print("%  ");
  SerialUSB.print("temperature = ");
  SerialUSB.print(temp);
  SerialUSB.println("C");

err:
  delay(2000);
}

////////////////////////////////////////////////////////////////////////////////////////
//

int TemperatureAndHumidityPin;

void TemperatureAndHumidityBegin(int pin)
{
  TemperatureAndHumidityPin = pin;
  DHT22Init(TemperatureAndHumidityPin);
}

bool TemperatureAndHumidityRead(float* temperature, float* humidity)
{
  byte data[5];
  float f = NAN;

  DHT22Start(TemperatureAndHumidityPin);
  for (int i = 0; i < 5; i++) data[i] = DHT22ReadByte(TemperatureAndHumidityPin);

  DHT22Finish(TemperatureAndHumidityPin);

  if (!DHT22Check(data, sizeof (data))) return false;
  f = data[0];
  f *= 256;
  f += data[1];
  f *= 0.1;
  *humidity = f;

  f = data[2] & 0x7F;
  f *= 256;
  f += data[3];
  f *= 0.1;
  if (data[2] & 0x80) {
    f *= -1;
  }
  *temperature = f;

  return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//

void DHT22Init(int pin)
{
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

void DHT22Start(int pin)
{
  // Host the start of signal
  digitalWrite(pin, LOW);
  delay(18);

  // Pulled up to wait for
  pinMode(pin, INPUT);
  while (!digitalRead(pin)) ;

  // Response signal
  while (digitalRead(pin)) ;

  // Pulled ready to output
  while (!digitalRead(pin)) ;
}

byte DHT22ReadByte(int pin)
{
  byte data = 0;

  for (int i = 0; i < 8; i++) {
    while (digitalRead(pin)) ;

    while (!digitalRead(pin)) ;
    unsigned long start = micros();

    while (digitalRead(pin)) ;
    unsigned long finish = micros();

    if ((unsigned long)(finish - start) > 50) data |= 1 << (7 - i);
  }

  return data;
}

void DHT22Finish(int pin)
{
  // Releases the bus
  while (!digitalRead(pin)) ;
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

bool DHT22Check(const byte* data, int dataSize)
{
  if (dataSize != 5) return false;

  byte sum = 0;
  for (int i = 0; i < dataSize - 1; i++) {
    sum += data[i];
  }

  return data[dataSize - 1] == sum;
}

////////////////////////////////////////////////////////////////////////////////////////

今回の肝は
TemperatureAndHumidityRead関数の中でしょうか。
こちらはライブラリを参考にバイト値から計算する方法をモロパクリしております。
多分これで問題ないはず。。
ライブラリには他にも機能が合った気がしますが、多分これくらいしか使わないので割愛(´ω`)

またこちらを応用してSORACOMBeamでデータを送信するスケッチを書きました。

#include <WioLTEforArduino.h>
#include <stdio.h>

#define SENSOR_PIN    (WIOLTE_D38)
#define INTERVAL  (60000)
WioLTE Wio;

bool State_chg = false;
float State_temp = 0;

void setup()
{
  TemperatureAndHumidityBegin(SENSOR_PIN);
  SerialUSB.println("### I/O Initialize.");
  Wio.Init();

  SerialUSB.println("### Power supply ON.");
  Wio.PowerSupplyLTE(true);
  delay(5000);

  SerialUSB.println("### Turn on or reset.");
  if (!Wio.TurnOnOrReset()) {
    SerialUSB.println("### ERROR! ###");
    return;
  }
}

void loop()
{
  float temp;
  float humi;
  char data[100];
  int connectId;

  if (!TemperatureAndHumidityRead(&temp, &humi)) {
    SerialUSB.println("ERROR!");
    goto err;
  }

  SerialUSB.print("Current humidity = ");
  SerialUSB.print(humi);
  SerialUSB.print("%  ");
  SerialUSB.print("Def_temperature = ");
  SerialUSB.print(State_temp);
  SerialUSB.println("C");
  SerialUSB.print("temperature = ");
  SerialUSB.print(temp);
  SerialUSB.println("C");

 //温度センサーに変化があればフラグをON
  State_chg = false;
  if (State_temp != temp) {
    State_chg = true;
    State_temp = temp;
    SerialUSB.println("DIFF!!");
  }
// Soracom beamで送信
  if (State_chg) {
    SerialUSB.println("### Open.");
    connectId = Wio.SocketOpen("beam.soracom.io", 23080, WIOLTE_UDP);
    if (connectId < 0) {
      SerialUSB.println("### ERROR! ###");
      goto err;
    }

    SerialUSB.println("### Send.");
    sprintf(data, "{\"humi\":%f,\"temp\":%f}", humi, temp);
    SerialUSB.print("Send:");
    SerialUSB.print(data);
    SerialUSB.println("");
    if (!Wio.SocketSend(connectId, data)) {
      SerialUSB.println("### ERROR! ###");
      !Wio.SocketClose(connectId);
      goto err;
    }

    SerialUSB.println("### Receive.");
    int length;
    do {
      length = Wio.SocketReceive(connectId, data, sizeof (data));
      if (length < 0) {
        SerialUSB.println("### ERROR! ###");
        !Wio.SocketClose(connectId);
        goto err;
      }
    } while (length == 0);
    SerialUSB.print("Receive:");
    SerialUSB.print(data);
    SerialUSB.println("");

    SerialUSB.println("### Close.");
    if (!Wio.SocketClose(connectId)) {
      SerialUSB.println("### ERROR! ###");
      goto err;
    }
  }

err:
  delay(INTERVAL);
}
////////////////////////////////////////////////////////////////////////////////////////
//

int TemperatureAndHumidityPin;

void TemperatureAndHumidityBegin(int pin)
{
  TemperatureAndHumidityPin = pin;
  DHT22Init(TemperatureAndHumidityPin);
}

bool TemperatureAndHumidityRead(float* temperature, float* humidity)
{
  byte data[5];
  float f = NAN;

  DHT22Start(TemperatureAndHumidityPin);
  for (int i = 0; i < 5; i++) data[i] = DHT22ReadByte(TemperatureAndHumidityPin);

  DHT22Finish(TemperatureAndHumidityPin);

  if (!DHT22Check(data, sizeof (data))) return false;
  f = data[0];
  f *= 256;
  f += data[1];
  f *= 0.1;
  *humidity = f;

  f = data[2] & 0x7F;
  f *= 256;
  f += data[3];
  f *= 0.1;
  if (data[2] & 0x80) {
    f *= -1;
  }
  *temperature = f;

  return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//

void DHT22Init(int pin)
{
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

void DHT22Start(int pin)
{
  // Host the start of signal
  digitalWrite(pin, LOW);
  delay(18);

  // Pulled up to wait for
  pinMode(pin, INPUT);
  while (!digitalRead(pin)) ;

  // Response signal
  while (digitalRead(pin)) ;

  // Pulled ready to output
  while (!digitalRead(pin)) ;
}

byte DHT22ReadByte(int pin)
{
  byte data = 0;

  for (int i = 0; i < 8; i++) {
    while (digitalRead(pin)) ;

    while (!digitalRead(pin)) ;
    unsigned long start = micros();

    while (digitalRead(pin)) ;
    unsigned long finish = micros();

    if ((unsigned long)(finish - start) > 50) data |= 1 << (7 - i);
  }

  return data;
}

void DHT22Finish(int pin)
{
  // Releases the bus
  while (!digitalRead(pin)) ;
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

bool DHT22Check(const byte* data, int dataSize)
{
  if (dataSize != 5) return false;

  byte sum = 0;
  for (int i = 0; i < dataSize - 1; i++) {
    sum += data[i];
  }

  return data[dataSize - 1] == sum;
}

////////////////////////////////////////////////////////////////////////////////////////

まとめ

後はサーバ側でマイナスを検知した時にメールとかLINE@とかIFTTTとかご自由にどうぞ。という感じにしておきます。
SORACOMBeamを利用したのは、ソースをそのまま載せても固有値(トリガー先のアドレスとか)がバレないのと、コンソール上でトリガー先をサクッと変えられるのが良いなと思ったためです。

僕は自分用トリガーとDBをレンタルサーバ上に構築しているので、そこに飛ばしました。

また、今回は電源問題が無かったのでsetup()にLTE電源ON処理を入れて高速化しておりますが、
loop()の中にLTE電源ON→通信→LTE電源OFFという一連の流れを入れたほうが、より省電力になります。

スターターキットネタでは無かったのですが、物足りない方は是非試してみてくださいね(・ω・)ノ