Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'Firebase'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Zawody/Konkursy/Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona

Znaleziono 4 wyniki

  1. Ten artykuł jest częścią serii "Firebase w zastosowaniach IoT" #1 - Czym jest Firebase? Jak zacząć? #2 - Firebase z ESP32 i ESP8266 #3 - Wyświetlanie danych użytkownikowi poprzez stronę internetową #4 - Projekt praktyczny, Hosting Ostatnio dowiedzieliśmy się czym jest Firebase, jak stworzyć projekt i jak zadbać o jego bezpieczeństwo. Teraz nadszedł czas na bardziej praktyczne zastosowanie. W tej części omówimy dostęp do Firestore i RTDB z poziomu ESP8266 i ESP32. Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » Wykorzystywane w dalszych przykładach bazy danych uzupełnijmy tak jak na poniższych zrzutach ekranu. Realtime Database Firestore DANE DOSTĘPOWE Do połączenia się z naszym projektem będą potrzebne jego identyfikator oraz klucz API. Możemy je znaleźć wchodząc w ustawienia naszego projektu. Widok ustawień projektu z zaznaczonym identyfikatorem i kluczem API. Będzie też potrzebny adres URL naszego projektu, który możemy znaleźć w zakładce RTDB. Widok zakładki RTDB z zaznaczonym adresem URL projektu. PRZYGOTOWANIE Do obsługi Firebase na płytkach ESP będziemy wykorzystywać bibliotekę Firebase_ESP_Client by mobizt. W tym celu na początku musimy ją zaimportować do naszego Arduino IDE (pobierając z Github lub wyszukując w menedżerze bibliotek Firebase Arduino Client Library for ESP8266 and ESP32). Dalsze przykłady będą bazować na przykładach dostarczonych przez twórcę tej biblioteki. Niezależnie od tego, z których usług Firebase chcemy korzystać, na początku musimy zaimportować biblioteki, połączyć się z wifi i zainicjalizować połączenie z Firebase. Importujemy biblioteki. Zwróćmy uwagę, że dzięki zaimportowaniu ich w taki sposób, nasz kod będzie działał na ESP8266 i na ESP32 bez konieczności modyfikowania go. // dołączamy bibliotekę WiFi w zależności od płytki #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif // dołączenie biblioteki do obsługi Firebase #include <Firebase_ESP_Client.h> Następnie definiujemy obiekty, z których będziemy później korzystać: //Definiujemy obiekt Firebase Data FirebaseData fbdo; //Definiujemy FirebaseAuth, gdzie będziemy trzymać dane do uwierzytelniania FirebaseAuth auth; // Definiujemy FirebaseConfig, gdzie będziemy trzymać dane do konfiguracji FirebaseConfig config; Kolejnym krokiem jest połączenie z wifi. Robimy to wewnątrz funkcji setup(): void setup() { Serial.begin(115200); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); // ustawiamy swoje dane dostępu do wifi Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(300); } } Musimy jeszcze zainicjować połączenie z naszym projektem: void setup() { // łączenie z wifi... //Do ustawień konfiguracji (config) zapisujemy klucz API i adres URL naszego projektu config.host = FIREBASE_HOST; //"PROJECT_ID.firebaseio.com" config.api_key = API_KEY; //Do ustawień uwierzytelniania zapisujemy dane do konta użytkownika, którego stworzyliśmy w poprzedniej części artykułu auth.user.email = USER_EMAIL; // "test@test.test" auth.user.password = USER_PASSWORD; // "123456" //Inicjalizujemy połączenie z Firebase używając ustawionych danych Firebase.begin(&config, &auth); //Opcjonalnie ustawiamy automatyczne łączenie z WiFi po zerwaniu połączenia (zalecane) Firebase.reconnectWiFi(true); } Gotowe! Nasz program łączy się już z naszym projektem i za moment będziemy mogli korzystać z dobrodziejstw oferowanych przez Firebase. Na chwilę oderwijmy się jednak od Firebase i spójrzmy na inną funkcjonalność, którą zapewnia nam ta biblioteka. OPERACJE NA FORMACIE JSON Twórca biblioteki dostarcza nam możliwość łatwego operowania na obiektach JSON za pomocą biblioteki FirebaseJson. Przyda nam się to podczas pracy z bazami danych, bo RTDB jest jednym wielkim drzewem JSON (omawialiśmy to w poprzedniej części artykułu). Do dyspozycji mamy 3 typy obiektów: FirebaseJson - struktura Json, dane przechowywane są w { } FirebaseJsonArray - tablica, którą możemy umieścić wewnątrz FirebaseJson, dane przechowywane są w [ ] FirebaseJsonData - typ służący do przechowywania fragmentów pobranych z FirebaseJson np. sama wartości temperatury z czujnik1. Przyjrzyjmy się najważniejszym funkcjom oferowanym przez tę bibliotekę: // deklaracja obiektu FirebaseJson FirebaseJson json; // deklaracja obiektu FirebaseJsonArray FirebaseJsonArray arr; // deklaracja obiektu FirebaseJsonData FirebaseJsonData jsonData; // dodanie nowej gałęzi z jej wartością json.add(“ścieżka”, wartość); // edytowanie, nadpisanie lub utworzenie nowej (jeśli nie istnieje) gałęzi json.set(“ścieżka”, wartość); // usuwanie gałęzi json.remove(“scieżka”); // zapisywanie obiektu json do postaci napisu (string) String nazwa_zmiennej_string; json.toString(nazwa_zmiennej_string, true); // czyszczenie zawartości obiektu json.clear(); // eksport zawartości gałęzi do jsonData json.get(jsonData, "ścieżka"); // zamienianie napisu (typ string) w obiekt FirebaseJson json.setJsonData(string); // dodawanie zawartości do obiektu FirebaseJsonArray arr.add(wartość); arr.add(“ścieżka”, wartość); // edytowanie, nadpisanie lub utworzenie nowej zawartości w obiekcie FirebaseJsonArray arr.set(“ścieżka”, wartość); // usuwanie zawartości komórki tablicy arr.remove(“ścieżka”); // zapisywanie obiektu arr do postaci napisu (string) String nazwa_zmiennej_string; arr.toString(nazwa_zmiennej_string, true); // czyszczenie zawartości obiektu arr arr.clear(); // eksport zawartości komórki tablicy do jsonData arr.get(jsonData, "ścieżka"); // parametr określający czy do jsonData udało się zapisać dane - true lub false jsonData.success // parametr określający typ danych w jsonData: string, int, double, bool, object, array, null, undefined jsonData.type // wypisywanie zawartości jsonData dla różnych typów danych Serial.println(jsonData.stringValue); Serial.println(jsonData.intValue); Serial.println(jsonData.boolValue); Serial.println(jsonData.floatValue); Serial.println(jsonData.doubleValue); Przykładowe zastosowanie: #include <Arduino.h> #include <FirebaseJson.h> void setup() { Serial.begin(115200); FirebaseJson json; FirebaseJson json1; json1.add("temperatura", 23.5); json1.add("wilgotnosc", 67); json.set("led/led1", false); json.set("led/led2", true); json.set("pomiary/czujnik1", json1); // dodajemy json1 jako wartość gałęzi "pomiary/czujnik1" String jsonStr; json.toString(jsonStr, true); Serial.println("----------------------------"); Serial.println(jsonStr); /* Otrzymujemy: { "led": { "led1": false, "led2": true }, "pomiary": { "czujnik1": { "temperatura": 23.5, "wilgotnosc": 67 } } } */ FirebaseJsonArray arr; arr.add("pomiar1"); arr.add("pomiar2"); arr.set("[3]/pomiar3/temperatura", "34"); arr.set("[3]/pomiar3/wilgotnosc", "44"); arr.set("[3]/pomiar4/temperatura", "11"); arr.set("[3]/pomiar4/wilgotnosc", "22"); json.clear(); json.add("pomiary",arr); // do gałęzi "pomiary" jako wartość dodajemy tablicę arr json.toString(jsonStr, true); Serial.println("----------------------------"); Serial.println(jsonStr); /* Otrzymujemy: { "pomiary": [ "pomiar1", //[0] - add automatycznie numeruje od zera "pomiar2", //[1] - następnie dodaje element o id 1 null, //[2] - element o id 2 jest null, bo pomiar3 i pomiar4 zapisujemy do elementu o id 3 { //[3] - tutaj dodajemy pomiar 3 i 4 "pomiar3": { "temperatura": "34", "wilgotnosc": "44" }, "pomiar4": { "temperatura": "11", "wilgotnosc": "22" } } ] } */ arr.clear(); FirebaseJsonArray arr2; arr.add("banan"); arr.add("gruszka"); arr.add("winogrono"); arr2.add("pomidor"); arr2.add("ogorek"); arr2.add("ziemniak"); json.clear(); json.add("owoce",arr); json.add("warzywa",arr2); json.toString(jsonStr, true); Serial.println("----------------------------"); Serial.println(jsonStr); /* Otrzymujemy: { "owoce": [ "banan", "gruszka", "winogrono" ], "warzywa": [ "pomidor", "ogorek", "ziemniak" ] } */ arr.clear(); arr2.clear(); arr.set("banan"); arr.set("gruszka"); arr.set("winogrono"); arr2.set("pomidor"); arr2.set("ogorek"); arr2.set("ziemniak"); json.clear(); json.add("owoce",arr); json.add("warzywa",arr2); json.toString(jsonStr, true); Serial.println("----------------------------"); Serial.println(jsonStr); /* Otrzymujemy: { "owoce": [ "root": [ "root": [ "root": [] ] ] ], "warzywa": [ "root": [ "root": [ "root": [] ] ] ] } Jest to przykład niepoprawnego użycia set(). Nie możemy set() używać zamiennie z add()! */ } void loop() { } Omówiliśmy tylko podstawowe funkcjonalności. Dokładne omówienie wszystkich funkcji, które oferuje ta biblioteka, możemy znaleźć w jej dokumentacji. OBSŁUGA REALTIME DATABASE Większość funkcji do obsługi RTDB wywołujemy w postaci Firebase.RTDB.nazwa_funkcji() . ODCZYT DANYCH Z RTDB Metoda, której będziemy używać zależy od tego, czy znamy typ danych w bazie. Jeśli wiemy jakiego typu są dane, które chcemy odczytać możemy użyć funkcji określonych dla konkretnych typów danych: getInt, getBool, getFloat, getDouble, getString, getJSON Jeśli tego nie wiemy możemy użyć funkcji get i sprawdzić typ otrzymanych danych za pomocą obiekt_FirebaseData.dataType() np. fbdo.dataType(). Ogólna postać wywołania wymienionych wyżej funkcji wygląda następująco: Firebase.RTDB.nazwa_funkcji(obiekt, "ścieżka") //obiekt to wskaźnik na utworzony wcześniej obiekt FirebaseData // dla uproszczenia w dalszej części będziemy go nazywać fbdo //np: Firebase.RTDB.nazwa_funkcji(&fbdo, "/pomiary/czujnik1/temperatura") Wywołanie tych funkcji zwraca prawdę, jeśli udało się odczytać dane, lub fałsz, jeśli wystąpił błąd. Po wywołaniu funkcji otrzymane dane będą przechowywane w obiekcie FirebaseData (będziemy go nazywać fbdo). W zależności od ich typu możemy odczytać je za pomocą jednego z poleceń: fbdo.intData() fbdo.floatData() fbdo.doubleData() fbdo.boolData() fbdo.stringData() fbdo.jsonString() fbdo.jsonObject() fbdo.jsonObjectPtr() fbdo.jsonArray() fbdo.jsonArrayPtr() fbdo.jsonData() fbdo.blobData() Próba pozyskania danych innego typu niż uzyskane z bazy zwróci pustkę (pustą zmienną/ pusty obiekt / pustą tablicę). Przykład zastosowania, gdy nie znamy typu: if(Firebase.RTDB.get(&fbdo, "/led/led1")) { //Sukces Serial.print("Udalo sie odczytac dane! typ = "); Serial.println(fbdo.dataType()); if(fbdo.dataType() == "int"){ Serial.print("wartosc = "); Serial.println(fbdo.intData()); }else if(fbdo.dataType() == "bool"){ if(fbdo.boolData()) Serial.println("wartosc: true"); else Serial.println("wartosc: false"); } }else{ //Błąd, wypisz powód Serial.print("Blad pobierania danych: "); Serial.println(fbdo.errorReason()); } Przykład zastosowania, gdy znamy typ odczytywanych danych: if (Firebase.RTDB.getInt(&fbdo, "/pomiary/czujnik1/wilgotnosc")) { if (fbdo.dataType() == "int") { Serial.println(fbdo.intData()); } } else { Serial.println(fbdo.errorReason()); } ZAPIS DANYCH DO RTDB Dane do RTDB możemy zapisać na 3 sposoby: set - zapisuje lub zmienia dane w konkretnym miejscu (konkretna ścieżka) np. możemy zmienić temperaturę w /pomiary/czujnik1/temperatura. Metoda ta spowoduje nadpisanie danych w określonej lokalizacji, w tym wszelkich węzłów podrzędnych. Oznacza to, że zapisując tylko wartość temperatury do lokalizacji /pomiary/czujnik1 utracimy zapisaną wartość wilgotności. update - zmienia dane w konkretnym miejscu bez nadpisywania pozostałych danych. Oznacza to, że zapisując tylko wartość temperatury do lokalizacji /pomiary/czujnik1 zapisana wartość wilgotności pozostanie bez zmian. push - dodaje nowy węzeł do bazy nadając mu unikalne id. Ma to zastosowanie np. jeżeli chcemy gromadzić historię pomiarów (a nie tylko ostatni). Przy zapisie za pomocą set i push, analogicznie jak podczas odczytu danych, możemy korzystać z różnych funkcji dla różnych typów danych: set, setInt, setFloat, setDouble, setBool, setString, setJSON, setArray, setBlob, setFile, push, pushInt, pushFloat, pushDouble, pushBool, pushString, pushJSON, pushArray, pushBlob, pushFile Funkcje te zwracają true lub false, w zależności od tego, czy udało się zapisać dane, czy nie. Standardowo wywołanie funkcji wygląda następująco: Firebase.RTDB.nazwa_funkcji(&fbdo, “ścieżka”, wartość); Dodatkowo istnieją funkcje, za pomocą których możemy zapisać aktualny czas serwera. pushTimestamp(&fbdo, “ścieżka”); setTimestamp(&fbdo, “ścieżka”); Chcąc skorzystać z metody update musimy nasze dane umieścić w strukturze FirebaseJson. Mamy do dyspozycji dwie funkcje: updateNode(&fbdo, “ścieżka”, &json); // serwer zwraca odpowiedź updateNodeSilent(&fbdo, “ścieżka”, &json); // serwer nie zwraca odpowiedzi Przykład zastosowania: // dołączamy bibliotekę WiFi w zależności od płytki #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif // dołączenie biblioteki do obsługi Firebase #include <Firebase_ESP_Client.h> //Definiujemy obiekt Firebase Data FirebaseData fbdo; //Definiujemy FirebaseAuth, gdzie będziemy trzymać dane do uwierzytelniania FirebaseAuth auth; // Definiujemy FirebaseConfig, gdzie będziemy trzymać dane do konfiguracji FirebaseConfig config; void setup() { Serial.begin(115200); WiFi.begin("WIFI_SSID", "WIFI_HASLO"); Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(300); } //Do ustawień uwierzytelniania zapisujemy dane do konta użytkownika auth.user.email = "test@test.test"; auth.user.password = "123456"; //Do ustawień konfiguracji (config) zapisujemy klucz API i adres projektu config.host = "https://ADRES.firebaseio.com"; config.api_key = "API_KEY"; //Inicjalizujemy połączenie z Firebase używając ustawionych danych Firebase.begin(&config, &auth); //Opcjonalnie ustawiamy automatyczne łączenie z WiFi po zerwaniu połączenia Firebase.reconnectWiFi(true); //----------------- ODCZYT --------------------------------------// Serial.print("GETINT: wilogotnosc: "); if (Firebase.RTDB.getInt(&fbdo, "/pomiary/czujnik1/wilgotnosc")) { if (fbdo.dataType() == "int") { Serial.println(fbdo.intData()); } } else { Serial.println(fbdo.errorReason()); } //----------------------- UPDATE ---------------------------------// Serial.println("Robie UPDATE"); FirebaseJson json; json.add("temperatura",55.8); if(Firebase.RTDB.updateNode(&fbdo, "/pomiary/czujnik1", &json)) { //sukces Serial.println("Update - sukces"); }else{ //blad Serial.print("Blad w update: "); Serial.println(fbdo.errorReason()); } delay(5000); //---------------------- PUSH --------------------------------------// Serial.println("Robie PUSH"); json.clear().add("temperatura",34.4); json.add("wilgotnosc", 55); if(Firebase.RTDB.pushJSON(&fbdo, "/pomiary/", &json)) { //sukces Serial.println("Push - sukces"); }else{ //blad Serial.print("Blad w push: "); Serial.println(fbdo.errorReason()); } delay(10000); //-------------------- SET - WERSJA 1 -------------------------------// Serial.println("Robie SET v1"); if(Firebase.RTDB.setFloat(&fbdo, "/pomiary/czujnik1/temperatura", 99.9)) { //sukces Serial.println("Set v1 - sukces"); }else{ //blad Serial.print("Blad w set v1: "); Serial.println(fbdo.errorReason()); } delay(5000); //------------------- SET - WERSJA 2 --------------------------// Serial.println("Robie SET v2"); json.clear().add("temperatura",33.3); if(Firebase.RTDB.setJSON(&fbdo, "/pomiary/czujnik1", &json)) { //sukces Serial.println("Set v2 - sukces"); }else{ //blad Serial.print("Blad w set v2: "); Serial.println(fbdo.errorReason()); } delay(5000); //----------------- ZAPIS CZASU ------------------------------// Serial.println("Robie zapis czasu"); if(Firebase.RTDB.pushTimestamp(&fbdo, "/czas")) { //sukces Serial.println("Zapis czasu - sukces"); }else{ //blad Serial.print("Blad w zapisie czasu: "); Serial.println(fbdo.errorReason()); } } void loop() {} Kolejne etapy wykonywania programu: 0. Widok przed uruchomieniem programu. 1. Update na /pomiary/czujnik1 zmieniło tylko wartość temperatury (wilgotność pozostała bez zmian). 2. Push stworzyło nową gałąź o unikalnym id. 3. Set wykonane na /pomiary/czujnik1/temperatura zmodyfikowało tylko gałąź z temperaturą. 4. set wykonane na /pomiary/czujnik1 (tak samo jak update) nadpisało całą gałąź. Zapisało nową wartość temperatury, ale w przeciwieństwie do update straciliśmy zapisaną wartość wilgotności. 5. Zapis czasu. Widok monitora portu szeregowego. USUWANIE DANYCH Z RTDB Możemy usunąć całą gałąź przy pomocy funkcji deleteNode(&fbdo, “ścieżka”). NASŁUCHIWANIE ZMIAN W CZASIE RZECZYWISTYM Czasami potrzebujemy wiedzieć, że jakieś dane w bazie uległy zmianie (np. użytkownik poprzez stronę internetową chce włączyć lampkę). Autor biblioteki udostępnił do tego celu funkcje pozwalające na śledzenie zmian w wybranej gałęzi. Rozpoczęcie nasłuchiwania zmian: beginStream(&fbdo, “ścieżka”); Ustawienie jaka funkcja ma zostać wywołana po wykryciu zmian w bazie: setStreamCallback(&fbdo, “nazwa_funkcji”, “nazwa_funkcji_wywoływanej_po_przekroczeniu_limitu_czasu”); W praktyce możemy to przetestować na przykładowym kodzie dostarczonym przez autora biblioteki. W Arduino IDE z przykładów dla tej biblioteki wybieramy RTDB->Stream_Callback. Usuwamy całą zawartość funkcji loop() oraz zbędne zmienne i funkcję printResult(FirebaseData &data), zmieniamy String path na "/led/led1" i ustawiamy nasze dane dostępowe (wifi, dane użytkownika, dane bazy). Tak zmodyfikowany kod będzie wyświetlał informacje w monitorze portu szeregowego za każdym razem, gdy zmienimy wartość w gałęzi "/led/led1" w bazie (my na razie zrobimy to ręcznie w konsoli Firebase). Zmodyfikowany kod: /** * Created by K. Suwatchai (Mobizt) * * Email: k_suwatchai@hotmail.com * * Github: https://github.com/mobizt * * Copyright (c) 2021 mobizt * */ #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <Firebase_ESP_Client.h> /* 1. Define the WiFi credentials */ #define WIFI_SSID "xxxxxxx" #define WIFI_PASSWORD "xxxxxxx" /* 2. Define the Firebase project host name and API Key */ #define FIREBASE_HOST "https://ADRES.firebaseio.com" #define API_KEY "API_KEY" /* 3. Define the user Email and password that alreadey registerd or added in your project */ #define USER_EMAIL "test@test.test" #define USER_PASSWORD "123456" //Define FirebaseESP8266 data object FirebaseData fbdo1; FirebaseAuth auth; FirebaseConfig config; String path = "/led/led1"; void printResult(FirebaseStream &data); // funkcja, która zostanie wywołana po wykryciu zmiany wartości w bazie void streamCallback(FirebaseStream data) { Serial.println("Stream Data1 available..."); Serial.println("STREAM PATH: " + data.streamPath()); Serial.println("EVENT PATH: " + data.dataPath()); Serial.println("DATA TYPE: " + data.dataType()); Serial.println("EVENT TYPE: " + data.eventType()); Serial.print("VALUE: "); printResult(data); Serial.println(); } void streamTimeoutCallback(bool timeout) { if (timeout) { Serial.println(); Serial.println("Stream timeout, resume streaming..."); Serial.println(); } } void setup() { Serial.begin(115200); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(300); } Serial.println(); Serial.print("Connected with IP: "); Serial.println(WiFi.localIP()); Serial.println(); /* Assign the project host and api key (required) */ config.host = FIREBASE_HOST; config.api_key = API_KEY; /* Assign the user sign in credentials */ auth.user.email = "test@test.test"; auth.user.password = "123456"; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); #if defined(ESP8266) //Set the size of WiFi rx/tx buffers in the case where we want to work with large data. fbdo1.setBSSLBufferSize(1024, 1024); #endif //Set the size of HTTP response buffers in the case where we want to work with large data. fbdo1.setResponseSize(1024); if (!Firebase.RTDB.beginStream(&fbdo1, path.c_str())) { Serial.println("------------------------------------"); Serial.println("Can't begin stream connection..."); Serial.println("REASON: " + fbdo1.errorReason()); Serial.println("------------------------------------"); Serial.println(); } // rozpoczynamy nasłuchiwanie zmian Firebase.RTDB.setStreamCallback(&fbdo1, streamCallback, streamTimeoutCallback); } void loop() { } void printResult(FirebaseStream &data) { if (data.dataType() == "int") Serial.println(data.intData()); else if (data.dataType() == "float") Serial.println(data.floatData(), 5); else if (data.dataType() == "double") printf("%.9lf\n", data.doubleData()); else if (data.dataType() == "boolean") Serial.println(data.boolData() == 1 ? "true" : "false"); else if (data.dataType() == "string" || data.dataType() == "null") Serial.println(data.stringData()); else if (data.dataType() == "json") { Serial.println(); FirebaseJson *json = data.jsonObjectPtr(); //Print all object data Serial.println("Pretty printed JSON data:"); String jsonStr; json->toString(jsonStr, true); Serial.println(jsonStr); Serial.println(); Serial.println("Iterate JSON data:"); Serial.println(); size_t len = json->iteratorBegin(); String key, value = ""; int type = 0; for (size_t i = 0; i < len; i++) { json->iteratorGet(i, type, key, value); Serial.print(i); Serial.print(", "); Serial.print("Type: "); Serial.print(type == FirebaseJson::JSON_OBJECT ? "object" : "array"); if (type == FirebaseJson::JSON_OBJECT) { Serial.print(", Key: "); Serial.print(key); } Serial.print(", Value: "); Serial.println(value); } json->iteratorEnd(); } else if (data.dataType() == "array") { Serial.println(); //get array data from FirebaseData using FirebaseJsonArray object FirebaseJsonArray *arr = data.jsonArrayPtr(); //Print all array values Serial.println("Pretty printed Array:"); String arrStr; arr->toString(arrStr, true); Serial.println(arrStr); Serial.println(); Serial.println("Iterate array values:"); Serial.println(); for (size_t i = 0; i < arr->size(); i++) { Serial.print(i); Serial.print(", Value: "); FirebaseJsonData *jsonData = data.jsonDataPtr(); //Get the result data from FirebaseJsonArray object arr->get(*jsonData, i); if (jsonData->typeNum == FirebaseJson::JSON_BOOL) Serial.println(jsonData->boolValue ? "true" : "false"); else if (jsonData->typeNum == FirebaseJson::JSON_INT) Serial.println(jsonData->intValue); else if (jsonData->typeNum == FirebaseJson::JSON_FLOAT) Serial.println(jsonData->floatValue); else if (jsonData->typeNum == FirebaseJson::JSON_DOUBLE) printf("%.9lf\n", jsonData->doubleValue); else if (jsonData->typeNum == FirebaseJson::JSON_STRING || jsonData->typeNum == FirebaseJson::JSON_NULL || jsonData->typeNum == FirebaseJson::JSON_OBJECT || jsonData->typeNum == FirebaseJson::JSON_ARRAY) Serial.println(jsonData->stringValue); } } else if (data.dataType() == "blob") { Serial.println(); for (size_t i = 0; i < data.blobData().size(); i++) { if (i > 0 && i % 16 == 0) Serial.println(); if (i < 16) Serial.print("0"); Serial.print(data.blobData()[i], HEX); Serial.print(" "); } Serial.println(); } else if (data.dataType() == "file") { Serial.println(); File file = data.fileStream(); int i = 0; while (file.available()) { if (i > 0 && i % 16 == 0) Serial.println(); int v = file.read(); if (v < 16) Serial.print("0"); Serial.print(v, HEX); Serial.print(" "); i++; } Serial.println(); file.close(); } } Po uruchomieniu program wypisze w monitorze portu szeregowego aktualny stan gałęzi led/led1 i będzie czekać aż dokonamy zmian tej gałęzi w bazie. Za każdym razem, gdy dokonamy zmiany zostanie wypisany aktualny stan gałęzi. Widok monitora portu szeregowego po uruchomieniu programu i po zmianie wartość led1 z true na false. OBSŁUGA FIRESTORE Teraz zajmiemy się obsługą Firestore. Analogicznie do RTDB funkcje dotyczące Firestore wywołujemy w postaci Firebase.Firestore.nazwa_funkcji() . ODCZYT DANYCH W przeciwieństwie do RTDB nie mamy oddzielnej funkcji dla każdego rodzaju danych. Do pobierania danych z Firestore używamy tylko funkcji getDocument(). W ogólności funkcja ta wygląda następująco: getDocument(FirebaseData *fbdo, “id_projektu”, “id_bazy_danych”, “ścieżka”, “maska”); id_bazy_danych to najczęściej “” lub (default), więc my będziemy używać postaci: getDocument(&fbdo, “id_projektu”, "", “ścieżka”, “maska”) “maska” wskazuje, które elementy ze ścieżki mają być zwrócone. Pozwala to na pewnego rodzaju filtrowanie danych. Pominięcie tego argumentu zwróci całą zawartość ze ścieżki. Pozyskane dane możemy odczytać przy pomocy fbdo.payload(). Funkcja ta zwraca nam tekst (string), który wyglądem przypomina drzewo JSON. Przykład odczytania z bazy zawierającej w kolekcji “pomiary” dokumenty o id “czujnik2” i “czujnik3” (tak jak uzupełniliśmy na początku tej części). Ścieżka “pomiary”, brak maski. Przykład odczytu z tej samej bazy, ale ze ścieżką “pomiary/czujnik2”, brak maski. Przykład odczytu z tej samej bazy, ale ze ścieżką “pomiary” i maską “wilgotnosc”. Aby dostać się do konkretnej wartości możemy zamienić ten string na obiekt FirebaseJson. Służy do tego funkcja setJsonData(string). FirebaseJson json; json.setJsonData(fbdo.payload()); Następnie możemy pozyskać dane z gałęzi, która nas interesuje za pomocą funkcji get(). Teraz spróbujemy odczytać temperaturę odczytaną przez czujnik2 ze stworzonej przez nas bazy. Interesuje nas kolekcja pomiary i dokument czujnik2, więc jako ścieżkę ustawiamy "pomiary/czujnik2". Chcemy pobrać jedynie temperaturę (bez wilgotności), więc ustawiamy maskę “temperatura”. Firebase.Firestore.getDocument(&fbdo, FIREBASE_PROJECT_ID, "", "pomiary/czujnik2", "temperatura") Po wywołaniu funkcji możemy podejrzeć pobrane z bazy dane w monitorze portu szeregowego poleceniem: Serial.println(fbdo.payload()); Widok monitora portu szeregowego. Aby uzyskać samą wartość temperatury musimy najpierw zamienić to co otrzymaliśmy na obiekt FirebaseJson. FirebaseJson json; json.setJsonData(fbdo.payload()); Następnie zapisujemy wartość temperatury do FirebaseJsonData za pomocą funkcji get(). Z wyświetlonego w monitorze portu szeregowego schematu naszego obiektu JSON możemy odczytać, że ścieżką do naszej wartości temperatury będzie "fields/temperatura/doubleValue". FirebaseJsonData jsonData; json.get(jsonData, "fields/temperatura/doubleValue"); Ścieżka do wartości temperatury. Wartość temperatury jest zapisana jako typ double, więc aby ją uzyskać odczytujemy parametr: jsonData.doubleValue Cały kod wygląda następująco: #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <Firebase_ESP_Client.h> /* 1. Define the WiFi credentials */ #define WIFI_SSID "xxxx" #define WIFI_PASSWORD "xxxx" /* 2. Define the Firebase project host name and API Key */ #define FIREBASE_HOST "https://ADRES.firebaseio.com" #define API_KEY "API_KEY" #define FIREBASE_PROJECT_ID "PROJECT_ID" /* 3. Define the user Email and password that alreadey registerd or added in your project */ #define USER_EMAIL "test@test.test" #define USER_PASSWORD "123456" //Define Firebase Data object FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; void setup() { Serial.begin(115200); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(300); } Serial.println(); /* Assign the project host and api key (required) */ config.host = FIREBASE_HOST; config.api_key = API_KEY; /* Assign the user sign in credentials */ auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); if (Firebase.Firestore.getDocument(&fbdo, FIREBASE_PROJECT_ID, "", "pomiary/czujnik2", "temperatura")) { Serial.println("WIDOK W POSTACI JSON"); Serial.println("------------------------------------"); Serial.println(fbdo.payload()); Serial.println("------------------------------------"); FirebaseJson json; json.setJsonData(fbdo.payload()); FirebaseJsonData jsonData; json.get(jsonData, "fields/temperatura/doubleValue"); Serial.print("Wartosc temperatury: "); Serial.println(jsonData.doubleValue); } else { Serial.println("FAILED"); Serial.println("REASON: " + fbdo.errorReason()); Serial.println("------------------------------------"); Serial.println(); } } void loop() { } Wynik w monitorze portu szeregowego. ZAPIS DANYCH Do zapisu możemy użyć funkcji createDocument() (do tworzenia) oraz patchDocument() (do edytowania). Funkcje te analogicznie do poprzednich zwracają true lub false. createDocument(FirebaseData *fbdo, “id_projektu”, “id_bazy_danych”, “ścieżka”, “wartosc”); patchDocument(FirebaseData *fbdo, “id_projektu”, “id_bazy_danych”, “ścieżka”, “wartosc”, “pola_do_zmodyfikowania”); Do tych funkcji wartość musimy podać w takiej postaci, w jakiej przed chwilą odczytywaliśmy funkcją getDocument(), to znaczy napis (string) w postaci drzewa JSON. Załóżmy, że chcemy prowadzić historię pomiarów. Dane będziemy przechowywać w kolekcji historia w oddzielnym dokumencie dla każdego pomiaru (nazwanym pomiar_nr). Do każdego dokumentu będziemy zapisywać temperaturę i wilgotność. Pomiary będziemy robić co 10 sekund. Oczekiwany rezultat. Na początku tworzymy zawartość dokumentu i zapisujemy ją do zmiennej typu String: String content; FirebaseJson js; js.set("fields/temperatura/doubleValue", String(random(10000)/100.00).c_str()); // tempertura to losowa liczba niecałkowita 0.00 - 99.99 js.set("fields/wilgotnosc/integerValue", String(random(100)).c_str()); // wilgotność to losowa liczba całkowita 0-99 js.toString(content); Teraz tworzymy ścieżkę do dokumentu. Składa się ona z nazwy kolekcji i id dokumentu: String documentPath = "historia/pomiar_" + String(numer); gdzie numer to zmienna, po której iterujemy pętlę wykonującą się co 10 sekund. Oto jak będzie wyglądać nasze wysyłanie funkcji createDocument: Firebase.Firestore.createDocument(&fbdo, FIREBASE_PROJECT_ID, "", documentPath.c_str(), content.c_str()) Cały kod wygląda następująco: #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <Firebase_ESP_Client.h> #define WIFI_SSID "xxxx" #define WIFI_PASSWORD "xxxx" #define FIREBASE_HOST "https://ADRES.firebaseio.com" #define API_KEY "API_KEY" #define FIREBASE_PROJECT_ID "PROJECT_ID" #define USER_EMAIL "test@test.test" #define USER_PASSWORD "123456" FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; void setup() { Serial.begin(115200); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(300); } Serial.println(); config.host = FIREBASE_HOST; config.api_key = API_KEY; auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); } unsigned long dataMillis = 0; int numer = 0; void loop() { if (millis() - dataMillis > 10000 || dataMillis == 0) { dataMillis = millis(); String content; FirebaseJson js; String documentPath = "historia/pomiar_" + String(numer); js.set("fields/temperatura/doubleValue", String(random(10000)/100.00).c_str()); js.set("fields/wilgotnosc/integerValue", String(random(100)).c_str()); js.toString(content); numer++; Serial.println("------------------------------------"); Serial.println("Create a document..."); if (Firebase.Firestore.createDocument(&fbdo, FIREBASE_PROJECT_ID, "", documentPath.c_str(), content.c_str())) { Serial.println("SUKCES"); } else { Serial.println("BLAD: " + fbdo.errorReason()); } } } Po uruchomieniu programu nasz baza Firestore zacznie się zapełniać kolejnymi pomiarami co 10 sekund: pomiar_0, pomiar_1, pomiar_2…, a w monitorze portu szeregowego zobaczymy komunikat o sukcesie. Widok monitora portu szeregowego - sukces! Jednak jeżeli uruchomimy program ponownie to dostaniemy błąd! W monitorze portu szeregowego wyskakuje błąd 😞 Dzieje się tak dlatego, że funkcja createDocument() służy tylko do tworzenia dokumentów, które jeszcze nie istnieją. Ponowna próba stworzenia dokumentu pomiar_0 powoduje błąd. Jeżeli chcemy edytować już istniejący dokument musimy użyć funkcji patchDocument(). W naszym kodzie musimy zmienić tylko linijkę z getDocument() na: Firebase.Firestore.patchDocument(&fbdo, FIREBASE_PROJECT_ID, "", documentPath.c_str(), content.c_str(),"temperatura,wilgotnosc") W miejscu “pola_do_zmodyfikownia” wpisujemy "temperatura,wilgotnosc" (bez spacji!), ponieważ chcemy zaktualizować w bazie temperaturę oraz wilgotność. Jeżeli wpisalibyśmy tylko jeden z tych parametrów (np. “temperatura”) to wartość pozostałych parametrów (wilgotność) nie uległaby zmianie. Gdy używamy patch, a podany dokument nie istnieje to zostanie on utworzony (tak jak byśmy użyli create). USUWANIE DOKUMENTÓW Do usuwania dokumentów służy funkcja deleteDocument(). PODSUMOWANIE W tej części nauczyliśmy się zapisywać i odczytywać dane z RTDB i z Firestore za pomocą ESP8266 i ESP32. Dowiedzieliśmy się także jak operować na obiektach JSON w Arduino IDE. Przedstawiona biblioteka daje jeszcze wiele możliwości, których nie omówiliśmy. Polecam zapoznać się z dokumentacją biblioteki, gdzie są one dokładnie opisane. W dokumentacji Firebase znajduje się także opis działania Firebase z innymi językami programowania oraz dostęp poprzez API. W kolejnej części dowiemy się więcej o prezentowaniu zawartości naszej bazy użytkownikowi. Ten artykuł jest częścią serii "Firebase w zastosowaniach IoT" #1 - Czym jest Firebase? Jak zacząć? #2 - Firebase z ESP32 i ESP8266 (właśnie to czytasz)
  2. Ten artykuł jest częścią serii "Firebase w zastosowaniach IoT" #1 - Czym jest Firebase? Jak zacząć? #2 - Firebase z ESP32 i ESP8266 #3 - Wyświetlanie danych użytkownikowi poprzez stronę internetową #4 - Projekt praktyczny, Hosting Potrafimy już obsługiwać Firebase za pomocą urządzeń ESP oraz przez JavaScript. Dzisiaj podsumujemy wiedzę zdobytą w poprzednich trzech częściach kursu i wykorzystamy ją w praktyce. Zbudujemy prosty system smarthome. Będzie on umożliwiał użytkownikowi na zdalne: sprawdzenie aktualnej temperatury w pokoju sprawdzenie kilku ostatnich pomiarów wilgotności ziemi w doniczce kwiatka sterowanie diodą led System ten jest bardzo prosty, ale umożliwi nam praktyczne wykorzystanie wielu funkcji Firebase. Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » Potrzebny sprzęt: ESP8266 (ew. ESP32) cyfrowy czujnik temperatury DS18B20 czujnik wilgotności gleby dioda led rezystory 4.7kΩ , 330Ω płytka stykowa, przewody połączeniowe... Jeśli nie masz któregoś z czujników, to możesz użyć dowolnego innego. QUO VADIS? Najpierw musimy określić jak dokładnie będzie działać nasz system. Urządzenie będzie co określony czas wysyłać do bazy wartość temperatury, zastępując wcześniej zapisaną wartość, oraz wysyłać wartość wilgotności gleby tworząc nową gałąź (nie tracimy poprzednich wyników). Będzie nasłuchiwać zmian w gałęzi, w której jest zapisany stan diody led i odpowiednio ją zapalać lub gasić. Zalogowany użytkownik będzie mógł poprzez stronę internetową sprawdzić aktualną wartość temperatury oraz 4 ostatnie pomiary wilgotności, a także zobaczyć aktualny stan diody i zdecydować o jej włączeniu lub wyłączeniu. Nasza baza danych będzie przechowywała mało informacji, które nie będą bardzo zagnieżdżone, a my nie będziemy wykonywać skomplikowanych operacji na danych, więc w naszym projekcie użyjemy Realtime Database. Nie chcemy, aby sąsiad mógł się bawić naszą diodą, więc użyjemy Auth do uwierzytelniania użytkowników. KONFIGURACJA FIREBASE Na początku tworzymy projekt Firebase, konfigurujemy Auth (uwierzytelnianie za pomocą e-mail) i dodajemy użytkownika np. test@test.test, 123456 (jest to opisane w I części kursu). Tworzenie projektu i konfiguracja Auth. Następnie dodajemy do projektu Realtime Database i konfigurujemy zabezpieczenia, aby tylko zalogowany użytkownik mógł mieć dostęp do naszej bazy. Dodawanie RTDB, reguły zabezpieczeń. Teraz tworzymy strukturę danych w bazie: w gałęzi led będziemy przechowywać stan diody (true/false) w gałęzi temperatura będziemy przechowywać wartość ostatniego pomiaru temperatury i czas tego pomiaru (w czasie uniksowym) w gałęzi wilgotnosc będziemy przechowywać kolejne pomiary wilgotności. Klucz będzie generowany automatycznie, każdy wpis będzie zawierał zmierzoną wartość i czas pomiaru. Struktura naszej bazy danych. Musimy jeszcze dodać aplikację webową do naszego projektu i pozyskać dane dostępowe projektu (klucz API, adres URL). Dodawanie aplikacji, dane dostępowe. ESP8266 HARDWARE Mając skonfigurowany Firebase możemy wziąć się za budowę naszego urządzenia. Łączymy: wyjście czujnika wilgotności do pinu A0 ESP wyjście czujnika temperatury DS18B20 do pinu D2 oraz wpinamy rezystor 4.7kΩ między wyjściem, a dodatnią szyną zasilania (tak jak zostało to opisane w kursie Arduino) dłuższą nożkę diody do pinu D2, a krótszą przez rezystor 330Ω do GND Całość prezentuje się następująco: Połączenie. SOFTWARE Teraz zajmiemy się pisaniem oprogramowania dla naszego ESP. Tutaj przyda się wiedza z II części kursu. Czujnik wilgotności jest najczęściej czujnikiem analogowym i wymaga kalibracji. W tym celu wgrywamy na płytkę przykład 01.Basics -> AnalogReadSerial z ArduinoIDE. Następnie sprawdzamy jakie wartości pokazują się w monitorze portu szeregowego, gdy czujnik znajduje się w suchym miejscu (zakładamy wilgotność 0%) oraz po zanurzeniu w wodzie (wilgotność 100%) i zapamiętujemy te wartości. U mnie wynoszą one odpowiednio 720 i 270. Tworzymy nowy projekt, importujemy biblioteki do czujnika temperatury i do Firebase oraz definiujemy obiekty Firebase. #include <OneWire.h> #include <DallasTemperature.h> // dołączamy bibliotekę WiFi w zależności od płytki #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif // dołączenie biblioteki do obsługi Firebase #include <Firebase_ESP_Client.h> //Definiujemy obiekt Firebase Data FirebaseData fbdo; FirebaseData fbdo_stream; //Definiujemy FirebaseAuth, gdzie będziemy trzymać dane do uwierzytelniania FirebaseAuth auth; // Definiujemy FirebaseConfig, gdzie będziemy trzymać dane do konfiguracji FirebaseConfig config; Następnie definiujemy numery pinów, zmierzone przed chwilą wartości wilgotności oraz czas między pomiarami. #define LED_PIN D3 #define TEMP_PIN D2 #define SOIL_PIN A0 const int AirValue = 720; // wartość wilgotności w powietrzu - 0% const int WaterValue = 270; // watość wilgotności w wodzie - 100% const int TEMP_DELAY = 10000;//ms const int SOIL_DELAY = 20000;//ms Teraz konfigurujemy czujnik temperatury: OneWire oneWire(TEMP_PIN); DallasTemperature temp(&oneWire); Zdefiniujmy sobie funkcję, która będzie mierzyła wilgotność i zwróci wynik w procentach. Funkcja odczytuje wartość z czujnika, przeskalowuje ją z wartości maksymalnej i minimalnej, które zmierzyliśmy, do 0-100%. Dodatkowo wprowadzamy zabezpieczenie, żeby wynik zawsze mieścił się w przedziale 0-100% int getSoil() // pomiar wilgotnosci, zwraca % { int soilValue = analogRead(SOIL_PIN); int soilPercent = map(soilValue, AirValue, WaterValue, 0, 100); if(soilPercent > 100) soilPercent = 100; else if (soilPercent <0) soilPercent = 0; return(soilPercent); } Tworzymy funkcję do odczytu temperatury: float getTemperature() { temp.requestTemperatures(); return(temp.getTempCByIndex(0)); } Wewnątrz setup() ustawiamy pin diody jako wyjście, załączamy czujnik temperatury, łączymy się z wifi i konfigurujemy połączenie z Firebase (tak jak jest to opisane w II części kursu). void setup() { Serial.begin(115200); pinMode(D3, OUTPUT); temp.begin(); WiFi.begin("ssid", "haslo"); //zmienić Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(300); } //Do ustawień uwierzytelniania zapisujemy dane do konta użytkownika auth.user.email = "test@test.test"; auth.user.password = "123456"; //Do ustawień konfiguracji (config) zapisujemy klucz API i adres projektu config.host = "https://xxxxx-default-rtdb.firebaseio.com"; // zmienić config.api_key = "xxxxxxxx"; // zmienić //Inicjalizujemy połączenie z Firebase używając ustawionych danych Firebase.begin(&config, &auth); //Ustawiamy automatyczne łączenie z WiFi po zerwaniu połączenia Firebase.reconnectWiFi(true); } Wewnątrz funkcji loop() tworzymy dwa warunki (pomiar temperatury i wilgotności), które wykonują się co określony wcześniej czas. Korzystamy do tego z millis(), tak jak zostało to opisane w kursie Arduino. W każdym z tych warunków mierzymy odpowiednio temperaturę lub wilgotność i wysyłamy do RTDB. void loop() { static unsigned long prevTempMillis = 0; static unsigned long prevSoilMillis = 0; if(millis()-prevTempMillis >= TEMP_DELAY) { prevTempMillis = millis(); float temp = getTemperature(); Serial.print("T: "); Serial.println(temp); Serial.println(); if(Firebase.RTDB.setFloat(&fbdo, "/temperatura/wartosc", temp)) { //sukces Serial.println("Set v1 - sukces"); }else{ //blad Serial.print("Blad w set v1: "); Serial.println(fbdo.errorReason()); } Firebase.RTDB.setTimestamp(&fbdo, "/temperatura/czas"); } if(millis()-prevSoilMillis >= SOIL_DELAY) { prevSoilMillis = millis(); int soil = getSoil(); Serial.print("W: "); Serial.print(soil); Serial.println(" %"); Serial.println(); FirebaseJson json; json.add("wartosc",soil); if(Firebase.RTDB.pushJSON(&fbdo, "/wilgotnosc/", &json)) { //sukces Serial.println("Push - sukces"); }else{ //blad Serial.print("Blad w push: "); Serial.println(fbdo.errorReason()); } String path = "/wilgotnosc/"; path = path + fbdo.pushName(); path = path + "/czas"; Firebase.RTDB.setTimestamp(&fbdo, path.c_str()); } } Zostało nam tylko ustawić nasłuchiwanie zmian w gałęzi led. W tym celu tworzymy funkcję streamCallback(), która wykonuje się po wykryciu zmiany w bazie, oraz pustą funkcję streamTimeoutCallback(), która wykonuje się po przekroczeniu limitu czasu połączenia (my zostawiamy ją pustą, ale musimy ją stworzyć). void streamCallback(FirebaseStream data) { if (data.dataType() == "int") digitalWrite(LED_PIN, data.intData()); else if (data.dataType() == "boolean") digitalWrite(LED_PIN, data.boolData()); } void streamTimeoutCallback(bool timeout) { ; } Ostatnim krokiem jest włączenie nasłuchiwania wewnątrz setup(): Firebase.RTDB.beginStream(&fbdo_stream, "/led"); Firebase.RTDB.setStreamCallback(&fbdo_stream, streamCallback, streamTimeoutCallback); Cały kod wygląda następująco: #include <OneWire.h> #include <DallasTemperature.h> // dołączamy bibliotekę WiFi w zależności od płytki #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif // dołączenie biblioteki do obsługi Firebase #include <Firebase_ESP_Client.h> //Definiujemy obiekt Firebase Data FirebaseData fbdo; FirebaseData fbdo_stream; //Definiujemy FirebaseAuth, gdzie będziemy trzymać dane do uwierzytelniania FirebaseAuth auth; // Definiujemy FirebaseConfig, gdzie będziemy trzymać dane do konfiguracji FirebaseConfig config; #define LED_PIN D3 #define TEMP_PIN D2 #define SOIL_PIN A0 const int AirValue = 720; // watość wilgotności w powietrzu - 0% const int WaterValue = 270; // watość wilgotności w wodzie - 100% const int TEMP_DELAY = 10000;//ms const int SOIL_DELAY = 20000;//ms OneWire oneWire(TEMP_PIN); DallasTemperature temp(&oneWire); int getSoil() // pomiar wilgotnosci, zwraca % { int soilValue = analogRead(SOIL_PIN); //put Sensor insert into soil Serial.print("Wilgotnosc: "); //później zakomentować Serial.println(soilValue); int soilPercent = map(soilValue, AirValue, WaterValue, 0, 100); if(soilPercent > 100) soilPercent = 100; else if (soilPercent <0) soilPercent = 0; return(soilPercent); } float getTemperature() { temp.requestTemperatures(); return(temp.getTempCByIndex(0)); } void streamCallback(FirebaseStream data) { if (data.dataType() == "int") digitalWrite(LED_PIN, data.intData()); else if (data.dataType() == "boolean") digitalWrite(LED_PIN, data.boolData()); } void streamTimeoutCallback(bool timeout) { ; } void setup() { Serial.begin(115200); pinMode(D3, OUTPUT); temp.begin(); WiFi.begin("xxxx", "xxxx"); //zmienić Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(300); } //Do ustawień uwierzytelniania zapisujemy dane do konta użytkownika auth.user.email = "test@test.test"; auth.user.password = "123456"; //Do ustawień konfiguracji (config) zapisujemy klucz API i adres projektu config.host = "https://xxxxx-default-rtdb.firebaseio.com"; //zmienić config.api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"; //zmienić //Inicjalizujemy połączenie z Firebase używając ustawionych danych Firebase.begin(&config, &auth); //Opcjonalnie ustawiamy automatyczne łączenie z WiFi po zerwaniu połączenia Firebase.reconnectWiFi(true); Firebase.RTDB.beginStream(&fbdo_stream, "/led"); Firebase.RTDB.setStreamCallback(&fbdo_stream, streamCallback, streamTimeoutCallback); } void loop() { static unsigned long prevTempMillis = 0; static unsigned long prevSoilMillis = 0; if(millis()-prevTempMillis >= TEMP_DELAY) { prevTempMillis = millis(); float temp = getTemperature(); Serial.print("T: "); Serial.println(temp); Serial.println(); if(Firebase.RTDB.setFloat(&fbdo, "/temperatura/wartosc", temp)) { //sukces Serial.println("Set v1 - sukces"); }else{ //blad Serial.print("Blad w set v1: "); Serial.println(fbdo.errorReason()); } Firebase.RTDB.setTimestamp(&fbdo, "/temperatura/czas"); } if(millis()-prevSoilMillis >= SOIL_DELAY) { prevSoilMillis = millis(); int soil = getSoil(); Serial.print("W: "); Serial.print(soil); Serial.println(" %"); Serial.println(); FirebaseJson json; json.add("wartosc",soil); if(Firebase.RTDB.pushJSON(&fbdo, "/wilgotnosc/", &json)) { //sukces Serial.println("Push - sukces"); }else{ //blad Serial.print("Blad w push: "); Serial.println(fbdo.errorReason()); } String path = "/wilgotnosc/"; path = path + fbdo.pushName(); path = path + "/czas"; Firebase.RTDB.setTimestamp(&fbdo, path.c_str()); } } Po wgraniu programu na płytkę nasze urządzenie zacznie wysyłać do bazy co 10 sekund wartość temperatury i co 20 sekund wartość wilgotności oraz włączać lub wyłączać diodę, w zależności od wartości gałęzi led. Działanie. STRONA INTERNETOWA Teraz zajmiemy się interfejsem dla naszego użytkownika. Nasz projekt Firebase możemy zintegrować z zewnętrznymi narzędziami (np. NodeRed, Sketchware, ...), napisać aplikację mobilną lub stworzyć własną stronę w html+css+js. My skorzystamy z tej ostatniej ścieżki. Tworzenie własnej strony od zera daje nam dużo możliwości, to my decydujemy o każdym szczególe naszego serwisu. Niestety wymaga to umiejętności programowania w html+css+js, aby osiągnąć zamierzony efekt. W tym kursie skupiamy się na poznawaniu Firebase, więc nasza strona będzie bardzo prosta, ale nic nie stoi na przeszkodzie, aby dodać do niej więcej funkcji. Oto nasz dzisiejszy cel: Widok gotowej strony. Na początku tworzymy plik index.html. Robimy w nim szkielet naszej strony i konfigurujemy połączenie z Firebase. Zamieszczamy w nim identyfikatory wskazujące: miejsce na formularz logowania panel diody, miejsce na aktualny stan diody, przycisk do zmieniania stanu diody panel temperatury, miejsce na aktualną wartość temperatury, miejsce na czas ostatniego pomiaru panel na listę ostatnich pomiarów wilgotności Dane w tych miejscach będziemy uzupełniać z poziomu JavaScript. Cały kod wygląda następująco (zwróć uwagę na komentarze): <!DOCTYPE HTML> <html lang="pl"> <html> <head> <meta charset="utf-8" /> <title>Panel użytkownika</title> <!-- Importujemy skrypty do obsługi Firebase --> <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-database.js"></script> <!-- Dołączamy plik css odpowiadający za wygląd naszej strony --> <link rel="stylesheet" href="styles.css"> </head> <body> <h1>Firebase w zastosowaniach IoT</h1> <div id="formularz_logowania"></div> <!-- Miejsce na formularz logowania --> <div> <!-- Miejsca na informacje o diodzie, temperaturze i wilgotności --> <div id="panel-led" class="panel"> Stan diody: <span id="wartosc-led">[stan]</span> <button id="przycisk-led">Zmień</button> </div> <div id="panel-temperatura" class="panel"> Temperatura: <b><span id="wartosc-temperatura">[wartość]</span></b> (Ostatni pomiar: <span id="czas-temperatura">[czas]</span>) </div> <div id="panel-wilgotnosc" class="panel"></div> </div> <script> // Konfigurujemy dane dostępowe do Firebase var config = { apiKey: "xxxxxxxxx", authDomain: "xxxxx.firebaseapp.com", databaseURL: "https://xxxxx-default-rtdb.firebaseio.com", projectId: "xxxxx", storageBucket: "xxxxx.appspot.com", messagingSenderId: "xxxxxxxx", appId: "xxxxxxxxxxxxxxxxxxxx" }; firebase.initializeApp(config); // Inicjalizujemy Auth const auth = firebase.auth(); // Inicjalizujemy RTDB const rtdb = firebase.database(); </script> <!-- Dołączamy nasze pliki .js --> <script src="login.js"></script> <script src="temperatura.js"></script> <script src="wilgotnosc.js"></script> <script src="led.js"></script> </body> </html> Przygotowałem prosty plik .css, który zadba o trochę ładniejszy wygląd naszej strony (nie jestem mistrzem designu 🙃). .panel { width: 90%; padding: 5px; margin: 5px; border: 3px solid gray; float: left; border-radius: 15px; } .kafelek-wilgotnosc { width: 90%; padding: 5px; margin: 5px; border: 3px solid gray; float: left; border-radius: 15px; } Teraz zajmijmy się mózgiem naszej strony, czyli kodem js. Dla lepszej czytelności postanowiłem, że stworzymy 4 pliki, ale równie dobrze wszystko mogłoby się znaleźć w jednym. Są to: login.js - obsługa funkcji logowania i wylogowywania użytkownika temperatura.js - nasłuchiwanie i aktualizowanie wartości temperatury wilgotnosc.js - nasłuchiwanie i aktualizowanie wartości wilgotności led.js - nasłuchiwanie i aktualizowanie stanu diody, obsługa przycisku LOGIN W pliku login.js umieścimy logikę odpowiedzialną za logowanie i wylogowywanie użytkownika. Na początku sprawdzamy czy użytkownik jest już zalogowany - jeśli tak to w miejscu o id="formularz_logowania" wstawiamy przycisk służący do wylogowania, jeśli nie to wstawiamy tam formularz do logowania. Wykorzystamy do tego poznaną w III części kursu funkcję auth.onAuthStateChanged(). Użytkownik zalogowany / niezalogowany. Gdy zalogowany użytkownik kliknie przycisk “Wyloguj się” zostanie wywołana funkcja auth.signOut() oraz wypisany alert "Wylogowano pomyślnie". Wtedy zadziała także funkcja auth.onAuthStateChanged() wewnątrz pozostałych plików js, która zmieni wartości diody, temperatury i wilgotności na pusty tekst "", a przycisk “Wyloguj się” na formularz logowania. Gdy niezalogowany użytkownik uzupełni i zatwierdzi formularz, zostanie wywołana funkcja auth.signInWithEmailAndPassword(email, haslo), która spróbuje zalogować użytkownika podanymi danymi. Jeśli się to uda zostanie wyświetlony alert “Pomyślnie zalogowano”. Wtedy zadziała także auth.onAuthStateChanged() wewnątrz pozostałych plików js, która zacznie pobierać i wyświetlać wartości z bazy danych, a formularz logowania zamieni na przycisk “Wyloguj się”. Jeśli przy próbie logowania wystąpi błąd to zostanie on wypisany użytkownikowi. Cały kod wygląda następująco: const uchwyt_formularz_logowania = document.querySelector('#formularz_logowania'); // uchwyt do miejsca na formularz logowania auth.onAuthStateChanged(user => { if (user) { //zalogowany // zamiast formularza logowania umieszczamy na stronie przycisk do wylogowania uchwyt_formularz_logowania.innerHTML = '<button id="wyloguj-button">Wyloguj się</button>'; const wyloguj_button = document.querySelector('#wyloguj-button'); // dodajemy funkcję wywoływaną po naciśnięciu przycisku wyloguj_button.addEventListener('click', (e) =>{ e.preventDefault(); auth.signOut().then(() => {alert("Wylogowano pomyślnie.")}) }); } else { //wylogowany //umieszczamy formularz logowania na stronie uchwyt_formularz_logowania.innerHTML =`<form id="formularz"> <input type="email" name="email" placeholder="email"> <input type="password" name="haslo" placeholder="haslo"> <button>Zaloguj</button> </form>`; const formularz = document.querySelector('#formularz'); // dodajemy funkcję wywoływaną po zatwierdzeniu formularza formularz.addEventListener('submit', (e)=>{ e.preventDefault(); //odczytujemy dane wpisane przez użytkownika const email = formularz['email'].value; const haslo = formularz['haslo'].value; auth.signInWithEmailAndPassword(email, haslo).then((cred) =>{ alert("Pomyślnie zalogowano"); }).catch(function(error) { // jeśli coś się nie udało to sprawdzamy powód var errorCode = error.code; var errorMessage = error.message; if (errorCode === 'auth/wrong-password') { alert('Hasło nieprawidłowe!'); } else if (errorCode === 'auth/user-not-found') { alert('Nie ma takiego użytkownika!'); } else if (errorCode === 'auth/invalid-email') { alert('Wpisz poprawny adres email!'); } else { alert(errorMessage); } }); }); } }) TEMPERATURA Analogicznie jak w przypadku login.js, w temperatura.js zawartość widziana przez użytkownika będzie zależeć od jego stanu zalogowania. Jeżeli użytkownik jest wylogowany to w miejscach o id “wartosc-temperatura” i “czas-temperatura” będzie pustka “”. Jeśli jest zalogowany to uruchamiamy nasłuchiwanie zmian temperatury w bazie danych za pomocą funkcji on(). Po wykryciu zmiany zamieniamy wyświetlone wartości “wartosc-temperatura” i “czas-temperatura” na nowe. Cały kod wygląda następująco: const uchwyt_wartosc_temp = document.querySelector('#wartosc-temperatura'); const uchwyt_czas_temp = document.querySelector('#czas-temperatura'); auth.onAuthStateChanged(user => { if (user) { //zalogowany // nasłuchujemy zmian rtdb.ref('temperatura').on('value', (snapshot) => { uchwyt_wartosc_temp.textContent = snapshot.val()['wartosc']; // wypisujemy na stronie nową wartość temperatury var d = new Date(); d.setTime(snapshot.val()['czas']); uchwyt_czas_temp.textContent = d.toLocaleString("pl-PL"); // wypisujemy datę/czas w polskim formacie }); } else //wylogowany - pokazujemy pusty szkielet strony - bez danych { uchwyt_wartosc_temp.textContent = ''; uchwyt_czas_temp.textContent = ''; } }); WILGOTNOŚĆ W wilgotnosc.js nasłuchujemy zmian wilgotności analogicznie do temperatura.js, ale zamiast jednego wyniku odczytujemy ostatnie 4 pomiary i dla każdego z nich tworzymy kafelek, który umieszczamy w miejsce starych kafelków na liście o id “panel-wilgotnosc”. Kod: const uchwyt_panel_wilgotnosc = document.querySelector('#panel-wilgotnosc'); auth.onAuthStateChanged(user => { if (user) { //zalogowany //funkcja tworząca kafelek z wartością wilgotności oraz czasem pomiaru i umieszczająca go na liście function dodajWilgotnosc(wartosc, czas){ // tworzymy kafelek let kafelek = document.createElement('div'); kafelek.className = "kafelek-wilgotnosc"; //tworzymy elementy kafelka let elWartosc = document.createElement('div'); let elCzas = document.createElement('div'); // zapisujemy odczytaną z bazy wartość wilgotności do kafelka elWartosc.textContent = wartosc; // zapisujemy czas pomiaru do kafelka var d = new Date(); d.setTime(czas); elCzas.textContent = d.toLocaleString("pl-PL"); // dodajemy elementy do kafelka kafelek.appendChild(elWartosc); kafelek.appendChild(elCzas); // dodajemy kafelek na górze listy pomiarów uchwyt_panel_wilgotnosc.insertBefore(kafelek, uchwyt_panel_wilgotnosc.childNodes[0]); } // nasłuchiwanie zmian/nowych pomiarów rtdb.ref('wilgotnosc').orderByKey().limitToLast(4).on('value', (snapshot) => { //sortujemy po id wpisu i wybieramy ostatnie 4 (najnowsze) uchwyt_panel_wilgotnosc.textContent=''; for (const pomiar in snapshot.val()) { dodajWilgotnosc(snapshot.val()[pomiar]['wartosc'], snapshot.val()[pomiar]['czas']); } }); } else //wylogowany - pokazujemy pusty szkielet strony - bez danych uchwyt_panel_wilgotnosc.textContent=''; }); LED W led.js nasłuchujemy zmian stanu diody analogicznie jak temperatury. Oprócz tego kodujemy obsługę wciśnięcia przycisku. Aktualny stan diody przechowujemy w zmiennej stan i po wciśnięciu przycisku zapisujemy do bazy stan przeciwny niż aktualny za pomocą funkcji update(). Kod: const uchwyt_wartosc_led = document.querySelector('#wartosc-led'); const uchwyt_przycisk_led = document.querySelector('#przycisk-led'); auth.onAuthStateChanged(user => { if (user) { //zalogowany var stan = false; // nasłuchujemy zmian stanu diody rtdb.ref('led').on('value', (snapshot) => { uchwyt_wartosc_led.textContent = snapshot.val(); //wypisujemy na stronie nowy stan diody stan = snapshot.val(); }); // funkcja wywoływana po kliknięciu przycisku function zmienLed() { if(stan == true || stan == 1) { var json = { led : false }; rtdb.ref('/').update(json); } else { var json = { led : true }; rtdb.ref('/').update(json); } } uchwyt_przycisk_led.onclick = zmienLed; } else //wylogowany - pokazujemy pusty szkielet strony - bez danych { uchwyt_wartosc_led.textContent=''; } }); DZIAŁANIE Nasza strona jest już gotowa. Możemy ją przetestować otwierając plik index.html w przeglądarce. HOSTING Nasza strona działa, ale jest dostępna tylko z poziomu naszego komputera. Takie rozwiązanie nas nie zadowala, bo chcielibyśmy sterować naszym system z dowolnego urządzenia z dowolnego miejsca na świecie. Aby było to możliwe musimy zapewnić naszemu projektowi hosting. Możemy stworzyć własny domowy hosting (np. na Raspberry Pi), skorzystać z hostingu innej firmy lub wykorzystać Hosting Firebase. Tak jak całe Firebase jest on dostępny za darmo w ramach limitów. Aby rozpocząć, przechodzimy do zakładki Hosting w konsoli Firebase i klikamy “Get started”. Wyświetli się nam instrukcja jak krok po kroku umieścić naszą stronę na serwerze. Na początku musimy zainstalować na komputerze narzędzia Firebase wpisując w wierszu poleceń: npm install -g firebase-tools Następnie w wierszu poleceń przechodzimy do pliku na komputerze, gdzie chcemy zainicjalizować nasz projekt. Działa to analogicznie do tworzenia repozytoriów Gita. W Windowsie służy do tego polecenie: cd "ścieżka_do_pliku" Następnie musimy się zalogować kontem google, na którym mamy projekt Firebase za pomocą polecenia: firebase login Gdy już jesteśmy zalogowani inicjujemy projekt wpisując: firebase init Naszym oczom powinien ukazać się piękny napis FIREBASE i zaczną wyskakiwać pytania. Spokojnie, co prawda będzie dużo pytań, ale nie jest to egzamin, więc nie musisz się stresować 😉 . W pierwszym pytaniu Firebase zapyta się nas czy na pewno chcemy w tym miejscu zainicjować projekt. Wpisujemy Yes. Pierwsze pytanie. Odpowiedź: Yes. Następnie musimy zaznaczyć, z których usług Firebase korzystamy w tym projekcie. Wybieramy Database i Hosting. Drugie pytanie. W kolejnym kroku wybieramy, z którym projektem w konsoli Firebase ma być powiązany projekt, który inicjalizujemy na komputerze. My wybieramy istniejący projekt, który wcześniej utworzyliśmy. Wybór istniejącego projektu. Następne dwa pytania dotyczą zasad bezpieczeństwa oraz nazwy publicznego katalogu projektu. Zostawiamy wartości domyślne klikając enter. Kolejne pytanie (już 6?) dotyczy formy naszej aplikacji webowej. Musimy zdecydować, czy ma to być aplikacja z tylko jedną podstroną (/index.html), czy z większą ilością podstron. W naszym wypadku mamy tylko jeden plik html, więc możemy wybrać Y (choć nie ma to większego znaczenia). Wpisujemy Y. Ostatnie (nareszcie) pytanie dotyczy integracji z GitHubem. My na razie tego nie chcemy, więc wybieramy N. Udało się! Właśnie zainicjalizowaliśmy katalog z projektem Firebase na naszym komputerze. Po wejściu w folder, który wybraliśmy do przechowywania naszego projektu, możemy zobaczyć kilka plików oraz folder public. Właśnie ten folder interesuje nas najbardziej, bo wewnątrz public będą przechowywane wszystkie pliki naszej strony. Automatycznie utworzone pliki. Wewnątrz public usuwamy automatycznie utworzony plik index.html i przenosimy w to miejsce pliki tworzące naszą stronę: index.html styles.css temperatura.js wilgotnosc.js login.js led.js Zawartość katalogu public. Zanim wyślemy naszą stronę w świat, możemy podejrzeć jak będzie wyglądała. W tym celu w wierszu poleceń, z ustawioną ścieżką naszego projektu, wpisujemy: firebase serve Możemy podejrzeć naszą stronę wpisując w przeglądarce: http://localhost:5000 Podgląd strony przed wrzuceniem do sieci. Jeżeli wszystko się zgadza, możemy wrzucić naszą stronę na serwer poleceniem: firebase deploy Jeżeli wszystko się uda, w konsoli powinniśmy zobaczyć adres url, pod którym jest dostępna nasza strona. Domyślnie jest to: https://id-projektu.web.app Udało się! Nasza strona jest już dostępna publicznie 🙂 Możemy zarządzać kolejnymi wersjami naszej strony z poziomu konsoli Firebase. Konsola Firebase. Gotowe! Nasza strona jest już dostępna publicznie. Na razie jest ona bardzo prosta, ale istnieje wiele możliwości jej rozwoju np.: personalizacja wyników dla różnych użytkowników logowanie za pomocą mediów społecznościowych poprawienie designu kolory kafelków zależne od wartości temperatury i wilgotności Teraz zostało nam tylko umieścić czujnik wilgotności w doniczce i nasz pierwszy projekt Firebase jest gotowy 🙂 PODSUMOWANIE Tym optymistycznym akcentem kończymy naszą wycieczkę do krainy Firebase. To była ostatnia część naszego kursu, w której podsumowaliśmy zdobytą wiedzę i nauczyliśmy się korzystać z Hostingu Firebase. Mam nadzieję, że chociaż trochę przybliżyłem Ci temat Firebase. Jest to wspaniałe narzędzie, które daje mnóstwo możliwości. Zachęcam do samodzielnego zgłębienia tematu, bo kwestie, które poruszyłem są tylko wierzchołkiem góry lodowej. PS. Jeśli masz pomysł do czego można zastosować Firebase, koniecznie podziel się nim w komentarzu 🙂 Ten artykuł jest częścią serii "Firebase w zastosowaniach IoT" #1 - Czym jest Firebase? Jak zacząć? #2 - Firebase z ESP32 i ESP8266 #3 - Wyświetlanie danych użytkownikowi poprzez stronę internetową #4 - Projekt praktyczny, Hosting (właśnie to czytasz)
  3. Ten artykuł jest częścią serii "Firebase w zastosowaniach IoT" #1 - Czym jest Firebase? Jak zacząć? #2 - Firebase z ESP32 i ESP8266 #3 - Wyświetlanie danych użytkownikowi poprzez stronę internetową #4 - Projekt praktyczny, Hosting Wiemy już czym jest Firebase oraz jak połączyć go z urządzeniami bazującymi na ESP. Teraz zajmiemy się kontaktem z użytkownikiem. Dowiemy się jak zintegrować Auth, RTDB i Firestore ze stroną internetową, tworząc interfejs dla użytkownika. UWAGA: Do pełnego zrozumienia tej część kursy potrzebna jest podstawowa znajomość html i js. Naszym celem jest zapoznanie z Firebase, więc nie będziemy zwracać szczególnej uwagi na pełną poprawność kodów html i js. W internecie jest dostępnych wiele kursów na ten temat i myślę, że każdy zainteresowany znajdzie odpowiedni dla siebie. Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » KONFIGURACJA W FIREBASE Na początku musimy dodać aplikację webową do naszego projektu. W tym celu przechodzimy do strony głównej projektu w Firebase i klikamy Dodaj aplikację -> Aplikacja sieciowa oraz nadajemy nazwę naszej aplikacji. Dodawanie nowej aplikacji sieciowej. W następnym kroku wyświetlą się nam dane, których będziemy potrzebować do łączenia plików strony internetowej z Firebase. Widok po dodaniu aplikacji. W każdej chwili możemy też je znaleźć przechodząc do ustawień projektu. Widok danych dostępowych w ustawieniach projektu. ZACZYNAMY! Na początku tworzymy plik html i umieszczamy w nim podstawowy szablon. <html> <head> </head> <body> </body> </html> Między znacznikami <head> </head> dołączamy skrypt do obsługi Firebase oraz skrypty do konkretnych usług, których będziemy używać. Wersję 8.2.9 w poniższym kodzie można zamienić na nowszą. // Dołączenie skryptu do obsługi Firebase <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-app.js"></script> // Dołączenie skryptów dla konkretnych usług <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-firestore.js"></script> // Firestore <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-auth.js"></script> // Auth <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-database.js"></script> // RTDB Jak widać jest to kilka początkowych linijek ze skopiowanych wcześniej danych dostępowych. W tym przykładzie dołączyliśmy Firestore, Auth i RTDB. Sposób dołączania innych usług jest opisany w dokumentacji. Następnie wewnątrz znaczników <body></body> wstawiamy skrypt <script></script> z danymi naszego projektu: var firebaseConfig = { apiKey: "xxxxxxxxxxxxx", authDomain: "xxxx.firebaseapp.com", databaseURL: "https://xxxx-default-rtdb.firebaseio.com", projectId: "xxxx", storageBucket: "xxxx.appspot.com", messagingSenderId: "1234567890", appId: "xxxxxxxxxxxxx" }; Tuż pod spodem inicjalizujemy Firebase i wybrane usługi: // Inicjalizujemy Firebase firebase.initializeApp(firebaseConfig); // Inicjalizujemy Firestore const db = firebase.firestore(); db.settings({ timestampsInSnapshots: true }); // Inicjalizujemy Auth const auth = firebase.auth(); // Inicjalizujemy RTDB const rtdb = firebase.database(); W kolejnym kroku tworzymy plik JavaScript (.js), w którym będzie część logiczna naszej strony i dołączamy go do pliku html. Cały kod html wygląda następująco: <html> <head> <!-- Dołączamy skrypt do obsługi Firebase --> <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-app.js"></script> <!-- Dodajemy skrypty do obsługi poszczególnych produktów https://firebase.google.com/docs/web/setup#available-libraries --> <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-firestore.js"></script> <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-database.js"></script> </head> <body> <!-- Tutaj jakaś zawartość strony... --> <script> // Wklejamy dane dostępowe do naszego projektu var firebaseConfig = { apiKey: "xxxxxx", authDomain: "xxxxx", databaseURL: "xxxxx", projectId: "xxxxx", storageBucket: "xxxxx", messagingSenderId: "xxxxx", appId: "xxxxx" }; // Inicjalizujemy Firebase firebase.initializeApp(firebaseConfig); // Inicjalizujemy Firestore const db = firebase.firestore(); db.settings({ timestampsInSnapshots: true }); // Inicjalizujemy Auth const auth = firebase.auth(); // Inicjalizujemy RTDB const rtdb = firebase.database(); </script> <!-- Dołączenie naszego pliku .js--> <script src="nazwa.js"></script> </body> </html> AUTH W serwisach internetowych uwierzytelnianie użytkowników jest jedną z podstawowych funkcji. Stworzenie samodzielnie bezpiecznego i sprawnego systemu do zarządzania użytkownikami może być skomplikowane i wymaga dużo pracy. Zaraz zobaczymy jak łatwo da się to zrobić przy użyciu Firebase Auth. Podczas inicjalizacji Auth stworzyliśmy uchwyt do tej usługi o nazwie auth: const auth = firebase.auth(); Z tego powodu wszystkie funkcje, które dotyczą Auth będziemy wywoływać w postaci: auth.nazwaFunkcji() SPRAWDZANIE STANU UŻYTKOWNIKA Na początku nauczymy się sprawdzać czy osoba przeglądająca naszą stronę jest zalogowanym użytkownikiem, czy nie. Służy do tego funkcja onAuthStateChanged(), która wykrywa w czasie rzeczywistym zmianę stanu zalogowania użytkownika. Wewnątrz tej funkcji możemy określić co ma się stać, gdy użytkownik jest zalogowany, a co gdy nie. auth.onAuthStateChanged(user => { if (user) { console.log('ZALOGOWANY: ', user); } else { console.log('NIEZALOGOWANY'); } }) W tym przykładzie user to obiekt przedstawiający naszego użytkownika. Jeżeli użytkownik jest zalogowany to przechowuje informacje o nim, a jeżeli nie jest zalogowany to user wynosi null. Z tego powodu stan zalogowania możemy sprawdzić zwykłą instrukcją warunkową if. Jeżeli użytkownik jest zalogowany to program wypisze w konsoli ZALOGOWANY: dane_użytkownika, a jeżeli nie jest zalogowany to wypisze NIEZALOGOWANY. Oczywiście zamiast tego możemy samodzielnie zdecydować co ma się zadziać np. wywołanie określonej funkcji, przekierowanie na inną stronę itp. Umieszczamy powyższy fragment kodu w pliku .js dołączonym do naszego pliku html i otwieramy go w przeglądarce. Powinna ukazać nam się pusta strona. To dlatego, że nie stworzyliśmy zawartości naszej strony w pliku html, a tylko wypisywaliśmy komunikaty do konsoli. Pusto... Jak się dostać do konsoli? Klikamy prawy przycisk myszy, wybieramy Zbadaj i przechodzimy do zakładki console. Powinniśmy teraz widzieć komunikat wypisany przez nasz program. Widok konsoli. Na razie pojawia się tam NIEZALOGOWANY, ponieważ nie poznaliśmy jeszcze mechanizmu logowania. Zaraz to zmienimy! LOGOWANIE I WYLOGOWYWANIE Do logowania użytkownika za pomocą adresu e-mail i hasła służy funkcja: auth.signInWithEmailAndPassword(email, haslo); Jeżeli dopiszemy to polecenie poniżej polecenia sprawdzającego stan zalogowania (email zamieniamy na “test@test.test”, a hasło na “123456”) i odświeżymy stronę to najpierw otrzymamy komunikat o niezalogowanym użytkowniku, a później o zalogowanym wraz z danymi użytkownika. Widok konsoli - 2 komunikaty. Wynika to z tego, że na początku byliśmy niezalogowani, a zalogowaliśmy się dopiero po sprawdzeniu stanu zalogowania (kod logowania jest poniżej kodu sprawdzania). Jeśli jednak teraz odświeżymy stronę to dostaniemy tylko komunikat o zalogowanym użytkowniku. Wynika to z tego, że przeglądarka zapamiętała nasze zalogowanie i nas nie wylogowała. Widok konsoli - przeglądarka nas pamięta. Jak zatem się wylogować? Służy do tego funkcja: auth.signOut(); Dodajmy ją zatem na końcu naszego kodu i sprawdźmy co się stanie. Widok konsoli. Co tu się wydarzyło!? Chyba nie o to nam chodziło… Najpierw pojawia się komunikat o zalogowanym użytkowniku, co się zgadza, bo byliśmy wcześniej zalogowani. Później komunikat o wylogowaniu, a na końcu znowu o zalogowaniu. Przecież w kodzie umieściliśmy te polecenia w odwrotnej kolejności! Okazuje się, że zalogowanie zajmuje dużo więcej czasu niż wylogowanie, więc wylogowanie dokonało się zanim udało nam się zalogować. Ktoś mógłby się zastanawiać, czy wywołanie funkcji signOut() nie powinno zaczekać na koniec wywołania funkcji signIn(). Otóż nie! Jest to bardzo niebezpieczne zjawisko i może powodować trudne do wykrycia błędy np. gdy postanowimy wyświetlać spersonalizowaną stronę użytkownika i kod ją generujący wywoła się zanim funkcja signIn() skutecznie go zaloguje. Błąd ten da się łatwo naprawić. Wykorzystamy do tego celu funkcję then(), która zapisana po funkcji signIn() zaczeka, aż wykona się ona w całości i dopiero pozwoli na wywołanie funkcji signOut(). Więcej o funkcji then() można dowiedzieć się tutaj. Zamiast signIn i signOut zapisujemy: auth.signInWithEmailAndPassword("test@test.test", "123456").then(function(){auth.signOut();}); Wylogowanie nastąpiło dopiero po zalogowaniu - sukces 😎 Oczywiście nie zawsze użytkownikowi udaje się poprawnie zalogować. Jeżeli przy logowaniu wystąpi błąd, to chcielibyśmy wiedzieć z czego on wynika. Wystarczy, że na końcu naszej funkcji logowania dopiszemy: .catch(function(error) {console.log(error);}); Teraz jeżeli nastąpi błąd podczas logowania to zostanie on wypisany w konsoli. Przykład błędu po podaniu nieprawidłowego hasła. Oprócz wypisania w konsoli całego błędu możemy odczytać tylko jego kod za pomocą error.code i na tej podstawie informować użytkownika o błędzie. Najczęstsze kody błędów to: auth/wrong-password - niepoprawne hasło auth/user-not-found - nie ma takiego użytkownika auth/invalid-email - niepoprawny adres e-mail DODAWANIE UŻYTKOWNIKÓW Z poziomu aplikacji webowej możemy też tworzyć konta nowym użytkownikom. Służy do tego funkcja: auth.createUserWithEmailAndPassword(email, hasło) Po stworzeniu konta użytkownika jest on automatycznie zalogowany. Polecam lekturę dokumentacji, gdzie dokładniej są opisane wszystkie możliwości Auth. RTDB Teraz zajmiemy się obsługą Realtime Database w JavaScript. Podczas inicjalizacji RTDB stworzyliśmy uchwyt do tej usługi o nazwie rtdb. Analogicznie do Auth, wszystkie funkcje dotyczące RTDB będą miały postać: rtdb.nazwaFunkcji(); W części I tego kursu ustawiliśmy reguły RTDB w taki sposób, aby do odczytu i zapisu danych wymagane było zalogowanie. Z tego powodu funkcje operujące na RTDB powinny zostać wywołane po funkcji logowania lub po funkcji sprawdzającej stan zalogowania, a najlepiej wewnątrz then(). POJEDYNCZY ODCZYT Do jednorazowego odczytu danych z bazy służy funkcja get(). Musimy ją poprzedzić funkcją ref() wskazującą na gałąź, którą chcemy odczytać: rtdb.ref('ścieżka').get() //np. rtdb.ref('led/led1').get() Aby uzyskać dostęp do odczytanych danych musimy jeszcze dopisać: .then(function(snapshot) { //treść funkcji }); Dane odczytane z bazy znajdują się wewnątrz obiektu snapshot. Teraz wypiszmy do konsoli wartość zapisaną w 'led/led1': auth.signInWithEmailAndPassword("test@test.test", "123456").then(function () { rtdb.ref('led/led1').get().then(function (snapshot) { if (snapshot.exists()) { console.log(snapshot.val()); } else { console.log("Nie odczytano danych!"); } }).catch(function (error) { //łapanie błędów console.error(error); }); }).catch(function (error) { console.log(error); }); Widok konsoli z wartością ‘led/led1’. Widok konsoli, gdy zmienimy ścieżkę na ‘led’ (w bazie wewnątrz led znajdują się led1 z wartością true i led2 z wartością false). NASŁUCHIWANIE ZMIAN W CZASIE RZECZYWISTYM Funkcja get() odczytuje dane tylko raz - w momencie wywołania. Jednak czasem potrzebujemy na bieżąco dowiadywać się o nowych zmianach w bazie bez odświeżania strony np. na bieżąco śledzić zmiany temperatury. Umożliwia nam to funkcja on(): rtdb.ref('led').on('value', (snapshot) => { console.log(snapshot.val()); //co ma się wykonać po wykryciu zmiany }); Nową wartość w obserwowanej gałęzi możemy odczytać za pomocą snapshot.val(). Widok konsoli. Każda zmiana wartości w gałęzi ‘led’ to nowy komunikat. W takiej postaci funkcja on() reaguje na każdą zmianę wartości we wskazanej gałęzi. Czasami jednak interesuje nas tylko konkretny rodzaj modyfikacji np. tylko dodanie nowej gałęzi lub tylko zmiana istniejącej wartości. W tym celu należy zmienić ‘value’ na: ‘child_added’ - jeżeli interesuje nas tylko dodanie nowej gałęzi. W snapshot znajduje się tylko wartość nowej gałęzi. Po odświeżeniu strony wypisze pojedynczo wartość wszystkich istniejących gałęzi należących do podanej ścieżki np. oddzielnie wartości led1 i led2 dla ścieżki ‘led’. ‘child_changed’ - jeżeli istnieje nas tylko zmiana wartości w istniejących gałęziach. Po odświeżeniu strony nic nie zwraca dopóki jakaś gałąź nie zostanie zmodyfikowana. ‘child_removed’ - jeżeli interesuje nas tylko usunięcie gałęzi. Zwraca wartość usuniętej gałęzi. ZAPIS DANYCH DO BAZY Zapisać dane do RTDB możemy na 3 sposoby, tak jak w przypadku zapisu za pomocą ESP: set() - zapisuje lub zmienia dane w konkretnym miejscu (konkretna ścieżka) np. możemy zmienić temperaturę w /pomiary/czujnik1/temperatura. Metoda ta spowoduje nadpisanie danych w określonej lokalizacji, w tym wszelkich węzłów podrzędnych. Oznacza to, że zapisując tylko wartość temperatury do lokalizacji /pomiary/czujnik1 utracimy zapisaną wartość wilgotności. update() - zmienia dane w konkretnym miejscu bez nadpisywania pozostałych danych. Oznacza to, że zapisując tylko wartość temperatury do lokalizacji /pomiary/czujnik1 zapisana wartość wilgotności pozostanie bez zmian. push() - dodaje nową gałąź do bazy nadając jej unikalne id. Ma to zastosowanie np. jeżeli chcemy gromadzić historię pomiarów (a nie tylko ostatni). Dane do zapisu, które podajemy jako argument tych funkcji, muszą być w postaci JSON. var json = { wilgotnosc : 58 }; // zmienia się tylko wartość wilgotności - temperatura bez zmian rtdb.ref('pomiary/czujnik1').update(json); // tracimy wartość temperatury rtdb.ref('pomiary/czujnik1').set(json); // dodajemy nową gałąź o automatycznie generowanym id rtdb.ref('pomiary').push(json); Widok początkowy. Po wywołaniu update(). Po wywołaniu set(). Po wywołaniu push(). Jeżeli chcemy wiedzieć jakie id dokumentu wygenerowała funkcja push() możemy podczas jej wywołania dopisać .key: console.log(rtdb.ref('pomiary').push(json).key); FIRESTORE Teraz zajmiemy się obsługą Firestore w JavaScript. Wszystkie funkcje dotyczące Firestore będą miały postać: db.nazwaFunkcji(); W Firestore zamiast ref() używamy .collection("nazwa_kolekcji").doc("nazwa_dokumentu"). Dla Firestore także ustawiliśmy reguły bezpieczeństwa, więc także musimy najpierw zalogować użytkownika. POJEDYNCZY ODCZYT Do jednorazowego odczytu danych z bazy służy funkcja get(). Aby odczytać dane z konkretnego dokumentu musimy użyć jej w takiej postaci: db.collection("nazwa").doc("nazwa").get(); //np. db.collection("pomiary").doc("czujnik2").get(); Aby uzyskać dostęp do odczytanych danych musimy jeszcze dopisać: .then((doc) => {//treść funkcji }); Dane odczytane z bazy znajdują się wewnątrz doc. Za pomocą doc.exists możemy sprawdzić czy jakieś dane zostały pobrane z bazy. Aby odczytać dane musimy użyć: doc.data() W ten sposób uzyskujemy wszystkie dane wskazanego dokumentu w postaci json. Kolekcja pomiary, dokument czujnik2 - uzyskaliśmy wartość temperatury i wilgotności. Jeżeli interesuje nas konkretne pole w danym dokumencie musimy po kropce wpisać jego nazwę: doc.data().nazwa_pola Kolekcja pomiary, dokument czujnik2, doc.data().temperatura. Możemy także odczytać wszystkie dokumenty w kolekcji. W tym przypadku doc.data() i doc.data().nazwa_pola wywołujemy oddzielnie dla każdego odczytanego dokumentu: db.collection("pomiary").get().then((querySnapshot) => { querySnapshot.forEach((doc) => { // funkcja wykonująca się dla każdego dokumentu }); }); // np: // wypisanie id oraz zawartości wszystkich dokumentów w kolekcji “pomiary” db.collection("pomiary").get().then((querySnapshot) => { querySnapshot.forEach((doc) => { console.log(doc.id, " => ", doc.data()); }); }); Wypisanie id oraz zawartości wszystkich dokumentów w kolekcji “pomiary”. Istnieje także możliwość filtrowania wyników - zapytanie zwróci tylko dokumenty spełniające określone warunki. W tym celu pomiędzy collection() a get() musimy dopisać klauzulę where(): where("pole", "warunek", “wartość”) //np: where("temperatura", ">", 50) db.collection("pomiary").where("temperatura", ">", 50).get().then(...) where("temperatura", ">", 50) nie zwraca nam dokumentu czujnik2, bo jest w nim zapisana temperatura 34.5 Wszystkie dostępne warunki zapytań, konstruowanie zapytań złożonych oraz sortowanie kolejności zwracanych dokumentów możemy znaleźć w dokumentacji. NASŁUCHIWANIE ZMIAN W CZASIE RZECZYWISTYM Tak samo jak w RTDB, możemy w czasie rzeczywistym reagować na zmiany w bazie. Możemy ustalić co ma się wydarzyć, gdy dokona się jakakolwiek zmiana w podanej kolekcji lub reagować tylko na konkretny typ zmiany (np. dodanie nowego dokumentu lub modyfikacja już istniejącego): db.collection('pomiary').onSnapshot(snapshot => { let changes = snapshot.docChanges(); changes.forEach(change => { //funkcja wykonująca się dla każdego dokumentu, dla którego zaszła zmiana console.log(change.doc.id, " => ", change.doc.data()); // Możemy rozróżniać typy zmian if(change.type == 'added'){ console.log("Dodano"); } if (change.type === "modified") { console.log("Zmodyfikowano"); } }); }); Przykład dla powyższego kodu - wypisanie początkowe wszystkich dokumentów, modyfikacja wilgotności w czujnik2, dodanie czujnik4. ZAPIS DANYCH DO BAZY Podobnie jak w RTDB, w Firebase mamy 3 sposoby zapisu danych do bazy: set, update i add (zamiast push). Funkcja set nadpisuje istniejący lub tworzy nowy dokument o podanej nazwie (wszystkie wcześniejsze dane zapisane w tym dokumencie są tracone): db.collection("pomiary").doc("czujnik5").set({ temperatura: 12.35, wilgotnosc: 97 }) Dodano czujnik5. Ponowne wywołanie set(), ale tym razem tylko z parametrem temperatury, usunie zapisaną wartość temperatury: db.collection("pomiary").doc("czujnik5").set({ temperatura: 42.35 }) Funkcja add() dodaje do określonej kolekcji nowy dokument o automatycznie generowanym id: db.collection("pomiary").add({ temperatura: 32.35, wilgotnosc: 56 }) Funkcja update() aktualizuje istniejący dokument: db.collection("pomiary").doc("czujnik2").update({ temperatura: 37.75, wilgotnosc: 59 }) Zaktualizowane wartości temperatury i wilgotności. Jeżeli podamy do zaktualizowanie nie wszystkie istniejące pola, to pozostałe pola nie zmienią swojej wartości: db.collection("pomiary").doc("czujnik2").update({ wilgotnosc: 63 }) Jeśli podamy jej dokument, który nie istnieje to wyskoczy błąd: db.collection("pomiary").doc("czujnik99").update({ temperatura: 32.35, wilgotnosc: 56 }) (wiel)BŁĄD 🐪 Za to możemy dodać w dokumencie pola, które wcześniej nie istniały: db.collection("pomiary").doc("czujnik2").update({ nieistnieje: true }) Dodaliśmy pole, które wcześniej nie istniało. Dodatkowo update pozwala nam zapisywać czas serwera... db.collection("pomiary").doc("czujnik2").update({ timestamp: firebase.firestore.FieldValue.serverTimestamp() }) Zapisaliśmy czas serwera. … a także inkrementować wartość wybranego pola: db.collection("pomiary").doc("czujnik2").update({ wilgotnosc: firebase.firestore.FieldValue.increment(1) //inkrementacja o 1 - jedynkę można zastąpić dowolną liczbą }) PODSUMOWANIE Jeżeli udało Ci się wytrzymać do tego momentu to gratuluję! W tych trzech częściach starałem się przekazać jak najwięcej informacji na temat Firebase przez co wyszły one bardzo treściwe. Obiecuję, że to już ostatnia tak nudna część 😉. W następnym odcinku bajki połączymy wszystkie zdobyte do tej pory informacje tworząc prosty system oparty na Firebase, który umieścimy na dedykowanym hostingu. Ten artykuł jest częścią serii "Firebase w zastosowaniach IoT" #1 - Czym jest Firebase? Jak zacząć? #2 - Firebase z ESP32 i ESP8266 #3 - Wyświetlanie danych użytkownikowi poprzez stronę internetową (właśnie to czytasz)
  4. Ten artykuł jest częścią serii "Firebase w zastosowaniach IoT" #1 - Czym jest Firebase? Jak zacząć? #2 - Firebase z ESP32 i ESP8266 #3 - Wyświetlanie danych użytkownikowi poprzez stronę internetową #4 - Projekt praktyczny, Hosting Tworząc projekty IoT często musimy przetwarzać dane i mieć do nich dostęp spoza sieci domowej. W zależności od potrzeb konkretnego projektu możemy postawić swój serwer np. na Raspberry Pi lub korzystać z gotowych rozwiązań innych firm, których na rynku jest mnóstwo. Dzisiaj przyjrzymy się propozycji od Google, czyli Firebase. Czym jest Firebase? Firebase to zestaw narzędzi od Google pozwalający w łatwy sposób tworzyć backend aplikacji webowych i mobilnych. Dzięki swojej prostocie, dużym możliwościom i rozbudowanej społeczności, tworzącej biblioteki do obsługi wielu języków programowania, jest także często wykorzystywany w systemach IoT. Firebase to zestaw wielu różnych usług, z których większość przeznaczona jest do profesjonalnego zastosowania przez twórców aplikacji. Dla nas (w kontekście projektów IoT) najważniejsze będą: Cloud Firestore i Realtime Database - bazy danych NoSQL Authentication - narzędzie pozwalające na łatwe zarządzanie logowaniami i użytkownikami Hosting - hosting dla naszego projektu Storage - usługa do przechowywania plików np. zdjęć i filmów Google Firebase pozwala też na integrację z innymi usługami od Google m.in. Google Cloud, gdzie możemy np. analizować dane za pomocą sztucznej inteligencji. Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » Ile to kosztuje? Usługi Firebase są udostępniane w systemie pay as you go. Pozwala to na łatwe skalowanie w zależności od aktualnych potrzeb projektu, bo płacimy tylko za to co wykorzystujemy. Google udostępnia darmowy plan Spark, który w zupełności wystarcza do zastosowań domowych i małych projektów. Jeżeli jednak darmowy limit nam nie starcza, w każdej chwili możemy przejść na płatny plan Blaze. Dokładne limity i ceny, wraz z intuicyjnym kalkulatorem, są opisane w cenniku. Bazy danych w Firestore Firebase daje nam do wyboru dwa rodzaje baz danych (obie NoSQL). Pierwszą z nich jest Realtime Database (RTDB). Jest to starsza usługa, gdzie dane są przechowywane w postaci jednego wielkiego drzewa JSON. Nowszym rozwiązaniem jest Cloud Firestore, gdzie dane przechowywane są w postaci zbioru dokumentów. Pozwala to wykonywać bardziej złożone zapytania na danych i umożliwia lepszą skalowalność projektu. W RTDB dane są przechowywane w postaci drzewa JSON. W Firestore dane przechowywane są w kolekcjach, które podzielone są na dokumenty. To, że RTDB jest starsza nie oznacza, że jest jest gorsza - wszystko zależy od wymagań konkretnego projektu. W dokumentacji znajduje się dokładniejsze porównanie tych baz danych wraz z quizem, który pomoże wybrać odpowiednią bazę do danego projektu. Tworzymy nasz pierwszy projekt Skoro już wiemy czym jest Firebase to nadszedł czas, aby utworzyć nasz pierwszy projekt. W tym celu przechodzimy do konsoli Firebase (wymagane jest zalogowanie kontem Google). Tutaj pokazane są wszystkie nasze dotychczasowe projekty. Klikamy okienko z wielkim plusem i napisem Dodaj projekt. Widok wszystkich naszych projektów w konsoli Firebase. Następnie nadajemy naszemu projektowi nazwę. W kolejnym kroku mamy możliwość podpięcia Google Analytics do naszego projektu, ale my na razie to pominiemy. Tworzenie projektu Firebase. Po utworzeniu naszego projektu pokazuje nam się ekran główny. Gdy mamy gotowy projekt to możemy podpinać do niego wybrane usługi i narzędzia Firebase. Po lewej stronie znajduje się boczny pasek menu, za pomocą którego możemy przejść do wybranych narzędzi. Ekran główny projektu. Na początku podepniemy Realtime Database. W tym celu przechodzimy do karty usługi klikając w menu bocznym. Następnie klikamy Utwórz bazę danych. Widok przed utworzeniem bazy danych. Wybieramy lokalizację serwera dla naszej bazy danych: USA lub Europa (na razie w wersji beta). W następnym kroku ustawiamy reguły zabezpieczeń dostępu do naszej bazy danych. Możemy wybrać tryb blokady (dostęp do zapisu i odczytu jest zablokowany dla wszystkich) lub tryb testowy (dostęp do zapisu i odczytu jest dostępny dla wszystkich użytkowników internetu). My na początku wybieramy tryb testowy. Więcej o regułach zabezpieczeń powiemy sobie później. Tworzenie bazy danych RTDB. I gotowe! Mamy pustą bazę danych i możemy zacząć z niej korzystać. Uzupełnijmy ją przykładowymi danymi - baza przechowująca ostatni pomiar z czujnika temperatury i wilgotności. Dodawanie danych do bazy RTDB. Teraz podepniemy bazę danych Firestore. Postępujemy analogicznie jak przy dodawaniu Realtime Database. W bocznym menu wybieramy Firestore i klikamy Utwórz bazę danych. Następnie wybieramy reguły zabezpieczeń (znowu tryb testowy) i lokalizację serwera. Tworzenie bazy danych Firestore. Gotowe! Uzupełnijmy bazę przykładowymi danymi (zwróćmy uwagę na różnicę w strukturze w porównaniu z analogicznym przykładem dla RTDB). Dodawanie kolekcji. Dodawanie nowego dokumentu w kolekcji. Uwierzytelnianie użytkowników W aplikacjach mobilnych, ale i w systemach IoT, zawsze gdy przechowujemy dane w internecie, ważne jest, aby zabezpieczyć je przed dostępem niepowołanych osób. Nie chcemy przecież, aby sąsiad mógł sterować odkurzaczem w naszym inteligentnym domu. Twórcy Firebase o tym pomyśleli i udostępnili usługę Firebase Authentication (Auth). Pozwala ona na łatwe uwierzytelnianie użytkowników i zarządzanie ich kontami. Umożliwia uwierzytelnianie poprzez zewnętrznego dostawcę (np. Google, Facebook...) lub poprzez e-mail i hasło. Dla tej ostatniej metody obsługuje również wysyłanie wiadomości e-mail o resetowaniu hasła i weryfikacji adresu e-mail. Aby dodać Auth do naszego projektu przechodzimy do zakładki Authentication w bocznym menu i klikamy Rozpocznij. Powinniśmy zostać przeniesieni do sekcji Sign-in method, gdzie widzimy wszystkie dostępne metody uwierzytelniania użytkowników. Aby móc używać danej metody musimy najpierw na nią kliknąć i ją włączyć. My na początku włączymy weryfikację przez e-mail. Dostępne metody uwierzytelniania użytkowników. Brawo! Właśnie udało nam się dodać metodę uwierzytelniania użytkowników poprzez e-mail. Jednak zanim będziemy mogli się zalogować musimy najpierw utworzyć konto użytkownika. Możemy to zrobić umieszczając w kodzie strony/aplikacji formularz do rejestracji lub zrobić to ręcznie z poziomu konsoli. My nie mamy jeszcze strony internetowej, więc skorzystamy z tej drugiej metody. W tym celu przechodzimy do sekcji Users i klikamy Dodaj użytkownika. Wpisujemy adres e-mail oraz hasło dla konta dodawanego użytkownika. Po zatwierdzeniu nowy użytkownik powinien pojawić się na liście. Dodawanie konta naszego pierwszego użytkownika. Zabezpieczenie baz danych Tworząc bazy danych wybraliśmy tryb testowy zabezpieczeń. Oznacza to, że obecnie każdy użytkownik internetu może modyfikować dane w naszych bazach danych, czego chcemy uniknąć (zazwyczaj). Pomocne będą reguły zabezpieczeń baz danych. Możemy ustalić, kto będzie mieć możliwość zapisu i odczytu danych w naszej bazie. Możemy skonfigurować oddzielne reguły zabezpieczeń dla każdej operacji i dla konkretnych części bazy danych np. możemy pozwolić użytkownikowi na dostęp do tych dokumentów, które są nazwane identyfikatorem tego użytkownika. Teraz ograniczymy dostęp do obu naszych baz tylko dla zalogowanych użytkowników. W tym celu przechodzimy do sekcji Reguły odpowiednio dla każdej z baz. RTDB W RTDB reguły mają postać: { "rules": { ".read": "warunek1", //odczytywanie danych z bazy jest możliwe, gdy spełniony jest warunek1 ".write": "warunek2", //zapisywanie danych do bazy jest możliwe, gdy spełniony jest warunek2 } } Domyślnie tryb testowy pozwala na dostęp wszystkim przez 30 dni. Reguła ta wygląda następująco: { "rules": { ".read": "now < 1616108400000", // 2021-3-19 ".write": "now < 1616108400000", // 2021-3-19 } } Reguły możemy dowolnie modyfikować. Teraz zezwolimy na odczyt i zapis w całej bazie tylko zalogowanemu użytkownikowi za pomocą warunku "auth != null": { "rules": { ".read": "auth != null", ".write": "auth != null", } } Działanie wpisanych przez nas warunków możemy testować w środowisku do testowania reguł. Działanie środowiska do testowania reguł. Firestore W Firestore reguły mają postać: rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} //ścieżka dostępu do dokumentu - w tym przykładzie definiujemy identyczne reguły dla wszystkich dokumentów { allow read: if warunek1; //odczytywanie danych z bazy jest możliwe, gdy spełniony jest warunek1 allow write: if warunek2; //zapisywanie danych do bazy jest możliwe, gdy spełniony jest warunek2 } } } Domyślnie tryb testowy pozwala na dostęp wszystkim przez 30 dni. Reguła ta wygląda następująco: rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.time < timestamp.date(2021, 3, 19); } } } Reguły możemy dowolnie modyfikować. Teraz zezwolimy na odczyt i zapis w całej bazie tylko zalogowanemu użytkownikowi za pomocą warunku request.auth != null: rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth != null; } } } Tak jak w przypadku RTDB działanie wpisanych przez nas warunków możemy testować w środowisku do testowania reguł. Działanie środowiska do testowania reguł. Więcej możliwości ustawienie reguł zabezpieczeń znajduje się w dokumentacji RTDB i dokumentacji Firestore. Podsumowanie W tej części dowiedzieliśmy się czym jest Firebase, jakie typy baz danych oferuje, jak stworzyć projekt Firebase oraz czym jest i jak działa Firebase Authentication. W kolejnych częściach przejdziemy już do zastosowania praktycznego.
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.