SORACOM BeamをレンタルサーバのPHPで取得する&温湿度センサーで温度が変わったらデータ送信

どうもくろにゃんこたんです。
SORACOMネタが続きます。

ハンズオンでは手軽に出来る(本当に手軽!!)SORACOM Harvestを利用することが多いのですが、やっぱり自分のサーバでもデータを取得してみたいですよね(´ω`)
HarvestからもAPIで取得は可能なのですが、どうせなら自分でトリガーを作ってWebhookで飛ばしたデータを取得してみたいと思います!

ハンズオンから一歩だけ進むだけなのですが、意外と躓くポイントもあったりするので解説を踏まえて書いていきます(・ω・)ノ
レンタルサーバではPHPが一般的に普及しているので、今回はPHPで取得してログを出力するところまで行います。

SORACOM Beamの設定

まず自分のサーバのエンドポイントを決めておきます。
例えば
http://kuronyankotan.com/trigger/index.php
(本当はこのURLは存在しないので注意)
この場合「index.php」なので短縮して
http://kuronyankotan.com/trigger/
でも良いですね!

その場合は最後の「/」まできっちり書いておきます。

まずはSORACOM ユーザーコンソールのグループ設定で、SORACOM Beamの設定を行います。

こんな感じ(´ω`)
今回はUDP→HTTP/HTTPSエンドポイントを選択しました。

コンソールはこれで終わりっ(´ω`)

取得用のPHPを用意する

一番肝心ですね。
さて普通にPOSTされるので下記の様なコードを用意します。

NGコード

<?php 
file_put_contents("log.txt",json_encode($_POST));

はい。以上。

ですが。。。ココに罠がありまして、結果は

[]

となります。
なぜに?POSTなのに?
となる方はなるかもしれません。
実はSORACOM BeamではContent-type「Application/json」で送られるため、通常の$_POSTでは取得できません。

というわけで、どうするかというと直接inputを読みに行きます。

NGコード

<?php 
    $json_string = file_get_contents('php://input');
    file_put_contents("data.txt",json_encode($json_string));

これでデータの取得が出来ます。
ですが、もう一点このままではBASE64でエンコードされたデータが来るのでそのままテキストログを見ても何のことかよくわかりません。
なのでもう一歩分解する必要があります。

OKコード

<?php 
    $json_string = file_get_contents('php://input'); //読み取り
    $obj = json_decode($json_string,1); //一度デコード
    $payload = base64_decode ($obj['payload']); // ペイロード部分がBASE64で来るのでデコード
    file_put_contents("data.txt",json_encode($payload)); // ログに保存

これで無事にデータが取得出来ます。(・ω・)ノ

ついでにカスタムヘッダー情報にIMEIとIMSIを付与させているのでこれも見たいのでgetallheaders関数で保存してみます。

<?php 
    $json_string = file_get_contents('php://input'); //読み取り
    $obj = json_decode($json_string,1); //一度デコード
    $payload = base64_decode ($obj['payload']); // ペイロード部分がBASE64で来るのでデコード
    file_put_contents("data.txt",json_encode($payload)); // ログに保存

    file_put_contents("header.txt",json_encode(getallheaders ())); // ←追加

これでほぼSORACOM Beamからの情報を取得出来ます。
後はDBに入れるなり、メール通知をするなり、APIでつぶやくなり出来るようになります。

と、ここで終わってしまうのもつまらないのでWioLTEに繋いだ温湿度センサーを使って、温度に変化があればトリガーに情報を送信する。というサンプルを作ってみます。

温度に変化があればSoracom Beamで情報を送信するサンプル

今回はWioLTEのスケッチ例にある温湿度センサーとSoracomharvestのサンプルを使って、更に温度が変化したらという判定を入れました。

#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;
  DHT11Init(TemperatureAndHumidityPin);
}

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

  DHT11Start(TemperatureAndHumidityPin);
  for (int i = 0; i < 5; i++) data[i] = DHT11ReadByte(TemperatureAndHumidityPin);
  DHT11Finish(TemperatureAndHumidityPin);

  if(!DHT11Check(data, sizeof (data))) return false;
  if (data[1] >= 10) return false;
  if (data[3] >= 10) return false;

  *humidity = (float)data[0] + (float)data[1] / 10.0f;
  *temperature = (float)data[2] + (float)data[3] / 10.0f;

  return true;
}

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

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

void DHT11Start(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 DHT11ReadByte(int pin)
{
  byte data = 0;

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

    while (!digitalRead(pin)) ;
    long start = micros();
  
    while (digitalRead(pin)) ;
    long finish = micros();
    
    if ((long)(finish - start) > 50) data |= 1 << (7 - i);
  }

  return data;
}

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

bool DHT11Check(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;
}

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


毎度送信しても良いような気がしますが、貧乏性なのでパケットを節約するために書きました。
始めに温度を0℃と仮定して、そこから変更があればLTE網で送信するというものです。
温湿度センサー自体がちょっと複雑なので、後半はほぼサンプルのままですのでご安心ください。

また、「DHT11」の型番のセンサーは氷点下は測れません。。。
僕の家の外でやってみたら、データが来なくて焦りました(´ω`;)

氷点下を測るためには負の回路という謎なものを搭載しないといけないので、センサー自体がちょっとお高めです。

本当は氷点下になったらつぶやくとか通知とかを考えていたのですが、今回はセンサーが手元に無いので断念しました。。。

まとめ

こんな感じでデータを送信から取得まで出来ます。
分かってしまえば簡単ですね!

僕の場合はとりあえずMySQLで温度と湿度とIMEIとIMSIと生ログをDBに格納して、最新データを画面に表示するように作ってみました!
グラフでも良かったのですが、思いついたので(´ω`)

かわいいと思いません?(笑)

元ツィートはこちら

ちょっと楽をして50秒で画面をリフレッシュするようにしています。
(60秒刻みのデータなので、60秒だと画面を表示した時にまるまる59秒くらい遅れたデータ表示されることになる可能性があってですね。。。
ズレを意図的に作ってあります。)

SORACOM使うとすぐにAWSでLamda使って~あーしてこーしてという話が出てくるのですが、
そんなに難しいこと考えなくてもPHPとレンタルサーバがあれば、受信も蓄積も応用も可能なので、ぐっと身近に感じられるかと思います。
AWSはちょっと敷居が高いなーと思う方も、SORACOMのSIMを買ったり、ハンズオンに参加したら是非トライしてみてくださいね!

以上です。
ここまで読んでいただいて、ありがとうございます(´ω`)人