blog.ijacek007.cz

Blog o všem trochu jinak.

Portál pro bastlíře laskakit.cz má v prodeji zajímavou desku s čipem ESP32 a 72 RGB adresovatelnými diodami. Pomocí připraveného FW můžete na mapě zobrazovat například teploty napříč republikou. Ta pravá zábava ale nastává jako vždy, když použijete připravený modul pro vlastní projekt a právě na to se dnes podíváme.


Obrázek

Objednat, vybalit, zapojit a jen se dívat, není to samé jako připravený nástroj v našem případě mapu vzít a připravit si na ní vlastní projekt. Protože pravidelně streamuji na Twitchi, rozhodl jsem se tuto mapku předělat tak, aby zobrazovala vracející se návštěvníky a tím se defacto online rozzářily na mapě, jakmile napíšou do chatu.


Steam

Máme náš stream propojený s webovým portálem, kde uživatelům Twitche na streamu počítáme různé statistiky. Pak stačilo jen návštěvníky chatu přesvědčit, aby si v portále nastavily svůj okres. Upravil jsem tedy FW mapy, aby načítala data z našeho webu. Pokud je stream off-line mapa si načte data teploty a zobrazí je. Pokud je ale stream online, pak se načítání dat změní ze souboru, který se stará o zobrazení návštěvníků. Jamile napíše v chatu někdo, kdo si nastavil okres, do minuty si jej mapa načte a jeho ledku okresu rozsvítí.


Obrázek

Mapka je velmi pěkná. Nicméně poměrně malá a na streamu nejde úplně vidět. Připravil jsem na stream ještě zobrazení virtuální mapy, která načítá data, jako její fyzický bratr, a tak je možné mapku zobrazit i zvětšenou, aby byly vidět detaily. Další zábava pak přišla s myšlenkou, kdy uživatelé streamu vymysleli, že by mohli během vysílání po mapě cestovat. Rozšířily jsme tak příkaz okres, aby při prvním použití jen uložil okres návštěvníka a pokud uživatel použije příkaz znovu, může ze svého nastaveného okresu „odcestovat“ a tím po mapě v podobě svého světélka cestovat.


Obrázek

Protože pro stream je například důležité ovládat svit led diod, aby se v kameře neslívaly v jeden světelný bod nebo různě měnit nastavení času aktualizace či testování jednotlivých bodu na mapě, rozšířil jsem firmware i o možnost jednoduchého testu či nastavení. Stále ale platí, že hlavní logiku obstarává webserver, který rozhoduje, co na mapě zobrazí nebo jakou to bude mít barvu. Jak je vidět, tak u podobných desek se rozhodně nemusíte omezovat na to, co před vámi autoři již nachystali, ale například si vymyslet úplně vlastní použití.

#include "wifi_nast.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>          // https://github.com/bblanchon/ArduinoJson v6+
#include "Freenove_WS2812_Lib_for_ESP32.h"
#include <WebServer.h>              // Knihovna pro webový server
#include <Preferences.h>            // Knihovna pro ukládání dat do NVRAM
#define LEDS_COUNT  72
#define LEDS_PIN        25
#define CHANNEL         0

// URL with JSON data
const char* json_url = /**/"http://stream.ijacek007.cz/mapacr.php";
unsigned long lastTime = 0;
unsigned long timerDelay = 60000;
int lastid, value, color, first_time;
Freenove_ESP32_WS2812 strip = Freenove_ESP32_WS2812(LEDS_COUNT, LEDS_PIN, CHANNEL, TYPE_GRB);
String sensorReadings;
float sensorReadingsArr[3];

// Initialize array with number of districts readed from TMEP,
// which we will later populate with values from JSON
float TMEPDistrictTemperatures[77];

// We need to map our LED order with district order from TMEP,
// so here we have array with positions of districts on TMEP that we get from JSON
// starting from zero, so: index 0 (first LED) is district with ID 9 and so on
// until we have district for every from our 72 LEDs
int TMEPDistrictPosition[72] = {
 9, 11, 12, 8, 10, 13, 6, 15, 7, 5, 3, 14, 16, 67, 66, 4, 2, 24, 17, 1, 68, 18, 65, 64, 0,
 25, 76, 20, 69, 19, 27, 23, 73, 70, 21, 29, 28, 59, 22, 71, 61, 63, 30, 72, 31, 26, 48,
 46, 33, 39, 58, 49, 51, 47, 57, 40, 32, 35, 56, 38, 55, 34, 45, 41, 50, 36, 54, 52, 37,
 44, 53, 43
};

// --- Web Server proměnné a objekty ---
WebServer server(80); // Vytvoření webového serveru na portu 80
Preferences preferences; // Objekt pro Preferences

// Proměnná pro ukládání jasu (0-255)
int currentBrightness = 20; // Výchozí jas, pokud není uložena žádná hodnota


// --- Funkce pro obsluhu webového serveru ---
void handleRoot() {
  String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ESP32 Mapa CR</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
        .container { background-color: #f0f0f0; padding: 20px; border-radius: 8px; max-width: 400px; margin: auto; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        h1 { color: #333; }
        label { display: block; margin-bottom: 10px; font-size: 1.1em; }
        input[type="range"] { width: 80%; margin-bottom: 20px; }
        button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; margin: 5px; }
        button.restart { background-color: #f44336; }
        button:hover { opacity: 0.8; }
        p { margin-top: 20px; font-size: 0.9em; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Nastavení Mapa ČR</h1>
        <p>IP adresa: )rawliteral" + WiFi.localIP().toString() + R"rawliteral(</p>
        <form action="/set_brightness" method="get">
            <label for="brightness">Jas LED: <span id="brightnessValue"></span></label>
            <input type="range" id="brightness" name="value" min="0" max="255" value=")rawliteral" + String(currentBrightness) + R"rawliteral(" oninput="updateBrightness(this.value)">
            <button type="submit">Nastavit jas</button>
        </form>
         <hr>
        <form action="/set_update_speed" method="get">
            <label for="update_speed">Rychlost aktualizace (sekundy): <span id="updateSpeedValue"></span></label>
            <input type="range" id="update_speed" name="value" min="30" max="3600" value=")rawliteral" + String(timerDelay / 1000) + R"rawliteral(" oninput="updateUpdateSpeed(this.value)">
            <button type="submit">Nastavit rychlost</button>
        </form>
        <hr>
         <p>
            <a href="/test">Testovací stránka LED</a>
        </p>

        <form action="/restart" method="post">
            <button type="submit" class="restart">Restartovat ESP32</button>
        </form>
    </div>

 <script>
        function updateBrightness(val) {
            document.getElementById('brightnessValue').innerText = val;
        }
        function updateUpdateSpeed(val) {
            document.getElementById('updateSpeedValue').innerText = val;
        }
        window.onload = function() {
            updateBrightness(document.getElementById('brightness').value);
            updateUpdateSpeed(document.getElementById('update_speed').value);
        };
    </script>
</body>
</html>
)rawliteral";
  server.send(200, "text/html", html);
}

void handleSetBrightness() {
  if (server.hasArg("value")) {
    int newBrightness = server.arg("value").toInt();
    if (newBrightness >= 0 && newBrightness <= 255) {
      currentBrightness = newBrightness;

      // Uložení jasu do paměti
      preferences.begin("mapa-cr", false); // Otevři namespace 'mapa-cr' pro zápis
      preferences.putInt("jas", currentBrightness); // Ulož proměnnou 'jas'
      preferences.end(); // Uzavři namespace

      strip.setBrightness(currentBrightness); // Aplikuj nový jas na pásek
      strip.show(); // Je důležité zavolat show() po změně jasu
      Serial.print("Jas nastaven a uložen na: ");
      Serial.println(currentBrightness);
    }
  }
  server.sendHeader("Location", "/"); // Přesměruj zpět na hlavní stránku
  server.send(302, "text/plain", "");

}

void handleSetUpdateSpeed() {
  if (server.hasArg("value")) {
    int newSpeedSeconds = server.arg("value").toInt();
    // Ověříme rozsah (např. 10 sekund až 1 hodina = 3600 sekund)
    if (newSpeedSeconds >= 10 && newSpeedSeconds <= 3600) {
      timerDelay = (unsigned long)newSpeedSeconds * 1000; // Převod na milisekundy

      preferences.begin("mapa-cr", false);
      preferences.putULong("update_delay", timerDelay); // Ulož jako unsigned long
      preferences.end();

      Serial.print("Rychlost aktualizace nastavena a uložena na: ");
      Serial.print(newSpeedSeconds);
      Serial.println(" sekund.");
    } else {
      Serial.print("Neplatná hodnota rychlosti aktualizace: ");
      Serial.println(newSpeedSeconds);
    }
  }
  server.sendHeader("Location", "/");
  server.send(302, "text/plain", "");
}

void handleRestart() {
  server.send(200, "text/html", "<h1>Restartuji ESP32...</h1><p>Prosím, počkejte několik sekund.</p>");
  delay(1000); // Dej čas prohlížeči, aby zobrazil zprávu
  ESP.restart(); // Restartování ESP32
}
// --- Nová funkce pro zobrazení testovací stránky LED ---
void handleTestPage() {
  String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <title>Test LED diod</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
        .container { background-color: #f0f0f0; padding: 20px; border-radius: 8px; max-width: 400px; margin: auto; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        h1 { color: #333; }
        label { display: block; margin-bottom: 10px; font-size: 1.1em; }
        input[type="number"], button { padding: 10px; margin: 5px; border-radius: 5px; border: 1px solid #ddd; }
        button { background-color: #007bff; color: white; cursor: pointer; }
        button:hover { opacity: 0.8; }
        p { margin-top: 20px; font-size: 0.9em; color: #666; }
        a { color: #007bff; text-decoration: none; margin: 0 10px; }
        a:hover { text-decoration: underline; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Test jednotlivých LED diod</h1>
        <p>Nastav barvu LED diody zadáním jejího ID (0 až )rawliteral" + String(LEDS_COUNT - 1) + R"rawliteral() a hodnoty:</p>
        <ul>
            <li><b>-99:</b> LED zhasne (černá)</li>
            <li><b>-15 až 40:</b> Teplota v °C, která se namapuje na barvu</li>
        </ul>
        <form action="/set_test_led" method="get">
            <label for="led_id">ID diody (0 - )rawliteral" + String(LEDS_COUNT - 1) + R"rawliteral()):</label>
            <input type="number" id="led_id" name="id" min="0" max=")rawliteral" + String(LEDS_COUNT - 1) + R"rawliteral(" value="0" required><br><br>
            <label for="temp_value">Hodnota (-99 nebo -15 až 40):</label>
            <input type="number" id="temp_value" name="temp" min="-99" max="40" value="0" required><br><br>
            <button type="submit">Nastavit LED</button>
        </form>
        <hr>
        <p>
            <a href="/">Zpět na hlavní stránku</a>
        </p>
    </div>
</body>
</html>
)rawliteral";
  server.send(200, "text/html", html);
}

// --- Funkce pro zpracování nastavení testovací LED ---
void handleSetTestLed() {
  if (server.hasArg("id") && server.hasArg("temp")) {
    int ledId = server.arg("id").toInt();
    float tempValue = server.arg("temp").toFloat();

    if (ledId >= 0 && ledId < LEDS_COUNT) {
      if (tempValue == -99.0) {
        strip.setLedColorData(ledId, 0, 0, 0); // Zhasnout LED
        Serial.print("LED ID "); Serial.print(ledId); Serial.println(": Zhasnuto (hodnota -99)");
      } else if (tempValue >= -15.0 && tempValue <= 40.0) {
        int color_wheel_value = map(tempValue, -15, 40, 170, 0);
        strip.setLedColorData(ledId, strip.Wheel(color_wheel_value));
        Serial.print("LED ID "); Serial.print(ledId); Serial.print(": Nastavena teplota "); Serial.print(tempValue); Serial.print(" -> Barva (Wheel) "); Serial.println(color_wheel_value);
      } else {
        strip.setLedColorData(ledId, 0, 0, 0); // Neplatná hodnota, zhasni
        Serial.print("LED ID "); Serial.print(ledId); Serial.print(": Neplatná hodnota teploty "); Serial.println(tempValue);
      }
      strip.show(); // Aplikuj změnu na LED pásek
    } else {
      Serial.print("Neplatné ID LED: "); Serial.println(ledId);
    }
  }
  server.sendHeader("Location", "/test"); // Přesměruj zpět na testovací stránku
  server.send(302, "text/plain", "");
}


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

    Serial.begin(115200);
    delay(10);
     strip.begin();

     // Načti jas z paměti při startu
  preferences.begin("mapa-cr", false); // Otevři namespace 'mapa-cr' pro čtení
  currentBrightness = preferences.getInt("jas", 20); // Načti 'jas', pokud neexistuje, použij 20 jako výchozí
  timerDelay = preferences.getULong("update_delay", 60000); // Načti 'update_delay', pokud neexistuje, použij 60000ms (1 min) jako výchozí

  preferences.end(); // Uzavři namespace

  strip.setBrightness(currentBrightness); // Nastav načtený jas
  Serial.print("Načtený jas z paměti: ");
  Serial.println(currentBrightness);
    // Zhasnutí všech LED diod ručně

    // Zhasnutí všech LED diod ručně (OPRAVENO)
    for (int i = 0; i < LEDS_COUNT; i++) {
        // Nastav barvu LED diody s indexem 'i' na černou (0, 0, 0 pro RGB)
        strip.setLedColorData(i, 0, 0, 0);
        // NEVOLEJ strip.show() ani delay() uvnitř této smyčky!
    }
    // Až po nastavení všech diod zavolej strip.show() JEDNOU
    strip.show(); // Odešli změny na LED pásek
    Serial.println("All LEDs should be off now.");
    delay(50); // Krátká pauza, pokud by LED potřebovaly čas na aktualizaci, ale obvykle není nutná


    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid3);
    WiFi.begin(ssid3, password3);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());


// --- Nastavení webového serveru ---
  server.on("/", handleRoot); // Když někdo přistoupí na hlavní stránku (IP adresa)
  server.on("/set_brightness", handleSetBrightness); // Když se odešle formulář pro nastavení jasu
  server.on("/set_update_speed", handleSetUpdateSpeed); // Rychlost nacitani
  server.on("/restart", HTTP_POST, handleRestart); // Když se klikne na tlačítko restartu (POST požadavek)
  server.on("/test", handleTestPage); // Nová testovací stránka
  server.on("/set_test_led", handleSetTestLed); // Zpracování formuláře pro testovací LED


  server.begin(); // Spustí webový server
  Serial.println("HTTP server started");


}

void loop()
{
     server.handleClient(); // Důležité: Obsluhuj požadavky webového serveru v loopu!

    // Did we wait long enough or was it just powered on?
    if ((millis() - lastTime) > timerDelay || first_time == 0) {
        if(WiFi.status()== WL_CONNECTED){
            first_time = 1;
            sensorReadings = httpGETRequest(json_url);
            DynamicJsonDocument doc(6144);
            DeserializationError error = deserializeJson(doc, sensorReadings);
            if (error) {
                Serial.print(F("deserializeJson() failed: "));
                Serial.println(error.f_str());
                return;
            }

            String tmp;
            float maxTemp = -99;
            float minTemp =  99;
            // Read all TMEP districts with their indexes
            for (JsonObject item : doc.as<JsonArray>()) {
                int TMEPdistrictIndex = item["id"];
                // Substract 1, so index will start from 0
                TMEPdistrictIndex -= 1;
                double h = item["h"];
                TMEPDistrictTemperatures[TMEPdistrictIndex] = h;

                // find the min and max temperature for adjusting of color layout
                tmp = TMEPDistrictTemperatures[TMEPdistrictIndex];
                if (tmp.toFloat() < minTemp) minTemp = tmp.toFloat();
                if (tmp.toFloat() > maxTemp) maxTemp = tmp.toFloat();
            }

             // Now go through our LEDs and we will set their colors
            for (int LED = 0; LED < LEDS_COUNT - 1; LED++) {
                // Get color for correct district LED
                if (TMEPDistrictTemperatures[TMEPDistrictPosition[LED]] == -99 ){
                strip.setLedColorData(LED, 0, 0, 0);
                }else {
                color = map(TMEPDistrictTemperatures[TMEPDistrictPosition[LED]], -15, 40, 170, 0);
                strip.setLedColorData(LED, strip.Wheel(color));
                }
            }

            strip.show();
        }
        else {
            Serial.println("WiFi Disconnected");
        }
        lastTime = millis();
    }
}

String httpGETRequest(const char* serverName) {
    WiFiClient client;
    HTTPClient http;
    http.begin(client, serverName);
    int httpResponseCode = http.GET();
    String payload = "{}";
    if (httpResponseCode>0) {
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);
        payload = http.getString();
    }
    else {
        Serial.print("Error code: ");
        Serial.println(httpResponseCode);
    }
    http.end();
    return payload;
}

Mohlo by vás zajímat

Twitch kanál

Webový portál ke kanálu na Twitchi

Adresa prodejce interaktivní mapy laskakit.cz


Štítky článku elektronika | myslenky | programovani | zajimavosti |
Autor Ijacek.007 23.10.2025 Opravil(a) sokorka zobrazeno 60x
Předchozí článek Autodesk Tinkercad jak emulovat kód i chování Arduina zdarma


gravatar

Vložit komentář

Nick *:
WWW:
Email * (nezobrazuje se ):
Gravatar:
Pamatuj si mě:
Komentář článku *:
Opiš následující text: *

* - vyžadované údaje. RSS kanál s komentáři

Přihlášení



Audioknihy

Jsme milovníci audio knížek, kterých aktuálně máme zakoupených 486. Poslech všech dohromady zabral přes 6729 hodin.

Z tohoto množství jsme si již stihli poslechnout téměř 48% tedy 233 audioknih.

Aktuálně poslouchaná audioknihakniha je Orlova kořist

Poslední hodnocenou audioknihou je Úsvit sklizně Hodnocení audioknihy 4/5.

Nejlépe hodnocenou audioknihou je Astronautův průvodce životem na Zemi Hodnocení audioknihy 4/5.

Reklama