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.
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.
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í.
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.
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

