[Project-P002] ESP8266 ส่งค่าอุณหภูมิและความชื้น ที่อ่านได้ขึ้น Firebase และแจ้งเตือนทาง Line

Guild Line

.                 Project นี้จะเป็น Project ตัวอย่าง โดยใช้ ESP8266 ไป microcontroller อ่านค่าอุณหภูมิและความชื้น ด้วย DHT11 โดยจะส่งข้อมูลแจ้งเตือนไปทาง Line พร้อมกับ ส่งข้อมูลขึ้น Firebase โดยไม่ต้องพึ่งไลบรารี่ภายนอก ผ่านฟังกชั่น TD_Set_Firebase().  ซึ่งจะสามารถส่งได้เรื่อยๆ ไม่เกิดปัญหา WatchDog Timer แต่อย่างใด

ProjectFritzing

การต่อวงจร

  • ต่อเซนเซอร์ DHT11 เข้าที่ขา D2 ของ NodeMCU  (ในรูป ให้ใช้เป็น แบบโมดูลแทน)

ArduinoIDE’s SketchFile Code

#include <WiFiClientSecureAxTLS.h>
#include <ESP8266WiFi.h>
#include <DHT.h>                // ใช้ของ Adafruit

//--------------------------------------------------------
#define WIFI_SSID             "---------------"   // ชื่อ WiFi Router
#define WIFI_PASSWORD         "---------------"   // รหัส WiFi
#define FIREBASE_HOST         "https://--------------.firebaseio.com/"
#define FIREBASE_AUTH         "--------------------------------------"
#define LINE_TOKEN            "---------------"   // รหัส Line Token
//--------------------------------------------------------
/* ฟังกชั่น สำหรับ รับและส่งข้อมูลไปยัง Firebase*/
String  TD_Get_Firebase(String path );                                // สำหรับรับ
int     TD_Set_Firebase(String path, String value, bool push=false ); // สำหรับส่ง
int     TD_Push_Firebase(String path, String value ); // สำหรับส่งแบบ Pushing data
// ฟังกชั่นสำหรับส่ง LineNotify ใช้ร่วมกับ การส่ง Firebase ได้สบายๆ
bool    TD_LineNotify(String message);                // ส่งสำเร็จจะคืนค่าเป็น true
//--------------------------------------------------------

DHT dht( D2, DHT11);         // ประกาศตัวแปรเซนเซอร์ dht ที่ขา GPIO5 แบบ DHT11

void setup() {
  Serial.begin(115200); Serial.println();
  dht.begin(); 
  
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);    // เชื่อมต่อไวไฟ
  while(!WiFi.isConnected()) delay(400);   // รอจนกว่าเชื่อมต่อสำเร็จ
  Serial.println(WiFi.localIP());          // แสดงค่า IP หลังเชื่อมต่อได้

  TD_LineNotify("Hello");                  // ทดสอบส่ง LineNotify ออกไป
}

uint32_t timer;
uint16_t cnt=0;
void loop() {
  if( millis() -timer > 3000) {                  // กำหนดส่งทุกๆ 3 วินาที
    timer = millis();
    
    float temperature = dht.readTemperature();   // อ่านค่า อุณหภูมิ DHT
    float humidity = dht.readHumidity();         // อ่านค่า ความชื้น DHT
  
    if (isnan(temperature) || isnan(humidity)) return;   // หากอ่าน DHT ไม่ได้ ให้เริ่มรอบใหม่
  
    // เตรียมข้อมูล Json
    char buf[100];
    sprintf(buf, "{\"temperature\":%.2f,\"humidity\":%.2f}", temperature, humidity);
    String logDHTJson = String(buf);

    // จัดแสดงค่าเซนเซอร์ที่อ่านได้ ในรูปแบบ json และ ส่งค่านี้ไปตามโปรโตคอลต่างๆ
    Serial.printf("[%d] %s\n", ++cnt, logDHTJson.c_str()); // แสดง ค่าเซนเซอร์ที่อ่านได้ ออกทาง Serial Monitor
    TD_Set_Firebase( "logDHT", logDHTJson);                // ส่ง ไปยัง Firebase ที่ logDHT
    TD_LineNotify(logDHTJson);                             // ส่ง ไปยัง Line
  } 
}

//----------- ข้างล่างนี้ไม่ต้องแก้ไขอะไร-----------------------------------
/**********************************************************
 * ฟังกชั่น TD_Set_Firebase 
 * ใช้สำหรับ ESP8266 กำหนด ค่า value ให้ path ของ Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคินค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
#ifndef FIREBASE_FINGERPRINT  
// Fingerprint ของ https://www.firebaseio.com 
// ใช้ได้ตั้งแต่ 01/08/2018 17:21:49 GMT ถึง หมดอายุสิ้นสุด 27/03/2019 00:00:00 GMT
//#define FIREBASE_FINGERPRINT  "6F D0 9A 52 C0 E9 E4 CD A0 D3 02 A4 B7 A1 92 38 2D CA 2F 26"
// ใช้ได้ตั้งแต่ 02/2020 ...  
#define FIREBASE_FINGERPRINT  "03 D6 42 23 03 D1 0C 06 73 F7 E2 BD 29 47 13 C3 22 71 37 1B"
#endif 

int TD_Set_Firebase(String path, String value, bool push ) {
  axTLS::WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  int httpCode = 404; // Not Found
  String firebase_method = (push)? "POST " : "PUT ";
  if( ssl_client.connect( host.c_str(), 443)){
    if( ssl_client.verify( FIREBASE_FINGERPRINT, host.c_str())){
      String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
      String request = "";
            request += firebase_method + uri +" HTTP/1.1\r\n";
            request += "Host: " + host + "\r\n";
            request += "User-Agent: ESP8266\r\n";
            request += "Connection: close\r\n";
            request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n";
            request += "Content-Length: "+String( value.length())+"\r\n\r\n";
            request += value;

      ssl_client.print(request);
      while( ssl_client.connected() && !ssl_client.available()) delay(10);
      if( ssl_client.connected() && ssl_client.available() ) {
        resp      = ssl_client.readStringUntil('\n');
        httpCode  = resp.substring(resp.indexOf(" ")+1, resp.indexOf(" ", resp.indexOf(" ")+1)).toInt();
      }
    }else{
      Serial.println("[Firebase] can't verify SSL fingerprint");
    }
    ssl_client.stop();    
  } else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return httpCode;
}

/**********************************************************
 * ฟังกชั่น TD_Push_Firebase 
 * ใช้ กำหนด ค่า value ให้ path ของ Firebase แบบ Pushing Data (POST method)
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคินค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
int TD_Push_Firebase(String path, String value ){
  return TD_Set_Firebase(path,value, true);
}
/**********************************************************
 * ฟังกชั่น TD_Get_Firebase
 * ใช้ รับ ค่า value ของ path ที่อยู่บน Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * path เป็น ข้อมูลประเภท String
 * คืนค่าของฟังกชั่น คือ value ของ path ที่กำหนด ในข้อมูลประเภท String
 * 
 **********************************************************/
String TD_Get_Firebase(String path ) {
  axTLS::WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  String value = "";
  if( ssl_client.connect( host.c_str(), 443)){
    if( ssl_client.verify( FIREBASE_FINGERPRINT, host.c_str())){
      String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
      String request = "";
            request += "GET "+ uri +" HTTP/1.1\r\n";
            request += "Host: " + host + "\r\n";
            request += "User-Agent: ESP8266\r\n";
            request += "Connection: close\r\n";
            request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n\r\n";

      ssl_client.print( request);
      while( ssl_client.connected() && !ssl_client.available()) delay(10);
      if( ssl_client.connected() && ssl_client.available() ) {
        while( ssl_client.available()) resp += (char)ssl_client.read();
        value = resp.substring( resp.lastIndexOf('\n')+1);
      }
    }else{
      Serial.println("[Firebase] can't verify SSL fingerprint");
    }
    ssl_client.stop();    
  } else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return value;
}
/**********************************************************
 * ฟังกชั่น TD_LineNotify สำหรับส่งเตือนข้อความไปทาง Line
 * หากส่งสำเร็จ คืนค่าเป็น true หากไม่ คืนค่าเป็น false
 **********************************************************/
bool TD_LineNotify(String message){
  axTLS::WiFiClientSecure ssl_client;

  if (!ssl_client.connect("notify-api.line.me", 443)) {
    Serial.println("[LineNotify] can't connect to notify-api.line.me");
    return false;
  }
  int    httpCode = 404;
  String body = "message=" + message;
  String request = "";
        request += "POST /api/notify HTTP/1.1\r\n";
        request += "Host: notify-api.line.me\r\n";
        request += "Authorization: Bearer " + String(LINE_TOKEN) + "\r\n";
        request += "Cache-Control: no-cache\r\n";
        request += "User-Agent: ESP8266/ESP32\r\n";
        request += "Connection: close\r\n";
        request += "Content-Type: application/x-www-form-urlencoded\r\n";
        request += "Content-Length: " + String(body.length()) + "\r\n\r\n" + body;
  ssl_client.print(request);

  while( ssl_client.connected() && !ssl_client.available()) delay(10);
  if( ssl_client.connected() && ssl_client.available() ) {
    String resp = ssl_client.readStringUntil('\n');
    httpCode    = resp.substring(resp.indexOf(" ")+1, resp.indexOf(" ", resp.indexOf(" ")+1)).toInt();
  }
  ssl_client.stop();    
  return (httpCode == 200);
}

ผลที่ปรากฏทาง Serial Monitor
SerialMonitor

ผลที่ปรากฏทาง console ของ Firebase
Firebase

ผลที่ปรากฏทาง Line

Screenshot_2018-03-18-18-04-08-228_jp.naver.line.android

[Project-P001] ESP8266 ส่งค่า sensor DHT22 ขึ้นจอ และ ส่งไปยัง Firebase ด้วย

Guild Line

.                 Project นี้จะเป็น Project ตัวอย่าง  โดยใช้ ESP8266 เป็น microcontroller ทำการอ่านค่าอุณหภูมิและความชื้น ด้วย เซนเซอร์ DHT22 จำนวน 2 ตัว   พร้อมกับ เซนเซอร์ Soil Sensor วัดค่าความชื้นในดิน แล้วส่งออกไปยังหน้าจอ LCD  และส่งข้อมูลขึ้น Firebase ด้วย json format   โดยส่งผ่านฟังกชั่น TD_Set_Firebase() โดยลดการพึ่งไลบรารี่ภายนอก

Example_Fritzing

การต่อวงจร

  • DHT22 ตัวแรก ต่อขา sensor ที่ขา D3  ของ NodeMCU
  • DHT22 ตัวที่สอง ต่อขา sensor ที่ขา D4 ของ NodeMCU
  • Soil  Sensor ต่อที่ขา A0 ของ NodeMCU
  • LCD 20×4  ต่อที่ขา D1 และ D2 ของ NodeMCU

    ( DHT22 ในรูปเปลี่ยนเป็น แบบ โมดูล แทน )

ArduinoIDE’s SketchFile Code


#include <WiFiClientSecureAxTLS.h>
#include <ESP8266WiFi.h>
#include <DHT.h>                // ใช้ของ Adafruit https://github.com/adafruit/DHT-sensor-library
#include <Wire.h>
#include <LiquidCrystal_I2C.h>  // ใช้ของ DFRobot.com

//--------------------------------------------------------
#define WIFI_SSID             "--------------------"  // WiFi Router
#define WIFI_PASSWORD         "--------------------"  // รหัส WiFi
#define FIREBASE_HOST         "----------------------------"  // Host Firebase
#define FIREBASE_AUTH         "----------------------------"  // รหัสของฐานข้อมูลข้างต้น
//--------------------------------------------------------
/* ฟังกชั่น สำหรับ รับและส่งข้อมูลไปยัง Firebase*/
String  TD_Get_Firebase(String path );                //  รับจาก Firebase
int     TD_Set_Firebase(String path, String value, bool push=false ); // ส่งไปยัง Firebase
int     TD_Push_Firebase(String path, String value ); //  ส่งไปยัง Firebase แบบ Pushing Data
//--------------------------------------------------------
LiquidCrystal_I2C lcd(0x27,20,4);    // ประกาศตัวแปรสำหรับจอ lcd
int Soil = A0;                       // ขา pin ของ soil sensor
//DHT dht( D3, DHT11);               // หากใช้ DHT11 ให้กำหนดแบบตัวอย่างนี้
DHT dht( D3, DHT22);                 // ขา pin ของ DHT22 ตัวแรก
DHT dht2( D4, DHT22);                // ขา pin ของ DHT22 ตัวที่ 2

//--------------------------------------------------------

void setup() {
  Serial.begin(115200); Serial.println();
  dht.begin(); 
  dht2.begin();
  lcd.init(); lcd.backlight();

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);    // เชื่อมต่อไวไฟ
  while(!WiFi.isConnected()) delay(400);   // รอจนกว่าเชื่อมต่อสำเร็จ
  Serial.println(WiFi.localIP());          // แสดงค่า IP หลังเชื่อมต่อได้
}

void loop() {
  float temperature = dht.readTemperature();   // อ่านค่า อุณหภูมิ DHT22 ตัวแรก
  float humidity = dht.readHumidity();         // อ่านค่า ความชื้น DHT22 ตัวแรก
  float temperature2 = dht2.readTemperature();
  float humidity2 = dht2.readHumidity();
  int sensorsoil  = map(analogRead(Soil), 0, 1023, 100, 0 ); // อ่านค่า soil sensor

  if (isnan(temperature) || isnan(humidity)) return;   // หากอ่าน DHT22 ตัวแรก ไม่ได้ ให้เริ่มใหม่
  if (isnan(temperature2) || isnan(humidity2)) return; // หากอ่าน DHT22 ตัวที่2  ไม่ได้ ให้เริ่มใหม่

  // แสดงค่าที่อ่านได้ทั้งหมดออกหน้าจอ Serial Monitor
  Serial.printf("T:%.2f ; H:%.2f ; T2:%.2f ; H2:%.2f ; soil:%d\n", 
           temperature, humidity, temperature2, humidity2 , sensorsoil);

  // ตัวอย่าง ค่าที่อ่านได้ ออกจอ lcd
  lcd.setCursor(0, 1); lcd.print("           ");
  lcd.setCursor(0, 1); lcd.print(temperature);
  lcd.setCursor(0, 2); lcd.print("           ");
  lcd.setCursor(0, 2); lcd.print(humidity);
  lcd.setCursor(0, 3); lcd.print("           ");
  lcd.setCursor(0, 3); lcd.print(sensorsoil);
  
  // เตรียมข้อมูล Json
  char buf[100];
  sprintf(buf, "{\"temperature\":%.2f,\"humidity\":%.2f}", temperature, humidity);
  String logDHTJson1 = String(buf);
  sprintf(buf, "{\"temperature\":%.2f,\"humidity\":%.2f}", temperature2, humidity2);
  String logDHTJson2 = String(buf);

  // ส่งข้อมูล json ขึ้น Firebase ด้วยฟังกชั่น TD_Set_Firebase() แบบไม่ต้องพึ่งไลบรารี่
  TD_Set_Firebase( "logDHT", logDHTJson1);    // ส่งขึ้น Firebase แบบปกติ
  TD_Set_Firebase( "logDHT2", logDHTJson2);   // ส่งขึ้น Firebase แบบปกติ
  delay(500);
}

//----------- ข้างล่างนี้ไม่ต้องแก้ไขอะไร-----------------------------------
/**********************************************************
 * ฟังกชั่น TD_Set_Firebase 
 * ใช้กำหนด ค่า value ให้ path ของ Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคินค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
#ifndef FIREBASE_FINGERPRINT  // Fingerprint ของ https://www.firebaseio.com
// ใช้ได้ตั้งแต่ 01/08/2018 17:21:49 GMT ถึง หมดอายุสิ้นสุด 27/03/2019 00:00:00 GMT
// #define FIREBASE_FINGERPRINT  "6F D0 9A 52 C0 E9 E4 CD A0 D3 02 A4 B7 A1 92 38 2D CA 2F 26"
// ใช้ได้ตั้งแต่ 02/2020 ...  
#define FIREBASE_FINGERPRINT  "03 D6 42 23 03 D1 0C 06 73 F7 E2 BD 29 47 13 C3 22 71 37 1B"
#endif

int TD_Set_Firebase(String path, String value, bool push ) {
  axTLS::WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  int httpCode = 404; // Not Found
  String firebase_method = (push)? "POST " : "PUT ";
  if( ssl_client.connect( host.c_str(), 443)){
    if( ssl_client.verify( FIREBASE_FINGERPRINT, host.c_str())){
      String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
      String request = "";
            request += firebase_method + uri +" HTTP/1.1\r\n";
            request += "Host: " + host + "\r\n";
            request += "User-Agent: ESP8266\r\n";
            request += "Connection: close\r\n";
            request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n";
            request += "Content-Length: "+String( value.length())+"\r\n\r\n";
            request += value;

      ssl_client.print(request);
      while( ssl_client.connected() && !ssl_client.available()) delay(10);
      if( ssl_client.connected() && ssl_client.available() ) {
        resp      = ssl_client.readStringUntil('\n');
        httpCode  = resp.substring(resp.indexOf(" ")+1, resp.indexOf(" ", resp.indexOf(" ")+1)).toInt();
      }
    }else{
      Serial.println("[Firebase] can't verify SSL fingerprint");
    }
    ssl_client.stop();    
  } else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return httpCode;
}

/**********************************************************
 * ฟังกชั่น TD_Push_Firebase 
 * ใช้ กำหนด ค่า value ให้ path ของ Firebase แบบ Pushing Data (POST method)
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคินค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
int TD_Push_Firebase(String path, String value ){
  return TD_Set_Firebase(path,value, true);
}
/**********************************************************
 * ฟังกชั่น TD_Get_Firebase
 * ใช้ รับ ค่า value ของ path ที่อยู่บน Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * path เป็น ข้อมูลประเภท String
 * คืนค่าของฟังกชั่น คือ value ของ path ที่กำหนด ในข้อมูลประเภท String
 * 
 **********************************************************/
String TD_Get_Firebase(String path ) {
  axTLS::WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  String value = "";
  if( ssl_client.connect( host.c_str(), 443)){
    if( ssl_client.verify( FIREBASE_FINGERPRINT, host.c_str())){
      String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
      String request = "";
            request += "GET "+ uri +" HTTP/1.1\r\n";
            request += "Host: " + host + "\r\n";
            request += "User-Agent: ESP8266\r\n";
            request += "Connection: close\r\n";
            request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n\r\n";

      ssl_client.print( request);
      while( ssl_client.connected() && !ssl_client.available()) delay(10);
      if( ssl_client.connected() && ssl_client.available() ) {
        while( ssl_client.available()) resp += (char)ssl_client.read();
        value = resp.substring( resp.lastIndexOf('\n')+1);
      }
    }else{
      Serial.println("[Firebase] can't verify SSL fingerprint");
    }
    ssl_client.stop();    
  } else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return value;
}

ผลที่ปรากฏบน Realtime Database ของ Firebase

.                ตัวอย่าง code ข้างต้น จะเป็นการส่งข้อมูลเซนเซอร์ DHT22 ทั้ง 2 ตัว แบบ JSON โดย ผลที่ปรากฏบน console ของ Firebase ข้อมูลจะขึ้นมาในลักษณะดังรูป

example8266

18 มีนาคม 2561
Ven.PanyaVuddh

(ตอนที่ 2) เชื่อมต่อ ESP32 กับ Firebase แบบไม่ต้องพึ่งไลบรารี่ภายนอก

logo
.          การติดต่อไปยัง Firebase นั้น แต่เดิมนั้น เราๆ เพื่อนๆชาว Maker จะใชักันโดยผ่านทางไลบรารี่ภายนอกที่มีคือ  FirebaseArduino เป็นหลัก ซึ่งก็มีความสะดวกใช้ดีครับ.  อย่างที่กล่าวไปในบทความที่แล้ว คือ บางทีก็เกิด Watchdog Time ขึ้นได้เวลาไปรวมหลายๆ โคดด้วยกัน.  ผู้เขียนก็พึ่งไปพบข้อความนึงของทางผู้พัฒนาได้กล่าวไว้ใน Readme ว่า ไลบรารี่ยังอยู่ในระหว่างการพัฒนาอย่างหนัก ยังมีความไม่เสถียรอยู่

The Arduino library (FirebaseArduino Library) is under heavy development, experimental, unversioned and its API is not stable.

            แสดงว่า ทางผู้พัฒนาก็ทราบปัญหาที่เกิดขึ้นอยู่ และ เมื่อผู้เขียน ต้องการจะให้ ESP32 คุยกับ Firebase เพื่อแชร์ข้อมูลระหว่าง ESP8266 และ Android อื่นๆด้วยกันบ้าง ก็พบอีกปัญหานึงอีกว่า ตัวไลบรารี่ FirebaseArduino ยังไม่ได้สนับสนุนให้ใช้กับ ESP32 อีกต่างหาก.

.           เพื่อแก้ปัญหาเหล่านี้  ในบทความนี้ เราจะมา ใช้ ESP32 ติดต่อไปยัง Firebase แบบกระทัดรัด เน้นความเสถียร โดยจะอาศัยเพียงฟังกชั่นเท่าที่จำเป็นที่มาใน ESP32 library Core กันดีกว่า

.           ก่อนอื่น เพื่อนๆ ต้องสร้างฐานข้อมูลบน Firebase ขึ้นมาก่อนครับ และ ให้เก็บค่าที่จำเป็น 2 ค่า คือ FIREBASE_HOST และ FIREBASE_AUTH ไว้ก่อน (หากใครยังไม่ทราบวิธี ให้ตามไปอ่านได้ใน บทความที่แล้ว) สำหรับการติดต่อจาก ESP32 ไปยัง Firebase ทำอย่างไร ไปดูกัน

เชื่อมต่อ จาก ESP32 มายังฐานข้อมูล Firebase

.           ใน sketch file ของ ESP32 ของเรา เริ่มต้น ให้กำหนด ค่า SSID , PASSWORD ของ ไวไฟ ที่เราต้องการให้ ESP32 ทำการเชื่อมต่อ และกำหนด ค่า FIREBASE_HOST และ FIREBASE_AUTH ที่เราได้ตระเตรียมไว้ พร้อมกำหนดชื่อฟังกชั่น 3 ฟังกชั่น ตามภาพ โดยมี

  • String TD32_Get_Firebase(String path)
    ใช้สำหรับ ESP32 รับค่า path บน Firebase ที่ต้องการ
  • int  TD32_Set_Firebase(String path, String value, bool path=false)
    ใช้สำหรับ ESP32 ส่งค่าไปยัง path บน Firebase โดยเขียนข้อมูลทับ path เดิม
  • int  TD32_Push_Firebase(String path, String value)
    ใช้สำหรับ ESP32 ส่งค่าไปยัง path บน Firebase โดยเขียนข้อมูลเพิ่มเข้าไปใน path เดิมไปเรื่อยๆ และไม่มีการเขียนทับข้อมูลเดิม

esp32_basic

.           จากนั้น ก็ให้เชื่อมต่อ ESP32 เข้าสู่ WiFi ตามปกติ

esp32_wificonnect

.           สำหรับการส่งข้อมูลขึ้นไปเก็บ ยัง Firebase ก็ใช้คำสั่งผ่านทางฟังกชั่น  TD32_Set_Firebase()  โดยข้อมูลจะไปทับค่าเดิมหากมี path บน Firebase อยู่แล้ว  หากไม่มี path บน Firebase จะเป็นการสร้าง path ขึ้นมาใหม่แล้วจัดเก็บ .   ตัวอย่างจะสมมติ จำลองค่าที่อ่านได้จาก sensor อุณหภูมิและความชื้น แล้วส่งขึ้น Firebase บน path ที่ชื่อ Sensor/Temp และ Sensor/Humid ตามลำดับ ดังนี้.

esp32_td32setfirebase

.           สำหรับการดึงค่าจากฐานข้อมูลบน Firebase ช่น ต้องการดึง ค่าจาก Sensor/Temp และ  Sensor/Humid ก็ใช้ ฟังก์ชั่น TD32_Get_Firebase() ตามตัวอย่างดังนี้

esp32_td32getfirebase

.           เพียงเท่านี้ เราก็สามารถ ส่งค่า และ รับค่า ไปมาระหว่าง ESP32 กับ Firebase ได้เช่นเดียวกับ ESP8266 แล้ว

.           อนึง หากต้องการจัดเก็บที่ path เดิมบน Firebase แต่ไม่ต้องการเขียนทับ ให้เพิ่มข้อมูลต่อไปเรื่อยๆ ให้ใช้ฟังกชั่น TD32_Push_Firebase() แทน  TD32_Set_Firebase() 

ตัวอย่าง Code ทั้งหมด
พร้อม ฟังก์ชั่น TD32_Set_Firebase() และ TD32_Get_Firebase()


#include <WiFiClientSecure.h>

//--------------------------------------------------------
#define SSID                  "---------------"  // ชื่อ Internet WiFi Router
#define PASSWORD              "---------------"  // รหัสลับของ WiFi Router

#define FIREBASE_HOST         "https://------------.firebaseio.com/"  // กำหนด host ของฐานข้อมูลบน Firebase
#define FIREBASE_AUTH         "----xxxxxx-xxxx--xxxxx---xxxx-xxxx--"  
//--------------------------------------------------------
/* ฟังกชั่น สำหรับ รับและส่งข้อมูลไปยัง Firebase ใช้สำหรับ ESP32 */
String  TD32_Get_Firebase(String path );               // รับค่า path จาก Firebase
int     TD32_Set_Firebase(String path, String value, bool push=false ); // ส่งค่าขึ้น Firebase  (ทับข้อมูลเดิมใน path เดิม)
int     TD32_Push_Firebase(String path, String value); // ส่งค่าขึ้น Firebase แบบ Pushing data  (เพิ่มเข้าไปใหม่เรื่อยๆใน path เดิม)
//--------------------------------------------------------

void setup() {
  Serial.begin(115200); Serial.println();
  WiFi.begin(SSID, PASSWORD);                       // ทำการเชื่อมต่อ WiFi
  while(WiFi.status()!= WL_CONNECTED ) delay(400);  // รอจนกว่าจะเชื่อมต่อ WiFi ได้
  Serial.println();
  Serial.println(WiFi.localIP());                   // แสดงค่า IP หลังเชื่อมต่อ WiFi สำเร็จ

  TD32_Set_Firebase("name", "\"Trident_ESP32\"");   // กำหนด ค่า "Trident_ESP32" ให้กับ ตัวแปร name บน Firebase
  Serial.println( TD32_Get_Firebase("name"));       // ดึงค่าของ name บน Firebase กลับลงมาแสดงบน ESP32
}

int cnt;
uint32_t timer;
void loop() {
  if( millis() -timer > 3000) {       // ทำการอ่านค่าและส่งทุกๆ 3 วินาที
    timer = millis();
    
    float t = (float)random(2000,4000)/100;  // อ่านค่า อุณหภูมิ (สมมติใช้ค่า random แทน)
    float h = (float)random(1000,9000)/100;  // อ่านค่า ความชื้น (สมมติใช้ค่า random แทน)

    TD32_Set_Firebase("Sensor/Temp", String(t));  // ตั้งค่า อุณหภูมิไปยัง Firebase ที่ Sensor/Temp
    TD32_Push_Firebase("Sensor/Humid", String(h)); // ตั้งค่า อุณหภูมิไปยัง Firebase ที่ Sensor/Humid แบบ Pushing data

    /* ตัวอย่าง อ่านค่า จาก Firebase ลงมายัง ESP32 */
    Serial.println(TD32_Get_Firebase("Sensor/Temp"));  // อ่านค่า Sensor/Temp จาก Firebase มาแสดง
    Serial.println(TD32_Get_Firebase("Sensor/Humid")); // อ่านค่า Sensor/Humid จาก Firebase มาแสดง
    Serial.printf("(%d) Temp : %.2f ; Humid : %.2f\n", ++cnt, t, h);  // แสดงค่าที่อ่านได้ทาง Serial Monitor
  }
}

/**********************************************************
 * ฟังกชั่น TD32_Set_Firebase
 * สำหรับ ESP32 ใช้กำหนด ค่า value ให้ path ของ Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคืนค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
// Root CA ของ https://www.firebaseio.com  
// ใช้ได้ตั้งแต่ 01/08/2018 17:21:49 GMT ถึง หมดอายุสิ้นสุด 27/03/2019 00:00:00 GMT
const char* FIREBASE_ROOT_CA= \
        "-----BEGIN CERTIFICATE-----\n" \
        "MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw\n" \
        "HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs\n" \
        "U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy\n" \
        "MTUwMDAwNDJaMFQxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg\n" \
        "U2VydmljZXMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzMw\n" \
        "ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKUkvqHv/OJGuo2nIYaNVW\n" \
        "XQ5IWi01CXZaz6TIHLGp/lOJ+600/4hbn7vn6AAB3DVzdQOts7G5pH0rJnnOFUAK\n" \
        "71G4nzKMfHCGUksW/mona+Y2emJQ2N+aicwJKetPKRSIgAuPOB6Aahh8Hb2XO3h9\n" \
        "RUk2T0HNouB2VzxoMXlkyW7XUR5mw6JkLHnA52XDVoRTWkNty5oCINLvGmnRsJ1z\n" \
        "ouAqYGVQMc/7sy+/EYhALrVJEA8KbtyX+r8snwU5C1hUrwaW6MWOARa8qBpNQcWT\n" \
        "kaIeoYvy/sGIJEmjR0vFEwHdp1cSaWIr6/4g72n7OqXwfinu7ZYW97EfoOSQJeAz\n" \
        "AgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH\n" \
        "AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHfCuFCa\n" \
        "Z3Z2sS3ChtCDoH6mfrpLMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYu\n" \
        "MDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdv\n" \
        "b2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dz\n" \
        "cjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYc\n" \
        "aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEA\n" \
        "HLeJluRT7bvs26gyAZ8so81trUISd7O45skDUmAge1cnxhG1P2cNmSxbWsoiCt2e\n" \
        "ux9LSD+PAj2LIYRFHW31/6xoic1k4tbWXkDCjir37xTTNqRAMPUyFRWSdvt+nlPq\n" \
        "wnb8Oa2I/maSJukcxDjNSfpDh/Bd1lZNgdd/8cLdsE3+wypufJ9uXO1iQpnh9zbu\n" \
        "FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy\n" \
        "7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV\n" \
        "c7o835DLAFshEWfC7TIe3g==\n" \
        "-----END CERTIFICATE-----\n";

int TD32_Set_Firebase(String path, String value, bool push ) {
  WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  int httpCode = 404; // Not Found

  String firebase_method = (push)? "POST " : "PUT ";
  ssl_client.setCACert(FIREBASE_ROOT_CA);
  if( ssl_client.connect( host.c_str(), 443)){
    String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
    String request = "";
          request +=  firebase_method + uri +" HTTP/1.1\r\n";
          request += "Host: " + host + "\r\n";
          request += "User-Agent: TD_ESP32\r\n";
          request += "Connection: close\r\n";
          request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n";
          request += "Content-Length: "+String( value.length())+"\r\n\r\n";
          request += value;

    ssl_client.print(request);
    while( ssl_client.connected() && !ssl_client.available()) delay(10);
    if( ssl_client.connected() && ssl_client.available() ) {
      resp      = ssl_client.readStringUntil('\n');
      httpCode  = resp.substring(resp.indexOf(" ")+1, resp.indexOf(" ", resp.indexOf(" ")+1)).toInt();
    }
    ssl_client.stop();    
  }
  else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return httpCode;
}

/**********************************************************
 * ฟังกชั่น TD32_Push_Firebase
 * สำหรับ ESP32 ใช้กำหนด ค่า value ให้ path ของ Firebase
 * แบบ Pushing data (เติมเข้าไปที่ path เรื่อยๆ ไม่ทับของเดิม)
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคืนค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
int TD32_Push_Firebase(String path, String value){
    return TD32_Set_Firebase(path,value,true);
}
/**********************************************************
 * ฟังกชั่น TD32_Get_Firebase 
 * ใช้สำหรับ EPS32 รับ ค่า value ของ path ที่อยู่บน Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * path เป็น ข้อมูลประเภท String
 * คืนค่าของฟังกชั่น คือ value ของ path ที่กำหนด ในข้อมูลประเภท String
 * 
 **********************************************************/
String TD32_Get_Firebase(String path ) {
  WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  String value = "";
  ssl_client.setCACert(FIREBASE_ROOT_CA);
  if( ssl_client.connect( host.c_str(), 443)){
    String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
    String request = "";
          request += "GET "+ uri +" HTTP/1.1\r\n";
          request += "Host: " + host + "\r\n";
          request += "User-Agent: TD_ESP32\r\n";
          request += "Connection: close\r\n";
          request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n\r\n";

    ssl_client.print( request);
    while( ssl_client.connected() && !ssl_client.available()) delay(10);
    if( ssl_client.connected() && ssl_client.available() ) {
      while( ssl_client.available()) resp += (char)ssl_client.read();
      value = resp.substring( resp.lastIndexOf('\n')+1, resp.length()-1);
    }
    ssl_client.stop();    
  } else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return value;
}

ผลจากตัวอย่างโคด

.           ผลโคดข้างต้น ข้อมูลจาก ESP32 ที่ส่งมายัง Firebase เมื่อดูจาก console ของ Firebase จะแสดงให้เห็นถึงความแตกต่างระหว่างการส่งข้อมูลด้วย TD32_Set_Firebase() และ TD32_Push_Firebase() ปรากฏดังตัวอย่าง

FirebaseView2.png

(ตอนที่ 1) เชื่อมต่อ ESP8266 กับ Firebase แบบไม่ต้องพึ่งไลบรารี่ภายนอก

logo

.           Firebase นั้นเป็น ฐานข้อมูล(database) อีกแบบหนึ่งที่เปิดให้บริการโดย Google  ในที่นี้จะ ใช้ในส่วนของ realtime database  อย่างเดียว  โดยเราจะใช้ Firebase เป็นจุดรวมข้อมูล ที่เราจะทำการส่งขึ้นไปบน internet ผ่านทาง esp8266/esp32  และ จุดรวมข้อมูลของเรานี้ เราจะสามารถใช้อุปกรณ์อื่นๆ เช่น มือถือ หน้าเวป หรือแม้แต่ esp8266/esp32 อีกตัว เข้าไปดึงข้อมูล เพื่อมาใช้ประโยชน์ต่อไป

.           ตามปกติการรับส่ง esp8266 ระหว่าง Firebase เราจะใช้ผ่านไลบรารี่ ซึ่งก็คือ ไลบรารี่ FirebaseArduino นั่นเอง ซึ่งก็มีความสะดวกใช้ แต่ในบางครั้ง ก็สร้างความเทอะทะ และอาจเกิด WatchDog Time เวลาไปรวมๆกันทำงานผ่านไลบรารี่อื่นๆ หลายๆไลบรารี่พร้อมๆกัน

.           เพื่อการลดความเทอะทะ และ สร้างความยืดหยุ่น บทความนี้จะมาสร้าง Function แบบที่กระทัดรัด สำหรับ รับและส่ง ข้อมูลที่เราต้องการไปยัง Firebase โดยอาศัยเพียงฟังกชั่นพื้นฐานที่มาจาก ESP8266 Arduino Core ที่มีอยู่แล้วกันดีกว่า

1 เพิ่มฐานข้อมูล บน Firebase กันก่อน

.           ให้เข้าไปที่  https://console.firebase.google.com/   แล้วกดเพิ่ม Project (Add Project)

firebase_console

.           กำหนด ชื่อ โปรเจค (ในที่นี้คือ ฐานข้อมูลสำหรับจะเก็บข้อมูลนั่นแหละครับ) และสร้างให้เรียบร้อย

create_database

.           หลังสร้างเสร็จให้เข้าไปที่ Project ที่เราพึ่งทำการสร้าง แล้วเลือก database ตามภาพ

select_database

.           จากนั้นก็เปิดใช้งาน ฐานข้อมูล แบบ Realtime Database ที่เราจะใช้ ESP8266/ESP32 มาเก็บ/ดึงข้อมูล

realtime_database

.           จากนั้น เราจะต้องไป copy ค่าที่สำคัญ อยู่ 2 ค่าที่จะนำไปใช้ใน ESP8266/ESP32 เพื่อการเชื่อมต่อมายัง Firebase โดยค่า แรกที่เราต้องใช้คือ ค่า FIREBASE_HOST โดยเข้าไป copy ได้จากที่
FIREBASE_HOST

.           ส่วนอีกค่า จะเปรียบเสมือนเป็น รหัสลับ สำหรับเข้ามายังฐานข้อมูลนี้ เพื่อใช้งาน ค่าที่ 2 นี้ให้เข้าไปที่  Project Overview > Project Settings

project_setting

.           ใน Project Settings  ในเลือก  SERVICE ACCOUNTS > Database Secrets

Database_secrets

.           ใน Database Secrets  ให้เลื่อนมาด้านล่าง จะเห็น ชื่อ Database ที่สร้างไป พร้อมมีค่า Secret (รหัสลับ) เห็น เลือกปุ่ม Show แล้ว copy ค่า รหัสลับนี้เก็บไว้ ค่านี้เราจะคือค่า FIREBASE_AUTH  ที่เราจะใช้ขณะต้องการนำ ESP8266/ESP32 มาเชื่อมต่อกับ ฐานข้อมูลนี้
FIREBASE_AUTH.png

.           ก็เป็นอัน ว่า เราจะได้ ค่าที่สำคัญที่จำเป็น 2 ค่า คือ FIREBASE_HOST และ FIREBASE_AUTH สำหรับใช้เชื่อมต่อจาก ESP8266/ESP32

2 เชื่อมต่อ จาก ESP8266 มายังฐานข้อมูล Firebase

.           ขั้นตอนการ กำหนด ข้อมูล และ ดึงข้อมูลจาก ESP8266 ไปยัง ฐานข้อมูล Firebase จะเป็นขั้นตอนที่ง่ายมากๆ

.           ใน sketch file บน ArduinoIDE ให้กำหนด ค่า SSID , PASSWORD ของ ไวไฟ ที่เราต้องการให้ ESP8266/ESP32 ทำการเชื่อมต่อ และกำหนด ค่า FIREBASE_HOST และ FIREBASE_AUTH ที่เราได้ตระเตรียมไว้ในขั้นตอนข้างต้น พร้อมกำหนดชื่อฟังกชั่น 3 ฟังกชั่น ตามภาพ โดยมี

  • String TD_Get_Firebase(String path)   
    ใช้สำหรับ ESP8266 รับค่า path บน Firebase ที่ต้องการ
  • int  TD_Set_Firebase(String path, String value, bool path=false)
    ใช้สำหรับ ESP8266 ส่งค่าไปยัง path บน Firebase โดยเขียนข้อมูลทับ path เดิม
  • int  TD_Push_Firebase(String path, String value)
    ใช้สำหรับ ESP8266 ส่งค่าไปยัง path บน Firebase โดยเขียนข้อมูลเพิ่มเข้าไปใน path เดิมไปเรื่อยๆ และไม่มีการเขียนทับข้อมูลเดิม

esp_code01

.           จากนั้น ให้เชื่อมต่อ ESP8266 เข้าสู่ WiFi ตามปกติ  (ตามตัวอย่างจะเป็น code อย่างกระทัดรัด)

wifi_connect

.           สำหรับการส่งข้อมูลขึ้นไปเก็บ ยัง Firebase สมมติ ให้ เรามีข้อมูล อุณหภูมิและความชื้นที่อ่านได้จาก sensor แล้วจะทำการส่งขึ้นไปเก็บยัง  ตัวแปร  Sensor/Temp  และ Sensor/Humid บน Firebase ก็ใช้คำสั่งผ่านทางฟังกชั่น  TD_Set_Firebase() ดังตัวอย่างนี้

set_firebase

*** หมายเหตุ การส่งทุกครั้งให้ แปลงเป็นข้อมูล String เสียก่อน ***

.           สำหรับการดึงค่าจากฐานข้อมูลบน Firebase เช่น ต้องการดึง ค่าจาก Sensor/Temp และ  Sensor/Humid ก็ใช้ ฟังกชั่น TD_Get_Firebase() ตามตัวอย่างดังนี้

get_Firebase

.           อนึง หากต้องการจัดเก็บที่ path เดิมบน Firebase แต่ไม่ต้องการเขียนทับ ให้เพิ่มข้อมูลต่อไปเรื่อยๆ ให้ใช้ฟังกชั่น TD_Push_Firebase() แทน  TD_Set_Firebase() 

.           โดยข้อมูล จะไปขึ้นที่ค่าของ Sensor/Temp และ Sensor/Humid
บน Firebase ดังนี้

Firebase_view

เพียงเท่านี้ เราก็สามารถแชร์ ค่าต่างๆ ไปมาระหว่าง ESP8266 กับ ฐานข้อมูล Firebase ได้โดยง่าย
ทำให้เราสามารถนำไปใช้แชร์ ข้อมูลนี้ร่วมกับ อุปกรณ์อื่นๆได้ต่อไปได้

ตัวอย่าง Code ทั้งหมด
พร้อม ฟังก์ชั่น TD_Set_Firebase() และ TD_Get_Firebase()


#include <WiFiClientSecureAxTLS.h>
#include <ESP8266WiFi.h>

//--------------------------------------------------------
#define SSID          "----------" // ชื่อ Internet WiFi Router
#define PASSWORD      "----------" // รหัสลับของ WiFi Router

#define FIREBASE_HOST "https://--------------------------/"
#define FIREBASE_AUTH "***********************************"
//--------------------------------------------------------
/* ฟังกชั่น สำหรับ ESP8266 รับและส่งข้อมูลไปยัง Firebase*/
String  TD_Get_Firebase(String path );                                // สำหรับรับ
int     TD_Set_Firebase(String path, String value, bool push=false ); // สำหรับส่ง
int     TD_Push_Firebase(String path, String value ); // สำหรับส่งแบบ Pushing data
//--------------------------------------------------------

void setup() {
  Serial.begin(115200); Serial.println();
  WiFi.begin(SSID, PASSWORD);             // ทำการเชื่อมต่อ WiFi
  while(!WiFi.isConnected()) delay(400);  // รอจนกว่าจะเชื่อมต่อ WiFi ได้
  Serial.println(WiFi.localIP());         // แสดงค่า IP หลังเชื่อมต่อ WiFi สำเร็จ
}
int cnt;
uint32_t timer;
void loop() {
  if( millis() -timer > 3000) {       // ทำการอ่านค่าและส่งทุกๆ 3 วินาที
    timer = millis();
    
    float t = (float)random(2000,4000)/100;  // อ่านค่า อุณหภูมิ (สมมติใช้ค่า random แทน)
    float h = (float)random(1000,9000)/100;  // อ่านค่า ความชื้น (สมมติใช้ค่า random แทน)

    TD_Set_Firebase("Sensor/Temp", String(t));  // ตั้งค่า อุณหภูมิไปยัง Firebase ที่ Sensor/Temp
    TD_Set_Firebase("Sensor/Humid", String(h)); // ตั้งค่า อุณหภูมิไปยัง Firebase ที่ Sensor/Humid
    Serial.println(TD_Get_Firebase("Sensor/Temp"));  // อ่านค่า Sensor/Temp จาก Firebase มาแสดง
    Serial.println(TD_Get_Firebase("Sensor/Humid")); // อ่านค่า Sensor/Humid จาก Firebase มาแสดง
    Serial.printf("(%d) Temp : %.2f ; Humid : %.2f\n", ++cnt, t, h);  // แสดงค่าที่อ่านได้ทาง Serial Monitor
  }
}

/**********************************************************
 * ฟังกชั่น TD_Set_Firebase 
 * ใช้สำหรับ ESP8266 กำหนด ค่า value ให้ path ของ Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคินค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
#ifndef FIREBASE_FINGERPRINT  // Fingerprint ของ https://www.firebaseio.com
// ใช้ได้ตั้งแต่ 01/08/2018 17:21:49 GMT ถึง หมดอายุสิ้นสุด 27/03/2019 00:00:00 GMT
//#define FIREBASE_FINGERPRINT  "6F D0 9A 52 C0 E9 E4 CD A0 D3 02 A4 B7 A1 92 38 2D CA 2F 26"
// ใช้ได้ตั้งแต่ 02/2020 ...  
#define FIREBASE_FINGERPRINT  "03 D6 42 23 03 D1 0C 06 73 F7 E2 BD 29 47 13 C3 22 71 37 1B"
#endif

int TD_Set_Firebase(String path, String value , bool push) {
  axTLS::WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  int httpCode = 404; // Not Found

  String firebase_method = (push)? "POST " : "PUT ";
  if( ssl_client.connect( host, 443)){
    if( ssl_client.verify( FIREBASE_FINGERPRINT, host.c_str())){
      String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
      String request = "";
            request +=  firebase_method + uri +" HTTP/1.1\r\n";
            request += "Host: " + host + "\r\n";
            request += "User-Agent: ESP8266\r\n";
            request += "Connection: close\r\n";
            request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n";
            request += "Content-Length: "+String( value.length())+"\r\n\r\n";
            request += value;

      ssl_client.print(request);
      while( ssl_client.connected() && !ssl_client.available()) delay(10);
      if( ssl_client.connected() && ssl_client.available() ) {
        resp      = ssl_client.readStringUntil('\n');
        httpCode  = resp.substring(resp.indexOf(" ")+1, resp.indexOf(" ", resp.indexOf(" ")+1)).toInt();
      }
    }else{
      Serial.println("[Firebase] can't verify SSL fingerprint");
    }
    ssl_client.stop();    
  } else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return httpCode;
}
/**********************************************************
 * ฟังกชั่น TD_Push_Firebase 
 * ใช้สำหรับ ESP8266  กำหนด ค่า value ให้ path ของ Firebase แบบ Pushing Data (POST method)
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * ทั้ง path และ  value ต้องเป็น ข้อมูลประเภท String
 * และ คืนค่าฟังกชั่น กลับมาด้วย http code
 * 
 * เช่น หากเชื่อมต่อไม่ได้ จะคินค่าด้วย 404 
 * หากกำหนดลงที่ Firebase สำเร็จ จะคืนค่า 200 เป็นต้น
 * 
 **********************************************************/
int TD_Push_Firebase(String path, String value ){
  return TD_Set_Firebase(path,value, true);
}
/**********************************************************
 * ฟังกชั่น TD_Get_Firebase
 * ใช้สำหรับ ESP8266  รับ ค่า value ของ path ที่อยู่บน Firebase
 * โดย path อยู่ใน รูปแบบ เช่น "Room/Sensor/DHT/Humid" เป็นต้น
 * 
 * path เป็น ข้อมูลประเภท String
 * คืนค่าของฟังกชั่น คือ value ของ path ที่กำหนด ในข้อมูลประเภท String
 * 
 **********************************************************/
String TD_Get_Firebase(String path ) {
  axTLS::WiFiClientSecure ssl_client;
  String host = String(FIREBASE_HOST); host.replace("https://", "");
  if(host[host.length()-1] == '/' ) host = host.substring(0,host.length()-1);
  String resp = "";
  String value = "";
  if( ssl_client.connect( host, 443)){
    if( ssl_client.verify( FIREBASE_FINGERPRINT, host.c_str())){
      String uri = ((path[0]!='/')? String("/"):String("")) + path + String(".json?auth=") + String(FIREBASE_AUTH);      
      String request = "";
            request += "GET "+ uri +" HTTP/1.1\r\n";
            request += "Host: " + host + "\r\n";
            request += "User-Agent: ESP8266\r\n";
            request += "Connection: close\r\n";
            request += "Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n\r\n";

      ssl_client.print( request);
      while( ssl_client.connected() && !ssl_client.available()) delay(10);
      if( ssl_client.connected() && ssl_client.available() ) {
        while( ssl_client.available()) resp += (char)ssl_client.read();
        value = resp.substring( resp.lastIndexOf('\n')+1);
      }
    }else{
      Serial.println("[Firebase] can't verify SSL fingerprint");
    }
    ssl_client.stop();    
  } else {
    Serial.println("[Firebase] can't connect to Firebase Host");
  }
  return value;
}

หมายเหตุ

  • ใน ESP8266 สามารถทำงานได้ ตั้งแต่ ESP8266 Arduino Core รุ่น 2.4.1 ล่าสุด จนถึง 2.3.0 ได้ (Core รุ่นต่ำกว่านี้ไม่ได้ทดสอบ) และ สามารถ ใช้ร่วมกับการส่ง LineNotify ได้อย่างไม่มีปัญหา
  • สำหรับ ESP32 ติดตามได้ในบทความหน้าครับ