Projekt ESP32 online

Odpovědět
Pablo74
Příspěvky: 127
Registrován: 03 lis 2019, 17:00

Projekt ESP32 online

Příspěvek od Pablo74 » 05 lis 2024, 18:53

Projekt je v počáteční fázi a vše se ještě stokrát změní.

Začínám pracovat na menším projektu s pracovním názvem ESP32 online. Cílem je obousměrná komunikace s vývojovou deskou s jednočipem ESP32 přes internet.

Data se budou ukládat do Firebase Realtime Database, každá deska bude mít svůj objekt JSON v databázi se šesti klíči (položky), s pevně danými názvy A až F; tedy deska s názvem devA bude mít objekt v /devboards/devA a klíč F bude dostupný přes referenci /devboards/devA/values/F. Klíčů (položek) je pro každou desku šest, A až F. Pevné názvy jsou zvoleny záměrně, kvůli snadnější implementaci.

Každá deska může mít naprosto odlišný kód a používat klíče A až F různým způsobem, libovolně pro čtení hodnot i pro jejich zápis. Přes wifi bude připojena do internetu a k databázi Firebase.

Mobilní/webová aplikace bude mít dashboard, kde bude možnost data nejen zobrazit, ale i měnit; a to nejen pro jednu konkrétní desku, ale i pro více různých desek.

Řekněme, že desky devA až devC budou obsahovat čidlo teploty, tlaku a relativní vlhkosti a každá z nich tyto hodnoty publikuje v klíčích A až C do databáze.

Na PC/tabletu/mobilu půjde zobrazit na Dashboard tyto informace různým způsobem. Dashboardy totiž půjde vytvářet a uloženy budou také v databázi a dostupné přes referenci /dashboards/dashboardA atd.

Na dashboard budou různé komponenty, některé jen hodnotu zobrazí (to dává smysl třeba pro teplotu, tlak, relativní vlhkost), některé komponenty umožní data i měnit a deska tyto data použije v programu (třeba hodnotu intenzity osvětlení, dobu sepnutí světla, …).

Projekt je v počáteční fázi a vše se ještě stokrát změní.

Zatím mám strukturu dat v realtime databázi pro /devboards a /dashboards a několik komponent pro vizualizaci a příp. nastavení dat, konkrétně TextComponent, SliderComponent, InputComponent a ButtonsComponent. Používám VueJS verze 3 (option api), Quasar Framework a taky pomoc od AI.

JSON objekt pro desku vypadá takhle:

Kód: Vybrat vše

{
"devA": 
  {
    "values": {
      "A": "246",
      "B": 82,
      "C": 10,
      "D": "20",
      "E": "Yes",
      "F": "No"
  }
}
Dashboard je založen na umístění komponent do layoutu, malý příklad dvou komponent vedle sebe, JSON objekt:

Kód: Vybrat vše

[
  {
    "component": "slider",
    "devboard": "devA",
    "key": "B"
  },
  {
    "class": "text-white",
    "component": "text",
    "conditions": [
      {
        "class": "bg-red",
        "expression": "value < 50"
      },
      {
        "class": "bg-green",
        "expression": "value >= 50"
      }
    ],
    "devboard": "devA",
    "key": "B",
    "props": {
      "next": "%",
      "prev": "rel. vlhkost"
    }
  }
]
První JSON objekt je komponenta slider, má jen tři klíče, druhý z nich se odkazuje na název desky (devA) a třetí na její klíč (B). Jde o slider, tj. tato hodnota se dá měnit a změna se rovnou bez prodlevy zapíše do databáze.

Druhý JSON objekt reprezentuje text, zdánlivě jednoduchou komponentu, ale je v ní implementováno i něco logiky. Odkazuje se na stejnou desku (devA) a stejný klíč (B), tím se dá snadno ověřit, že změna hodnoty na slideru se opravdu propíše do databáze a v této komponentě text se správně zobrazí.
Komponenta toho umí ale daleko víc, než jen prosté zobrazení číselné hodnoty. Klíče prev a next umožní zobrazit text před a za hodnotou z databáze (klíč B z desky devA). Dokonce je možnost dynamicky měnit vizuální reprezentaci hodnoty, to zajišťují klíče class (CSS třída, která se aplikuje) a expression (podmínka, za které se CSS třída aplikuje). V tomto konkrétním případě se hodnoty menší než 50 zobrazí jako bílý text na červeném pozadí (CSS třída bg-red znamená červené pozadí) a hodnoty větší než 50 se zobrazí s pozadím zeleným (CSS třída bg-green).

Dashboard jako JSON objekt píšu ručně a vložím ho do databáze.

To je současná fáze projektu, občas si sem uložím myšlenku nebo pokrok.

Pablo74
Příspěvky: 127
Registrován: 03 lis 2019, 17:00

Re: Projekt ESP32 online

Příspěvek od Pablo74 » 12 lis 2024, 08:19

Pro představu: co mám hotovo a jak to vypadá - přidávám dva náhledy z mobilu (ořezáno). Můžete se všimnout, že zobrazení umí reagovat na hodnotu, např. u relativní vlhkosti mám mezní hodnotu 50, co je pod je podbarveno červeně, co je nad je zeleně. Jak jsem toho docílil je v předchozím příspěvku.
ESP32_online_01_A.png
ESP32_online_02_A.png

Pablo74
Příspěvky: 127
Registrován: 03 lis 2019, 17:00

Re: Projekt ESP32 online

Příspěvek od Pablo74 » 17 lis 2024, 22:19

Malý povzdech...

Pracuju teď na kódu v ESP32, který má komunikovat s databází Firebase realtime database. S vydatnou pomocí AI, konkrétně Claude Sonnet 3.5, se snažím o komunikaci. Do hry vstupují pravidla (Firebase rules), autentifikace, token atd.

Nu a musím říct, že se nedaří. Nic mi nefunguje, a to kolem toho skáčeme s AI dva dny a zkouším kde co. Snažím se o naslouchání změn v datech v databázi, ne o permanentní dotazování se každých pár sekund.

Budu muset změnit způsob autentifikace, zřejmě na email a heslo. Snad se mi zadaří.

Pablo74
Příspěvky: 127
Registrován: 03 lis 2019, 17:00

Re: Projekt ESP32 online

Příspěvek od Pablo74 » 15 úno 2025, 14:24

Po dlouhé době jsem se k tomu vrátil a začalo se mi částečně dařit.

Po mnoha peripetiích, práce mé i práce AI (Claude Sonnet 3.5) mám dílčí úspěch. Podařilo se mi dosáhnout stavu, kdy ESP32 po připojení na wifi a úspěšném (!) přihlášení do Firebase začne naslouchat změnám v datech ve Firebase Realtime Database. Přihlášení mám přes Firebase Token, který jsem vygeneroval v konzoli Firebase (už nevím jak, ale to půjde snadno najít).

Zatím mám funkční kód, který v případě, že v klíči /A je textový řetězec (string) vypíše jeho obsah na displej.

Dá se říct, že základ mám tedy hotový a teď budu muset kód pro ESP32 upravovat, doplňovat, testovat, ladit..., snad do zdárného konce. Ještě to bude dost práce.

Cílem projektu je obousměrně komunikovat mezi ESP32 a mobilem (web aplikace), ať už budu kdekoli.

Pablo74
Příspěvky: 127
Registrován: 03 lis 2019, 17:00

Re: Projekt ESP32 online

Příspěvek od Pablo74 » 15 úno 2025, 22:18

Dosavadní kód, kdy ESP32 nic nezapisuje (neposílá) do Firebase, ale reaguje na změny v klíči /A a /B pro danou desku, pokud jsou tyto hodnoty textový řetězec (string), vypíše je na displej (128 x 64, 1 bit), knihovna u8g2.

Kód: Vybrat vše

#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <addons/TokenHelper.h>
#include <addons/RTDBHelper.h>

#include <Wire.h>
#include <U8g2lib.h>

// Konfigurace
#define WIFI_SSID "... WiFi SSID ..."
#define WIFI_PASSWORD "... WiFi password ..."
#define API_KEY "... Firebase Web API Key ..."
#define DATABASE_URL "... Firebase database URL ..."

// Přidejte vlastní token zde - musíte ho vygenerovat ve Firebase Admin SDK
#define FIREBASE_AUTH_TOKEN "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----\n"

#define DEVICE_ID "devA"

U8G2_SSD1306_128X64_NONAME_F_SW_I2C
u8g2(U8G2_R0, 22, 21, U8X8_PIN_NONE);

FirebaseData fbdo;
FirebaseData stream;
FirebaseAuth auth;
FirebaseConfig config;

float floatA = 0;
float floatB = 0;
float floatC = 0;
float floatD = 0;
float floatE = 0;
float floatF = 0;

String strA; 
String strB;
String strC;
String strD;
String strE;
String strF;

unsigned long streamReconnectMillis = 0;
int reconnectCount = 0;

void displayFirebaseString(int x, int y, String str){
  u8g2.setDrawColor(0);
  u8g2.drawBox(0, y-14, 128, 16);
  u8g2.setDrawColor(1);
  u8g2.drawStr(x, y, str.c_str());
  u8g2.sendBuffer();
}

void streamCallback(FirebaseStream data) {
    Serial.println("Stream callback zavolán!");
    Serial.printf("Cesta: %s\n", data.dataPath().c_str());
    Serial.printf("Typ dat: %s\n", data.dataType().c_str());
    
    if(data.dataType() == "json") {
        FirebaseJson &json = data.jsonObject();
        String jsonStr;
        json.toString(jsonStr, true);
        Serial.println("Přijatá data: " + jsonStr);
    }

    //
    if (data.dataType() == "string"){
      if (data.dataPath() == "/A"){
        strA = data.stringData();
        Serial.println("Přijatá data, A = " + strA);
        displayFirebaseString(0, 12, strA);
      }
      if (data.dataPath() == "/B"){
        strB = data.stringData();
        Serial.println("Přijatá data, B = " + strB);
        displayFirebaseString(0, 30, strB);
      }
    }
}

void streamTimeoutCallback(bool timeout) {
    if (timeout) {
        Serial.println("Stream timeout!");
    }
    if (!stream.httpConnected()) {
        Serial.printf("HTTP kód: %d\n", stream.httpCode());
        Serial.printf("Důvod: %s\n", stream.errorReason().c_str());
    }
}

bool setupStream() {
    if (!Firebase.ready()) {
        Serial.println("Firebase není připraven!");
        return false;
    }

    String path = "/devboards/" + String(DEVICE_ID) + "/values";
    Serial.printf("Pokus o připojení ke streamu na cestě: %s\n", path.c_str());

    stream.setBSSLBufferSize(4096, 1024);
    stream.setResponseSize(4096);

    if (!Firebase.RTDB.beginStream(&stream, path.c_str())) {
        Serial.printf("Chyba při nastavování streamu: %s\n", stream.errorReason().c_str());
        return false;
    }

    Firebase.RTDB.setStreamCallback(&stream, streamCallback, streamTimeoutCallback);
    Serial.println("Stream úspěšně nastaven");
    return true;
}

bool initializeFirebase() {
    config.api_key = API_KEY;
    config.database_url = DATABASE_URL;
    config.timeout.serverResponse = 10000;
    
    Serial.println("Inicializuji Firebase...");
    Firebase.begin(&config, &auth);
    Firebase.reconnectWiFi(true);

    // Anonymní přihlášení
    Serial.println("Pokus o anonymní přihlášení...");
    if (Firebase.signUp(&config, &auth, FIREBASE_AUTH_TOKEN, "")) {
        Serial.println("Anonymní přihlášení úspěšné");
        Serial.print("Čekám na token");
        while (auth.token.uid.empty()) {
            Serial.print(".");
            delay(1000);
        }
        Serial.printf("\nToken získán: %s", auth.token.uid);
        return true;
    } else {
        Serial.printf("Chyba přihlášení: %s\n", config.signer.signupError.message.c_str());
        return false;
    }
}

bool createNewUser() {
    Serial.println("Vytvářím nového anonymního uživatele...");
    
    if (!Firebase.signUp(&config, &auth, FIREBASE_AUTH_TOKEN, "")) {
        Serial.printf("Chyba při vytváření uživatele: %s\n", config.signer.signupError.message.c_str());
        return false;
    }
    
    Serial.println("Anonymní přihlášení úspěšné");
    Serial.print("Čekám na token");
    
    unsigned long startTime = millis();
    while (auth.token.uid.empty()) {
        if (millis() - startTime > 10000) {  // 10 sekund timeout
            Serial.println("\nTimeout při čekání na token!");
            return false;
        }
        Serial.print(".");
        delay(1000);
    }
    
    Serial.printf("\nToken získán: %s\n", auth.token.uid.c_str());
    
    return true;
}

void setup() {
    Serial.begin(115200);
    Serial.println("\nStartuji aplikaci...");

    u8g2.begin();
    u8g2.setFont(u8g2_font_7x14B_tr);
    
    // WiFi připojení
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("Připojování k WiFi");
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(300);
    }
    Serial.println("\nPřipojeno k WiFi");
    Serial.println("IP: " + WiFi.localIP().toString());
    
    // Inicializace Firebase
    if (!initializeFirebase()) {
        Serial.println("Chyba při inicializaci Firebase!");
        ESP.restart();
        return;
    }

    // Test zápisu do databáze
    Serial.println("Test zápisu do databáze...");
    if (Firebase.RTDB.setInt(&fbdo, "/devboards/" + String(DEVICE_ID) + "/test", 1)) {
        Serial.println("Test zápisu úspěšný");
    } else {
        Serial.println("Test zápisu selhal:");
        Serial.println("Důvod: " + fbdo.errorReason());
    }

    // Nastavení streamu
    if (!setupStream()) {
        Serial.println("Počáteční nastavení streamu selhalo!");
    }
}

void loop() {
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("WiFi odpojeno!");
        ESP.restart();
        return;
    }

    if (!stream.httpConnected()) {
        unsigned long currentMillis = millis();
        if (currentMillis - streamReconnectMillis > 5000) {
            streamReconnectMillis = currentMillis;
            reconnectCount++;
            
            Serial.printf("Pokus o obnovení streamu #%d\n", reconnectCount);
            if (setupStream()) {
                reconnectCount = 0;
                Serial.println("Stream obnoven!");
            } else {
                Serial.println("Obnovení streamu selhalo");
                if (reconnectCount > 5) {
                    Serial.println("Příliš mnoho pokusů o reconnect, restartuji...");
                    ESP.restart();
                }
            }
        }
    }

    delay(1000);
}
Kód není úplně vyšperkovaný, je to průběžná, ale již funkční verze, dost toho napsala AI.
Je dobré zmínit, že ESP32 reaguje až na změnu v datech, takže prvotně načtená data nezobrazí. Jako autentizaci do Firebase používám anonymní přihlášení, to je potřeba nakonfigurovat ve Firebase konzoli.

Výhodou mého řešení (většinu kódu ale napsala AI) je oproti jiným v tom, že já se z ESP32 aktivně nedotazuji na data ve Firebase, ale naslouchám změnám v datech. Toto řešení jsem nikde na internetu nenašel.

Firebase můžete používat zdarma, stačí se zaregistrovat, nakonfigurovat a používat; kvalitních návodů psaných i ve formě videí jsou spousty.

Pablo74
Příspěvky: 127
Registrován: 03 lis 2019, 17:00

Re: Projekt ESP32 online

Příspěvek od Pablo74 » 17 úno 2025, 17:01

Projekt je v počáteční fázi a vše se ještě stokrát změní.

Projekt ESP32 má být univerzálnějším základem pro různá použití, jde o jednosměrnou či obousměrnou komunikaci mezi ESP32 a mobilem (tabletem, ...).

Došlo mi, že struktura dat v databázi Firebase není vhodná, protože bych musel v každém programu pro ESP32 řešit, jakého datového typu jsou jednotlivé klíče (např. devA/F).

Původně zamýšlenou strukturu dat

Kód: Vybrat vše

{
"devA": 
  {
    "values": {
      "A": "246",
      "B": 82,
      "C": 10,
      "D": "20",
      "E": "Yes",
      "F": "No"
  }
}
nahradím univerzálnější strukturou, kdy budu mít vždy šest textových řetězců (str), šest čísel (num) a šest logických hodnot (log) - to pro jednu desku ESP32 naprosto stačí a jeden zpětný kanál (ack) pro potvrzení s časovým razítkem a textovým řetězcem.

Kód: Vybrat vše

{
"devA": 
  {
    "values": {
      "str": {
        "A": "",
        ...
        "F": ""
      },
      "num": {
        "A": 0,
        ...
        "F": 0
      },
      "log": {
        "A": false,
        ...
        "F": false
      },
      "ack": {
        "ts": 1739807571,
        "info": ""
      }
  }
}

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 1 host