電子工作初心者がESP32からFirebase RealtimeDatabaseにデータを投げるまで

この記事は、[大阪工業大学 Advent Calendar 2019](https://adventar.org/calendars/4049)の19日目の記事です。

##:自己紹介
自分は最近やっとアウトプットをしようかなと思い始めた大学2回生です。[twitter](https://twitter.com/Alice18003)やってます。最近セキュリティ系の勉強を始めたばかりの初心者です。まぁ今回はセキュリティは全く関係ないのですが... なので、いろいろ表現に誤りがあるとは思いますがそこは何卒ご容赦を。

##:はじめに
自分は自宅でブルーベリーを栽培してるのですが、それをIoTにしようと思い始めました。まったくの初心者だったため何もかもググりまくって完成させました。その時自分が欲しい情報が少なかった(※見つけられなかった)ので少しでもこれから始める人が躓かないように記事にすることにしました。

##:材料
・[ＥＳＰ３２－ＤｅｖＫｉｔＣ　ＥＳＰ－ＷＲＯＯＭ－３２開発ボード](http://akizukidenshi.com/catalog/g/gM-11819/)
・[Ａｒｄｕｉｎｏ用　土壌湿度センサー　Ｓｏｉｌ　Ｍｏｉｓｔｕｒｅ　Ｓｅｎｓｏｒ](http://akizukidenshi.com/catalog/g/gM-07047/)
・ブレッドボードなど小物
・arduino IDE

##:ESP32でアナログセンサーから読みよとる
最初にesp32は俗にいうarduino系のため開発もほとんどarduinoと同じようにできる。しかし一部ESP32特有のものがあるので気を付けなければならない。今回のアナログセンサーからの読み取りではarduinoと同じコードで読みとれるが、デジタルセンサーではarduinoと同じではできないので気を付けないといけない。

```c++:analogsensor.ino
void setup(){
    Serial.begin(115200);
    while(!Serial);
}
void loop(){
    Serial.println("sensor value");
    int value = analogRead(35);
    Serial.println(value);
    delay(1000);
}
```

まぁ見てわかる通りloop関数内の"analogRead()"で読みとることができる。そして初心者の僕が詰まったのは**どのpinにさせばいいんだ！**コード内にさらっと出てきた"35"ってだれだよ！ググれば出てくるんだがマイコンにはもちろん各pinに役割があってその中に"ADC"というのがあって、それに対応してるpinに刺せばいいらしい。詳しくは[ここのサイト](https://trac.switch-science.com/wiki/esp32_tips)を見ればESP32の仕様が大体わかるはずです。
##:ESP32をWiFiにつなげる
先ほどセンサーからデータを読みとれたので次はデータを投げるためにまず、ネットにつなげる必要があります。そのために必要な”WiFi.h"をインクルードする必要があります。WiFiにつなげるだけならとても簡単な以下のコードでできます。

```c++:WiFiconnect.ino
#include <WiFi.h>

const char* ssid = "ssid";
const char* password = "password";

void setup() {
  Serial.begin(115200);
  while(!Serial);

  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED){
    delay(500); //再接続待機
    Serial.print(".");
  }
  Serial.println("WiFi connected");
  Serial.print("IP address");
  Serial.println(WiFi.localIP());
}

void loop() {
 
}
```
なんとWiFiにつなげるの"WiFi.begin(ssid, password)のたった一行で済みます。"WiFi.status()で接続状態を取得することができます

##:実際にFirebase RealtimeDatabaseに投げる
Firebase RealtimeDatabaseを作成するところはほかのもっとわかりやすく書いてある記事があると思うのでそれを参照してください。
DBを作ったうえで今回必要な値は
・DBにアクセスするためのパス
・BDにアクセスするための認証キー（作った人だけ）
の二つだけです。
ここで覚えておきたいのはFirebase RealtimeDatabaseは**Database**というより**ディクショナリに近い**イメージを持っておいてください
そこを踏まえたうえで、自分はブルーベリーの湿度管理なので時間を名前にしますが、そこはご自由に


```c++:esp32_FBDB.ino
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <String.h>
#define JST 3600* 9
WiFiClientSecure client;

const char* ssid ="ssid name";
const char* password = "ssid password";
const char* server = "DB host";
const char* firebase_auth = "DB secret key"; // firebase's secret
String user_path = "table directory";

uint32_t WebGet_LastTime = 0;
struct tm timeInfo;
String year; 
String mon;
String day;


void setup(){
  Serial.begin(115200);
  while (!Serial);
  WiFi_conect();
  configTime(JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); //set NTP
  
  WebGet_LastTime = 200000;
}

void loop(){
  String now_time = create_time(); //get time
  int value = analogRead(35); //get humidity
  SendPatchRequest( now_time, String( value ) ); //send the data
  delay(28000); //every 30 seconds
}

//WiFiconnect function
void WiFi_conect(){
  WiFi.begin(ssid, password); //connect to WiFi
  while(WiFi.status() != WL_CONNECTED){ 
      Serial.print(".");
      delay(1000); // reconect 1 sencond later
  }
  Serial.println("WiFi connected");
  Serial.println("IP address");
  Serial.println(WiFi.localIP()); //esp32's localIPaddress
}

//get time
void create_time(){
  String now_time;
  getLocalTime(&timeInfo); //get time

  year = String(timeInfo.tm_year + 1900); //convert to A.D.
  mon = String(timeInfo.tm_mon + 1); //convert to real month
  day = String(timeInfo.tm_mday);

  if(int(timeInfo.tm_hour) / 10 == 0){ //add "0" when the hour is single digit
    String now_hour = "0" + String(timeInfo.tm_hour);
    now_time += now_hour;
  }else{
    String now_hour = String(timeInfo.tm_hour);
    now_time += now_hour;
  }
 
  if(int(timeInfo.tm_min) / 10 == 0){ //add "0" when the min is single digit
    String now_min = "0" + String(timeInfo.tm_min);
    now_time += now_min;
  }else{
    String now_min = String(timeInfo.tm_min);
    now_time += now_min;
  }

  if(int(timeInfo.tm_sec) / 10 == 0){ //add "0" when the sec is single digit
    String now_sec= "0" + String(timeInfo.tm_sec);
    now_time += now_sec;
  }else{
    String now_sec = String(timeInfo.tm_sec);
    now_time += now_sec;
  }

  return now_time;
}

//write firebase DB
void SendPatchRequest(String str1, String str2){
  if (!client.connect(server, 443)){ //connect DB
    Serial.println("Connection Failed!");
  }else{
    Serial.println("Connected to sucsess!");
    String URL;
    URL = "PATCH /"; //http request method
    URL += user_path + "/" + year + "/" + mon + "/" + day;
    URL += ".json?auth=";
    URL += String(firebase_auth);
    URL += " HTTP/1.1\r\n";

    String body = "{\"" + str1 + "\":\"" + str2 + "\"}";

    String head;
    head = "Host: ";
    head += String(server) + "\r\n";
    head += "Connection: keep-alive\r\n";
    head += "Content-Length: ";
    head += String(body.length()) + "\r\n";
    head += "\r\n";

    client.print(URL);
    client.print(head);
    client.print(body);

    Serial.println("####### Send HTTP Patch Request #######");
    Serial.print(URL);
    Serial.print(head);
    Serial.print(body);

    checkServerRespons();

    delay(2);
    client.stop();
    delay(2);
  }
}

//receive response
void checkServerRespons(){
  Serial.println("####### Firebase server HTTP Response #######");
  while(client.connected()){
    String res = client.readStringUntil('\n');
    Serial.println(res);
    if(res == "\r"){
      Serial.println("-------------- Response Headers Recived");
      break;
    }
  }

  while(client.available()){
    char c = client.read();
    Serial.print(c);
  }
  Serial.println("\r\n------------ Body Received");
}
```

サーバーにつなげるのに"WiFiClientSecure.h"をインクルードする必要があります。
自分が一番躓いたのがhttp requestを作るとこでした。""で囲った部分が可変です。
・中身
"method" "URL" HTTP/1.1\r\n
HOST: "distination server" Content-Length "body.length" \r\n
"body"

データをDBに投げるだけでいいのでHTTP request のメソッドはPATCHで十分です。
・GET　　ほしいものを取得
・POST 　データの挿入
・PATCH  データの更新

URLはDBまでのpathを入れれば大丈夫です。
HOSTは今回はFirebaseのserverを入れてやります。

今回はレスポンスを考慮してないので特に書いてないのですが、処理があるならcheckServerRespons()に処理を書く必要があります。

##:おわりに
継ぎ接ぎだらけのコードなのでググったらもっといいコードが出てくると思うのでこの記事ではこんな感じのコードを書けばいいんだなと思っていただけたら嬉しいです。今回はアナログセンサーを用いましたが、デジタルセンサー([bme280](http://akizukidenshi.com/catalog/g/gK-09421/))でデータを読むとこまでですが[コード](https://github.com/alice18003/esp32_bme280_i2c/blob/master/bme280_i2c.ino)もありますので参考までにどうぞ