Disclaimer: I am a cadet pilot. Not a software developer.
My job involves aerodynamics,radio frequencies, and checklists. Not pull requests, API documentation, and certainly not coding. I don’t have a degree in computer science, just aviation and psychology honours. What I have is ‘ The Itch.’
You know the one, the constant nagging feeling when you want a specific tool that feeds your ADHD but doesn’t exist yet. But you are stubborn enough to try to build it yourself.
In the past, I scratched this itch with python script running on my NAS server, yes i have one. My proudest creation wasn’t a sleek app; it was a brute-force bot that hammered the DGCA Pariksha website every second during high-traffic exam seasons. It monitored uptime and sent me a ping the instant the server came back online, allowing me to snag a registration slot seconds before the site crashed again.
It wasn’t pretty. But it worked.
I rely heavily on vibe coding. A mix of asking my dev friends stupid questions, I’m not good at coding, but i am excellent at problem solving. Pasting error logs into LLMs, and brute-forcing my way to a solution until the red text turns green.
Why I Built a DIY Flight Tracker for TRMNL

The idea came to me while I was sitting in the academy, staring out the big glass window (too big for a window, let’s go with a wall). I had heard about the TRMNL. I knew it was slow. Even with TRMNL+, the fastest refresh is 5 minutes.
But I sat there thinking: What if I could just look and see exactly what plane is flying over me right now?
Not in real-time video game speed. I didn’t need that. I just needed a snapshot. A passive radar scope that updated silently every few minutes, giving me situational awareness without the need to unlock a bright, distracting iPad.
I also wanted to know my physical recovery stats from my smart ring without risking a morning doom-scroll on my phone.
I needed a dashboard that respected my silence.
Here is how I fumbled my way through building it using Cloudflare Workers, some rusty high-school geometry, and a lot of trial and error.
System Architecture: Cloudflare Workers & TRMNL
The genius of TRMNL is that it’s incredibly dumb (complimentary). It doesn’t process data, it just knows how to show it in a particular manner i.e liquid files. This means the brain isn’t n my desk, it’s in a cloud.
I chose Cloudflare Workers as the backend for three reasons:
- It’s fast (Edge computing).
- It’s free (I will never hit the request cap).
- It speaks JSON (The native language of TRMNL).
The flow looks like this:
TRMNL (Poller) → Cloudflare Worker → Multiple APIs (Mashup) → Render Logic → TRMNL.
I didn’t just build one view. I built three distinct instruments.
Building the Instrument: Live Flight Radar Logic

I thought this would be easy. Get the plane gps coordinates via ADSB, and put a dot on the screen. But boy was I wrong. Almost too wrong. This turned into a three part engineering nightmare involving hardcoded databases and trigonometry.
1. The dictionary problem
The airplanes.live API gives you callsign like AIC243. To a computer thats fine. To a non aviator, it’s cryptic. I wanted the soul of the data. I wanted to see Air India, not AIC.
A sensible person would use an external database API. I am not a sensible person. I built a massive, hard-coded lookup table directly inside the worker script, mapping hundreds of ICAO codes to their human names.
const AIRLINE_MAP = {
// --- NORTH AMERICA ---
AAL: "American", UAL: "United", DAL: "Delta",
// --- ASIA ---
SIA: "Singapore Airlines", AIC: "Air India", IGO: "IndiGo",
// --- MILITARY ---
RCH: "US Air Force", CNV: "US Navy"
};
Is it efficient? No. Does it make the file larger? Yes. But seeing “US Air Force” pop up on my e-ink screen instead of “RCH” brings me a level of joy that efficiency cannot buy.
2. The Round Earth vs. Flat Screen
The TRMNL screen is an 800×480 grid of pixels. The world is an oblate spheroid.
I had to use the Haversine Formula to calculate the Great Circle distance between my house and the plane. Doing this was necessary to get the X,Y coordinates of where to put the plane relative to the center (me).
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
return (R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))) * 0.539957;
}3. The Collision Problem (Mini-ATC)
This was the final friction point. On a 7.5-inch screen, if two planes are flying close together, their text labels overlap and become unreadable.
I wrote a custom function called resolveLabels that acts like a mini Air Traffic Controller. It looks at every plane and tries to place the label in one of 4 positions (Bottom-Right, Top-Left, etc.). If a spot is taken, it moves the label to the next available slot.
The PFD: Combining Airplanes.live and ADSB.lol APIs
While the radar is cool, i wanted to know what’s the plane flying overhead me right now, and where is it is going. Not live but approximately . I wanted a telemetry dashboard.
The problem is that no single free API gives you both Live Position and Route Info (Origin/Destination) reliably. So, I had to Frankenstein two APIs together.

1. The API Mashup
- Source A (airplanes.live): Gives me the physics (Lat, Lon, Altitude).
- Source B (adsb.lol): Gives me the context (Route, Airport Names).
My worker fetches the live traffic first. Then, it takes the callsigns and asks adsb.lol: “Hey, where is AIC243 headed?”.
2. The Staggered Fetch (Avoiding the Banhammer)
The adsb.lol API is free and run by the community. I didn’t want to hammer their server with 10 simultaneous requests every time my screen refreshed.
I implemented a Staggered Fetch loop. Instead of blasting all requests at once, the code waits 250ms between each call.
// Jitter: Wait 250ms * index to avoid API block
await new Promise(r => setTimeout(r, i * 250));This “politeness delay” ensures I get my data without tripping any rate limiters.
3. The Name Parser
Finally, I wrote a helper function to extract the full airport names from the route data. Instead of showing DEL -> BOM, the dashboard parses the JSON to display Indira Gandhi International airport -> Chhatrapati Shivaji International airport. It feels premium, like a real airport departure board.
Integrating Ultrahuman Ring Data with E-Ink
The Ultrahuman ring was on my radar for months. It’s a viable alternative to the Apple Watch and fitness trackers. The best part is that there is no screen to constantly nag you with buzzes and notifications.
However, there was a major hurdle you usually need a ring to get API access. Luckily, I had a friend who owned one. I reached out to the Ultrahuman team, shared my vision for the e-ink dashboard, and they were more than happy to send over a developer unit.

The 6,500-Line Firehose
I assumed I could just ping their API and get my current “Recovery Score.” I was wrong. The Ultrahuman API doesn’t just send a summary it sends a 6,500-line JSON log. It dumps every heartbeat, every movement index, and every temperature fluctuation for every minute of the day.
The “Needle in the Haystack” Worker
I had to write a parsing engine specifically to find three numbers. The “Recovery Score” was buried inside a generic metrics array, inside an object keyed by date.
Here is the logic I used to dig it out:
// Digging into the nested structure
const metricsRoot = json?.data?.metrics;
const dateKey = Object.keys(metricsRoot)[0];
// Finding the needle in the haystack
const sleepModule = allItems.find(m => m.type === 'sleep')?.object;
const recScoreObj = allItems.find(m => m.type === 'recovery_index')?.object;
return { recoveryScore: recScoreObj?.value || '--' };
Designing for E-Ink: Liquid Templating & CSS
All the data processing was just the infrastructure. The real challenge and the art is how you present the data. A dashboard isn’t useful if it’s ugly. If I’m going to have a screen on my desk 24/7, it needs to feel less like a spreadsheet and more like a cockpit instrument.
I didn’t start with code; I started with my Supernote, sketching layouts until I found a hierarchy that felt heavy and industrial.
The design evolved into three distinct visual languages:
The SSR
For tracking the aircraft at a distance. I wanted something that feels analog. I rejected modern map interfaces entirely. I wanted a scope, something that felt pulled from an ATC tower in 1985.
- Dotted Range Rings: Instead of solid lines that would overwhelm the display, subtle dotted rings at 5nm intervals provide spatial reference without visual noise.
- Vector Tails: The diamond markers have directional lines whose length indicates speed a subtle visual cue that conveys motion at scale.
- The “N” Marker: Purely decorative, but essential for the fantasy. That single letter in the top right sells the illusion of a proper radar scope.
The Proximity Monitor (The PFD View)
- The Typography: The callsign is massive. It anchors the screen.
- The Telemetry Strip: I created a bottom row with thick black borders for Altitude, Speed, and Heading. This grounds the design and gives it that instrument feel.
The most frustrating and the most rewarding part of the process. TRMNL liquid templating language. Liquid is deceptively simple. It’s just HTML with some logic tags. But getting it pixel-perfect on a low-resolution e-ink screen is a game of millimeters.
- “Is that border 2px or 3px?”
- “Why is the airline name wrapping to the next line?”
- “How do I invert the colors for the night mode?”
I spent more hours “tinkering” with CSS padding and font sizes than I did writing the actual JavaScript. I would tweak a margin by 5 pixels, wait 15 seconds for the preview to render, realize it looked bad, and do it again.
But that friction was worth it. Because when you finally nail it. When the data flows perfectly into that grid and the screen refreshes to show a crisp, balanced UI it feels less like a webpage and more like a piece of hardware.
THE PHILOSOPHY TAX (Why I Chose The Hard Way)
A professional developer would have solved this in 2 hours:
- Use a database API for airline names (don’t hardcode).
- Use a charting library for the radar (don’t do trigonometry).
- Display JSON directly on screen (don’t parse for specific fields).
It took me 40 days to build these 3 plugins from scratch.
The difference i cared about the details that don’t matter technically but matter emotionally
The airliner names
I could have displayed AIC243 and been done with that, but seeing ‘Air India’ instead makes the detail better. That emotional accuracy cost me 3 hrs of hardcoding the airline mapping.
The Label Collision
I could have let the text overlap. E-ink refreshes slowly; most people wouldn’t notice. But I would notice. I would knowthe screen was showing bad data. The payoff is a screen that looks professional, like an actual ATC display.
The Side Quests (The Aesthetic)
Somewhere in the middle of fighting with trigonometry, I got distracted.
I realized that the band Cigarettes After Sex uses exclusively black-and-white album art. It was aesthetically perfect for a 1-bit screen.
So, I stopped working on the radar for a week and built a plugin that just cycles through their album covers.
Did this help me track planes? No.
Did it help me track my sleep? No.
But it proved to me that this screen wasn’t just a tool; it was a canvas.
The question I kept asking myself: “Would I be proud to show this to another pilot?”
Every hour I spent on “unnecessary” details was an hour spent ensuring the answer stayed “yes.”
This is the opposite of Silicon Valley’s “move fast and break things.” This is: Move slow. Don’t break anything. Make it beautiful.
The verdict
This project wasn’t efficient. But that’s not the point. The point is, I built it.
I learnt a lot and had a lot of fun doing this. Celebrating a debug with friends. Showing around the design and getting feedback from my batch mates.
Now when i sit down and hear a plane flying overhead me. I don’t have to open Flightradar24 to get blinded by the notifications, the information is already there on my desk.
I don’t check my phone in the morning to see my recovery stats and get sucked into a doom-scroll.
I just glance at the black slate on my desk.
- Status: Recovery 92. Cleared for takeoff.
- Traffic: Air India 604 (Indira Gandhi Int’l → Chhatrapati Shivaji Int’l) passing West.
Situational awareness. No notifications. No dopamine. Just the data I need, and the silence I earned.