Pakistan Christian TV

Breaking news and world news from Pakisthan Christian TV on Business, Sports, Culture. Video news. News from the US, Europe, Asia Pacific, Africa, Middle East, America.

We have programmed a radar map of the Czech Republic. It shows where it is currently raining and you can even put it on the wall – Živě.cz

If cloudy skies over the weekend disrupted your family’s plans, I’d like to publicly apologize, Thursday. tweet. In short, I needed to test my functionality Physical radar precipitation mapsand therefore ordered to cross the rainy front through the Czech Republic.

Wait, wait, what are physical radar maps? Well, one of the hot topics on Twitter Czech DIY in the past few months has been the production of a printed circuit board with the perimeter of the Czech Republic and dozens of addressable RGB LEDs in place of regional cities and counties.

with the first version on github I already splurged at the end of the year Your motherA few days ago, the domestic LaskaKit also released its own fully completed map.

Video: Watch how we programmed a step-by-step physical radar map of precipitation over the Czech Republic

It is called simply Interactive map of the Czech Republicyou can buy it for 698 CZK and its board Carved in the shape of the Republic Offers:

  • Dimensions 265 x 150 mm
  • 72x addressable RGB LED with WS2812B controller (PDF) with city designations
  • Wi-Fi SoC control ESP32-WROOM
  • USB/UART CH9102F chip (PDF)
  • USB-C for power and programming
  • Replacement power pins for 5 volts / 3 amps source
  • conductor uŠup / STEMMA / Qwiic to Connecting I²C peripherals
  • Sign boards ESP32-WROOM module for soldering other peripherals
  • 4x mounting hole 2.5 mm in diameter

In summary, The map is actually a complete microcomputerwhich you can r Install support ESP32 chips can be easily programmed even in Arduino.

Connect to the TMEP service

On GitHub you can also find some exampleswhich will connect to the Czech site TMEPit will download air temperature data for individual regions and then light up each city’s LEDs in the appropriate color. In fact, TMEP is a great weather database with graphs for do-it-yourselfers who can upload their data to it without having to build their own website on the greenfield.

Michal Sevczyk aka is behind the TMEP project MultiTricker And we played with one of the services important to the local DIY scene in a separate article.

Precipitation data is under a free license, but…

But today we are going to try something different. Since I ride my bike to work and get wet almost every time in these uncertain times, once LaskaKit started showing off what it was up to on Twitter it was pretty obvious to me.

Click for a larger image
CHMÚ web app with precipitation radar data for May 14 12:20 UTC

Cities are spread relatively evenly across the country, so I thought I could try displaying current data from the CHMÚ precipitation radars on a map.

To be specific, I’m all about data from this web applicationwhich is available under a free license Creative Commons BY-NC-ND. condition Author Name (BY) I just passed one and like this Do not use it for commercial purposes (NC), the code for today’s program can be found at the bottom of the article and is freely available.

Click for a larger image
The chosen license basically allows you to take the radar image only in its original form, but you may not manipulate it in any way – publish derivative works

But I’ll break it down a bit Meaningless prescription Do not treat (ND) which basically dictates that I can’t do any calculations and conversions with the data I’m getting. For the needs of the article, I therefore claim the principles of journalistic license and fair use. The rest of you and for Feel free to use your data. This fully complies with the license.

Radar layer web address

If you take a closer look at the map of the Czech Hydrometeorological Institute, you will definitely notice that the animated application uses several layers with separate raster lines. There is a simplified base map of the republic, on which a transparent bitmap image with its precipitation data is displayed.

Click for a larger image
PNG transparent radar layer

A precipitation layer is created every full ten minutes and its time corresponds to world time UTC, respectively Greenwich Mean GMT. So if you write the article on Sunday May 14, 2023 And the clock is ticking 14:22 CET (GMT+2), latest bitmap with radar layer in format PNG I can download from web address:

https://www.chmi.cz/files/portal/docs/meteo/rad/inca-cz/data/czrad-z_max3d_masked/pacz2gmaps3.z_max3d.20230514.1220.0.png

Note that the data timestamp is directly part of the URL in the format YYYYMMDD. HHM0. So general 2023moon 05day 14hour 12 and accurate 20. Again, this is UTC/GMT, so it’s identical 2:20 p.m Central European Summer Time.

Python will do the parsing, and the Arduino will light up the LED

Today’s project consists of two parts. The first will be a simple script in Python 3.x and the second will be a simpler Arduino program with an HTTP server that will run on our map of the Czech Republic.

Why did we divide everything into two parts? Since Python is running on a mainframe computer, Raspberry Pi or maybe somewhere on the internet will take care of quick parsing of the PNG image. Technically, Arduino will also be able to do this, we just need some libraries to decode PNG.

Of course, there are such, for example PNGHowever, the code will be really complicated, because we will also have to take care of downloading the file and saving it on a flash memory or maybe somewhere on an external storage in the form of an SD card, etc.

It asks to download the image and the pillow processes it

On the contrary, I want the simplest program possible on an ESP32 chip, that will only listen over HTTP for cities that should light up, so I’ll do all the magic in Python and script that should run periodically.

In Python, it’s really all about a few dozen lines of source code, because we’re calling two well-known libraries into battle Requests And Pillow. Requests is now essentially the standard HTTP client for Python and Pillow is the core library for working with images.

On a computer with Python, you can install both libraries, for example, via the PIP package installer, or take a look at the documentation under the links above:

pip install pillow requests

The shortest possible code snippet, which uses requests to download the last radar image, load it into the pad library as an object and then save it to your computer’s disk, could look like this:

import requests
from PIL import Image
from io import BytesIO

r = requests.get("https://www.chmi.cz/files/portal/docs/meteo/rad/inca-cz/data/czrad-z_max3d_masked/pacz2gmaps3.z_max3d.20230514.1220.0.png")
bitmapa = Image.open(BytesIO(r.content))
bitmapa.save("snimek.png")

Our production code will be more complex and, of course, handled in cases where something goes wrong – for example, you enter the wrong URL for an image to download.

We also used the io built-in library and object in the code Bytsuwhich will convert the response of the CHMÚ web server into a stream of raw bytes, as if we were opening a normal binary file from a disk on a computer.

See also  McDonald's import experience: "We ordered 800, we barely got to 300...but we still made money."

We will convert the bitmap to RGB format

The bitmap is used for the radar image Indexed color palette, which is not a bad thing, but at first we all imagine the usual red, green and blue channels in RGB format. So, in the next step, we’ll convert the image to RGB:

bitmapa = bitmapa.convert("RGB")

We pass through the bitmap and draw it using colored characters directly on the command line

Since the image is loaded into RAM, we can now go through it line by line to get the channel values ​​R, G, and B:

for y in range(bitmapa.height):
    for x in range(bitmapa.width):
        r, g, b = bitmapa.getpixel((x, y))

Many modern command lines/shells can already use special lines today ANSI escape sequence Working with RGB color, so let’s try drawing a radar image, for example, directly in Windows Terminal.

We’ll just display every ten pixels of the bitmap – so they all fit on the device – as a double text character ██ in the color of that pixel.

For y in range (0, bitmap.height, 10): For x in range (0, bitmap.width, 10): r, g, b = bitmap.getpixel ((x, y)) print(f" \x1b[38;2;{r};{g};{b}m██\x1b[0m", end="")
    print("")

A takhle jako na obrázku níže bude vypadat výsledek. To není vůbec špatně, viďte? A přitom celý program v Pythonu zabral směšných 11 řádků kódu!

Klepněte pro větší obrázek
Stažený radarový snímek převedený na barevné znaky a zobrazený v příkazové řádce
Klepněte pro větší obrázek
Pro srovnání jeho plnotučná předloha v PNG s rozměry 680 × 460 px

Takže ještě jednou a tentokrát celý kód na jedné hromadě:

import requests
from PIL import Image
from io import BytesIO
r = requests.get("https://www.chmi.cz/files/portal/docs/meteo/rad/inca-cz/data/czrad-z_max3d_masked/pacz2gmaps3.z_max3d.20230514.1220.0.png")
bitmapa = Image.open(BytesIO(r.content))
bitmapa = bitmapa.convert("RGB")
for y in range(0, bitmapa.height, 10):
    for x in range(0, bitmapa.width, 10):
        r, g, b = bitmapa.getpixel((x, y))
        print(f"\x1b[38;2;{r};{g};{b}m██\x1b[0m", end="")
    print("")

Mesta.csv obsahuje souřadnice všech 72 obcí

Heuréka! Už umíme zjišťovat barvu pixelů radarového snímku na různých souřadnicích X;Y a také víme, že Česko se v obrázku nachází uprostřed, kde jakákoliv nenulová barva představuje déšť dle stupnice od modré přes zelenou, žlutou, červenou až po bílou pro naprostou spoušť.

Nulová RGB barva (0, 0, 0) ve skutečnosti nepředstavuje černou, ale průhlednou, PNG je totiž transparentní.

Klepněte pro větší obrázek
Databáze 72 měst na mapě Česka s indexy RGB LED a geografickými souřadnicemi

Takže co dál? Nyní bych si mohl podle mapky na webu ČHMÚ zaznamenat hrubé X;Y souřadnice měst, která jsou i na desce od LaskaKitu, a poté zjišťovat barevný odstín jen v těchto místech.

Já jsem ovšem lenoch, který ručně nic měřit nebude, a tak jsem raději využil otevřených dat ČÚZK a stáhl si seznam a geografické souřadnice všech obcí v Česku, ze kterých jsem poté pouze křížově vytáhl oněch 72 měst na tištěné desce. Jejich názvy jsou na GitHubu projektu.

Výsledkem je textový soubor mesta.csv, kde má každý řádek formát:

ID;název;zem. šířka; zem. délka

Takže třeba hned první záznam pro Děčín:

0;Děčín;50.772656;14.212861

ID je v tomto případě index adresovatelné RGB LED na mapě od LaskaKitu, takže kdybych chtěl rozsvítit světýlko Děčína, vím, že je hned první v pořadí.

Přepočítáme GPS souřadnice na pixelové

Předpokládejme, že radarová vrstva používá univerzální kartografickou projekci WGS-84, jak ji známe z běžných webových map. Ostatně, ČHMÚ ji volitelně promítá i nad OpenStreetMap, takže tomu tak nejspíše bude. A i kdyby ne, měřítko je tak maličké, že budou malé i případné projekční chyby.

Klepněte pro větší obrázek
Zjištění úhlových rozměrů vrstvy s bitmapou radaru pro snadný přepočet na pixely

Stručně řečeno, zjistil jsem si zeměpisné souřadnice krajních bodů radarové vrstvy, která má fixní rozměry 680×460 pixelů, a tak mezi nimi nyní mohu jednoduše přepočítávat stupňové a pixelové vzdálenosti:

  • Levý horní roh: 52,1670717° s.š., 11,2673442° v.d.
  • Pravý dolní roh: 48,1° s.š., 20,7703153° v.d.
  • Pixelové rozměry: 680 × 460 px
  • Stupňové rozměry: 9.5029711° z.d. × 4.0670717° z.š
  • 1 vertikální pixel: je roven 0,008841460217 stupňům zeměpisné šířky
  • 1 horizontální pixel: je roven 0,0139749575 stupňům zeměpisné délky

Máme-li tedy seznam měst představující RGB LED na desce od LaskaKitu a jejich geografické souřadnice, můžeme jedno po druhém projít ve smyčce a podle údajů výše je přepočítat na pixelové souřadnice X;Y radarového snímku.

Pokud bude mít pixel RGB hodnotu vyšší než 0, 0, 0, víme, že v daném městě asi právě prší. Jak moc, už záleží na samotné barvě.

Zjišťovat barvu jediného pixelu, nebo plochy?

Zatímco geografické souřadnice představují jen jeden bod na mapě, plocha města je mnohem větší, a tak bychom mohli při kontrole, jestli v něm prší, používat nějaký širší záběr.

Klepněte pro větší obrázek
Pokud budeme kontrolovat jen souřadnice středu Plzně (černý puntík), blížící se déšť na předměstí neodhalíme. Vyplatí se proto projít třeba čtverec 5×5 okolních pixelů, do kterého nám už zasahují modré segmenty přicházejícího deště

Namísto zjištění barvy na souřadnicích X;Y bychom mohli třeba projít ve smyčce celý čtverec několika okolních pixelů a jako finální hodnotu použít buď aritmetický průměr zjištěných kanálů R, G a B, nebo naopak třeba tu nejvyšší hodnotu pro hledání a umocnění extrémů.

Pokud objevíme barvu, uložíme ji do pole JSON

My to ale nebudeme komplikovat, takže pokud na souřadnicích (středu) města objevíme barvu deště, index LED města a hodnoty kanálů RGB uložíme do dalšího pole ve formě struktury:

{
    "id": id,
    "r": r, 
    "g": g, 
    "b": b
}

Jakmile projdeme všechna města a zjistíme, že seznam těch, ve kterých prší, má nenulovou velikost, pomocí vestavěné knihovny json a její funkce dumps převedeme celý seznam se strukturami do prostého textu:

mesta_json = json.dumps(mesta_s_destem)

Kdyby pršelo jen v Děčíně, který, jak už víme, okupuje LED s indexem 0, pak by JSON vypadal třeba takto:

[
    {
        "id":0, 
        "r":0, 
        "g":108, 
        "b":192
    }
]

When the queue exceeds the entire region, on the other hand, JSON can span several thousand characters. This must be taken into account, because the ESP32 chip will subsequently load it into the RAM allocated to it, and everything should fit there. Fortunately, it is large enough.

See also  Bars with a new face. Country stores are changing too
Click for a larger image
In the end, our software optionally saves the converted radar image to a file with selected cities. Those where it is currently raining are marked with a colorful red frame

Finally we send JSON to the map for display

Since we said at the beginning that a simple HTTP server runs directly in the map from LaskaKit, which expects a list of LEDs to light up in the desired color, we will send to it the JSON generated using the HTTP POST protocol – similar to when we send, for example, the content of a form over the web .

Click for a larger image
We discovered the color of rain in cities

We will use the requests library for this again. If the map board will get the LAN IP after logging into the local Wi-Fi 192.168.1.100since even a simple HTTP server will listen directly on the ESP32 chip, then the code snippet could look like this:

formular = {"mesta": mesta_json}
r = requests.post(f"http://192.16.1.100/", data=formular)

HTTP server and JSON decoder running on Arduino

The web server on the side of the ESP32 chip will silently wait for a root HTTP request to arrive/reach from some HTTP client, and if so, it will ask if it contains the middleware cities with JSON.

Click for a larger image
“OK” is the response of our HTTP server on the republic map, if all goes well

If this is the case, use the Arduino standard library now Arduino We decode the field with the cities where it is raining, and assign an appropriate RGB color to the individual RGB according to its indicator.

The Adafruit NeoPixel RGB LED library lights up

At the beginning, we said that the board with the Czech Republic map is armed with 72 addressable RGB LEDs with a WS2812B controller. The latter is relatively common, so there are countless libraries available that make swapping LEDs much easier.

Click for a larger image
If the map will be powered by a computer via USB-C, we can look at the status messages directly in the Arduino development environment and its serial line terminal. As you can see, the program is currently activating dozens of lamps, because it is raining all over the republic

LaskaKitv Examples On GitHub it uses the library Frenovbut I reached for more global and cooked NeoPixel from Adafruit. Powering a specific LED is a matter of a few lines of code.

In full introduction, we create an object pixels Represents an instance of the Adafruit_NeopPixel class that we will control all 72 LEDs with:

Adafruit_NeoPixel pixely(72, 25, NEO_GRB + NEO_KHZ800);

first parameter 72 Represent number of LEDsthe second value 25 Then pin GPIO of the ESP32 chip, to which all LEDs are connected in series. The last configuration parameter represents the color coding format and connection speed.

Click for a larger image
The physical precipitation radar lit up in the colors of the CHMÚ instrument
Click for a larger image
This is what the real radar image looks like

Then we configure the library in the main Arduino setup function. First, we set the eight-bit brightness to a low value of 5, so that the lowest contrast shadows stand out and the map doesn’t burn too much electricity.

At the same time, we will turn off all the lights and confirm all changes in the method .Displays(). Until then, all the work is done in the cache only.

pixely.begin();
pixely.setBrightness(5);
pixely.clear();
pixely.show();

Now if we want the RGB LED of the city of Děčín (index 0) to be lit in dark blue (R=0, G=0, B=255), we just need to call:

pixely.setPixelColor(0, pixely.Color(0, 0, 255));
pixely.show();

We will call the same instruction when browsing the list of cities in JSON format, which we will send to ESP32 from Python. It only takes a moment, and the plate shaped like the Czech Republic will light up in the colors of the precipitation radar of the Czech Hydrometeorological Institute.

Source codes for today’s project

And that’s really it. Finally, complete and complete source codes should not be missing. First, the program code ledradar in Python and then program ledmapa.ino for arduino. Both files and of course also a database with city coordinates city. csv You can find our electronics programming series on GitHub.

Ledradar.py requires the IP address/URL of the RGB LED map as the first argument to the script. So, if you get the IP address 192.168.1.100 from your Wi-Fi router, we will set the current radar image with the command:

python ledradar.py 192.168.1.100

ledradar

from PIL import Image
from PIL import ImageDraw
import requests
from io import BytesIO
import json
from datetime import datetime, timedelta
import sys
from time import sleep

# -----------------------------------------------------------------------------
# GLOBALNI PROMENNE

logovani = True # Budeme vypisovat informace o běhu programu
kresleni = True # Uložíme snímek s radarem ve formátu radar_a_mesta_YYYYMMDD.HHM0.png
odesilani = True # Budeme odesílat data do LaskaKit mapy ČR

laskakit_mapa_url = sys.argv[1] # LAN IP/URL LaskaKit mapy ČR (bez http://) 

# Pracujeme v souřadnicovém systému WGS-84
# Abychom dokázali přepočítat stupně zeměpisné šířky a délky na pixely,
# musíme znát souřadnice levého horního a pravého dolního okraje radarového snímku ČHMÚ
# LEVÝ HORNÍ ROH
lon0 = 11.2673442
lat0 = 52.1670717
# PRAVÝ DOLNÍ ROH
lon1 = 20.7703153
lat1 = 48.1

# -----------------------------------------------------------------------------

# Alias pro print, který bude psát jen v případě,
# že má globální proměnná logovani hodnotu True
def printl(txt):
    if logovani:
        print(txt, flush=True)

# Funkce pro stažení bitmapy s radarovými daty z URL adresy:
# https://www.chmi.cz/files/portal/docs/meteo/rad/inca-cz/data/czrad-z_max3d_masked/pacz2gmaps3.z_max3d.{datum_txt}.0.png
# datum_txt musí být ve formátu UTC YYYYMMDD.HHM0 (ČHMÚ zveřejňuje snímky každých celých 10 minut)
# Pokud URL není validní (obrázek ještě neexistuje),
# pokusím se stáhnout bitmapu s o deset minut starší časovou značkou
# Počet opakování stanovuje proměnná pokusy 
def stahni_radar(datum=None, pokusy=5):
    if datum == None:
        datum = datetime.utcnow()

    while pokusy > 0:
        datum_txt = datum.strftime("%Y%m%d.%H%M")[:-1] + "0"
        url = f"https://www.chmi.cz/files/portal/docs/meteo/rad/inca-cz/data/czrad-z_max3d_masked/pacz2gmaps3.z_max3d.{datum_txt}.0.png"
        printl(f"Stahuji soubor: {url}")
        r = requests.get(url)
        if r.status_code != 200:
            printl(f"HTTP {r.status_code}: Nemohu stáhnout soubor")
            printl("Pokusím se stáhnout o 10 minut starší soubor")
            datum -= timedelta(minutes=10)
            pokusy -= 1
            sleep(.5)
        else:
            return True, r.content, datum_txt
    return False, None, datum_txt 

# Funkce pro obarvení textu v terminálu pomocí RGB
# Použijeme pro nápovědu, jakou barvu mají pixely radarových dat v daném městě
# Záleží na podpoře v příkazové řádce/shellu
# We Windows Terminalu a v současných linuxových grafických shellech to zpravidla funguje 
def rgb_text(r,g,b, text):
    return f"\x1b[38;2;{r};{g};{b}m{text}\x1b[0m"

# Začátek běhu programu
if __name__ == "__main__":
    printl("*** RGB LED Srážkový radar ***\n")
    # Rozměry bitmapy ve stupních
    sirka_stupne = lon1 - lon0
    vyska_stupne = lat0 - lat1

    # Pokusím se stáhnout bitmapu s radarovými daty
    # Pokud se to podaří, ok = True, bajty = HTTP data odpovědi (obrázku), txt_datum = YYYYMMDD.HHM0 staženého snímky
    ok, bajty, txt_datum = stahni_radar()
    if not ok:
        printl("Nepodařilo se stáhnout radarová data, končím :-(")
    else:
        # Z HTTP dat vytvoříme objekt bitmapy ve formátu PIL/Pillow
        try:
            bitmapa = Image.open(BytesIO(bajty))
        # Pokud se to nepodaří, ukončíme program s chybovým kódem
        except:
            printl("Nepodařilo se načíst bitmapu srážkového radaru")
            sys.exit(1)

        # Původní obrázek používá indexovanou plaetu barev. To se sice může hodit,
        # pro jendoduchost příkladu ale převedeme snímek na plnotučné RGB
        printl("Převádím snímek na RGB... ")
        bitmapa = bitmapa.convert("RGB")
        if kresleni:
            platno = ImageDraw.Draw(bitmapa)

        # Z pixelového rozměru bitmapy spočítáme stupňovou velikost vertikálního
        # a horizontálního pixelu pro další přepočty mezi stupniu a pixely
        velikost_lat_pixelu = vyska_stupne / bitmapa.height
        velikost_lon_pixelu = sirka_stupne / bitmapa.width

        printl(f"Šířka obrázku: {bitmapa.width} px ({sirka_stupne} stupňů zeměpisné délky)")
        printl(f"Výška obrázku: {bitmapa.height} px ({vyska_stupne} stupňů zeměpisné šířky)")
        printl(f"1 vertikální pixel je roven {velikost_lat_pixelu} stupňům zeměpisné šířky")
        printl(f"1 horizontální pixel je roven {velikost_lon_pixelu} stupňům zeměpisné délky")

        # V souboru mesta.csv máme po řádcích všechny obce na LaskaKit mapě
        # Řádek má formát: ID;název;zem. šířka;zem. délka
        # ID představuje pořadí RGB LED na LaskaKit mapě ČR
        mesta_s_destem = []
        printl("\nNačítám databázi měst... ")
        with open("mesta.csv", "r") as fi:
            mesta = fi.readlines()
            printl("Analyzuji, jestli v nich prší...")
            printl("-" * 80)
            # Projdeme město po po městě v seznamu
            for mesto in mesta:
                bunky = mesto.split(";")
                if len(bunky) == 4:
                    idx = bunky[0]
                    nazev = bunky[1]
                    lat = float(bunky[2])
                    lon = float(bunky[3])
                    # Spočítáme pixelové souřadnice města radarovém snímku
                    x = int((lon - lon0) / velikost_lon_pixelu)
                    y = int((lat0 - lat) / velikost_lat_pixelu)
                    # Zjistíme RGB na dané souřadnici, tedy případnou barvu deště
                    r,g,b = bitmapa.getpixel((x, y))
                    # Pokud je v daném místě na radarovém snímnku nenulová barva (transaprentní/transparentní PNG)
                    # asi v něm prší. Intenzitu deště určí konkrétní barva v rozsahu od světle modré přes zelenou, rudou až bílou
                    # Právě zde bychom tedy mohli detekovat i sílu deště, pro jednoduchost ukázky si ale vystačíme s prostou barvou 
                    if r+g+b > 0:
                        # Pokud jsme na začátku programu aktivovali kreslení,
                        # na plátno nakreslíme čtvereček s rozměry 10×10 px představující město
                        # Čtvereček bude mít barvu deště a červený obrys
                        if kresleni:
                            platno.rectangle((x-5, y-5, x+5, y+5), fill=(r, g, b), outline=(255, 0, 0))
                        # Pokud je aktivní logování, vypíšeme barevný text s údajem, že v daném městě prší
                        # a přidáme město na seznam jako strukturu {"id":id, "r":r, "g":g, "b":b}  
                        printl(f"💦  Ve městě {nazev} ({idx}) asi právě prší {rgb_text(r,g,b, f'(R={r} G={g} B={b})')}")
                        mesta_s_destem.append({"id": idx, "r": r, "g": g, "b": b})
                    else:
                            # Pokud v daném městě neprší, nakreslíme v jeho souřadnicích prázdný čtvereček
                            # s bílým obrysem
                            if kresleni:
                                platno.rectangle((x-5, y-5, x+5, y+5), fill=(0, 0, 0), outline=(255, 255, 255))
        
        # Prošli jsme všechna města, takže se podíváme,
        # jestli máme v seznamu nějaká, ve kterých prší
        if len(mesta_s_destem) > 0:
            # Pokud jsme aktivovali odesílání dat do LaskaKit mapy ČR,
            # uložíme seznam měst, ve kterých pršelo, jako JSON pole struktur
            # a tento JSON poté skrze HTTP POST formulář s názvem proměnné "mesta"
            # odešleme do LaskaKit mapy ČR, na které běží jenodudchý HTTP server
            if odesilani == True:
                printl("\nPosílám JSON s městy do LaskaKit mapy ČR...")
                mesta_json = json.dumps(mesta_s_destem)
                form_data = {"mesta": mesta_json}
                r = requests.post(f"http://{laskakit_mapa_url}/", data=form_data)
                if r.status_code == 200:
                    printl(r.text)
                else:
                    printl(f"HTTP {r.status_code}: Nemohu se spojit s LaskaKit mapou ČR na URL http://{laskakit_mapa_url}/")
        else:
            printl("Vypadá to, že v žádném městě neprší!")

        # Pokud je aktivní kreslení, na úplný závěr
        # uložíme PNG bitmapu radarového snímku s vyznačenými městy
        # do souboru radar_a_mesta_YYYYMMDD.HHM0.png
        if kresleni:
            bitmapa.save(f"radar_a_mesta_{txt_datum}.png")

ledmapa.ino

#include <WiFi.h>
#include <WebServer.h>
#include <Adafruit_NeoPixel.h> // https://github.com/adafruit/Adafruit_NeoPixel
#include <ArduinoJson.h> // https://arduinojson.org/

// Nazev a heslo Wi-Fi
const char *ssid = "Nazev 2.4GHz Wi-Fi site";
const char *heslo = "Heslo Wi-Fi site";

// Objekt pro ovladani adresovatelnych RGB LED
// Je jich 72 a jso uv serii pripojene na GPIO pin 25
Adafruit_NeoPixel pixely(72, 25, NEO_GRB + NEO_KHZ800);
// HTTP server bezici na standardnim TCP portu 80
WebServer server(80);
// Pamet pro JSON s povely
// Alokujeme pro nej 10 000 B, coz je hodne,
// ale melo by to stacit i pro jSON,
// ktery bude obsahovat instrukce pro vsech 72 RGB LED
StaticJsonDocument<10000> doc;

// Tuto funkci HTTP server zavola v pripade HTTP GET/POST pzoadavku na korenovou cestu /
void httpDotaz(void) {
  // Pokud HTTP data obsahuji parametr mesta
  // predame jeho obsah JSON dekoderu
  if (server.hasArg("mesta")) {
    DeserializationError e = deserializeJson(doc, server.arg("mesta"));
    if (e) {
      if (e == DeserializationError::InvalidInput) {
        server.send(200, "text/plain", "CHYBA\nSpatny format JSON");
        Serial.print("Spatny format JSON");
      } else if (e == DeserializationError::NoMemory) {
        server.send(200, "text/plain", "CHYBA\nMalo pameti RAM pro JSON. Navys ji!");
        Serial.print("Malo pameti RAM pro JSON. Navys ji!");
      }
      else{
        server.send(200, "text/plain", "CHYBA\nNepodarilo se mi dekodovat jSON");
        Serial.print("Nepodarilo se mi dekodovat jSON");
      }
    }
    // Pokud se nam podarilo dekodovat JSON,
    // zhasneme vsechny LED na mape a rozsvitime korektni barvou jen ty,
    // ktere jsou v JSON poli
    else {
      server.send(200, "text/plain", "OK");
      pixely.clear();
      JsonArray mesta = doc.as<JsonArray>();
      for (JsonObject mesto : mesta) {
        int id = mesto["id"];
        int r = mesto["r"];
        int g = mesto["g"];
        int b = mesto["b"];
        Serial.printf("Rozsvecuji mesto %d barvou R=%d G=%d B=%d\r\n", id, r, g, b);
        pixely.setPixelColor(id, pixely.Color(r, g, b));
      }
      // Teprve ted vyrobime signal na GPIO pinu 25,
      // ktery nastavi svetlo na jednotlivych LED
      pixely.show();
    }
  }
  // Pokud jsme do mapy poslali jen HTTP GET/POST parametr smazat, mapa zhasne
  else if (server.hasArg("smazat")) {
    server.send(200, "text/plain", "OK");
    pixely.clear();
    pixely.show();
  }
  // Ve vsech ostatnich pripadech odpovime chybovym hlasenim
  else {
    server.send(200, "text/plain", "CHYBA\nNeznamy prikaz");
  }
}

// Hlavni funkce setup se zpracuje hned po startu cipu ESP32
void setup() {
  // Nastartujeme serivou linku rychlosti 115200 b/s
  Serial.begin(115200);
  // Pripojime se k Wi-Fi a pote vypiseme do seriove linky IP adresu
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, heslo);
  Serial.printf("Pripojuji se k %s ", ssid);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Vypiseme do seriove linky pro kontrolu LAN IP adresu mapy
  Serial.printf(" OK\nIP: %s\r\n", WiFi.localIP().toString());
  // Pro HTTP pozadavku / zavolame funkci httpDotaz
  server.on("https://www.zive.cz/", httpDotaz);
  // Aktivujeme server
  server.begin();
  // Nakonfigurujeme adresovatelene LED do vychozi zhasnute pozice
  // Nastavime 8bit jas na hodnotu 5
  // Nebude svitit zbytecne moc a vyniknou mene kontrastni barvy
  pixely.begin();
  pixely.setBrightness(5);
  pixely.clear();
  pixely.show();
}

// Smycka loop se opakuje stale dokola
// a nastartuje se po zpracovani funkce setup
void loop() {
  // Vyridime pripadne TCP spojeni klientu se serverem
  server.handleClient();
  // Pockame 2 ms (prenechame CPU pro ostatni ulohy na pozadi) a opakujeme
  delay(2);
}