Obsah
LED mapa okresů ČR
Tvoje Máma (Twitter) má na svědomí výbornou mapu ČR - co okres, to jedna adresovatelná LEDka.
Informace o mapě, jejím osazení a další naleznete na oblíbeném serveru Chiptron.cz zde: Fantastická mapa České republiky. Co okres, to jedna adresovatelná RGB LED.
Formulář pro objednání: https://forms.gle/DAS6gksyBvNbf8TD8
GitHub se zdrojovým kódem: https://github.com/tomasbrincil/pcb_mapa_cr_1
Data pro okresy
Data dodává TMEP na základě mapy ČR a publikovaných venkovních čidel. Pokud měříte a jste na mapě venkovních čidel či ovzduší a máte vyplněný u čidla okres, pak je vaše měření zahrnuté do podkladových dat. V případě, že je čidel v okrese více, bere se průměr hodnot ze všech těchto čidel. Ve všech případech se bere pouze měření, které není starší jak 1 hodina. Pokud měření pro některý okres chybí, je vložena hodnota celorepublikového průměru ze všech naměřených okresů. Aktualizovaný seznam chybějících okresů naleznete zde: https://cdn.tmep.cz/app/export/okresy-cr-co-nemame.html
Data jsou JSON pole ve formátu:
[ { "id":1, "h":100 }, { "id":2, "h":72.7 }, ... ]
Podkladová data, aktualizovaná po minutě:
http://cdn.tmep.cz/app/export/okresy-cr-teplota.json
http://cdn.tmep.cz/app/export/okresy-cr-vlhkost.json
http://cdn.tmep.cz/app/export/okresy-cr-tlak.json
http://cdn.tmep.cz/app/export/okresy-cr-prasnost.json
Všechny čtyři hodnoty v jednom:
http://cdn.tmep.cz/app/export/okresy-cr-vse.json
h1 - teplota, h2 - vlhkost, h3 - tlak, h4 - kvalita ovzduší
Číselník okresů
V datech se okres váže na ID s tím, že pořadí okresů odpovídá pořadí adresovatelných LEDek na mapě:
ID | Okres |
---|---|
1 | Cheb |
2 | Sokolov |
3 | Karlovy Vary |
4 | Chomutov |
5 | Louny |
6 | Most |
7 | Teplice |
8 | Litoměřice |
9 | Ústí nad Labem |
10 | Děčín |
11 | Česká Lípa |
12 | Liberec |
13 | Jablonec nad Nisou |
14 | Semily |
15 | Jičín |
16 | Trutnov |
17 | Náchod |
18 | Hradec Králové |
19 | Rychnov nad Kněžnou |
20 | Ústí nad Orlicí |
21 | Pardubice |
22 | Chrudim |
23 | Svitavy |
24 | Šumperk |
25 | Jeseník |
26 | Bruntál |
27 | Olomouc |
28 | Opava |
29 | Ostrava-město |
30 | Karviná |
31 | Frýdek-Místek |
32 | Nový Jičín |
33 | Vsetín |
34 | Přerov |
35 | Zlín |
36 | Kroměříž |
37 | Uherské Hradiště |
38 | Hodonín |
39 | Vyškov |
40 | Prostějov |
41 | Blansko |
42 | Brno-město |
43 | Brno-venkov |
44 | Břeclav |
45 | Znojmo |
46 | Třebíč |
47 | Žďár nad Sázavou |
48 | Jihlava |
49 | Havlíčkův Brod |
50 | Pelhřimov |
51 | Jindřichův Hradec |
52 | Tábor |
53 | České Budějovice |
54 | Český Krumlov |
55 | Prachatice |
56 | Strakonice |
57 | Písek |
58 | Klatovy |
59 | Domažlice |
60 | Tachov |
61 | Plzeň-sever |
62 | Plzeň-město |
63 | Plzeň-jih |
64 | Rokycany |
65 | Rakovník |
66 | Kladno |
67 | Mělník |
68 | Mladá Boleslav |
69 | Nymburk |
70 | Kolín |
71 | Kutná Hora |
72 | Benešov |
73 | Příbram |
74 | Beroun |
75 | Praha-západ |
76 | Praha-východ |
77 | Praha |
Zdrojové kódy
Na GitHubu (https://github.com/tomasbrincil/pcb_mapa_cr_1) najdete dva příklady, které s daty s TMEPu pracují a potřebujete jen MapuTvojíMámy a vývojovou desku.
Pokud přidáte ještě dotykové tlačítko a OLED display, tak jsem připravil dva kódy, které je využívají. Zapojení komponent na PINy najdete na začátku kódu.
Model pro stojan na mapu najdete zde: https://www.printables.com/cs/model/457603-stojan-na-mapu-tvoji-mamy
Teplota a ovzduší z TMEPu
Dotykovým tlačítkem můžete přepínat mezi teplotou a ovzduším, na displeji se vám ukážou vždy minimální a maximální hodnoty pro celou republiku. Po pěti minutách zhasnou LEDky a displej, stisknutím tlačítka je opět proberete k životu.
- main.cpp
/* __ __ _______ _ _ __ __ | \/ | |__ __| (_|_) \/ | | \ / | __ _ _ __ __ _| |_ _____ _ _| \ / | __ _ _ __ ___ _ _ | |\/| |/ _` | '_ \ / _` | \ \ / / _ \| | | |\/| |/ _` | '_ ` _ \| | | | | | | | (_| | |_) | (_| | |\ V / (_) | | | | | | (_| | | | | | | |_| | |_| |_|\__,_| .__/ \__,_|_| \_/ \___/| |_|_| |_|\__,_|_| |_| |_|\__, | | | _/ | __/ | |_| |__/ |___/ Map with connected display and touch button, downloads data from TMEP.cz for Czech districts and display temperature/air quality. You can cycle between those with touch button. Display shows what data are you looking at and min/max value. More info about map: https://wiki.tmep.cz/doku.php?id=ruzne:led_mapa_okresu_cr Used components: - MapaTvojiMamy - LaskaKit ESP32-LPkit v2.4 - 0.91" OLED - Catalex touch sensor v2.0 (simply some capacative touch sensor) */ //////////////////// // Variables setup //////////////////// // Wi-Fi, JSON with data const char *ssid = "TVOJE_AP"; const char *password = "TVOJE_HESLO"; const char *json_url = "http://cdn.tmep.cz/app/export/okresy-cr-vse.json"; // After how long get new values - default 2 minutes unsigned long timerDelay = 120000; // After how go to "sleep" (LEDs off, display off, "wake up" by touching button) - default 5 minutes unsigned long offDelay = 300000; // LED strip - MapaTvojiMamy #define LEDS_COUNT 77 #define LEDS_PIN 14 #define CHANNEL 0 #define LEDS_BRIGHTNESS 10 // Touch button #define BUTTON_PIN 16 // Display PINs #define DISPLAY_CLOCK_PIN 25 #define DISPLAY_DATA_PIN 26 // What to show as default - temp or air char *defaultView = "temp"; // Display hello text char *displayText1 = "MapaTvojiMamy"; char displayText2[14] = ""; /////////// // Code /////////// // Libraries #include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson v6+ #include "Freenove_WS2812_Lib_for_ESP32.h" // Display #include <U8g2lib.h> #include <Wire.h> U8G2_SSD1306_128X32_UNIVISION_1_SW_I2C u8g2(U8G2_R0, DISPLAY_CLOCK_PIN, DISPLAY_DATA_PIN, /* reset=*/U8X8_PIN_NONE); // Strip Freenove_ESP32_WS2812 strip = Freenove_ESP32_WS2812(LEDS_COUNT, LEDS_PIN, CHANNEL, TYPE_GRB); // Variables unsigned long lastTime = 0; unsigned long lastTouchTime = 0; int lastid, value, color, firstTime = 1, isOn = 1; // Button state boolean oldState = LOW; String sensorReadings; double sensorReadingsArr[3]; // in JSON is h1 used for temperature and h4 for air quality, we will stick with these variables double h1[77]; // array for temperature measurements double h4[77]; // array for air quality measurements double h1min, h1max, h4min, h4max; void setup() { u8g2.begin(); pinMode(BUTTON_PIN, INPUT); Serial.begin(115200); delay(5); Serial.println(); strip.begin(); strip.setBrightness(LEDS_BRIGHTNESS); } void lightenUpYourMamaMap() { // Map value to color, set it to corresponding LED and show it // We already have arrays populated if(defaultView == "temp") { for(int i = 0; i < LEDS_COUNT; i = i + 1) { color = map(h1[i], -15, 40, 170, 0); strip.setLedColorData(i, strip.Wheel(color)); } } else { for(int i = 0; i < LEDS_COUNT; i = i + 1) { color = map(h4[i], -15, 40, 170, 0); strip.setLedColorData(i, strip.Wheel(color)); } } strip.show(); } void setValuesForDisplay() { if(defaultView == "temp") { displayText1 = "Teplota"; sprintf(displayText2, "%d.%01d / %d.%01d", (int)h1min, abs((int)(h1min*10)%10), (int)h1max, abs((int)(h1max*10)%10)); } else { displayText1 = "Ovzdusi"; sprintf(displayText2, "%d.%01d / %d.%01d", (int)h4min, abs((int)(h4min*10)%10), (int)h4max, abs((int)(h4max*10)%10)); } } 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; } void loop() { boolean newState = digitalRead(BUTTON_PIN); // Touching the button? if((newState == LOW) && (oldState == HIGH)) { // Short delay to debounce button. delay(40); // Check if button is still low after debounce. newState = digitalRead(BUTTON_PIN); // Button touched! if(newState == LOW) { Serial.print("Button touched, mode: "); lastTouchTime = millis(); // Are we on? Change what to show if(isOn == 1) { if(defaultView == "temp") { defaultView = "air"; } else { defaultView = "temp"; } setValuesForDisplay(); Serial.println(defaultView); // Let it shine lightenUpYourMamaMap(); } else { // "Powering up" Serial.println("go online"); isOn = 1; strip.setBrightness(LEDS_BRIGHTNESS); lightenUpYourMamaMap(); firstTime = 1; u8g2.setPowerSave(false); } } } // Let's go "offline" if(isOn == 1 && ((millis() - lastTouchTime) > offDelay)) { Serial.println("go offline"); isOn = 0; strip.setBrightness(0); lightenUpYourMamaMap(); u8g2.setPowerSave(true); } // Set the last-read button state to the old state. oldState = newState; // Did we wait long enough or was it just powered on? // Read JSON and populate variables with measurements from districts if(isOn == 1 && ((millis() - lastTime) > timerDelay || firstTime == 1)) { Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); if(WiFi.status() == WL_CONNECTED) { firstTime = 0; sensorReadings = httpGETRequest(json_url); WiFi.disconnect(); Serial.println("WiFi disconnected"); DynamicJsonDocument doc(12288); DeserializationError error = deserializeJson(doc, sensorReadings); if(error) { Serial.print(F("deserializeJson() failed: ")); Serial.println(error.f_str()); return; } // reset min/max values h1min = 100; h1max = -100; h4min = 1000; h4max = 0; for (JsonObject item : doc.as<JsonArray>()) { // Index, JSON starts with 1, we need to start with 0 so deduct 1 int ledIndex = item["id"]; ledIndex -= 1; double temp = item["h1"]; double air = item["h4"]; // Set minimums and maximums if(temp > h1max) { h1max = temp; } if(temp < h1min) { h1min = temp; } if(air > h4max) { h4max = air; } if(air < h4min) { h4min = air; } // Let's map air value to respective color on ColorWheel: // https://github.com/Freenove/Freenove_WS2812_Lib_for_ESP32/blob/master/extras/ColorWheel.jpg // green if(air < 20) { air = 85; } // orange else if(air < 40) { air = 45; } // red else if(air < 100) { air = 10; } // purple else if(air < 5000) { air = 200; } // Populate our two arrays h1[ledIndex] = temp; h4[ledIndex] = air; } setValuesForDisplay(); lightenUpYourMamaMap(); // Debugging values we got /* for(int i = 0; i < LEDS_COUNT; i = i + 1) { Serial.print("h1["); Serial.print(i); Serial.print("] value: "); Serial.println(h1[i]); Serial.print("h4["); Serial.print(i); Serial.print("] value: "); Serial.println(h4[i]); } */ } else { displayText1 = "NO WIFI"; Serial.println("WiFi not connected :( is reachable?"); } lastTime = millis(); } // Show texts on display if(isOn == 1) { u8g2.firstPage(); do { u8g2.setFont(u8g2_font_ncenB10_tr); u8g2.drawStr(0, 12, displayText1); u8g2.drawStr(0, 26, displayText2); } while (u8g2.nextPage()); } }
Kvíz "uhodni okres"
Zábavná hra pro celou rodinu - po odstartování se rozbliká LEDka konkrétního okresu, můžete hádat o který se jedná a stiskem dotykového tlačítka se zobrazí odpověď a následně se rozbliká další okres.
Kód by se dal modifikovat i pro použití bez tlačítka (prostě se odstartuje samo a LEDky se po nějaké pauze mění). Okresy se rozblikávají čistě náhodně - do budoucna by bylo dobré, kdyby v rámci jedné hry nedošlo k opakování jedné oblasti a postupně se prostřídalo vše.
- main.cpp
/* __ __ _______ _ _ __ __ | \/ | |__ __| (_|_) \/ | | \ / | __ _ _ __ __ _| |_ _____ _ _| \ / | __ _ _ __ ___ _ _ | |\/| |/ _` | '_ \ / _` | \ \ / / _ \| | | |\/| |/ _` | '_ ` _ \| | | | | | | | (_| | |_) | (_| | |\ V / (_) | | | | | | (_| | | | | | | |_| | |_| |_|\__,_| .__/ \__,_|_| \_/ \___/| |_|_| |_|\__,_|_| |_| |_|\__, | | | _/ | __/ | |_| |__/ |___/ Simple quiz "which district is lightened up?" Info about map: https://wiki.tmep.cz/doku.php?id=ruzne:led_mapa_okresu_cr Used components: - MapaTvojiMamy - LaskaKit ESP32-LPkit v2.4 - 0.91" OLED - Catalex touch sensor v2.0 (simply some capacative touch sensor) */ //////////////////// // Variables setup //////////////////// // After how go to "sleep" (LEDs off, display off, "wake up" by touching button) - default 5 minutes unsigned long offDelay = 300000; // LED strip - MapaTvojiMamy #define LEDS_COUNT 77 #define LEDS_PIN 14 #define CHANNEL 0 #define LEDS_BRIGHTNESS 10 // Touch button #define BUTTON_PIN 16 #define STATE_WHEN_TOUCHED HIGH // Display PINs #define DISPLAY_CLOCK_PIN 25 #define DISPLAY_DATA_PIN 26 // Variables for two lines of OLED display char *displayText1 = ""; char *displayText2 = ""; // Array with districts, index from 0 to corresponding LED char *districts[] { "Cheb", "Sokolov", "Karlovy Vary", "Chomutov", "Louny", "Most", "Teplice", "Litomerice", "Usti nad Labem", "Decin", "Ceska Lipa", "Liberec", "Jablonec nad Nisou", "Semily", "Jicin", "Trutnov", "Nachod", "Hradec Kralove", "Rychnov nad Kneznou", "Usti nad Orlici", "Pardubice", "Chrudim", "Svitavy", "Sumperk", "Jesenik", "Bruntal", "Olomouc", "Opava", "Ostrava-mesto", "Karvina", "Frydek-Mistek", "Novy Jicin", "Vsetin", "Prerov", "Zlin", "Kromeriz", "Uherske Hradiste", "Hodonin", "Vyskov", "Prostejov", "Blansko", "Brno-mesto", "Brno-venkov", "Breclav", "Znojmo", "Trebic", "Zdar nad Sazavou", "Jihlava", "Havlickuv Brod", "Pelhrimov", "Jindrichuv Hradec", "Tabor", "Ceske Budejovice", "Cesky Krumlov", "Prachatice", "Strakonice", "Pisek", "Klatovy", "Domazlice", "Tachov", "Plzen-sever", "Plzen-mesto", "Plzen-jih", "Rokycany", "Rakovnik", "Kladno", "Melnik", "Mlada Boleslav", "Nymburk", "Kolin", "Kutna Hora", "Benesov", "Pribram", "Beroun", "Praha-zapad", "Praha-vychod", "Praha" }; /////////// // Code /////////// // Display #include <U8g2lib.h> #include <Wire.h> U8G2_SSD1306_128X32_UNIVISION_1_SW_I2C u8g2(U8G2_R0, DISPLAY_CLOCK_PIN, DISPLAY_DATA_PIN, U8X8_PIN_NONE); // Strip #include "Freenove_WS2812_Lib_for_ESP32.h" Freenove_ESP32_WS2812 strip = Freenove_ESP32_WS2812(LEDS_COUNT, LEDS_PIN, CHANNEL, TYPE_GRB); // To know when we should go to sleep unsigned long lastTouchTime = 0; // More things to initialize int lastId = 50, isOn = 1, firstTime = 1, gameTime = 0; void setup() { u8g2.begin(); pinMode(BUTTON_PIN, INPUT); Serial.begin(115200); delay(5); Serial.println(); strip.begin(); strip.setBrightness(LEDS_BRIGHTNESS); } void randomLEDs() { for(int i = 0; i < LEDS_COUNT; i = i + 1) { strip.setLedColorData(i, strip.Wheel(map(rand() % 100, -15, 40, 170, 0))); } strip.show(); } void powerDown() { Serial.println("go offline"); isOn = 0; strip.setBrightness(0); randomLEDs(); u8g2.setPowerSave(true); } void powerUp() { isOn = 1; firstTime = 1; Serial.println("go online"); strip.setBrightness(LEDS_BRIGHTNESS); u8g2.setPowerSave(false); } void waitForButtonTouch() { int ButtonTouched = 0; while(ButtonTouched == 0) { boolean newState = digitalRead(BUTTON_PIN); // Touching the button? if(newState == STATE_WHEN_TOUCHED) { ButtonTouched = 1; // Are we offline? "Power up"! if(isOn == 0) { powerUp(); } lastTouchTime = millis(); Serial.print("Button touched."); // Reset button state to untouched break; } // Let's go "offline" if(isOn == 1 && ((millis() - lastTouchTime) > offDelay)) { powerDown(); } } } void drawOnDisplay() { u8g2.firstPage(); do { u8g2.setFont(u8g2_font_ncenB10_tr); u8g2.drawStr(0, 12, displayText1); u8g2.drawStr(0, 26, displayText2); } while (u8g2.nextPage()); } void countDownProcedure() { drawOnDisplay(); strip.setBrightness(LEDS_BRIGHTNESS); randomLEDs(); delay(200); strip.setBrightness(6); strip.setAllLedsColor(204, 204, 204); strip.show(); delay(200); } void loop() { // First time? Run intro! if(isOn == 1 && firstTime == 1) { // Something like "animation" randomLEDs(); displayText1 = "Mapa"; displayText2 = "OKR"; drawOnDisplay(); delay(1000); randomLEDs(); displayText1 = "MapaTvoji"; displayText2 = "OKRESNI"; drawOnDisplay(); delay(1000); randomLEDs(); displayText1 = "MapaTvojiMamy"; displayText2 = "OKRESNI KVIZ"; drawOnDisplay(); for(int i = 6; i > 0; i = i - 1) { delay(i * 100); randomLEDs(); } displayText1 = "MapaTvojiMamy"; displayText2 = "Dotkni se! :)"; drawOnDisplay(); waitForButtonTouch(); displayText1 = "Budeme hrat!"; displayText2 = "Priprav se..."; drawOnDisplay(); delay(1000); displayText1 = "HRAJEM!"; displayText2 = ""; drawOnDisplay(); delay(1000); firstTime = 0; } // Let's play! while(isOn == 1 && firstTime == 0) { displayText2 = ""; displayText1 = "3"; countDownProcedure(); displayText1 = "2"; countDownProcedure(); displayText1 = "1"; countDownProcedure(); // Random district int districtID = 0; int generate = 1; while(generate == 1) { districtID = esp_random() % 100; if(districtID < LEDS_COUNT) { generate = 0; } } // Show district on map for(int i = 0; i < LEDS_COUNT; i = i + 1) { if(i == districtID) { strip.setBrightness(LEDS_BRIGHTNESS); strip.setLedColorData(i, 0x00FF00); } else { strip.setBrightness(3); strip.setLedColorData(i, 0xCCCCCC); } } strip.show(); displayText1 = "Jaky je to okres?"; displayText2 = "Dotkni se!"; drawOnDisplay(); // Blink with district we are guessing strip.setBrightness(LEDS_BRIGHTNESS); for(int i = 0; i < 12; i = i + 1) { if(i % 2 == 1) { strip.setLedColorData(districtID, 0x00FF00); } else { strip.setLedColorData(districtID, 0xFF0000); } strip.show(); delay(200); } waitForButtonTouch(); // Didn't woke up? Show answer and move on if(firstTime == 0) { displayText1 = "Odpoved je..."; displayText2 = ""; drawOnDisplay(); delay(500); displayText1 = districts[districtID]; drawOnDisplay(); delay(3000); } } // Show texts on display if(isOn == 1) { drawOnDisplay(); } }