{"id":23230,"date":"2026-05-25T17:41:01","date_gmt":"2026-05-25T15:41:01","guid":{"rendered":"http:\/\/blog.wenzlaff.de\/?p=23230"},"modified":"2026-05-25T17:41:01","modified_gmt":"2026-05-25T15:41:01","slug":"ki-flugsensor-mit-esp32-mit-wifi-bluetooth-entwicklungsboard-28-zoll-lcd-tft-modul-mit-touch","status":"publish","type":"post","link":"http:\/\/blog.wenzlaff.de\/?p=23230","title":{"rendered":"KI: Flugsensor mit ESP32 mit WIFI &#038; Bluetooth Entwicklungsboard 2,8 Zoll LCD TFT Modul mit Touch"},"content":{"rendered":"<p>Wenn ich aus dem K\u00fcchenfenster schaue, sehe ich alle Flugzeuge die aus dem Osten kommen und in <a href=\"http:\/\/blog.wenzlaff.de\/?p=21861\" target=\"_blank\">Langenhagen<\/a> (EDDV, HAJ) landen wollen. Habe schon einige <a href=\"http:\/\/blog.wenzlaff.de\/?p=21929\" target=\"_blank\">Projekte<\/a> in diesem Zusammenhang gemacht, aber die sind noch alle per Hand, und nicht mit KI entstanden. <\/p>\n<p>Nun ist aus der Bucht ein ESP32 mit TFT und Touch angekommen, der noch eine Verwendung braucht. <\/p>\n<p>Ok, hier schon mal vorab das Ergebnis, es werden alle Flugzeuge die aus dem osten kommen und landen wollen auf dem ESP32 ausgegeben, sogar in verschiedenen Farben, je nach H\u00f6he:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-5-scaled.png\" alt=\"\" width=\"2560\" height=\"1440\" class=\"aligncenter size-full wp-image-23231\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-5-scaled.png 2560w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-5-300x169.png 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-5-1024x576.png 1024w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-5-768x432.png 768w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-5-1536x864.png 1536w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-5-2048x1152.png 2048w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Gestartet bin ich mit einem Bereich, von 20 km um Langenhagen, um das C++ Programm mit der Arduino IDE zu laufen zu bringen:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-1.png\" alt=\"\" width=\"1536\" height=\"1024\" class=\"aligncenter size-full wp-image-23235\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-1.png 1536w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-1-300x200.png 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-1-1024x683.png 1024w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-1-768x512.png 768w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Als n\u00e4chsten Schritt, habe ich mir die koordinaten f\u00fcr ein 2 km Rechteck von der Landebahn in Richtung Osten generiert: <!--more--><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-4.png\" alt=\"\" width=\"1536\" height=\"1024\" class=\"aligncenter size-full wp-image-23232\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-4.png 1536w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-4-300x200.png 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-4-1024x683.png 1024w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-4-768x512.png 768w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Dann hat mich interessiert, in welchen Winkel die Flugzeuge landen, wenn ich sie sehe, sind sie so ca. <strong>300 m<\/strong> hoch. Die KI sagt:<\/p>\n<p><strong>Spezifikation des Anflugvektors (Glide Path)<\/strong>:<\/p>\n<p>Bei einem Endanflug aus \u00f6stlicher Richtung &#8211; was einer Ausrichtung auf die Pisten <strong>27L<\/strong> oder <strong>27R<\/strong> des Flughafens Hannover-Langenhagen (EDDV) entspricht -ist der vertikale Anflugwinkel f\u00fcr kommerzielle Passagierjets auf den Standardwert von <strong>3\u00b0<\/strong> kalibriert. Das fand ich interessant. Dann habe ich mir noch eine Tabelle berechnen lassen, hier das <strong>Mathematisches Modell<\/strong> des Anflugprofils.<\/p>\n<p>Zur Berechnung der vertikalen Position (H\u00f6he h) des Luftfahrzeugs in Relation zur horizontalen Distanz (d) zum Aufsetzpunkt (Touchdown Zone) wird eine einfache trigonometrische Funktion appliziert. Da der Gleitpfad (Glide Slope) einen konstanten Winkel von 3,0\u00b0 aufweist, l\u00e4sst sich die H\u00f6he \u00fcber Grund (Above Ground Level, AGL) in Metern f\u00fcr jede diskrete St\u00fctzstelle berechnen.<\/p>\n<p>Die zugeh\u00f6rige Formel lautet:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.02.04.png\" alt=\"\" width=\"1026\" height=\"528\" class=\"aligncenter size-full wp-image-23238\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.02.04.png 1026w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.02.04-300x154.png 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.02.04-1024x527.png 1024w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.02.04-768x395.png 768w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Ah, dann mal bis 30 km, bis nach Lehrte die Werte berechnen, und dies Bild mit KI generieren:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-3.png\" alt=\"\" width=\"1600\" height=\"1143\" class=\"aligncenter size-full wp-image-23233\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-3.png 1600w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-3-300x214.png 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-3-1024x732.png 1024w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-3-768x549.png 768w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-3-1536x1097.png 1536w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Dann beide Bilder zusammen morphen, cool:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-2.png\" alt=\"\" width=\"1264\" height=\"842\" class=\"aligncenter size-full wp-image-23234\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-2.png 1264w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-2-300x200.png 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-2-1024x682.png 1024w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/bounding-box-eddv-2-768x512.png 768w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Genau das wollte ich wissen.<\/p>\n<p>Nun noch mit KI ein C++ Programm erstellen und in den ESP32 flashen. Hier die erste Version, mit 577 Zeilen. Da ist noch eine Einstellun n\u00f6tig, die ich noch nie verwendet haben. Und zwar das Paritions Schema: &#8222;Huge APP &#8230;&#8220; f\u00fcr die anderen ist das Programm zu gro\u00df geworden. Hier die Einstellung in der IDE:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.19.29.png\" alt=\"\" width=\"868\" height=\"792\" class=\"aligncenter size-full wp-image-23239\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.19.29.png 868w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.19.29-300x274.png 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/05\/wenzlaff.de-2026-05-25-um-17.19.29-768x701.png 768w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Und nun der versprochene Code. Es muss da noch bei Touch, die Details angezeigt werden und einige andere Anpassungen. Aber wie immer, sind meine KI Token abgelaufen bzw. verbraucht \ud83d\ude09<\/p>\n<pre class=\"minimize:true lang:c++ decode:true \" >\/**\r\n * FlugMonitor.ino \r\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n * Zeigt Flugzeuge im ~20-km-Radius um Langenhagen, Niedersachsen an,\r\n * die sich unterhalb von 2000 m H\u00f6he befinden.\r\n *\r\n * Datenquelle : OpenSky Network REST API (authenticated)\r\n *               https:\/\/opensky-network.org\/apidoc\r\n * Display     : 240\u00d7320 TFT via TFT_eSPI, LVGL 9.x\r\n *               Logische Aufl\u00f6sung nach Rotation 270\u00b0: 320\u00d7240\r\n *\r\n * Ben\u00f6tigte Arduino-Bibliotheken:\r\n *  - LVGL       &gt;= 9.x     (lv_conf.h: LV_FONT_MONTSERRAT_12\/14 = 1)\r\n *  - TFT_eSPI\r\n *  - ArduinoJson &gt;= 6.x\r\n *\r\n * Dateien:\r\n *  - FlugMonitor.ino   (diese Datei)\r\n *  - config.h          (WiFi- und OpenSky-Zugangsdaten)\r\n *\r\n * Authentifizierung : OAuth2 Client Credentials (Bearer Token) \r\n * @author Thomas Wenzlaff \r\n *\/\r\n#include &lt;lvgl.h&gt;\r\n#include &lt;TFT_eSPI.h&gt;\r\n#include &lt;WiFi.h&gt;\r\n#include &lt;WiFiClientSecure.h&gt;\r\n#include &lt;HTTPClient.h&gt;\r\n#include &lt;ArduinoJson.h&gt;\r\n#include &lt;map&gt;\r\n#include &lt;vector&gt;\r\n#include \"config.h\"\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Display\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n#define SCREEN_WIDTH       240\r\n#define SCREEN_HEIGHT      320\r\n#define DRAW_BUF_SIZE      (SCREEN_WIDTH * SCREEN_HEIGHT \/ 20 * (LV_COLOR_DEPTH \/ 8))\r\nstatic uint32_t draw_buf[DRAW_BUF_SIZE \/ 4];\r\n\r\n#define LOGICAL_W          320\r\n#define LOGICAL_H          240\r\n#define TOP_ZONE_H         46   \/\/ Header + Trennlinie + Statuszeile\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Datenstrukturen\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nstruct FlightInfo {\r\n    String icao24;\r\n    String callsign;\r\n    String country;\r\n    float  altitude;\r\n    float  speedKmh;\r\n    float  heading;\r\n    String departure;\r\n    String arrival;\r\n};\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ OAuth2-Token-Cache\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nstatic String g_accessToken = \"\";\r\nstatic time_t g_tokenExpiry = 0;\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Flugdaten &amp; Polling-Steuerung\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nstatic std::map&lt;String, FlightInfo&gt; g_cache;\r\nstatic std::vector&lt;FlightInfo&gt;      g_activeFlights;\r\nstatic int                          g_lastStatesCode = 0;\r\n\r\nstatic unsigned long g_lastStatesUpdate = 0;\r\nconst unsigned long  INTERVAL_STATES    = 15000UL;  \/\/ 15 Sekunden\r\nconst unsigned long  PENALTY_BACKOFF    = 60000UL;  \/\/ 60s Pause bei HTTP 429\r\nstatic bool          g_apiPenaltyActive = false;\r\nstatic bool          g_firstRun         = true;\r\nstatic unsigned long g_detailsPenaltyUntil = 0;\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ LVGL-Objekte\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nstatic lv_obj_t* g_headerLabel  = nullptr;\r\nstatic lv_obj_t* g_statusLabel  = nullptr;\r\nstatic lv_obj_t* g_flightList   = nullptr;\r\n\r\nstatic lv_style_t g_styleHeader;\r\nstatic lv_style_t g_styleStatus;\r\nstatic lv_style_t g_styleItem;\r\nstatic lv_style_t g_styleItemDim;\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ Blockierendes Delay mit laufendem LVGL\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic void delayLvgl(unsigned long ms) {\r\n    unsigned long start = millis();\r\n    while (millis() - start &lt; ms) {\r\n        lv_timer_handler();\r\n        delay(5);\r\n    }\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ Statuszeile\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic void setStatus(const char* msg) {\r\n    if (g_statusLabel != nullptr) {\r\n        lv_label_set_text(g_statusLabel, msg);\r\n        lv_timer_handler();\r\n    }\r\n    Serial.printf(\"[Status] %s\\n\", msg);\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ WiFi\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic void connectWiFi() {\r\n    if (WiFi.status() == WL_CONNECTED) return;\r\n    setStatus(\"WiFi verbinden ...\");\r\n    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);\r\n    for (int i = 0; i &lt; 40 &amp;&amp; WiFi.status() != WL_CONNECTED; i++) {\r\n        delay(500);\r\n        lv_timer_handler();\r\n        Serial.print('.');\r\n    }\r\n    Serial.println();\r\n    if (WiFi.status() == WL_CONNECTED) {\r\n        Serial.printf(\"[WiFi] IP: %s\\n\", WiFi.localIP().toString().c_str());\r\n        setStatus(\"WiFi verbunden.\");\r\n    } else {\r\n        setStatus(\"WiFi FEHLER!\");\r\n    }\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ OAuth2: Bearer-Token holen\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic bool refreshOAuthToken() {\r\n    Serial.println(\"[OAuth2] Token wird erneuert ...\");\r\n    setStatus(\"OAuth2-Token holen ...\");\r\n\r\n    WiFiClientSecure client;\r\n    client.setInsecure();\r\n    HTTPClient http;\r\n    if (!http.begin(client, OPENSKY_TOKEN_URL)) return false;\r\n\r\n    http.addHeader(\"Content-Type\", \"application\/x-www-form-urlencoded\");\r\n    http.setTimeout(12000);\r\n\r\n    String body = \"grant_type=client_credentials\";\r\n    body += \"&amp;client_id=\";     body += String(OPENSKY_CLIENT_ID);\r\n    body += \"&amp;client_secret=\"; body += String(OPENSKY_CLIENT_SECRET);\r\n\r\n    int code = http.POST(body);\r\n    lv_timer_handler();\r\n\r\n    if (code != HTTP_CODE_OK) {\r\n        char msg[40];\r\n        snprintf(msg, sizeof(msg), \"Token-Fehler: HTTP %d\", code);\r\n        setStatus(msg);\r\n        http.end();\r\n        return false;\r\n    }\r\n\r\n    String json = http.getString();\r\n    http.end();\r\n\r\n    DynamicJsonDocument doc(2048);\r\n    if (deserializeJson(doc, json)) { setStatus(\"Token: JSON-Fehler\"); return false; }\r\n\r\n    const char* token = doc[\"access_token\"];\r\n    int expiresIn     = doc[\"expires_in\"] | 300;\r\n    if (!token || strlen(token) == 0) { setStatus(\"Token: leer\"); return false; }\r\n\r\n    g_accessToken = String(token);\r\n    time_t now;\r\n    time(&amp;now);\r\n    g_tokenExpiry = now + expiresIn - TOKEN_EXPIRY_BUFFER_S;\r\n\r\n    Serial.printf(\"[OAuth2] Token OK. G\u00fcltig f\u00fcr %d s.\\n\", expiresIn);\r\n    setStatus(\"OAuth2-Token OK.\");\r\n    return true;\r\n}\r\n\r\nstatic bool ensureValidToken() {\r\n    time_t now;\r\n    time(&amp;now);\r\n    if (g_accessToken.isEmpty() || now &gt;= g_tokenExpiry) return refreshOAuthToken();\r\n    return true;\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ HTTP-GET mit Bearer-Token\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic int httpGet(const String&amp; url, String&amp; body) {\r\n    body = \"\";\r\n    if (WiFi.status() != WL_CONNECTED) return -1;\r\n    if (!ensureValidToken())            return -3;\r\n\r\n    WiFiClientSecure client;\r\n    client.setInsecure();\r\n    HTTPClient http;\r\n    if (!http.begin(client, url)) return -2;\r\n\r\n    http.addHeader(\"Authorization\", \"Bearer \" + g_accessToken);\r\n    http.addHeader(\"Accept\", \"application\/json\");\r\n    http.setTimeout(12000);\r\n\r\n    int code = http.GET();\r\n    lv_timer_handler();\r\n\r\n    if (code == HTTP_CODE_OK) {\r\n        body = http.getString();\r\n    } else {\r\n        if (code != 404) {\r\n            Serial.printf(\"[HTTP] Statuscode %d\\n\", code);\r\n        }\r\n        if (code == 401) {\r\n            g_accessToken = \"\";\r\n            g_tokenExpiry = 0;\r\n        }\r\n    }\r\n    http.end();\r\n    return code;\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ OpenSky: Zustandsvektoren (Bounding Box)\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic std::vector&lt;FlightInfo&gt; fetchStates() {\r\n    std::vector&lt;FlightInfo&gt; result;\r\n\r\n    String url = String(\"https:\/\/opensky-network.org\/api\/states\/all\")\r\n               + \"?lamin=\" + String(BBOX_LAT_MIN, 2)\r\n               + \"&amp;lamax=\" + String(BBOX_LAT_MAX, 2)\r\n               + \"&amp;lomin=\" + String(BBOX_LON_MIN, 2)\r\n               + \"&amp;lomax=\" + String(BBOX_LON_MAX, 2);\r\n\r\n    Serial.println(\"[States] Anfrage: \" + url);\r\n    String json;\r\n    g_lastStatesCode = httpGet(url, json);\r\n\r\n    if (g_lastStatesCode == 429) { setStatus(\"Rate-Limit (429)\");  return result; }\r\n    if (g_lastStatesCode == 401) { setStatus(\"Auth-Fehler (401)\"); return result; }\r\n    if (g_lastStatesCode != HTTP_CODE_OK || json.isEmpty()) {\r\n        char msg[40];\r\n        snprintf(msg, sizeof(msg), \"HTTP Fehler: %d\", g_lastStatesCode);\r\n        setStatus(msg);\r\n        return result;\r\n    }\r\n\r\n    DynamicJsonDocument doc(32768);\r\n    if (deserializeJson(doc, json)) { setStatus(\"JSON-Fehler\"); return result; }\r\n\r\n    JsonArray states = doc[\"states\"].as&lt;JsonArray&gt;();\r\n    if (states.isNull()) { setStatus(\"Keine Daten.\"); return result; }\r\n\r\n    for (JsonArray s : states) {\r\n        if (s.size() &lt; 14)   continue;\r\n        if (s[8].as&lt;bool&gt;()) continue;   \/\/ am Boden\r\n\r\n        float alt = -1.0f;\r\n        if (!s[7].isNull())       alt = s[7].as&lt;float&gt;();\r\n        else if (!s[13].isNull()) alt = s[13].as&lt;float&gt;();\r\n        if (alt &lt; 0.0f || alt &gt; MAX_ALTITUDE_M) continue;\r\n\r\n        FlightInfo fi;\r\n        fi.icao24   = s[0].as&lt;String&gt;();  fi.icao24.trim();\r\n        fi.callsign = s[1].isNull() ? \"N\/A\" : s[1].as&lt;String&gt;(); fi.callsign.trim();\r\n        fi.country  = s[2].isNull() ? \"---\" : s[2].as&lt;String&gt;();\r\n        fi.altitude = alt;\r\n        fi.speedKmh = s[9].isNull()  ? 0.0f : s[9].as&lt;float&gt;() * 3.6f;\r\n        fi.heading  = s[10].isNull() ? 0.0f : s[10].as&lt;float&gt;();\r\n        fi.departure = \"---\";\r\n        fi.arrival   = \"---\";\r\n        result.push_back(fi);\r\n    }\r\n\r\n    Serial.printf(\"[States] %d Flugzeug(e) unter %.0f m.\\n\",\r\n                  result.size(), MAX_ALTITUDE_M);\r\n    return result;\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ OpenSky: Flugdetails f\u00fcr einzelnes Flugzeug\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic int enrichWithFlightDetails(FlightInfo&amp; fi) {\r\n    time_t now;\r\n    time(&amp;now);\r\n    if (now &lt; 100000L) return -1;\r\n\r\n    String url = String(\"https:\/\/opensky-network.org\/api\/flights\/aircraft\")\r\n               + \"?icao24=\" + fi.icao24\r\n               + \"&amp;begin=\"  + String((long)now - 43200L)\r\n               + \"&amp;end=\"    + String((long)now);\r\n\r\n    String json;\r\n    int code = httpGet(url, json);\r\n    \r\n    \/\/ Bei Fehler direkt den Code zur\u00fcckgeben\r\n    if (code != HTTP_CODE_OK || json.isEmpty() || json == \"[]\") return code;\r\n\r\n    DynamicJsonDocument doc(4096);\r\n    if (deserializeJson(doc, json) || !doc.is&lt;JsonArray&gt;()) return code;\r\n    JsonArray arr = doc.as&lt;JsonArray&gt;();\r\n    if (arr.size() == 0) return code;\r\n\r\n    JsonObject flight = arr[arr.size() - 1].as&lt;JsonObject&gt;();\r\n    if (!flight[\"estDepartureAirport\"].isNull())\r\n        fi.departure = flight[\"estDepartureAirport\"].as&lt;String&gt;();\r\n    if (!flight[\"estArrivalAirport\"].isNull())\r\n        fi.arrival = flight[\"estArrivalAirport\"].as&lt;String&gt;();\r\n\r\n    Serial.printf(\"[Flights] %s: %s -&gt; %s\\n\",\r\n                  fi.icao24.c_str(), fi.departure.c_str(), fi.arrival.c_str());\r\n                  \r\n    return code; \/\/ Alles OK (200)\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ LVGL: Anzeige aktualisieren\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic void updateDisplay() {\r\n    \/\/ \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    char header[52];\r\n    struct tm ti;\r\n    if (getLocalTime(&amp;ti, 100)) {\r\n        snprintf(header, sizeof(header),\r\n                 \"* Langenhagen  %02d:%02d:%02d\",\r\n                 ti.tm_hour, ti.tm_min, ti.tm_sec);\r\n    } else {\r\n        snprintf(header, sizeof(header), \"* Langenhagen\");\r\n    }\r\n    lv_label_set_text(g_headerLabel, header);\r\n\r\n    \/\/ \u2500\u2500 Statuszeile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    char status[52];\r\n    snprintf(status, sizeof(status), \"%d Flugzeug(e) unter %.0f m\",\r\n             (int)g_activeFlights.size(), MAX_ALTITUDE_M);\r\n    lv_label_set_text(g_statusLabel, status);\r\n\r\n    \/\/ \u2500\u2500 Flug-Container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    lv_obj_clean(g_flightList);\r\n\r\n    if (g_activeFlights.empty()) {\r\n        lv_obj_t* lbl = lv_label_create(g_flightList);\r\n        lv_obj_add_style(lbl, &amp;g_styleStatus, 0);\r\n        lv_label_set_text(lbl, \"Keine Flugzeuge\\nim Radius erkannt.\");\r\n        lv_label_set_long_mode(lbl, LV_LABEL_LONG_WRAP);\r\n        lv_obj_set_width(lbl, LOGICAL_W - 16);\r\n    } else {\r\n        for (const FlightInfo&amp; fi : g_activeFlights) {\r\n            lv_color_t cardColor;\r\n            if      (fi.altitude &lt; 500.0f)  cardColor = lv_color_hex(0x3d1f1f);\r\n            else if (fi.altitude &lt; 1000.0f) cardColor = lv_color_hex(0x3d2e18);\r\n            else                            cardColor = lv_color_hex(0x152535);\r\n\r\n            lv_obj_t* card = lv_obj_create(g_flightList);\r\n            lv_obj_set_width(card, LOGICAL_W - 18);\r\n            lv_obj_set_height(card, LV_SIZE_CONTENT);\r\n            lv_obj_set_style_bg_color(card, cardColor, 0);\r\n            lv_obj_set_style_bg_opa(card, LV_OPA_COVER, 0);\r\n            lv_obj_set_style_border_color(card, lv_color_hex(0x30363d), 0);\r\n            lv_obj_set_style_border_width(card, 1, 0);\r\n            lv_obj_set_style_radius(card, 4, 0);\r\n            lv_obj_set_style_pad_all(card, 4, 0);\r\n            lv_obj_set_style_pad_row(card, 2, 0);\r\n            lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN);\r\n            lv_obj_remove_flag(card, LV_OBJ_FLAG_SCROLLABLE);\r\n\r\n            char line1[64];\r\n            snprintf(line1, sizeof(line1),\r\n                     \"[%s]  %.0fm  %.0fkm\/h  %.0f\\xc2\\xb0\",\r\n                     fi.callsign.c_str(), fi.altitude,\r\n                     fi.speedKmh, fi.heading);\r\n            lv_obj_t* lbl1 = lv_label_create(card);\r\n            lv_obj_add_style(lbl1, &amp;g_styleItem, 0);\r\n            lv_label_set_text(lbl1, line1);\r\n            lv_label_set_long_mode(lbl1, LV_LABEL_LONG_WRAP);\r\n            lv_obj_set_width(lbl1, LOGICAL_W - 30);\r\n\r\n            char line2[64];\r\n            snprintf(line2, sizeof(line2), \"%s  |  %s -&gt; %s\",\r\n                     fi.country.c_str(),\r\n                     fi.departure.c_str(),\r\n                     fi.arrival.c_str());\r\n            lv_obj_t* lbl2 = lv_label_create(card);\r\n            lv_obj_add_style(lbl2, &amp;g_styleItemDim, 0);\r\n            lv_label_set_text(lbl2, line2);\r\n            lv_label_set_long_mode(lbl2, LV_LABEL_LONG_WRAP);\r\n            lv_obj_set_width(lbl2, LOGICAL_W - 30);\r\n        }\r\n    }\r\n\r\n    lv_timer_handler();\r\n    lv_refr_now(NULL);\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ Haupt-Update\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic void fetchAndUpdateStates() {\r\n    setStatus(\"Lade Flugdaten ...\");\r\n\r\n    std::vector&lt;FlightInfo&gt; fresh = fetchStates();\r\n    delayLvgl(1000);\r\n\r\n    \/\/ Rate-Limit erkannt? Penalty aktivieren und abbrechen\r\n    if (g_lastStatesCode == 429) {\r\n        g_apiPenaltyActive = true;\r\n        g_lastStatesUpdate = millis(); \/\/ Timer resetten, damit Penalty greift\r\n        return; \r\n    }\r\n    g_apiPenaltyActive = false; \/\/ Wieder freigeben\r\n\r\n    if (g_lastStatesCode == HTTP_CODE_OK) {\r\n        std::map&lt;String, FlightInfo&gt; newCache;\r\n        bool detailsFetchedThisCycle = false; \/\/ Drosselung: max 1 Detail-Request pro Zyklus\r\n\r\n        for (FlightInfo&amp; fi : fresh) {\r\n            auto it = g_cache.find(fi.icao24);\r\n            if (it != g_cache.end()) {\r\n                fi.departure = it-&gt;second.departure;\r\n                fi.arrival   = it-&gt;second.arrival;\r\n            } else {\r\n                \/\/ Pr\u00fcfen, ob wir \u00fcberhaupt Detail-Abfragen machen d\u00fcrfen (Circuit Breaker) TODO\r\n                if (!detailsFetchedThisCycle &amp;&amp; millis() &gt; g_detailsPenaltyUntil) {\r\n                    setStatus((\"Route pr\u00fcfen: \" + fi.icao24).c_str());\r\n                    \r\n                    int detailCode = enrichWithFlightDetails(fi);\r\n                    detailsFetchedThisCycle = true;\r\n                    \r\n                    if (detailCode == 429) {\r\n                        Serial.println(\"[Flights] Rate-Limit 429 erreicht. Sperre Routen-Abfrage f\u00fcr 5 Minuten.\");\r\n                        g_detailsPenaltyUntil = millis() + 300000UL; \/\/ 5 Minuten Backoff\r\n                    }\r\n                    delayLvgl(1000);\r\n                }\r\n            }\r\n            newCache[fi.icao24] = fi;\r\n        }\r\n        g_cache = std::move(newCache);\r\n        g_activeFlights = std::move(fresh);\r\n    } else {\r\n        Serial.printf(\"[Update] States HTTP %d \u2013 zeige letzte bekannte Daten.\\n\",\r\n                      g_lastStatesCode);\r\n    }\r\n\r\n    updateDisplay();\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ LVGL: UI aufbauen\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nstatic void initUI() {\r\n    lv_obj_t* scr = lv_screen_active();\r\n    lv_obj_set_style_bg_color(scr, lv_color_hex(0x0d1117), 0);\r\n    lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);\r\n\r\n    lv_style_init(&amp;g_styleHeader);\r\n    lv_style_set_text_font(&amp;g_styleHeader, &amp;lv_font_montserrat_14);\r\n    lv_style_set_text_color(&amp;g_styleHeader, lv_color_hex(0x58a6ff));\r\n\r\n    lv_style_init(&amp;g_styleStatus);\r\n    lv_style_set_text_font(&amp;g_styleStatus, &amp;lv_font_montserrat_12);\r\n    lv_style_set_text_color(&amp;g_styleStatus, lv_color_hex(0x8b949e));\r\n\r\n    lv_style_init(&amp;g_styleItem);\r\n    lv_style_set_text_font(&amp;g_styleItem, &amp;lv_font_montserrat_12);\r\n    lv_style_set_text_color(&amp;g_styleItem, lv_color_hex(0xe6edf3));\r\n\r\n    lv_style_init(&amp;g_styleItemDim);\r\n    lv_style_set_text_font(&amp;g_styleItemDim, &amp;lv_font_montserrat_12);\r\n    lv_style_set_text_color(&amp;g_styleItemDim, lv_color_hex(0x8b949e));\r\n\r\n    \/\/ Header\r\n    g_headerLabel = lv_label_create(scr);\r\n    lv_obj_add_style(g_headerLabel, &amp;g_styleHeader, 0);\r\n    lv_label_set_text(g_headerLabel, \"* Langenhagen\");\r\n    lv_obj_align(g_headerLabel, LV_ALIGN_TOP_LEFT, 5, 4);\r\n\r\n    \/\/ Obere Trennlinie\r\n    lv_obj_t* sepTop = lv_obj_create(scr);\r\n    lv_obj_set_size(sepTop, LOGICAL_W - 2, 1);\r\n    lv_obj_align(sepTop, LV_ALIGN_TOP_MID, 0, 22);\r\n    lv_obj_set_style_bg_color(sepTop, lv_color_hex(0x30363d), 0);\r\n    lv_obj_set_style_border_width(sepTop, 0, 0);\r\n    lv_obj_set_style_pad_all(sepTop, 0, 0);\r\n\r\n    \/\/ Statuszeile\r\n    g_statusLabel = lv_label_create(scr);\r\n    lv_obj_add_style(g_statusLabel, &amp;g_styleStatus, 0);\r\n    lv_label_set_text(g_statusLabel, \"Starte ...\");\r\n    lv_obj_align(g_statusLabel, LV_ALIGN_TOP_LEFT, 5, 26);\r\n\r\n    \/\/ Scrollbarer Flug-Container (Nimmt nun den restlichen Platz ein)\r\n    g_flightList = lv_obj_create(scr);\r\n    lv_obj_set_size(g_flightList, LOGICAL_W, LOGICAL_H - TOP_ZONE_H);\r\n    lv_obj_align(g_flightList, LV_ALIGN_TOP_MID, 0, TOP_ZONE_H);\r\n    lv_obj_set_style_bg_color(g_flightList, lv_color_hex(0x0d1117), 0);\r\n    lv_obj_set_style_bg_opa(g_flightList, LV_OPA_COVER, 0);\r\n    lv_obj_set_style_border_color(g_flightList, lv_color_hex(0x30363d), 0);\r\n    lv_obj_set_style_border_width(g_flightList, 1, 0);\r\n    lv_obj_set_style_pad_all(g_flightList, 4, 0);\r\n    lv_obj_set_style_pad_row(g_flightList, 5, 0);\r\n    lv_obj_set_scroll_dir(g_flightList, LV_DIR_VER);\r\n    lv_obj_set_scrollbar_mode(g_flightList, LV_SCROLLBAR_MODE_AUTO);\r\n    lv_obj_set_flex_flow(g_flightList, LV_FLEX_FLOW_COLUMN);\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ setup()\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nvoid setup() {\r\n    Serial.begin(115200);\r\n    Serial.println(\"\\n[Setup] FlugMonitor startet ...\");\r\n\r\n    lv_init();\r\n    lv_display_t* disp = lv_tft_espi_create(\r\n        SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));\r\n    lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);\r\n\r\n    initUI();\r\n    lv_timer_handler();\r\n    lv_refr_now(NULL);   \r\n\r\n    connectWiFi();\r\n\r\n    setStatus(\"NTP synchronisieren ...\");\r\n    configTime(NTP_GMT_OFFSET_SEC, NTP_DAYLIGHT_OFFSET, NTP_SERVER);\r\n    struct tm ti;\r\n    if (getLocalTime(&amp;ti, 8000)) {\r\n        Serial.printf(\"[NTP] OK: %02d:%02d:%02d\\n\",\r\n                      ti.tm_hour, ti.tm_min, ti.tm_sec);\r\n        setStatus(\"NTP OK.\");\r\n    } else {\r\n        setStatus(\"NTP fehlgeschlagen.\");\r\n    }\r\n\r\n    if (!refreshOAuthToken()) {\r\n        setStatus(\"Auth fehlgeschlagen!\");\r\n        delayLvgl(3000);\r\n    }\r\n\r\n    setStatus(\"Warte vor erster Abfrage ...\");\r\n    delayLvgl(5000);\r\n\r\n    fetchAndUpdateStates();\r\n    g_lastStatesUpdate = millis();\r\n    g_firstRun         = false;\r\n\r\n    Serial.println(\"[Setup] Bereit.\");\r\n}\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ loop()\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\nvoid loop() {\r\n    lv_timer_handler();\r\n    unsigned long now = millis();\r\n\r\n    if (WiFi.status() != WL_CONNECTED) connectWiFi();\r\n\r\n    \/\/ Ermitteln des aktuellen Intervalls (inkl. Backoff-Strafe bei 429)\r\n    unsigned long currentStatesInterval = g_apiPenaltyActive ? PENALTY_BACKOFF : INTERVAL_STATES;\r\n\r\n    \/\/ Flugzeuge aktualisieren\r\n    if (now - g_lastStatesUpdate &gt;= currentStatesInterval) {\r\n        g_lastStatesUpdate = now;\r\n        fetchAndUpdateStates();\r\n    }\r\n\r\n    if (g_firstRun) g_firstRun = false;\r\n    \r\n    delay(5);\r\n}\r\n<\/pre>\n<p>Dann brauchen wir noch eine <strong>config.h<\/strong> f\u00fcr die WIFI und OpenSky Token im gleichen Verzeichnis:<\/p>\n<pre class=\"lang:c++ decode:true \" >#pragma once\r\n\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\/\/ config.h  \u2013  FlugMonitor Konfiguration\r\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\r\n\/\/ \u2500\u2500 WiFi \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n#define WIFI_SSID      \"WIFI-SSID\"\r\n#define WIFI_PASSWORD  \"PASSWORT\"\r\n\r\n\/\/ \u2500\u2500 OpenSky Network Zugangsdaten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Registrierung: https:\/\/opensky-network.org\/index.php?option=com_users&amp;view=registration\r\n#define OPENSKY_CLIENT_ID   \"client id\"\r\n#define OPENSKY_CLIENT_SECRET   \"Secret\"\r\n\r\n\/\/ \u2500\u2500 OAuth2 Token-Endpunkt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n#define OPENSKY_TOKEN_URL \\\r\n    \"https:\/\/auth.opensky-network.org\/auth\/realms\/opensky-network\" \\\r\n    \"\/protocol\/openid-connect\/token\"\r\n\r\n\/\/ \u2500\u2500 Bounding Box Langenhagen, Niedersachsen (~20 km Radius) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Mittelpunkt: 52.4398\u00b0 N, 9.7337\u00b0 O\r\n\/\/ Breite:  \u00b10.18\u00b0 (\u2248 20 km)\r\n\/\/ L\u00e4nge:   \u00b10.30\u00b0 (\u2248 20 km bei 52\u00b0 Breite)\r\n\/\/ #define BBOX_LAT_MIN  52.26\r\n\/\/ #define BBOX_LAT_MAX  52.62\r\n\/\/ #define BBOX_LON_MIN   9.43\r\n\/\/ #define BBOX_LON_MAX  10.04\r\n\r\n\/\/ Aktualisierte Bounding Box (Erweiterter Anflugsektor Osten)\r\n\/\/ Definitionsbereich:\r\n\r\n\/\/    S\u00fcdliche Begrenzung (Latitude Min): 52.40\u00b0N\r\n\/\/    N\u00f6rdliche Begrenzung (Latitude Max): 52.48\u00b0N\r\n\/\/    Westliche Begrenzung (Longitude Min): 9.71\u00b0E\r\n\/\/    \u00d6stliche Begrenzung (Longitude Max): 10.60\u00b0E\r\n#define BBOX_LAT_MIN  52.40\r\n#define BBOX_LAT_MAX  52.48\r\n#define BBOX_LON_MIN   9.71\r\n#define BBOX_LON_MAX  10.60 \r\n\r\n\/\/ \u2500\u2500 Filterkriterium \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n#define MAX_ALTITUDE_M  2000.0f   \/\/ Meter (barometrisch)\r\n\r\n\/\/ \u2500\u2500 Aktualisierungsintervall \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ OpenSky erlaubt authentifizierten Nutzern min. 5 s zwischen Anfragen.\r\n#define REFRESH_INTERVAL_MS  120000UL\r\n\r\n\r\n\/\/ \u2500\u2500 Puffer vor Token-Ablauf (Sekunden) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n#define TOKEN_EXPIRY_BUFFER_S  60\r\n\r\n\r\n\/\/ \u2500\u2500 Wartezeit vor erstem Request nach Boot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Verhindert 429 direkt nach dem Einschalten\r\n#define INITIAL_DELAY_MS  15000UL       \/\/ 15 Sekunden\r\n\r\n\/\/ \u2500\u2500 NTP-Zeitserver \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n#define NTP_SERVER            \"pool.ntp.org\"\r\n#define NTP_GMT_OFFSET_SEC    3600    \/\/ UTC+1 (MEZ)\r\n#define NTP_DAYLIGHT_OFFSET   3600    \/\/ +1h Sommerzeit (MESZ)\r\n\r\n<\/pre>\n<p>Code wird dann sp\u00e4ter nach Gitlab hochgeladen.<\/p>\n<p>Auch wenn der Papst vor Gefahren von K\u00fcnstlicher Intelligenz (KI) in seiner ersten Enzyklika &#8222;Magnifica Humanitas&#8220; (Gro\u00dfartige Menschheit) warnt, kann man sagen, das dieses Projekt ohne KI f\u00fcr mich in herk\u00f6mmlicher Weise einige <strong>Tage<\/strong> gebraucht h\u00e4tte. So konnte alles in ein paar Stunden &#8222;just for fun&#8220; erstellt werden. <\/p>\n<p>Software-Programmierer werden in Zukunft anders arbeiten, den in diesem Bereich ist die KI seit Jahren nicht mehr wegzudenken. In diesem Sinne, noch einen sch\u00f6nen Tag und sonnige Gr\u00fc\u00dfe aus EDDV bei 26,5 Grad Celsius.<\/p>\n<p><strong>Haftungsausschluss (Disclaimer)<\/strong><\/p>\n<p>Die in diesem Dokument\/generierten Inhalten bereitgestellten Daten, Simulationen und Informationen dienen ausschlie\u00dflich zu Bildungs- und Verst\u00e4ndZwecken. Sie wurden bewusst erzeugt, um theoretische Konzepte zu veranschaulichen und das Verst\u00e4ndnis von Flugdynamik, Steuerungsprinzipien oder verwandten technischen Zusammenh\u00e4ngen zu f\u00f6rdern.<\/p>\n<p>Die Daten sind ausdr\u00fccklich nicht f\u00fcr die Durchf\u00fchrung echter Flugman\u00f6ver, nicht f\u00fcr den operationellen Einsatz in der realen Luftfahrt und nicht f\u00fcr jegliche Art von praktischer Navigation oder Flugsteuerung geeignet.<\/p>\n<p>Die Verwendung dieser Informationen f\u00fcr reale Flugoperationen erfolgt ausschlie\u00dflich auf eigene Gefahr. Der Autor \u00fcbernimmt keinerlei Haftung f\u00fcr Sch\u00e4den, Verluste, Verletzungen oder sonstige Folgen, die direkt oder indirekt aus der Anwendung, Fehlinterpretation oder dem Missbrauch dieser Daten entstehen \u2013 sei es durch Personen, Organisationen oder Dritte.<\/p>\n<p>Jegliche \u00c4hnlichkeit mit tats\u00e4chlichen Flugman\u00f6vern oder operationalen Verfahren ist rein zuf\u00e4llig und nicht beabsichtigt. F\u00fcr den echten Flugsport und die Luftfahrt sind ausschlie\u00dflich offizielle, zertifizierte Quellen und qualifiziertes Fachpersonal heranzuziehen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wenn ich aus dem K\u00fcchenfenster schaue, sehe ich alle Flugzeuge die aus dem Osten kommen und in Langenhagen (EDDV, HAJ) landen wollen. Habe schon einige Projekte in diesem Zusammenhang gemacht, aber die sind noch alle per Hand, und nicht mit KI entstanden. Nun ist aus der Bucht ein ESP32 mit TFT und Touch angekommen, der &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/blog.wenzlaff.de\/?p=23230\" class=\"more-link\"><span class=\"screen-reader-text\">\u201eKI: Flugsensor mit ESP32 mit WIFI &#038; Bluetooth Entwicklungsboard 2,8 Zoll LCD TFT Modul mit Touch\u201c <\/span>weiterlesen<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","footnotes":""},"categories":[220,3107,5373,538,6310,6075,4931,79],"tags":[6314,6315,1991,721,1929,6313,6320,3020,684,6311,6321,683,6316,6312,6319,6317,6318,2222],"class_list":["post-23230","post","type-post","status-publish","format-standard","hentry","category-anleitung","category-c-programmierung","category-chatgpt","category-elektronik","category-esp32-mit-tft-und-touch","category-ki","category-planespotting","category-programmierung","tag-6314","tag-8-zoll","tag-airport","tag-bluetooth","tag-eddv","tag-entwicklungsboard","tag-enzyklika","tag-esp32","tag-flughafen","tag-flugsensor","tag-grossartige-menschheit","tag-haj","tag-lcd-tft","tag-lvgl","tag-magnifica-humanitas","tag-modul","tag-touch","tag-wifi"],"_links":{"self":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/23230","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=23230"}],"version-history":[{"count":3,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/23230\/revisions"}],"predecessor-version":[{"id":23240,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/23230\/revisions\/23240"}],"wp:attachment":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=23230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=23230"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=23230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}