Advertisement
TraumaticAI

ESP8266 - Lon-Lat Aircraft Tracker

Apr 16th, 2025 (edited)
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 22.47 KB | Source Code | 0 0
  1. #include <ESP8266WiFi.h>
  2. #include <ESP8266WebServer.h>
  3. #include <ESP8266HTTPClient.h>
  4. #include <ArduinoJson.h>
  5.  
  6. // WiFi credentials
  7. const char* ssid = "YOUR_SSID_HERE";
  8. const char* password = "YOUR_PASSWORD_HERE";
  9.  
  10. // Web server on port 80
  11. ESP8266WebServer server(80);
  12.  
  13. // Coordinates (Lon,Lat)
  14. const float LAT = ######; // Replace "######" with your LAT
  15. const float LON = ######; // Replace "######" with your LON
  16. int RADIUS = 2;  // Modifiable (in nautical miles)
  17.  
  18. // Update intervals
  19. const unsigned long UPDATE_INTERVAL = 8000;  // 8 seconds for API update
  20. unsigned long lastUpdate = 0;
  21. unsigned long lastDataTime = 0;
  22. float seenTime = 0;
  23. float initialSeenTime = 0;
  24.  
  25. // Aircraft data storage
  26. String currentCallsign = "";
  27. String currentType = "";
  28. String currentHex = "N/A";
  29. float currentAlt = 0;
  30. float currentSpeed = 0;
  31. float currentDirection = 0;
  32. float currentDistance = 0;
  33. bool hasAircraftData = false;
  34.  
  35. // Aircraft counter
  36. unsigned long lastResetTime = 0;
  37. int aircraftCount = 0;
  38. String seenCallsigns[50]; //Max Count is 50 due to RAM limitations on the ESP
  39. int seenCallsignsIndex = 0;
  40. const unsigned long DAY_LENGTH = 86400000;  // 24 hours
  41.  
  42. // LED configuration
  43. const int LED_PIN = 2;  // Built-in blue LED on most ESP8266 boards (GPIO2, D4)
  44. unsigned long lastFadeUpdate = 0;
  45. const unsigned long FADE_INTERVAL = 20;  // Update fade every 20ms for smooth effect
  46. int fadeValue = 0;  // Current PWM value (0-1023)
  47. bool fadeDirection = true;  // true = increasing, false = decreasing
  48.  
  49. // HTML for the web interface (Home Assistant style)
  50. const char* htmlPage = R"rawliteral(
  51. <!DOCTYPE html>
  52. <html lang="en">
  53. <head>
  54.  <meta charset="UTF-8">
  55.  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  56.  <title>Aircraft Tracker</title>
  57.  <style>
  58.    body {
  59.      background: #1c2526;
  60.      color: #e0e0e0;
  61.      font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  62.      margin: 0;
  63.      padding: 0.5rem;
  64.      display: flex;
  65.      justify-content: center;
  66.      align-items: flex-start;
  67.      min-height: 100vh;
  68.      box-sizing: border-box;
  69.    }
  70.    .card {
  71.      background-color: #212121;
  72.      border-radius: 0.5rem;
  73.      padding: 1rem;
  74.      width: 100%;
  75.      max-width: 600px;
  76.      min-width: 280px;
  77.      box-sizing: border-box;
  78.      box-shadow: 0 0.0625rem 0.1875rem rgba(0,0,0,0.3);
  79.      position: relative; /* For absolute positioning of api-status */
  80.    }
  81.    .card-header {
  82.      font-size: 1rem;
  83.      color: #03a9f4;
  84.      margin: 0 0 0.75rem 0;
  85.      font-weight: 500;
  86.      display: flex;
  87.      align-items: center;
  88.      gap: 0.5rem;
  89.    }
  90.    .data-grid {
  91.      display: grid;
  92.      grid-template-columns: auto 1fr;
  93.      gap: 0.5rem;
  94.      font-size: 0.875rem;
  95.    }
  96.    .data-label {
  97.      color: #b0b0b0;
  98.      font-weight: bold;
  99.      padding-right: 0.5rem;
  100.    }
  101.    .data-value {
  102.      color: #e0e0e0;
  103.    }
  104.    .settings {
  105.      margin-top: 0.75rem;
  106.      padding-top: 0.75rem;
  107.      border-top: 0.0625rem solid #424242;
  108.    }
  109.    .settings-row {
  110.      display: flex;
  111.      gap: 0.5rem;
  112.      flex-wrap: wrap;
  113.      align-items: center;
  114.    }
  115.    label {
  116.      color: #b0b0b0;
  117.      font-size: 0.875rem;
  118.      font-weight: bold;
  119.    }
  120.    input[type="number"] {
  121.      background-color: #424242;
  122.      color: #e0e0e0;
  123.      border: 0.0625rem solid #616161;
  124.      border-radius: 0.25rem;
  125.      padding: 0.25rem;
  126.      font-size: 0.875rem;
  127.      width: 3.75rem;
  128.      box-sizing: border-box;
  129.    }
  130.    button {
  131.      background-color: #03a9f4;
  132.      color: #fff;
  133.      border: none;
  134.      border-radius: 0.25rem;
  135.      padding: 0.375rem 0.75rem;
  136.      cursor: pointer;
  137.      font-size: 0.875rem;
  138.      transition: background-color 0.2s;
  139.    }
  140.    button:hover {
  141.      background-color: #0288d1;
  142.    }
  143.    .track-button {
  144.      background-color: #4caf50;
  145.      width: 6.25rem;
  146.    }
  147.    .track-button:hover {
  148.      background-color: #43a047;
  149.    }
  150.    .track-button:disabled {
  151.      background-color: #616161;
  152.      cursor: not-allowed;
  153.    }
  154.    #status {
  155.      margin-top: 0.5rem;
  156.      font-size: 0.75rem;
  157.      color: #757575;
  158.      text-align: center;
  159.    }
  160.    .loading-spinner {
  161.      display: inline-block;
  162.      width: 16px;
  163.      height: 16px;
  164.      border: 2px solid #03a9f4;
  165.      border-radius: 50%;
  166.      border-top-color: transparent;
  167.      animation: spin 1s linear infinite;
  168.      vertical-align: middle;
  169.    }
  170.    @keyframes spin {
  171.      to {
  172.        transform: rotate(360deg);
  173.      }
  174.    }
  175.    .success-icon {
  176.      color: #4caf50;
  177.      vertical-align: middle;
  178.    }
  179.    .api-status {
  180.      position: absolute;
  181.      bottom: 1rem;
  182.      right: 1rem;
  183.      display: flex;
  184.      align-items: center;
  185.      gap: 0.25rem;
  186.      font-size: 0.875rem;
  187.    }
  188.    .api-label {
  189.      color: rgba(176, 176, 176, 0.5); /* Semi-transparent #b0b0b0 */
  190.      font-weight: bold;
  191.    }
  192.    @media (max-width: 400px) {
  193.      .card {
  194.        padding: 0.75rem;
  195.        min-width: 200px;
  196.      }
  197.      .card-header {
  198.        font-size: 0.875rem;
  199.      }
  200.      .data-grid {
  201.        font-size: 0.75rem;
  202.        gap: 0.375rem;
  203.      }
  204.      input[type="number"], button {
  205.        font-size: 0.75rem;
  206.        padding: 0.2rem;
  207.      }
  208.      .track-button {
  209.        width: 4.5rem;
  210.      }
  211.      input[type="number"] {
  212.        width: 3rem;
  213.      }
  214.      .api-status {
  215.        bottom: 0.75rem;
  216.        right: 0.75rem;
  217.        font-size: 0.75rem;
  218.      }
  219.      .loading-spinner {
  220.        width: 12px;
  221.        height: 12px;
  222.        border-width: 2px;
  223.      }
  224.    }
  225.    @media (min-width: 600px) {
  226.      .card {
  227.        padding: 1.25rem;
  228.      }
  229.      .card-header {
  230.        font-size: 1.125rem;
  231.      }
  232.      .data-grid {
  233.        font-size: 1rem;
  234.      }
  235.      input[type="number"], button {
  236.        font-size: 1rem;
  237.        padding: 0.375rem;
  238.      }
  239.      .api-status {
  240.        bottom: 1.25rem;
  241.        right: 1.25rem;
  242.      }
  243.    }
  244.  </style>
  245. </head>
  246. <body>
  247.  <div class="card">
  248.    <div class="card-header">
  249.      <span>Aircraft Tracker</span>
  250.      <span id="status-icon"></span>
  251.    </div>
  252.    <div class="data-grid" id="aircraft-data">
  253.      <span class="data-label">Callsign:</span>
  254.      <span class="data-value" id="callsign">N/A</span>
  255.      <span class="data-label">Type:</span>
  256.      <span class="data-value" id="type">N/A</span>
  257.      <span class="data-label">Altitude:</span>
  258.      <span class="data-value" id="altitude">0 ft</span>
  259.      <span class="data-label">Speed:</span>
  260.      <span class="data-value" id="speed">0 mph</span>
  261.      <span class="data-label">Direction:</span>
  262.      <span class="data-value" id="direction">0°</span>
  263.      <span class="data-label">Distance:</span>
  264.      <span class="data-value" id="distance">0 NM</span>
  265.      <span class="data-label">Last Seen:</span>
  266.      <span class="data-value" id="seen">0 s ago</span>
  267.      <span class="data-label">Today:</span>
  268.      <span class="data-value" id="count">0</span>
  269.    </div>
  270.    <div class="settings">
  271.      <form id="settings-form">
  272.        <div class="settings-row">
  273.          <label for="radius">Radius (nm):</label>
  274.          <input type="number" id="radius" name="radius" min="1" max="50" value="%RADIUS%">
  275.          <button type="submit">Apply</button>
  276.          <button type="button" id="track-button" class="track-button" disabled>Track</button>
  277.        </div>
  278.      </form>
  279.      <div id="status"></div>
  280.    </div>
  281.    <div class="api-status">
  282.      <span class="api-label">API:</span>
  283.      <span id="loading-status"></span>
  284.    </div>
  285.  </div>
  286.  <script>
  287.    const statusIcon = document.getElementById('status-icon');
  288.    const loadingStatus = document.getElementById('loading-status');
  289.  
  290.    function setLoadingState(isLoading) {
  291.      if (isLoading) {
  292.        loadingStatus.innerHTML = '<span class="loading-spinner"></span>';
  293.      } else {
  294.        loadingStatus.innerHTML = '<span class="success-icon">✅</span>';
  295.      }
  296.    }
  297.  
  298.    function fetchData() {
  299.      setLoadingState(true);
  300.  
  301.      fetch('/data')
  302.        .then(response => response.json())
  303.        .then(data => {
  304.          setLoadingState(false);
  305.          statusIcon.textContent = data.hasData ? '🛩️: Aircraft Detected' : '🚫 No Aircraft Detected';
  306.          document.getElementById('callsign').textContent = data.callsign || 'N/A';
  307.          document.getElementById('type').textContent = data.type || 'N/A';
  308.          document.getElementById('altitude').textContent = data.altitude + ' ft';
  309.          document.getElementById('speed').textContent = Math.round(data.speed * 1.15078) + ' mph';
  310.          document.getElementById('direction').textContent = data.direction + '°';
  311.          document.getElementById('distance').textContent = data.distance.toFixed(1) + ' NM';
  312.          document.getElementById('seen').textContent = data.seen.toFixed(0) + ' s ago';
  313.          document.getElementById('count').textContent = data.count;
  314.          
  315.          const trackButton = document.getElementById('track-button');
  316.          if (data.hasData && data.hex && data.hex !== 'N/A') {
  317.            trackButton.disabled = false;
  318.            trackButton.onclick = () => {
  319.              window.open(`https://globe.adsbexchange.com/?icao=${data.hex}`, '_blank');
  320.            };
  321.          } else {
  322.            trackButton.disabled = true;
  323.            trackButton.onclick = null;
  324.          }
  325.        })
  326.        .catch(error => {
  327.          setLoadingState(false);
  328.          console.error('Error fetching data:', error);
  329.        });
  330.    }
  331.  
  332.    setInterval(fetchData, 2000);
  333.    fetchData();
  334.  
  335.    document.getElementById('settings-form').addEventListener('submit', function(e) {
  336.      e.preventDefault();
  337.      const radius = document.getElementById('radius').value;
  338.      fetch('/settings', {
  339.        method: 'POST',
  340.        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  341.        body: `radius=${radius}`
  342.      })
  343.      .then(response => response.text())
  344.      .then(text => {
  345.        document.getElementById('status').textContent = 'Settings updated';
  346.        setTimeout(() => document.getElementById('status').textContent = '', 3000);
  347.      })
  348.      .catch(error => {
  349.        document.getElementById('status').textContent = 'Error updating settings';
  350.        console.error('Error:', error);
  351.      });
  352.    });
  353.  </script>
  354. </body>
  355. </html>
  356. )rawliteral";
  357. // Function to infer aircraft type
  358. String getAircraftType(String model, String callsign) {
  359.   model.toUpperCase();
  360.   callsign.toUpperCase();
  361.   String callsignPrefix = callsign.length() >= 3 ? callsign.substring(0, 3) : callsign;
  362.  
  363.   if (model.startsWith("B737") || model.startsWith("B747") || model.startsWith("B757") ||
  364.       model.startsWith("B767") || model.startsWith("B777") || model.startsWith("B787") ||
  365.       model.startsWith("A220") || model.startsWith("A320") || model.startsWith("A330") ||
  366.       model.startsWith("A350") || model.startsWith("A380") || model.startsWith("E170") ||
  367.       model.startsWith("E190") || model.startsWith("CRJ") || model.startsWith("Q400") ||
  368.       model.startsWith("ATR") || model.startsWith("B38M")) {
  369.     return "Passenger";
  370.   }
  371.   if (callsignPrefix == "RYR" || callsignPrefix == "BAW" || callsignPrefix == "SHT" ||
  372.       callsignPrefix == "EZY" || callsignPrefix == "EJU" || callsignPrefix == "EXS" ||
  373.       callsignPrefix == "TOM" || callsignPrefix == "VIR" || callsignPrefix == "WUK" ||
  374.       callsignPrefix == "LOG" || callsignPrefix == "AUR" || callsignPrefix == "UAL" ||
  375.       callsignPrefix == "AAL" || callsignPrefix == "DAL" || callsignPrefix == "SWA" ||
  376.       callsignPrefix == "QFA" || callsignPrefix == "ANZ" || callsignPrefix == "SIA" ||
  377.       callsignPrefix == "JAL" || callsignPrefix == "CPA" || callsignPrefix == "UAE" ||
  378.       callsignPrefix == "QTR" || callsignPrefix == "ETD" || callsignPrefix == "AFR" ||
  379.       callsignPrefix == "DLH" || callsignPrefix == "EFW" || callsignPrefix == "KLM" ||
  380.       callsignPrefix == "IBE" || callsignPrefix == "AZA" || callsignPrefix == "SWR" ||
  381.       callsignPrefix == "THY" || callsignPrefix == "ETR" || callsignPrefix == "SAA" ||
  382.       callsignPrefix == "KQA" || callsignPrefix == "TAM" || callsignPrefix == "ARG" ||
  383.       callsignPrefix == "AVA" || callsignPrefix == "BCI" || callsignPrefix == "TAP") {
  384.     return "Passenger";
  385.   }
  386.   if (model.startsWith("C130") || model.startsWith("C17") || model.startsWith("F16") ||
  387.       model.startsWith("F18") || model.startsWith("F35") || model.startsWith("KC135") ||
  388.       model.startsWith("H60") || model.startsWith("CH47") || model.startsWith("MQ9") ||
  389.       model.startsWith("P8") || model.startsWith("A400") || model.startsWith("EUFI") ||
  390.       callsign.startsWith("RRR") || callsign.startsWith("NAVY") || callsign.startsWith("ASCOT") ||
  391.       callsign.startsWith("VANDAL") || callsign.startsWith("TARTAN") ||
  392.       callsignPrefix == "RFR" || callsignPrefix == "NOH" || callsignPrefix == "KIN" ||
  393.       callsignPrefix == "LCS" || callsignPrefix == "WAD" || callsignPrefix == "KRF" ||
  394.       callsignPrefix == "KRH" || callsignPrefix == "NVY" || callsignPrefix == "AIO" ||
  395.       callsignPrefix == "PAT" || callsignPrefix == "CNV" || callsignPrefix == "CGX" ||
  396.       callsignPrefix == "RFF" || callsignPrefix == "CHD" || callsignPrefix == "TTF" ||
  397.       callsignPrefix == "BRK") {
  398.     return "Military";
  399.   }
  400.   if (model.startsWith("EC35") || model.startsWith("H135") || model.startsWith("EC45") ||
  401.       model.startsWith("AS355") || callsign.startsWith("NPAS") || callsign.indexOf("POL") >= 0) {
  402.     return "Police";
  403.   }
  404.   if (model.startsWith("BE20") || model.startsWith("PC12") || model.startsWith("EC45") ||
  405.       model.startsWith("H145") || callsign.indexOf("MED") >= 0 || callsign.indexOf("AMB") >= 0 ||
  406.       callsign.startsWith("HELI") || callsign.indexOf("AIRE") >= 0 ||
  407.       callsignPrefix == "HLE" || callsignPrefix == "FLYDOC" || callsign.startsWith("MEDEVAC") ||
  408.       callsign.startsWith("EVAC") || callsign.startsWith("HOSP") || callsignPrefix == "RED") {
  409.     return "Ambulance";
  410.   }
  411.   if (model.startsWith("S92") || model.startsWith("AW189") || callsign.startsWith("G-") ||
  412.       callsign.indexOf("RESCUE") >= 0 || callsign.indexOf("COAST") >= 0 ||
  413.       callsign.startsWith("COASTGUARD") || callsignPrefix == "RES") {
  414.     return "Coast Guard";
  415.   }
  416.   if (model.startsWith("B74F") || model.startsWith("B75F") || model.startsWith("AN12") ||
  417.       model.startsWith("C130") || callsign.indexOf("FEDEX") >= 0 || callsign.indexOf("UPS") >= 0 ||
  418.       callsign.indexOf("DHL") >= 0 || callsignPrefix == "GTI") {
  419.     return "Cargo";
  420.   }
  421.   if (model.startsWith("GLF") || model.startsWith("CL60") || model.startsWith("C25") ||
  422.       model.startsWith("LJ") || model.startsWith("F2TH") || model.startsWith("FA7X")) {
  423.     return "Private Jet";
  424.   }
  425.   if (model.startsWith("C172") || model.startsWith("C152") || model.startsWith("P28A") ||
  426.       model.startsWith("SR22") || model.startsWith("DA40") || model.startsWith("PA46")) {
  427.     return "General";
  428.   }
  429.   return "Unknown";
  430. }
  431.  
  432. void setup() {
  433.   // Initialize LED pin
  434.   pinMode(LED_PIN, OUTPUT);
  435.   analogWriteRange(1023);  // Set PWM range to 1023
  436.   digitalWrite(LED_PIN, HIGH);  // Ensure LED is off (active-low)
  437.  
  438.   Serial.begin(115200);
  439.   Serial.println("Setup started...");
  440.  
  441.   // Connect to WiFi
  442.   Serial.println("Connecting to WiFi...");
  443.   WiFi.begin(ssid, password);
  444.   int attempts = 0;
  445.   while (WiFi.status() != WL_CONNECTED && attempts < 20) {
  446.     delay(500);
  447.     Serial.print(".");
  448.     attempts++;
  449.   }
  450.   if (WiFi.status() == WL_CONNECTED) {
  451.     Serial.println("\nWiFi connected");
  452.     Serial.print("IP Address: ");
  453.     Serial.println(WiFi.localIP());
  454.   } else {
  455.     Serial.println("\nWiFi failed, restarting...");
  456.     delay(1000);
  457.     ESP.restart();
  458.   }
  459.  
  460.   // Setup web server routes
  461.   server.on("/", HTTP_GET, []() {
  462.     String page = htmlPage;
  463.     page.replace("%RADIUS%", String(RADIUS));
  464.     page.replace("%STATUS_ICON%", hasAircraftData ? "🛫" : "—");
  465.     server.send(200, "text/html", page);
  466.   });
  467.  
  468.   server.on("/data", HTTP_GET, []() {
  469.     DynamicJsonDocument doc(256);
  470.     doc["hasData"] = hasAircraftData;
  471.     doc["callsign"] = currentCallsign;
  472.     doc["type"] = currentType;
  473.     doc["altitude"] = currentAlt;
  474.     doc["speed"] = currentSpeed;
  475.     doc["direction"] = currentDirection;
  476.     doc["distance"] = currentDistance;
  477.     doc["seen"] = seenTime;
  478.     doc["count"] = aircraftCount;
  479.     doc["hex"] = currentHex;
  480.     String json;
  481.     serializeJson(doc, json);
  482.     server.send(200, "application/json", json);
  483.   });
  484.  
  485.   server.on("/settings", HTTP_POST, []() {
  486.     String response = "Settings updated";
  487.    
  488.     if (server.hasArg("radius")) {
  489.       int newRadius = server.arg("radius").toInt();
  490.       if (newRadius >= 1 && newRadius <= 50) {
  491.         RADIUS = newRadius;
  492.         Serial.println("Radius updated to: " + String(RADIUS));
  493.       } else {
  494.         response = "Invalid radius value";
  495.         Serial.println("Invalid radius: " + server.arg("radius"));
  496.       }
  497.     }
  498.    
  499.     server.send(200, "text/plain", response);
  500.   });
  501.  
  502.   server.begin();
  503.   Serial.println("Web server started");
  504.  
  505.   // Initialize reset time
  506.   lastResetTime = millis();
  507. }
  508.  
  509. void updateMainDisplay() {
  510.   if (WiFi.status() != WL_CONNECTED) {
  511.     hasAircraftData = false;
  512.     return;
  513.   }
  514.  
  515.   WiFiClientSecure client;
  516.   client.setInsecure();
  517.   HTTPClient http;
  518.   Serial.println("Fetching API data...");
  519.  
  520.   char url[128];
  521.   snprintf(url, sizeof(url), "https://api.adsb.lol/v2/lat/%.4f/lon/%.4f/dist/%d", LAT, LON, RADIUS);
  522.   Serial.print("URL: ");
  523.   Serial.println(url);
  524.  
  525.   if (http.begin(client, url)) {
  526.     int httpCode = http.GET();
  527.     Serial.print("HTTP Code: ");
  528.     Serial.println(httpCode);
  529.  
  530.     if (httpCode == HTTP_CODE_OK) {
  531.       WiFiClient *stream = http.getStreamPtr();
  532.       StaticJsonDocument<200> filter;
  533.       filter["ac"][0]["flight"] = true;
  534.       filter["ac"][0]["alt_baro"] = true;
  535.       filter["ac"][0]["gs"] = true;
  536.       filter["ac"][0]["track"] = true;
  537.       filter["ac"][0]["lat"] = true;
  538.       filter["ac"][0]["lon"] = true;
  539.       filter["ac"][0]["seen"] = true;
  540.       filter["ac"][0]["t"] = true;
  541.       filter["ac"][0]["hex"] = true;
  542.  
  543.       DynamicJsonDocument doc(3072);
  544.       DeserializationError error = deserializeJson(doc, *stream, DeserializationOption::Filter(filter));
  545.       if (error) {
  546.         Serial.println(F("JSON parse error: ") + String(error.c_str()));
  547.         hasAircraftData = false;
  548.       } else {
  549.         JsonArray aircraftList = doc["ac"];
  550.         if (aircraftList.size() > 0) {
  551.           float min_distance = 9999.0;
  552.           JsonObject closest_aircraft;
  553.           for (JsonObject aircraft : aircraftList) {
  554.             float aircraft_lat = aircraft["lat"] | 0.0;
  555.             float aircraft_lon = aircraft["lon"] | 0.0;
  556.             float delta_lat = aircraft_lat - LAT;
  557.             float delta_lon = (aircraft_lon - LON) * cos(LAT * PI / 180.0);
  558.             float distance = sqrt(delta_lat * delta_lat + delta_lon * delta_lon) * 60.0;
  559.             if (distance < min_distance) {
  560.               min_distance = distance;
  561.               closest_aircraft = aircraft;
  562.             }
  563.           }
  564.  
  565.           String newCallsign = closest_aircraft["flight"] | "N/A";
  566.           newCallsign.trim();
  567.           bool isUnique = true;
  568.           for (int i = 0; i < seenCallsignsIndex; i++) {
  569.             if (seenCallsigns[i] == newCallsign) {
  570.               isUnique = false;
  571.               break;
  572.             }
  573.           }
  574.           if (isUnique && newCallsign != "N/A" && seenCallsignsIndex < 50) {
  575.             seenCallsigns[seenCallsignsIndex++] = newCallsign;
  576.             aircraftCount++;
  577.             Serial.print("New aircraft detected. Total today: ");
  578.             Serial.println(aircraftCount);
  579.           }
  580.  
  581.           currentCallsign = newCallsign;
  582.           String aircraftModel = closest_aircraft["t"] | "Unknown";
  583.           currentType = getAircraftType(aircraftModel, currentCallsign);
  584.           currentAlt = closest_aircraft["alt_baro"] | 0;
  585.           currentSpeed = closest_aircraft["gs"] | 0.0;
  586.           currentDirection = closest_aircraft["track"] | 0.0;
  587.           currentDistance = min_distance;
  588.           float apiSeenTime = closest_aircraft["seen"] | 0.0;
  589.           currentHex = closest_aircraft["hex"] | "N/A";
  590.  
  591.           lastDataTime = millis();
  592.           initialSeenTime = apiSeenTime;
  593.           seenTime = apiSeenTime;
  594.           hasAircraftData = true;
  595.           Serial.println("Aircraft data updated");
  596.           Serial.print("Free Heap: ");
  597.           Serial.println(ESP.getFreeHeap());
  598.         } else {
  599.           Serial.println("No aircraft found");
  600.           hasAircraftData = false;
  601.           currentHex = "N/A";
  602.         }
  603.       }
  604.     } else {
  605.       hasAircraftData = false;
  606.       currentHex = "N/A";
  607.     }
  608.     http.end();
  609.   } else {
  610.     hasAircraftData = false;
  611.     currentHex = "N/A";
  612.   }
  613. }
  614.  
  615. void updateLED() {
  616.   if (hasAircraftData) {
  617.     // Fade in and out
  618.     unsigned long currentTime = millis();
  619.     if (currentTime - lastFadeUpdate >= FADE_INTERVAL) {
  620.       if (fadeDirection) {
  621.         fadeValue += 10;  // Increase brightness
  622.         if (fadeValue >= 1023) {
  623.           fadeValue = 1023;
  624.           fadeDirection = false;
  625.         }
  626.       } else {
  627.         fadeValue -= 10;  // Decrease brightness
  628.         if (fadeValue <= 0) {
  629.           fadeValue = 0;
  630.           fadeDirection = true;
  631.         }
  632.       }
  633.       // Apply PWM (invert for active-low LED)
  634.       analogWrite(LED_PIN, 1023 - fadeValue);
  635.       lastFadeUpdate = currentTime;
  636.     }
  637.   } else {
  638.     // Turn LED off
  639.     fadeValue = 0;
  640.     digitalWrite(LED_PIN, HIGH);  // Active-low, so HIGH is off
  641.   }
  642. }
  643.  
  644. void loop() {
  645.   server.handleClient();
  646.  
  647.   unsigned long currentTime = millis();
  648.  
  649.   // Reset counter if 24 hours have passed
  650.   if (currentTime - lastResetTime >= DAY_LENGTH) {
  651.     aircraftCount = 0;
  652.     seenCallsignsIndex = 0;
  653.     for (int i = 0; i < 50; i++) {
  654.       seenCallsigns[i] = "";
  655.     }
  656.     lastResetTime = currentTime;
  657.     Serial.println("Daily aircraft counter reset");
  658.   }
  659.  
  660.   // Update seen time every second
  661.   if (currentTime - lastUpdate >= 1000) {
  662.     if (lastDataTime > 0) {
  663.       seenTime = initialSeenTime + (currentTime - lastDataTime) / 1000.0;
  664.     } else {
  665.       seenTime = 0;
  666.     }
  667.   }
  668.  
  669.   // Update main data every 8 seconds
  670.   if (currentTime - lastUpdate >= UPDATE_INTERVAL) {
  671.     updateMainDisplay();
  672.     lastUpdate = currentTime;
  673.     Serial.println("Main data updated");
  674.   }
  675.  
  676.   // Update LED state
  677.   updateLED();
  678. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement