Display e dispositivi
Display da scrivania, cornici e-paper, pannelli LED, ESP32 e Raspberry Pi: come mostrare gli aerei vicini senza sprecare richieste, da un pezzo a qualche migliaio.
Due schemi, a seconda di quanti pezzi
Ogni dispositivo chiede da sé
Per pochi pezzi. È il più semplice: il dispositivo chiede gli aerei nel suo raggio. Ma la chiave sta nel firmware, dove si può estrarre, e ogni dispositivo in più conta sulla stessa chiave.
Un tuo server per tutti
Da qualche decina di pezzi in su. Il tuo server scarica una volta (la zona dei tuoi clienti, o il mondo intero con Pro) e ogni dispositivo chiede al server solo gli aerei vicini a sé. Il consumo non cresce con i pezzi venduti e la chiave non esce mai dal server.
Quanto consuma un dispositivo
Il dato si aggiorna ogni 2 secondi; per un display di solito bastano 15–30 secondi. Richieste al mese per un dispositivo che chiede da sé:
| Ogni | Al mese, per dispositivo | Dispositivi nel piano Free |
|---|---|---|
| 5 s | 518.400 | nessuno |
| 15 s | 172.800 | 1 |
| 30 s | 86.400 | 3 |
| 60 s | 43.200 | 6 |
Con lo schema del server il conto è uno solo: una richiesta ogni 30 secondi fa 86.400 richieste al mese, qualunque sia il numero di dispositivi.
Quanto pesa una risposta
Quanti aerei e quanti byte restituisce una richiesta, in alcuni posti:
| Zona | Raggio | Aerei | JSON | Con gzip |
|---|---|---|---|---|
| Milano Malpensa | 15 km | 5 | 4 KB | 1 KB |
| Londra Heathrow | 50 km | 37 | 33 KB | 7 KB |
| New York JFK | 50 km | 105 | 63 KB | 10 KB |
| Francoforte | 500 km | 662 | 670 KB | 111 KB |
| Mondo intero | — | ~11.000 | 7,2 MB | 1,1 MB |
Misura del 26 ottobre 2026, 21:24 ora italiana. Di sera in Europa: di giorno gli aerei sono di più.
Il codice
curl --compressed -H "X-Api-Key: LA_TUA_CHIAVE" \
https://api.flyitalyadsb.com/v2/lat/51.47/lon/-0.45/dist/15 // ESP32 (Arduino core 3.x) + ArduinoJson 7
// Gli aerei entro 15 km dal dispositivo, ogni 30 secondi.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "...";
const char* WIFI_PASS = "...";
const char* API_KEY = "LA_TUA_CHIAVE"; // meglio: chiedi al tuo server, che tiene la chiave
const char* URL = "https://api.flyitalyadsb.com/v2/lat/45.63/lon/8.72/dist/15";
void setup() {
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void loop() {
WiFiClientSecure tls;
tls.setInsecure(); // solo per la prova: in un prodotto fissa il certificato radice
HTTPClient http;
http.useHTTP10(true); // niente chunked: ArduinoJson legge lo stream
http.begin(tls, URL);
http.addHeader("X-Api-Key", API_KEY);
http.setUserAgent("my-desk-display/1.0");
int status = http.GET();
if (status == 200) {
JsonDocument filter; // tieni solo i campi che il display mostra
filter["ac"][0]["flight"] = true;
filter["ac"][0]["t"] = true;
filter["ac"][0]["alt_baro"] = true;
JsonDocument doc;
if (!deserializeJson(doc, http.getStream(), DeserializationOption::Filter(filter))) {
for (JsonObject a : doc["ac"].as<JsonArray>()) {
Serial.print(a["flight"] | "?");
Serial.print(" ");
Serial.print(a["t"] | "?");
Serial.print(" ");
if (a["alt_baro"].is<int>()) Serial.println(a["alt_baro"].as<int>());
else Serial.println("ground");
}
}
} else {
Serial.printf("HTTP %d\n", status); // 429: oltre il limite, riprova più tardi
}
http.end();
delay(30000); // ogni 30 s: 86.400 richieste al mese
} # server.py: una sola richiesta per tutti i tuoi dispositivi (Python 3.9+, nessuna dipendenza)
import gzip, json, math, os, threading, time, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse
KEY = os.environ["FIA_API_KEY"]
# Pro e Business: il mondo intero. Con Free usa la tua zona (fino a 500 km).
SOURCE = "https://api.flyitalyadsb.com/v2/lat/0/lon/0/dist/20000"
EVERY_S = 30
snapshot = {"now": 0, "ac": []}
def refresh():
while True:
try:
req = urllib.request.Request(SOURCE, headers={
"X-Api-Key": KEY, "Accept-Encoding": "gzip", "User-Agent": "my-product/1.0"})
with urllib.request.urlopen(req, timeout=30) as r:
body = r.read()
if r.headers.get("Content-Encoding") == "gzip":
body = gzip.decompress(body)
snapshot.update(json.loads(body))
except Exception as e: # continua a servire l’ultima copia
print("refresh failed:", e)
time.sleep(EVERY_S)
def km(la1, lo1, la2, lo2):
p = math.pi / 180
h = (math.sin((la2 - la1) * p / 2) ** 2
+ math.cos(la1 * p) * math.cos(la2 * p) * math.sin((lo2 - lo1) * p / 2) ** 2)
return 12742 * math.asin(math.sqrt(h))
class Near(BaseHTTPRequestHandler):
def do_GET(self): # /near?lat=51.47&lon=-0.45&km=15
q = {k: float(v[0]) for k, v in parse_qs(urlparse(self.path).query).items()}
ac = [a for a in snapshot["ac"]
if "lat" in a and km(q["lat"], q["lon"], a["lat"], a["lon"]) <= q.get("km", 15)]
body = json.dumps({"now": snapshot["now"], "ac": ac}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
threading.Thread(target=refresh, daemon=True).start()
ThreadingHTTPServer(("0.0.0.0", 8080), Near).serve_forever() Al posto di LA_TUA_CHIAVE la tua chiave: è gratuita, si chiede qui. Specifica completa in openapi.json.
Attenzione a
- Una chiave in un firmware si può estrarre. Se vendi molti pezzi, tienila sul tuo server.
- Raggio piccolo su un ESP32. Una risposta di 15 km pesa pochi KB; 50 km sopra New York già 63 KB. Filtra i campi come nell’esempio, o fai filtrare al tuo server.
- Un User-Agent tuo in ogni richiesta, e l’header
X-Api-Key. - Su un 429 aspetta i secondi di
Retry-Afterinvece di riprovare subito: le richieste respinte non contano, ma ripeterle non serve. - L’attribuzione sul dispositivo o nel manuale e sulla pagina del prodotto: la riga da usare.
- Il vantaggio feeder (limiti Pro gratis) è per uso non commerciale: non copre i pezzi che vendi.