ไม่ได้เขียนบทความเกี่ยวกับไมโครคอนโทรลเลอร์นานแล้วเหมือนกันนะเนี่ย เนื่องจากหลังๆมานี้ไม่ได้จับฮาร์ดแวร์ซักเท่าไร จนกระทั่งตอนงาน Google I/O ได้ชุดทดลองสำหรับบอร์ดไมโครคอนโทรลเลอร์มาเล่นนี่แหละ

ถึงแม้ว่าในบทความนี้จะใช้บอร์ด Wio Link แต่ก็สามารถใช้บอร์ดอื่นๆที่เป็น ESP8266 ได้เหมือนกันนะ ถึงแม้ว่าจะเป็นบอร์ดคนละตัว แต่ก็ใช้โค้ดตัวเดียวกันได้
Firebase Realtime Database จะเรียกสั้นๆว่า Firebase RTDB นะจ๊ะ

ในช่วงที่ไปงาน Google I/O 16 ก็ได้มีงานอื่นๆของทาง Google ด้วย ซึ่งพี่ที่ไปด้วยกันก็ได้ไปร่วมงานของ GDG ซึ่งในครั้งนี้มี Workshop เกี่ยวกับ Firebase และบอร์ด ESP8266 ให้ได้ลองทำกันด้วย

โดยใน Workshop จะให้กล่องอุปกรณ์ขนาดใหญ่ ที่ข้างในมีชุดตัวต่อ Lego แล้วก็บอร์ดไมโครคอนโทรลเลอร์ที่มาพร้อมกับเซ็นเซอร์ต่างๆให้อย่างครบครัน

แอบแปลกใจเหมือนกันว่าเอา Lego มาทำอะไรหรือว่า Lego เริ่มหันมาจับมือกับ Google? (เพราะปกติ Lego มีชุดหุ่นยนต์สำหรับเขียนโปรแกรมเป็นของตัวเองอยู่แล้ว) มารู้ทีหลังก็คือเป็นตัวช่วยดึงดูดความสนใจเฉยๆ เพราะ Workshop จะแบ่งกลุ่มละ 2 คน ช่วยกันทำ ซึ่งคนแรกก็ต้องทำในส่วนของบอร์ดไมโครฯ และอีกคนก็นั่งต่อ Lego นั่นเอง….

พอมานั่งดูชิ้นส่วน Lego ก็พบว่ามันคือชุด The Big Bang Theory นี่นาาาาาาา เข้าใจเลือกมาเหมือนกันนะ ให้ความรู้สึกเนิร์ดๆดี

ก็เลยใช้เวลาไปสองชั่วโมงเพื่อต่อจนเสร็จ

ก็ขอจบบทความรีวิวชุดตัวต่อ Lego : The Big Bang Theory เพียงเท่านี้ครับ ส่วนคราวหน้าจะเป็นชุดไหน น่าสนใจมากแค่ไหน ราคาเท่าไร ก็อย่าลืมติดตามกันนะครับ บ๊ายบายยยยยยยยย

ลืมไปว่าไม่ได้มารีวิว Lego…

กลับมาที่บอร์ดไมโครฯสำหรับ Workshop นี้ และสิ่งแรกที่เจ้าของบล็อกคิดอยู่ในหัวสมองก็ประมาณว่า

"ในที่สุดก็จะได้จับ Brillo แล้ว เย้!! ได้ลองใช้กับ Firebase ด้วย เย้!!"

อ่าวเฮ้ย ทำไมเป็นบอร์ด Wio Link ของ Seeed Studio ซะงั้นล่ะ!!!

พอไปดูรายละเอียดอีกที ก็เพิ่งจะเข้าใจว่ามันเป็น Workshop สำหรับ Firebase​ RTDB กับ Arduino Platform ซึ่งไม่ได้เกี่ยวอะไรกับ Brillo เล้ยยยยย (ในงาน Google I/O 16 ก็ไม่มีอะไรที่เกี่ยวกับ Brillo เปิดตัวด้วยนะ…)

Wio Link เป็นบอร์ดไมโครคอนโทรลเลอร์ที่ใช้ชิป ESP8266 โดยที่สามารถใช้งานได้เลย เพราะ Wio Link ได้เตรียมทุกๆอย่างไว้ให้พร้อมเกือบหมดแล้ว ไม่ว่าจะจุดต่อ JST 2.0, ปุ่มควบคุมการทำงาน จุดต่อ Micro USB และจุดต่อแบตเตอรีภายนอกที่สามารถชาร์จได้ด้วย

ก่อนจะไปต่อ ขอพูดถึง ESP8266 นิดหน่อยละกัน เผื่อมีผู้ที่หลงเข้ามาอ่านคนใดที่ไม่รู้จัก

ย้อนไปราวๆ 3–4 ปี (นับจากวันที่เขียนบทความนี้) บริษัท Espressif Systems ได้พัฒนาชิป ESP8266 ขึ้นมาแล้วชิปก็ได้ถูกนำไปทำเป็นโมดูลโดยบริษัท AI-Thinker เพื่อขายให้กับเหล่านักพัฒนาทั่วๆโลกอีกทีหนึ่งในราคาไม่ถึงสองร้อย (เมื่อก่อนพวก WiFi Module ที่มีอยู่ในตลาด ราคาถูกสุดก็ประมาณพันกว่าๆแล้ว) ซึ่งนับจากนั้นก็ถือว่าเป็นจุดเปลี่ยนที่ทำให้ก้าวเข้าสู่ยุคของ IoT ได้ง่ายขึ้น (ทั้งถูก ฉลาด และเป็น Access Point ได้ด้วยล่ะ!!)

ถึงแม้ว่าเดิมที ESP8266 นั้นทำหน้าที่เป็น WiFi Module ก็จริง แต่ตัวมันเองแท้จริงแล้วก็คือบอร์ดไมโครคอนโทรลเลอร์ตัวหนึ่งที่สามารถเข้าไปเขียนโปรแกรมสั่งงานมันได้ จึงมีการเอาเจ้า ESP8266 ไปใช้เป็นบอร์ดไมโครคอนโทรลเลอร์โดยตรงซะเลย ซึ่งก็มีการทำออกมาเป็นบอร์ดต่างๆ และ Wio Link ก็คือหนึ่งในนั้นนั่นเอง และนอกจากนี้ยังมีการพัฒนาให้ ESP8266 สามารถใช้งานได้สะดวกขึ้นและแพร่หลายมากขึ้น จนตอนนี้มันกลายเป็นบอร์ดที่สามารถเขียนด้วย Arduino IDE ได้แล้ว

ดังนั้นในบทความนี้ก็จะเป็นการใช้ ESP8266 เชื่อมต่อกับ Firebase RTDB ด้วย Arduino IDE นั่นเอง

และนอกจาก Wio Link แล้ว ในกล่องก็จะมี Module ต่างๆที่ใช้กับ Wio Link ไม่ว่าจะเป็น LED, Button, Light Sensor, 7-Segment, Smoke Sensor และอื่นๆมากมายที่แถมมาให้ครบชุดในกล่องนี้ เรียกได้ว่าเอาไปลองทำอะไรเล่นได้หลายๆอย่างเลยทีเดียว

สำหรับจุดต่อ JST 2.0 ของ Wio Link จะมีทั้งหมด 6 จุดด้วยกัน ซึ่งมีเลข Pin และคุณสมบัติต่างกันดังนี้

ถึงแม้จะดูว่ามีให้ใช้งานน้อย แต่เอาเข้าจริงเท่านี้ก็เพียงพอและครอบคลุมกับการทำงานหลายๆอย่างแล้วล่ะ

เตรียมโปรแกรมต่างๆให้พร้อม

  • ดาวน์โหลด Library ที่ชื่อว่า Firebase-ESP8266 พัฒนาโดย mobizt ให้เรียบร้อย โดยสามารถดาวน์โหลดผ่าน Library Manager ได้เลย
  • เลือก Board เป็น Seeed Wio Link
  • เลือก Upload Speed เป็น 115200
  • เลือก CPU Frequency เป็น 80Mhz
  • เลือก Flash Size เป็น 4M (FS:2MB OTA)
  • เลือก Port เป็นอันไหนก็ได้ที่เสียบ Wio Link มันแล้วโผล่ขึ้นมา (ของเจ้าของบล็อกเป็น /dev/cu.SLAB_USBtoUART)

เพื่อความมั่นใจว่าบอร์ดสามารถทำงานได้ปกติ ให้สร้างโปรเจคเปล่าๆขึ้นมาแล้วกด Upload เพื่อดูว่าสามารถใช้งานได้ปกติมั้ย

เมื่อพร้อมแล้วก็มาเริ่มกันเถอะ~

เนื่องจากในบทความนี้ต้องการใช้งาน Firebase RTDB ดังนั้นประกาศแค่ ESP8266WiFi.h กับ FirebaseESP8266.h ไว้ในโค้ดเท่านั้น

#include <FirebaseESP8266.h>
#include <ESP8266WiFi.h>

void setup() {
    // put your setup code here, to run once:

}

void loop() {
    // put your main code here, to run repeatedly:
}

และถ้าอยากจะสั่งให้ต่อ WiFi ซักแห่ง ก็ให้กำหนดค่า SSID และ Password ให้ถูกต้องโดยใช้คำสั่งของ ESP8266Wifi.h แบบนี้

#include <FirebaseESP8266.h>
#include <ESP8266WiFi.h>

#define WIFI_SSID "YOUR_WIFI_SSID" 
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"

void setup() {
    connectWifi();

    // Do something
}

void loop() {
    // Do something
}

void connectWifi() {
    Serial.begin(9600);
    Serial.println(WiFi.localIP());
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("connecting");
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(500);
    }
    Serial.println();
    Serial.print("connected: ");
    Serial.println(WiFi.localIP());
}

สามารถดูสถานะการเชื่อมต่อของ WiFi ผ่าน Serial Monitor ได้เลย เมื่อเชื่อมต่อสำเร็จก็จะแสดง IP Address ให้เห็นแบบนี้

การติดต่อใช้งาน Firebase RTDB

อย่างแรกเลยคือต้องไปสร้างโปรเจคของ Firebase บน Console ให้เรียบร้อยเสียก่อน แล้วเปิดใช้งาน Firebase RTDB ในโปรเจคนั้นๆให้เรียบร้อย

หลังจากที่สร้างเสร็จเรียบร้อยแล้ว จะได้ Hostname สำหรับใช้งาน Firebase RTDB เพื่อนำไปใช้งาน

โดยการนำ Hostname ไปใช้งานจะใช้แค่ส่วนที่เป็น Domain เท่านั้น ไม่จำเป็นต้องมี https:// และ / ปิดท้าย ดังนั้นของเจ้าของบล็อกก็จะเป็น wio-link-3a2a9.firebaseio.com (ของแต่ละคนจะได้ไม่เหมือนกัน)

นอกจากนี้จะต้องใช้ Database Secret ด้วย โดยเข้าไปดูได้ใน Project settings > Service accounts > Database secrets (โดย Project settings กดที่ปุ่มรูปเฟืองที่อยู่ข้างๆเมนู Project Overview)

Firebase ได้ประกาศให้ Database secrets เป็น Deprecated ไปแล้ว และแนะนำให้เปลี่ยนไปใช้ Firebase Admin SDK แทน แต่เนื่องจากต้องการใช้งานใน ESP8266 จึงไม่สามารถใช้ Firebase Admin SDK ได้ ทำให้ต้องใช้ Database secrets ต่อไป

โดยจะพบว่าในโปรเจคได้มีการสร้าง Database Secret ไว้ให้แล้ว สามารถกดดูรหัสดังกล่าวเพื่อนำไปใช้งานได้เลย

เตรียมพร้อมเสร็จเรียบร้อย เท่านี้ก็เหลือในส่วนของโค้ดบน Wio Link แล้วล่ะ

ถึงขั้นตอนนี้ผู้ที่หลงเข้ามาอ่านจะต้องมี Hostname และ Database Secret ของโปรเจค Firebase นั้นๆเก็บไว้เพื่อนำไปใช้งานในโค้ดนะ

มาดูโค้ดใน Firebase ESP8266 Library กันต่อ

สำหรับการเริ่มใช้งาน Firebase จะต้องกำหนด Hostname และ Database Secret แบบนี้ก่อนทุกครั้ง (และควรต่อ Internet ได้ด้วย)

...
#define FIREBASE_HOST "wio-link-AAAAA.firebaseio.com"
#define FIREBASE_KEY "ACpkeJGSDAAAAAAAApIGma0RebxLGv81AAAAAAAA"

void setup() {
    ...
    Firebase.begin(FIREBASE_HOST, FIREBASE_KEY);
}
...

แค่นี้เองหรอ? มันก็มีแค่นี้แหละ เพียงแค่ใช้คำสั่ง begin แล้วกำหนด Hostname กับ Database Secret ลงไป ก็สามารถใช้งานได้แล้ว

โดย Library ดังกล่าวนั้นจะรองรับข้อมูลได้หลากหลายประเภท ไม่ว่าจะเป็น Boolean, Integer, Float, Double, String, Blob และ JSON

การกำหนดข้อมูลลงใน Database

สามารถทำได้ทั้ง Set หรือ Push ได้เหมือนกับ Platform อื่นๆเลย โดยที่ Set คือการกำหนดค่าทับแทนที่ของเก่าลงไป ส่วน Push คือการเพิ่มข้อมูลชุดใหม่เข้าไป ซึ่งการจะใช้ Set หรือ Push นั้นขึ้นอยู่กับรูปแบบโครงสร้างฐานข้อมูลที่กำหนดไว้ใน Firebase RTDB

และถ้าต้องการอัปเดทข้อมูลแค่บางส่วนก็ให้ใช้ Update แทน

การกำหนดข้อมูลด้วย Set

Set คือการกำหนดค่าแทนที่ข้อมูลเดิมที่มีอยู่บน Firebase RTDB ซึ่งจะเหมาะกับรูปแบบข้อมูลที่ไม่จำเป็นต้องเก็บข้อมูลเดิมไว้

โดยมีรูปแบบคำสั่งดังนี้

FirebaseData firebaseData;
...
bool state = true;
if(Firebase.setBool(firebaseData, "/wio-link-01/state", state)) {
    Serial.println("Added"); 
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

โดยที่ FirebaseData มีไว้สำหรับเก็บค่าผลลัพธ์จากการทำงานของคำสั่งต่างใน Firebase ESP8266 ซึ่งให้ประกาศไว้เป็น Global Variable ไปเลย

และเมื่อดูที่คำสั่ง setBool(...) จะเห็นว่าต้องกำหนดค่าทั้งหมด 3 อย่างด้วยกัน คือ FirebaseData, Path ของข้อมูลใน Firebase RTDB และค่าที่การกำหนด

โดยคำสั่ง setBool(...) จะให้ผลลัพธ์เป็น Boolean เพื่อให้เช็คได้ว่าคำสั่งนั้นทำงานสำเร็จหรือไม่ ถ้าสำเร็จก็จะส่ง True กลับมา แต่ถ้าไม่สำเร็จก็จะเป็น False แล้วให้ไปเช็คสาเหตุของปัญหาจาก FirebaseData อีกทีหนึ่ง ด้วยคำสั่ง errorReason()

ซึ่งคำสั่งข้างบนนี้จะเป็นคำสั่งเพื่อใช้อธิบายซะมากกว่า เพราะในการใช้งานจริงๆก็มีลักษณะเป็นแบบนี้

#include <FirebaseESP8266.h>
#include <ESP8266WiFi.h>

#define WIFI_SSID "......" 
#define WIFI_PASSWORD "......"

#define FIREBASE_HOST "......"
#define FIREBASE_KEY "......"

FirebaseData firebaseData;
bool state = false;

void setup() {
    connectWifi();
    Firebase.begin(FIREBASE_HOST, FIREBASE_KEY);
}

void loop() {
    delay(1000);
    state = !state;
    if(Firebase.setBool(firebaseData, "/wio-link-01/state", state)) {
        Serial.println("Added"); 
    } else {
        Serial.println("Error : " + firebaseData.errorReason());
    }
}

void connectWifi() {
    ...
}

จากนั้นก็ให้โปรแกรมเริ่มทำงานแล้วเปิด Serial Monitor ขึ้นมาเพื่อดูว่าทำงานได้ถูกต้องหรือไม่ โดยโค้ดตัวอย่างนี้คือการกำหนดค่า Boolean ที่อยู่ใน Firebase RTDB ตาม Path ที่กำหนดไว้ และค่าจะเปลี่ยนสลับไปมาระหว่าง True กับ False ทุกๆ 1 วินาที

เนื่องจาก Firebase RTDB เป็น Database ที่มีจุดเด่นในเรื่องของการทำงานแบบ Realtime ดังนั้นตอนที่บอร์ด Wio Link ทำการอัปเดตค่าบน Database ทุกๆ 1 วินาที ก็จะสามารถเห็นการเปลี่ยนแปลงของข้อมูลดังกล่าวได้จาก Firebase Console ได้ทันที

ซึ่งการใช้คำสั่ง Set เพื่อกำหนด Path เดิมซ้ำๆก็จะเป็นการกำหนดค่าแทนที่ของเก่าตลอดเวลานั่นเอง

และถ้าอยากจะกำหนดข้อมูลเป็นแบบ JSON ก็สามารถใช้คำสั่งแบบนี้ได้เลย

FirebaseData firebaseData;
...
bool state = ...
double latitude = ...
double longitude = ...
double currentTimestamp = ...

FirebaseJson location;
location.set("latitude", latitude);
location.set("longtitude", longitude);

FirebaseJson data;
data.set("location", location);
data.set("isOn", state);
data.set("timestamp", currentTimestamp);

if(Firebase.setJSON(firebaseData, "/wio-link-01", data)) {
    Serial.println("Added"); 
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

เนื่องจาก Library ตัวนี้รองรับ JSON ผ่าน FirebaseJson  จึงช่วยให้นักพัฒนาสามารถกำหนดรูปแบบข้อมูลเพื่อส่งขึ้น Firebase RTDB ได้ตามใจชอบ

การกำหนดข้อมูลด้วย Push

การใช้ Push จะแตกต่างจาก Set เพราะข้อมูลจะถูกเพิ่มเข้าไปแทน ซึ่งจะเหมาะกับรูปแบบข้อมูลที่จำเป็นต้องเก็บข้อมูลเพิ่มเข้าไปเรื่อยๆ โดยข้อมูลแต่ละชุดจะมี Unique ID เป็นของตัวเอง (Firebase RTDB สร้างขึ้นมาให้เวลาใช้คำสั่ง Push ไม่สามารถกำหนดเองได้)

โดยมีรูปแบบคำสั่งแบบนี้

FirebaseData firebaseData;
...
bool state = ...
double currentTimestamp = ...

FirebaseJson data;
data.set("isOn", state);
data.set("timestamp", currentTimestamp);

if(Firebase.pushJSON(firebaseData, "/wio-link-01/records", data)) {
    Serial.println("Pushed : " + firebaseData.pushName()); 
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

จะเห็นว่าลักษณะคำสั่งของ Push นั้นจะค่อนข้างคล้ายกับ Set แต่เนื่องจากมีการสร้าง Unique ID สำหรับข้อมูลชุดนั้นๆด้วย ถ้าต้องการเรียกใช้  Unique ID ของข้อมูลชุดนั้นหลังจากที่เพิ่มเข้าไปเสร็จแล้ว ให้ใช้คำสั่ง FirebaseData.pushName() ได้เลย

การอัปเดตข้อมูลเดิมที่มีอยู่แล้วใน Database

ในบางครั้งการใช้ Set หรือ Push ก็อาจจะไม่ตอบโจทย์สำหรับกรณีที่ต้องการอัปเดตแค่ข้อมูลบางส่วนเท่านั้น เพราะการใช้ Set คือการกำหนดข้อมูลแทนที่ของเก่าทั้งหมด ส่วน Push ก็เป็นการเพิ่มข้อมูล

ดังนั้นถ้าต้องการอัปเดตข้อมูลแค่บางส่วนที่มีอยู่แล้วก็ให้ใช้ Update แทน

FirebaseData firebaseData;
bool newState = ...

FirebaseJson data;
data.set("isOn", newState);
data.set("timestamp", currentTimestamp);

if(Firebase.updateNode(firebaseData, "/wio-link-01/records/-MBV-yMI4Im1AnV1i8iE", data)) {
    Serial.println("Updated"); 
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

ในกรณีที่ FirebaseJson ที่สร้างขึ้นมานั้นมี Field แตกต่างไปจากเดิม ถ้า Field ไหนมีอยู่แล้วก็จะอัปเดตข้อมูลข้างในนั้น ถ้ายังไม่มีก็จะสร้างขึ้นมาใหม่ และ Field ที่มีอยู่แล้ว แต่ไม่ได้มีการอัปเดตก็จะคงอยู่เหมือนเดิม ไม่โดนลบหายไปไหน

การลบข้อมูลใน Database

สำหรับการลบข้อมูลนั้นสามารถกำหนด Path ที่ต้องการลบข้อมูลได้ง่ายๆแบบนี้เลย

FirebaseData firebaseData;
...
if(Firebase.deleteNode(firebaseData, "/wio-link-01/records/")) {
    Serial.println("Deleted"); 
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

ข้อมูลที่อยู่ใน Path นั้นๆก็จะถูกลบทิ้งในทันที

การอ่านข้อมูลจาก Database

เมื่อเพิ่ม, แก้ไข และลบข้อมูลได้แล้ว ก็ถึงเวลาของการอ่านข้อมูลที่อยู่บน Firebase RTDB กันบ้าง

โดยการอ่านข้อมูลใน Path ที่ต้องการสามารถทำได้ง่ายๆแบบนี้

FirebaseData firebaseData;
...
if(Firebase.getString(firebaseData, "/wio-link-01/name")) {
    String name = firebaseData.stringData();
    // Do something
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

ในกรณีที่เป็น JSON ก็จะต้องดึงข้อมูลของ JSON ทั้งก้อนมาก่อน แล้วค่อยดึงข้อมูลที่ต้องการทีละตัว

FirebaseData firebaseData;
...
if(Firebase.getString(firebaseData, "/wio-link-01/location")) {
    FirebaseJson &json = firebaseData.jsonObject();
    FirebaseJsonData data;
    json.get(data, "/latitude");
    double latitude = data.doubleValue;
    json.get(data, "/longitude");
    double longitude = data.doubleValue;
    // Do something
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

จะเห็นว่าใน FirebaseData มีคำสั่งอย่าง FirebaseData.jsonObject() อยู่ เพื่อดึงข้อมูลออกมาเป็น FirebaseJson และเมื่อต้องการดึงข้อมูลข้างในนั้นก็จะต้องใช้คำสั่ง FirebaseJson.get(FirebaseJsonData, String) เพื่อดึงข้อมูลที่ต้องการอีกทีหนึ่ง โดยข้อมูลที่ได้จะเก็บไว้ใน FirebaseJsonData นั่นเอง ส่วน String ที่กำหนดเข้าไปคือ Path ของ Field ที่ต้องการดึงข้อมูลนั่นเอง

ยกตัวอย่างเช่น JSON ที่ได้จาก Firebase RTDB เป็นแบบนี้

โค้ดตัวอย่างข้างล่างนี้ไม่เกี่ยวกับโค้ดข้างบน แค่อธิบายเพื่อให้เข้าใจมากขึ้นเท่านั้น
{
  "name": "wio-link", 
  "location": {
    "latitude" : 13.745329, 
    "longitude" : 100.534117
  }, 
  "version" : "1.0.0"
}

ถ้าต้องการดึงข้อมูลใน longitude ก็จะกำหนด Path ของ Field แบบนี้

FirebaseData firebaseData;
...
FirebaseJson &json = firebaseData.jsonObject();
FirebaseJsonData latitudeData;
json.get(latitudeData, "/location/latitude");
double latitude = latitudeData.doubleValue;

นอกจากนี้ FirebaseJsonData ยังมี Field ที่ชื่อว่า success อยู่ด้วย เพื่อให้นักพัฒนาสามารถเช็คในภายหลังได้ว่าคำสั่ง FirebaseJson.get(...) สามารถทำได้ถูกต้องหรือผ่าน (เพราะจริงๆแล้วคำสั่งนี้มันก็คือคำสั่ง Parse JSON นั่นเอง)

FirebaseData firebaseData;
...
FirebaseJson &json = firebaseData.jsonObject();
FirebaseJsonData latitudeData;
json.get(latitudeData, "/location/latitude");
if(latitudeData.success) {
    double latitude = latitudeData.doubleValue;
    // Do something
} else {
    // Error parsing
}

แล้วถ้าเป็นข้อมูลที่ Firebase RTDB สร้าง Unique ID ขึ้นมาเองล่ะ?

จะเห็นว่าโค้ดที่พูดถึง จะใช้กับข้อมูลที่อยู่ใน Path ที่ตายตัว เช่น /wio-link-01/location

แต่ถ้าเป็นข้อมูลที่มี ID เป็นของตัวเองล่ะ? เนื่องจากข้อมูลแบบนี้จะมีมากกว่า 1 ตัว ทำให้การกำหนด Path แบบตายตัวไม่สามารถทำได้ (เช่น /wio-link-01/record/{id})

เมื่ออ้างอิงจากข้อมูลในรูปภาพข้างบน ถ้าเจ้าของบล็อกดึงข้อมูลจาก wio-link-01/records ก็จะได้ข้อมูลที่อยู่ในนั้นเป็น JSON แบบนี้

{
  "-MBV-Bhdc-wFS0kKb41Q": {
    "isOn": true,
    "timestamp": 309742
  },
  "-MBV-_XQJQG4QZBHNJYB": {
    "isOn": true,
    "timestamp": 411504
  },
  "-MBV-yMI4Im1AnV1i8iE": {
    "isOn": true,
    "timestamp": 1300206
  },
  "-MBV5-tlvxZM-1SkSzmf": {
    "isOn": true,
    "timestamp": 1834389
  }
}

ซึ่งตัว Library ดังกล่าวก็มี FirebaseJson ให้ใช้งานอยู่แล้ว

FirebaseData firebaseData;
...
if (Firebase.getJSON(firebaseData, "/wio-link-01/records")) {
    FirebaseJson *json = firebaseData.jsonObjectPtr();
    size_t len = json->iteratorBegin();
    String key, value;
    int type = 0;
    for (size_t index = 0; index < len; index++)
    {
        yield();
        json->iteratorGet(index, type, key, value);
        if (type == FirebaseJson::JSON_OBJECT && key.length() > 1) {
            Serial.println(key + " : " + value);
        }
    }
    json->iteratorEnd();
    json->clear();
    // Do something
} else {
    Serial.println("Error : " + firebaseData.errorReason());
}

โดยจะต้องแปลงข้อมูลใน firebaseData ให้อยู่ในรูป Pointer ของ FirebaseJson เสียก่อน (มีคำสั่ง jsonObjectPtr() ให้อยู่แล้ว) จากนั้นจะต้องใช้ iteratorBegin(), iteratorGet(...) และ iteratorEnd() เพื่อดึงข้อมูลที่อยู่ข้างในออกมาใช้งาน

และเมื่อดูข้อมูลที่แสดงออกมาผ่าน Serial Monitor ก็จะแสดงผลลัพธ์ออกมาดังนี้

-MBV-Bhdc-wFS0kKb41Q : {"isOn":true,"timestamp":309742}
isOn : true
timestamp : 309742
-MBV-_XQJQG4QZBHNJYB : {"isOn":true,"timestamp":411504}
isOn : true
timestamp : 411504
-MBV-yMI4Im1AnV1i8iE : {"isOn":true,"timestamp":1300206}
isOn : true
timestamp : 1300206
-MBV5-tlvxZM-1SkSzmf : {"isOn":true,"timestamp":1834389}
isOn : true
timestamp : 1834389

จะเห็นว่า Length ที่ได้จากคำสั่ง json->iteratorBegin() เป็น 12 แทนที่จะเป็น 4 ตัว (เพราะข้อมูลมีแค่ 4 ชุด) นั่นก็เพราะว่าคำสั่งดังกล่าวจะรวมไปถึง Field แต่ละตัวที่อยู่ข้างในข้อมูลแต่ละชุดด้วย (ใน ณ ที่นี้คือ Unique ID, isOn และ timestamp รวมทั้งหมด 3 ตัวต่อข้อมูล 1 ชุด)

ทั้งนี้ก็เพราะว่าบอร์ดอย่าง ESP8266 ไม่ได้ออกแบบมาให้มีทรัพยากรเหลือเฟือมากพอที่จะจัดการกับข้อมูลอย่าง JSON ได้

การ Query ข้อมูล

ในคำสั่ง Get ทั้งหมดบน Firebase ESP8266 นั้นสามารถกำหนดสิ่งที่เรียกว่า QueryFilter เข้าไปได้ เพื่อให้ Firebase RTDB ทำการ Query เฉพาะข้อมูลที่ต้องการจริงๆเท่านั้น

เพราะ Memory บนบอร์ดไมโครคอนโทรลเลอร์ไม่ได้มีให้ใช้งานเยอะมากนัก ดังนั้นไม่ควร Query ข้อมูลเป็นจำนวนเยอะๆ และข้อมูลไม่ควรมีขนาดใหญ่ เพราะอาจจะทำให้ Memory ไม่เพียงพอต่อการใช้งานได้

ยกตัวอย่างเช่น เจ้าของบล็อกต้องการข้อมูลตัวสุดท้ายที่อยู่ใน /wio-link-01/records ก็สามารถใช้คำสั่งแบบนี้ได้เลย

FirebaseData firebaseData;
...
QueryFilter query;
query.orderBy("timestamp");
query.limitToLast(1);
if (Firebase.getJSON(firebaseData, "/wio-link-01/records", query)) {
    // Do something
}

แต่สิ่งหนึ่งที่ต้องทำเพิ่มเติมบน Firebase Console ก็คือการเพิ่ม Field ที่ต้องการ Query เข้าไปใน Firebase Rules ด้วย เพื่อให้ Database ทำการ Indexing เตรียมไว้ให้ ไม่เช่นนั้นจะใช้งาน Query ไม่ได้

{
  "rules": {
    "wio-link-01": {
    	"records": {
        ".indexOn": ["timestamp"]
      }
    },
    ...
  }
}

การอ่านข้อมูลแบบ Stream

เพราะว่าเจ๋งของ Firebase RTDB คือเรื่อง Realtime ที่สามารถรู้ได้ทันทีเมื่อข้อมูลใน Database มีการเปลี่ยนแปลง และแน่นอนว่า Library ตัวนี้ก็รองรับการทำงานแบบนี้ด้วยเช่นกัน โดยจะเรียกว่า Stream

สมมติว่าเจ้าของบล็อกข้อมูลอยู่ใน Database แบบนี้

แล้วเจ้าของบล็อกต้องการให้บอร์ด ESP8266 นั้นคอยอัปเดตข้อมูลจาก /wio-link-01/isOn ตลอดเวลาเพื่อทำงานบางอย่างตามที่กำหนดไว้

สำหรับการ Stream จะใช้คำสั่ง Firebase.setStreamCallback(...) เพื่อกำหนด Callback สำหรับการทำงานแบบ Stream และ Firebase.beginStream(...) เพื่อกำหนดว่าจะให้ทำการ Stream ข้อมูลที่ Path ใดใน Firebase RTDB ซึ่งทั้งสองคำสั่งนี้ควรเรียกใช้งานหลังจากที่เชื่อมต่อกับ Firebase RTDB ได้แล้ว โดยเรียกคำสั่งไว้ใน setup() นั่นเอง

...
#define FIREBASE_HOST "..."
#define FIREBASE_KEY "..."

FirebaseData firebaseData;

void setup() {
    connectWifi();
    Firebase.begin(FIREBASE_HOST, FIREBASE_KEY);
    Firebase.setStreamCallback(firebaseData, streamCallback, streamTimeoutCallback);
    if (!Firebase.beginStream(firebaseData, "/wio-link-01/isOn")) {
        Serial.println("Error : " + firebaseData.errorReason());
    }
    // Do something
}

void loop() {
    // Do something
}

void connectWifi() {
    ...
}

void streamCallback(StreamData data) {
    // Value in Firebase RTDB has updated
}

void streamTimeoutCallback(bool timeout) {
    if (timeout) {
        Serial.println("Stream timeout, resume streaming...");
    }
}

จากตัวอย่างโค้ดข้างบนนี้ streamCallback(StreamData) เป็นฟังก์ชันที่จะทำงานก็ต่อเมื่อข้อมูลใน Database มีการเปลี่ยนแปลง (และตอนดึงข้อมูลครั้งแรกสุดด้วย) ส่วน streamTimeoutCallback(bool) เป็นฟังก์ชันที่จะทำงานก็ต่อเมื่อเกิด Timeout ขึ้น

โดยเจ้าของบล็อกจะให้บอร์ด ESP8266 คอยอัปเดตข้อมูลจาก /wio-link-01/isOn นั่นเอง เมื่อใดที่ค่ามีการเปลี่ยนแปลง เจ้าบอร์ดตัวนี้ก็จะรู้ได้ทันที

ทีนี้มาดูที่ streamCallback(StreamData) กันต่อ (เพราะยังไม่ได้เพิ่มคำสั่งใดๆลงไป) ซึ่งในฟังก์ชันนี้จะส่งข้อมูลเข้ามาเป็นแบบ StreamDat ที่เป็นรูปแบบเดียวกับ FirebaseData แต่จะพิเศษตรงที่มีคำสั่งที่จะบอกให้รู้ว่าข้อมูล Field ไหนที่มีการเปลี่ยนแปลง

void streamCallback(StreamData data) {
    String path = data.streamPath();
    String type = data.dataType();
    bool state = data.boolData();
    // Do something
}

สิ่งที่ควรรู้สำหรับการใช้ Stream ก็คือ ถ้า Path ที่กำหนดนั้นมีข้อมูลแบบ Nested (เช่น /wio-link-01/location โดยที่ข้างในมี latitude กับ longitude อีกทีหนึ่ง) เวลาข้อมูลมีการอัปเดต ใน strealCallback จะส่งข้อมูลเฉพาะตัวที่มีการอัปเดตเท่านั้น ไม่ได้ส่งข้อมูลมาทั้งก้อน

ดังนั้นในตอนแรกสุดที่โปรแกรมเริ่มทำงานก็จะได้ข้อมูลออกมาเป็น JSON ของ Location แต่เมื่อทำการแก้ไขข้อมูลใดๆก็ตาม ก็จะได้ข้อมูลเฉพาะตัวที่ถูกแก้ไขเท่านั้น (เว้นแต่จะแก้ไขพร้อมๆกันทั้ง latitude และ longtitude)

ลองเขียนโปรเจคง่ายๆที่ใช้งานกับ Realtime Database

เพื่อความคลาสสิค เจ้าของบล็อกจึงเขียนโปรแกรมควบคุมไฟติด/ดับที่จะอ่านข้อมูลจาก Database แล้วสั่งงาน LED ตามค่าที่ได้แล้วกันเนอะ (ไม่ทำไฟกระพริบก็บุญแล้ว)

ซึ่งโค้ดก็จะไม่ยากมากนัก (ถ้าอ่านเนื้อหาที่ผ่านมาทั้งหมดนะ)

...
#define FIREBASE_HOST "..."
#define FIREBASE_KEY "..."

FirebaseData firebaseData;

const int ledPin = 14;

void setup() {
    pinMode(ledPin, OUTPUT);

    connectWifi();
    Firebase.begin(FIREBASE_HOST, FIREBASE_KEY);
    Firebase.setStreamCallback(firebaseData, streamCallback, streamTimeoutCallback);
    if (!Firebase.beginStream(firebaseData, "/wio-link-01/isOn")) {
        Serial.println("Error : " + firebaseData.errorReason());
    }
}

void loop() { }

void connectWifi() {
    ...
}

void streamCallback(StreamData data) {
    if(data.streamPath() == "/wio-link-01/isOn" && data.dataType() == "boolean") {
        digitalWrite(ledPin, data.boolData());
    }
}

void streamTimeoutCallback(bool timeout) {
    if (timeout) {
        Serial.println("Stream timeout, resume streaming...");
    }
}

จากนั้นก็ต่อ LED Socket Kit เข้าที่ขา 14 ของบอร์ด

โดยให้บอร์ด ESP8266 คอย Stream ข้อมูลจาก /wio-link-01/isOn แล้วนำค่าที่ได้ไปกำหนดให้กับขา 14 ของบอร์ดที่ต่ออยู่กับ LED Socket Kit นั่นเอง

ดังนั้นเวลาเจ้าของบล็อกเปลี่ยนค่าบน Firebase RTDB ระหว่าง True กับ False สิ่งที่เกิดขึ้นก็คือจะเห็น LED ที่อยู่บน LED Socket Kit ติดและดับตามค่าที่กำหนดนั่นเอง

เรื่องที่ควรรู้เกี่ยวกับ Firebase RTDB บน Arduino

ถึงแม้ว่า Firebase RTDB จะช่วยอำนวยความสะดวกให้กับนักพัฒนาเป็นอย่างมาก แต่ทว่า Firebase นั้นถูกสร้างขึ้นมาเพื่อใช้งานร่วมกับ Web หรือ Mobile App เป็นหลัก ดังนั้นจึงไม่มี Official Library สำหรับ Firebase บนไมโครคอนโทรลเลอร์อย่าง ESP8266

ดังนั้นในบทความนี้จึงหยิบ Library อย่าง Firebase ESP8266 มาใช้แทน ซึ่งเป็น Library ที่สร้างโดยนักพัฒนาทั่วไป จึงไม่ใช่ Unofficial แต่อย่างใด (แต่จากที่ดู Library ก็หมั่นอัปเดตอยู่ตลอดเวลา)

และในการใช้งานร่วมกับ Platform อื่นๆอย่าง Web หรือ Mobile App ต้องคำนึงถึงทรัพยากรในการทำงานด้วย เพราะ ESP8266 ไม่ได้มีทรัพยากรให้ใช้เหลือเฟือแบบ Platform อื่นๆ ดังนั้นข้อมูลที่จะ ESP8266 ต้องจัดการก็ควรจะลดความซับซ้อนให้น้อยลง เพื่อให้นำไปใช้งานได้ง่ายขึ้น ลดการใช้ทรัพยากรโดยไม่จำเป็นลง

สรุป

Web Server กลายเป็นสิ่งที่ขาดไปไม่ได้ในยุคที่ IoT เป็นที่แพร่หลาย เพราะเป็นเสมือนตัวกลางในการรวมข้อมูลของระบบทั้งหมดไว้ด้วยกัน

ซึ่งจริงๆแล้วในปัจจุบันก็มีบริการต่างๆของฝั่ง Web Server ที่จะช่วยให้นักพัฒนาสามารถสร้างแอปสำหรับ IoT ได้สะดวกขึ้น และ Firebase ก็เป็นหนึ่งในทางเลือกที่หลายๆคนสนใจเช่นกัน โดย Firebase RTDB เป็นแค่หนึ่งในบริการจากที่มีอยู่ทั้งหมดใน Firebase นั่นเอง

แต่ Firebase RTDB ก็มีข้อเสียอยู่บ้าง เช่น ไม่ได้มีระบบ Monitor เหมือนแบบ Platform สำเร็จรูปเฉพาะทาง เพราะว่า Firebase RTDB ไม่ได้ออกแบบมาเพื่อใช้งานเฉพาะเจาะจงแค่ด้านใดด้านหนึ่งเท่านั้น แต่สามารถนำไปประยุกต์ใช้งานกับระบบใดๆก็ได้ (มีความยืดหยุ่นสูง) ดังนั้นการ Monitor ข้อมูลที่มีอยู่ใน Firebase RTDB ก็จะต้องสร้างขึ้นเอง (หรือแม้กระทั่งการนำข้อมูลไปใช้งาน)

ถ้าผู้ที่หลงเข้ามาอ่านตัดสินใจใช้ Firebase RTDB สำหรับงานบน ESP8266 ก็ขอแนะนำ Library อย่าง Firebase ESP8266 เพราะเป็น Library ที่เตรียมคำสั่งต่างๆไว้ให้อย่างครอบคลุม อีกทั้งยังไม่ได้มีแค่ Firebase RTDB ด้วย (บทความนี้เน้นไปที่การใช้งาน Firebase RTDB เท่านั้น) และที่สำคัญ Library ตัวนี้พัฒนาขึ้นมาโดยคนไทยด้วยล่ะ 😉

แหล่งข้อมูลอ้างอิง