I built my own plane radar on a $20 ESP32 display, and I haven't opened Flightradar24 since

I built my own plane radar on a $20 ESP32 display, and I haven't opened Flightradar24 since

Published Jul 31, 2026, 2:00 PM EDT I’m Adam Conway, an Irish technology fanatic with a BSc in Computer Science and I'm XDA’s Lead Technical Editor. My Bachelor’s thesis was conducted on the viability of benchmarking the non-functional elements of Android apps and smartphones such as performance, and I’ve been working in the tech industry in some way or another since 2017. In my spare time, you’ll probably find me playing Counter-Strike or VALORANT, and you can reach out to me at adam@xda-developers.com, on Twitter as @AdamConwayIE, on Instagram as AdamConwayIE, or u/AdamConwayIE on Reddit. Sign in to your XDA account I'm sure I'm not the only one who's seen (or even just heard) a plane go overhead, followed by going straight to Flightradar24 to find out what it actually was. It's fun, but if you hadn't seen it or heard it, you'd never have known, and sometimes, you'll catch some genuinely interesting planes up above you. That's why I built my own plane radar on the WT32-SC01 Plus, an ESP32-S3-powered display. I was inspired by MatixYo's Plane Radar project, and the 3.5-inch 480x320 capacitive touchscreen is perfect for the job. I wrote the firmware in ESP-IDF, and it shows a live radar of anything flying near Dublin Airport. I can tap an aircraft to get its callsign, and tapping it again shows me the registration, aircraft type, current altitude, squawk, and its departure airport and arrival airport. The display can cost anywhere from $20-30, and it's a pretty great one, too. The only downside is that this board has a mere 2MB of PSRAM to work with, which gets tight fast when a single API response can temporarily claim over 100KB of it. It was a fun weekend project that came together thanks to other projects I've built with this display, and I'm really happy with how it turned out. The WT32-SC01 Plus is a lot of screen for the money It's bright and sharp The WT32-SC01 Plus comes with an ESP32-S3-WROVER module with 16MB of flash and the aforementioned 2MB of PSRAM, bonded to a 3.5-inch IPS panel running at 480x320 with an ST7796 controller and an FT6336U capacitive touch layer over the top. It's similar to the various Cheap Yellow Display boards, just bigger, sharper, and with a proper capacitive digitizer rather than a resistive one you could operate with a Nintendo DS stylus. I've used it for a lot of projects, and I absolutely love it. The display also doesn't use SPI like most cheaper panels do. Instead, it's wired up over an 8-bit Intel 8080 parallel bus, which the S3 drives through its LCD_CAM peripheral with DMA, and that gives you several times the bandwidth of a comparable SPI panel. By using the display bus to its fullest, I'm able to redraw the entire radar canvas four times a second, which is far more often than new data actually arrives, instead of falling back on hacks that might even look uglier. On top of that, I've got the pixel clock set to a fairly conservative 10MHz because I knew I was already pushing the limit of what this board could do. The radar scope is a square canvas 286 pixels a side, and every frame wipes it and redraws it. I also render a coastline and range rings alongside runways, aircraft, and trails. At two bytes a pixel and four frames a second it's roughly 650KB/s on the bus, which is more than enough to work with. That headroom is the entire reason I never had to think about partial updates. Memory was what I had to focus on most for this; there's no full-screen framebuffer, as a 480x320 canvas at two bytes a pixel is 300KB, or 600KB double-buffered, and that isn't happening on a board with just 2MB of PSRAM. LVGL renders in strips instead, into a pair of 480x20 line buffers that come to about 38KB between them and get reused strip after strip. The canvas stays persistent, though, as a 160KB block that's allocated at startup. I have a diagnostics page I built in early on to keep an eye on RAM, and with the aircraft store full, trail buffers, route cache, and every HTTP response body in there, I have about 1.8MB of PSRAM free, and around 84KB of the ESP32's fast internal RAM left remaining, too. The reason we use the internal RAM is because it's the only memory the LCD DMA engine can pull from reliably, so it's reserved almost entirely for transfer buffers. Everything else gets pushed out to the slower external RAM. The data is freely available Everything you need is free online There are a lot of regulations around aircraft, and most will have ADS-B Out given that it's been mandatory in most US and European controlled airspace since the start of 2020. Those planes broadcast their GPS position, altitude, ground speed, and identity themselves roughly once per second on 1090MHz. This means that if you have the hardware, the sky above you is basically filled with aircraft that constantly announce themselves to anyone who has a cheap dongle to detect them. Plenty of people have those dongles, and a lot of them feed that data back to community networks. My firmware here talks to adsb.fi, which is one of those networks. You make a request to their API with the latitude, longitude, and a radius of up to 250 nautical miles, and in response you get back JSON containing every aircraft they can see in that circle. You don't need an API key or account, and it's rate limited to one request per second. The network is for personal, non-commercial use only, and all they ask is that you credit them. The ADS-B payload gives you a callsign but not a route, so the origin and destination airports come from a second service called adsbdb, which maps callsigns to flight routes and is also free. That check only runs when you actually open an aircraft's detail card, rather than for everything on screen, and it shares the same network task as the ADS-B poll, so it's deliberately limited to one queued lookup at a time. Finally, it remembers the last sixteen callsigns it looked up, so tapping the same aircraft again should be instantaneous. A found route is kept for two hours, because a flight's route usually doesn't change halfway through, and a callsign adsbdb has never heard of is remembered as unknown for thirty minutes. The map underneath everything comes from two more free datasets. Runways and airport positions are from OurAirports, filtered down to 1,119 large airports and their 1,714 open runway strips, the coastlines are Natural Earth's 1:10m vector data, and both are outright public domain. With just four sources and a cheap ESP32-based board, you get everything you need to draw a working air traffic display. I had to really push the hardware and utilize every nook and cranny that I could to make it all work seamlessly. Fitting a world coastline into a microcontroller It's already small, but we need it smaller The next order of business, once I got it all working, was to get coastlines on the radar. Dublin Airport is on the east of the island, right beside the Irish Sea, and it's hard to actually figure out where anything is without that kind of orientation. There's one problem with Natural Earth's 1:10m coastline data, though: it's about 4MB of GeoJSON, which is text I'd have to parse at runtime, on a board where the entire application partition is 4MB. To get around that, I built a Python script that reduces it down. It runs Douglas-Peucker simplification over every polyline at two different tolerances, producing a fine level used at ranges under 50km and a coarse one for everything above, then chops each line into runs of at most 64 points and precomputes a bounding box for each run. At draw time the renderer can throw away an entire chunk with four float comparisons before it looks at a single vertex, which saves me potentially queuing thousands of line segments into LVGL's heap. The script also has a configurable byte budget that it can't exceed. I set it to target 1.5MB, and if the result is above that, it multiplies both tolerances by 1.5 and does the whole thing again until it fits. At the end of the process, I have 123,445 fine points and 37,584 coarse points, output as a 170,000-line C file taking up 4.7MB on disk, most of which is indentation and commas that the compiler throws away. The data itself comes to about 1.45MB, and the whole firmware image sits at 2.9MB in a 4MB app partition. The 1.5MB limit that I set comes from the partition table I created. This board has 16MB of flash to play with, but I split it into three 4MB slots and an additional megabyte of storage. The first 4MB partition is for the normal firmware, but the latter two are for over-the-air updates. Even though there's no update code in the firmware today, it's something I have ambitions to do, because it's a lot easier to build it with that constraint in mind rather than trying to cut it back later on. There are three ways to get out of that constraint, though. The first is pretty simple: delete the OTA slots, free 8MB, and I'm sorted. I could fit the entire unsimplified coastline easily, and with room to spare. However, that kills any ambition of an update mechanism, so that's out. As well, I could only include Ireland, but then it's useless for most people. i like that it carries coastlines for every country in the world, and I don't want to remove that. The third one is the one I'll probably do. My layout keeps a factory image and two OTA slots, as the standard ESP-IDF arrangement is just two slots with a small record that remembers which slot is active. Getting rid of the factory partition and splitting what's left between the other two takes each slot from 4MB to roughly 7.3MB, which nearly doubles the space the coastline has to live in and keeps the update mechanism completely intact. The reason app slots have to move together, incidentally, is that an update writes an entire image into whichever slot isn't running, so every slot has to be big enough to hold the whole application. If I grow one to 6MB, that means all three have to be 6MB, and that would put us 2MB over the total flash storage. By dropping the factory image, I get more storage, but what I'd lose is the guaranteed fallback to a working image. I don't really mind that, but this entire project was more or less a challenge to myself to try something different. The coastline is the only thing on the screen that isn't computed completely accurately. Aircraft and runways are placed with proper great-circle bearing and haversine distance, because where they are actually matters. The coastline gets a cheap local equirectangular approximation instead, two multiplications against a fixed kilometres-per-degree figure, because it carries thousands of vertices every frame and nobody is navigating by it. Five seconds between updates, and you can't really tell Dead-reckoning fills in the gaps Polling every five seconds would look terrible without anything else thrown on top, and it's pretty easy to imagine why. You'd see a plane sitting motionless, then it would jump, then you'd see it motionless again, followed by another jump. I wanted something a lot nicer to look at. The way around that is also why I redraw the display four times a second. On every redraw, I project each aircraft forward along its last known ground track at its last known ground speed, assuming it reported both, as anything missing a track or a speed just sits where it was. This technique is called dead reckoning, and it's used basically everywhere. The prediction is capped at twenty seconds of travel, so an aircraft that stops updating drifts that far and then holds. I also added a fading trail of previous positions behind each plane, drawn from the positions actually received rather than the predicted ones. At the 25km range a fetch takes 1,187 milliseconds and returns an 87,974 byte JSON document, which the ESP32 parses in full every five seconds. Drop to 10km on a quiet evening, with a couple of aircraft in range and a 5KB response, and a fetch still takes just under a second. Widening the range pulls a lot more data but costs very little extra time, as the cost is almost entirely the TLS handshake and the round trip rather than the payload itself. The store itself only has room for 96 tracked planes, and at a larger range it's nearly always full during the day. When a new plane comes into scope with nowhere to go, I evict whichever tracked aircraft is furthest away, or drop the newcomer instead if it's further out than everything I already have. What this means is that the planes I'm looking at are always the nearest 96 in the sky, instead of the first 96 the API happened to return. The store also only prunes stale aircraft after a successful fetch, so if the API goes away or the Wi-Fi drops, everything is frozen in time until it regains a connection. After three failed fetches in a row it flashes a warning along the bottom of the screen to let you know that it's failing to fetch data. It's a fun gadget to keep on my desk It looks good and works well I centred the radar on Dublin Airport for the purposes of testing, as there's a lot more air traffic at any given time than directly overhead my home. It's pretty neat to watch planes coming and going in the same direction, and it's even cooler to see them land perfectly on and take-off from the runway, given that the runway data came from a separate dataset. As well, I can tap plane and see a small chip in the top left, containing the callsign, altitude, and speed. Tapping the same plane again opens a detail card with the registration, the ICAO type resolved into an actual aircraft name, the vertical rate, the distance and bearing, the squawk, and the route. The viewable radius can be set to 5km, 10km, 25km, 50km, 100km and 250km with a pair of buttons, and there's a settings page plus a small web page served on your network for changing some settings and the center coordinates. It decodes emergency squawks, too. It picks out 7500 for unlawful interference, 7600 for radio failure, 7700 for a general emergency, and all of them display a red banner across the top of whatever screen you're on. If you want to build something like it, MatixYo's project is a much better place to start than writing your own firmware from scratch. It's an MIT licensed project, it targets an ESP32-C3 Super Mini with a 1.28-inch round display, and there are prebuilt binaries so you can have it running without compiling anything at all. Very little of this is actually mine though; especially when compared to the work that made it all possible. Community networks are the ones capturing the aircraft data from volunteers, coastlines and airports are made available in the public domain, and the idea behind it was totally inspired, too. All I did was write something that ingests that data and draws it to a display, but I absolutely love it.

Original Source

Read the full article at Xda-developers →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.