Minimal python script that polls a tar1090 ADS-B receiver JSON aircraft APIs, filters aircraft within given radius, asks an LLM (Gemini) if that plane is interesting for you using a custom prompt and if it is sends you a notification.
| Telegram Notification | NTFY Notification |
|---|---|
![]() |
![]() |
- Fetch all aircrafts from tar1090
- Filter by radius
- Skip notification if aircraft already seen
- Notify directly if aircraft has emergency squawk
- Skip notification if aircraft type matches the exclusion regex
- Skip notification if LLM doesn't think the aircraft is interesting
- Fetch planespotter photo and aircraft route via adsbdb
- Take a tar1090 map screenshot (if playwright and pillow are installed)
- Compose planespotter photo and screenshot into a single image
- Send the telegram/ntfy message
- Telegram for notifications. Create a bot via @BotFather and get your chat ID via @userinfobot. Optional if ntfy is configured.
- ntfy for notifications. Optional if Telegram is configured.
- tar1090 instance to get aircraft data. I use the one integrated with the excellent adsb.im.
- Google AI Studio for the interesting aircraft evaluation. If you want this step an API key is needed, but you can skip this step and get notified for every plane in range using the
--notify-alloption. - Planespotters, for getting the picture of the plane. No API key needed.
- ADSBExchange link is returned for each plane, if you want to keep track of it even if it goes out from your ADS-B receiver range.
- adsbdb for getting airline and flight route info from the callsign. No API key needed.
- Playwright for taking a tar1090 map screenshot of the aircraft position. Install with
pip install playwright && playwright install chromium. - Pillow for composing the planespotter photo and map screenshot into a single image. Install with
pip install pillow.
Copy the conf.json.example to conf.json and fill it out with your data. Then, run
python3 notify1090.pyIf you want to skip the LLM evaluation but still apply the type exclusion regex, run
python3 notify1090.py --skip-llmIf you want to skip both the LLM and the regex filter and get notified for every aircraft in radius, run
python3 notify1090.py --notify-allAnd if you want to wipe the seen-aircraft database
python3 notify1090.py --wipe-db(or you just delete the notify1090.db sqlite file)
All output goes to stdout and log.txt in the working directory, and each line has the timestamp.
poll #N: summary of each poll cycle (total / in radius / new)NOTIFY: aircraft flagged and notification sent, followed by the LLM reasonSKIP: aircraft evaluated but not interesting, followed by the LLM reasonEXCLUDE: aircraft type matchedexclude_type_regex, skipped before LLMEMERGENCY: squawk 7500/7600/7700 detected, notified immediately bypassing all filtersGEMINI ERROR: LLM call failed, retryingGEMINI RETRY FAILED: LLM timed out twice, aircraft notified anywayTELEGRAM ERROR: Telegram notification failed to sendNTFY ERROR: ntfy notification failed to sendPLANESPOTTERS ERROR: photo lookup failedADSBDB ERROR: route lookup failedSCREENSHOT ERROR: tar1090 screenshot failedCOMPOSE ERROR: image composition failed
Analyzing the LLM message inside the NOTIFY and SKIP logs could be useful for verifying if the LLM evaluation matches your expectation. You can get all these log lines with
grep "LLM REASON" log.txtBy executing the following file
python3 web.py --port 8080it's possible to start a small web server that reads the log.txt file and prints every aircraft decision entry (NOTIFY, SKIP, EXCLUDE, EMERGENCY) in a easier way.
| Log Viewer |
|---|
![]() |
| Field | Description |
|---|---|
tar1090_url |
Base URL of your tar1090 instance |
latitude / longitude |
Your location |
radius_km |
Notification radius |
prompt |
Natural language description of what you find interesting. Must end with instructions Reply with YES or NO followed by a colon and a one-line reason.. |
poll_interval_seconds |
How often to poll tar1090 |
seen_ttl_hours |
Hours after a previously seen aircraft is re-evaluated. |
exclude_type_regex |
Optional regex to check against the aircraft type field, to filter boring models before asking to the LLM |
gemini_api_key |
Google AI Studio API key (requires billing enabled) |
telegram_bot_token |
Telegram bot token from @BotFather. Optional if ntfy_topic is set. |
telegram_chat_id |
Your Telegram chat ID. Required if telegram_bot_token is set. |
ntfy_topic |
ntfy topic name. Optional if Telegram is configured. |
screenshot |
Enable tar1090 position screenshot. |
screenshot_params |
Full tar1090 query string for the screenshot URL. See tar1090 query docs. |
screenshot_viewport |
Viewport width in px |


