Guest User

Untitled

a guest
Jun 7th, 2026
343
0
216 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * Sofia ADS-B Radar for Bruce JS
  3.  * Device: M5StickC Plus2
  4.  *
  5.  * Area: Sofia Airport (LBSF), 65 km display radius
  6.  *
  7.  * Controls:
  8.  *   Previous / Up   -> previous aircraft
  9.  *   Next / Down     -> next aircraft
  10.  *   Select          -> refresh aircraft and selected route
  11.  *   Escape / Back   -> exit
  12.  */
  13.  
  14. var display = require("display");
  15. var keyboard = require("keyboard");
  16. var wifi = require("wifi");
  17.  
  18. var SOFIA_LAT = 42.6950;
  19. var SOFIA_LON = 23.4083;
  20. var RANGE_KM = 65;
  21. var REFRESH_MS = 15000;
  22. var FRAME_MS = 300;
  23. var MAX_PLANES = 40;
  24.  
  25. var API_URL = "http://api.adsb.lol/v2/point/42.6950/23.4083/35";
  26. var ROUTE_URL = "http://api.adsb.lol/api/0/routeset";
  27. var ADSBDB_ROUTE_URL = "http://api.adsbdb.com/v0/callsign/";
  28.  
  29. var BLACK = display.color(0, 0, 0);
  30. var DARK_GREEN = display.color(0, 55, 12);
  31. var GRID_GREEN = display.color(0, 105, 28);
  32. var GREEN = display.color(0, 255, 70);
  33. var DIM_GREEN = display.color(0, 150, 45);
  34. var WHITE = display.color(240, 255, 240);
  35. var YELLOW = display.color(255, 220, 40);
  36. var RED = display.color(255, 55, 55);
  37. var CYAN = display.color(50, 220, 255);
  38.  
  39. var screenW = display.width();
  40. var screenH = display.height();
  41.  
  42. var radarDiameter = screenH - 8;
  43. if (radarDiameter > screenW - 98) {
  44.   radarDiameter = screenW - 98;
  45. }
  46. if (radarDiameter < 70) {
  47.   radarDiameter = screenH - 8;
  48. }
  49.  
  50. var radarRadius = Math.floor(radarDiameter / 2);
  51. var radarX = radarRadius + 4;
  52. var radarY = Math.floor(screenH / 2);
  53. var panelX = radarX + radarRadius + 5;
  54. var panelW = screenW - panelX;
  55.  
  56. var planes = [];
  57. var selectedIndex = 0;
  58. var lastRefresh = 0;
  59. var lastStatus = "START";
  60. var sweepAngle = 0;
  61. var running = true;
  62. var panelDirty = true;
  63. var routeCache = {};
  64.  
  65. function trimText(value) {
  66.   if (value === undefined || value === null) {
  67.     return "";
  68.   }
  69.  
  70.   return String(value).replace(/^\s+|\s+$/g, "");
  71. }
  72.  
  73. function valueOr(value, fallback) {
  74.   if (value === undefined || value === null || value === "") {
  75.     return fallback;
  76.   }
  77.  
  78.   return value;
  79. }
  80.  
  81. function clamp(value, minimum, maximum) {
  82.   if (value < minimum) {
  83.     return minimum;
  84.   }
  85.   if (value > maximum) {
  86.     return maximum;
  87.   }
  88.   return value;
  89. }
  90.  
  91. function formatNumber(value, decimals, fallback) {
  92.   if (typeof value !== "number" || isNaN(value)) {
  93.     return fallback;
  94.   }
  95.  
  96.   return value.toFixed(decimals);
  97. }
  98.  
  99. function formatAltitude(value) {
  100.   if (value === "ground") {
  101.     return "GROUND";
  102.   }
  103.  
  104.   if (typeof value === "number" && !isNaN(value)) {
  105.     return Math.round(value * 0.3048) + " m";
  106.   }
  107.  
  108.   return "-- m";
  109. }
  110.  
  111. function distanceKm(lat1, lon1, lat2, lon2) {
  112.   var toRad = Math.PI / 180;
  113.   var dLat = (lat2 - lat1) * toRad;
  114.   var dLon = (lon2 - lon1) * toRad;
  115.   var a =
  116.     Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  117.     Math.cos(lat1 * toRad) *
  118.       Math.cos(lat2 * toRad) *
  119.       Math.sin(dLon / 2) *
  120.       Math.sin(dLon / 2);
  121.  
  122.   var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  123.   return 6371.0088 * c;
  124. }
  125.  
  126. function bearingDegrees(lat1, lon1, lat2, lon2) {
  127.   var toRad = Math.PI / 180;
  128.   var y = Math.sin((lon2 - lon1) * toRad) * Math.cos(lat2 * toRad);
  129.   var x =
  130.     Math.cos(lat1 * toRad) * Math.sin(lat2 * toRad) -
  131.     Math.sin(lat1 * toRad) *
  132.       Math.cos(lat2 * toRad) *
  133.       Math.cos((lon2 - lon1) * toRad);
  134.  
  135.   var bearing = Math.atan2(y, x) / toRad;
  136.   if (bearing < 0) {
  137.     bearing += 360;
  138.   }
  139.  
  140.   return bearing;
  141. }
  142.  
  143. function radarPoint(plane) {
  144.   var distance = distanceKm(SOFIA_LAT, SOFIA_LON, plane.lat, plane.lon);
  145.   var bearing = bearingDegrees(SOFIA_LAT, SOFIA_LON, plane.lat, plane.lon);
  146.   var angle = (bearing - 90) * Math.PI / 180;
  147.   var radial = clamp(distance / RANGE_KM, 0, 1) * radarRadius;
  148.  
  149.   return {
  150.     x: Math.round(radarX + Math.cos(angle) * radial),
  151.     y: Math.round(radarY + Math.sin(angle) * radial),
  152.     distance: distance,
  153.     bearing: bearing
  154.   };
  155. }
  156.  
  157.  
  158. function routeKey(plane) {
  159.   if (!plane) {
  160.     return "";
  161.   }
  162.  
  163.   return trimText(plane.flight)
  164.     .replace(/\s+/g, "")
  165.     .toUpperCase();
  166. }
  167.  
  168. function formatRoute(route) {
  169.   if (!route) {
  170.     return "";
  171.   }
  172.  
  173.   return String(route)
  174.     .replace(/-/g, " > ")
  175.     .replace(/~/g, " > ")
  176.     .replace(/,/g, " > ")
  177.     .replace(/\s+/g, " ");
  178. }
  179.  
  180. function parseAdsbLolRoute(responseBody) {
  181.   var payload = JSON.parse(responseBody);
  182.  
  183.   if (typeof payload === "string") {
  184.     payload = JSON.parse(payload);
  185.   }
  186.  
  187.   var result = null;
  188.   var route = "";
  189.  
  190.   if (payload && payload.length > 0) {
  191.     result = payload[0];
  192.   } else if (payload && payload.planes && payload.planes.length > 0) {
  193.     result = payload.planes[0];
  194.   }
  195.  
  196.   if (!result) {
  197.     return "";
  198.   }
  199.  
  200.   if (
  201.     result._airport_codes_iata &&
  202.     result._airport_codes_iata !== "unknown"
  203.   ) {
  204.     route = trimText(result._airport_codes_iata);
  205.   } else if (
  206.     result.airport_codes &&
  207.     result.airport_codes !== "unknown"
  208.   ) {
  209.     route = trimText(result.airport_codes);
  210.   } else if (
  211.     result.airport_codes_iata &&
  212.     result.airport_codes_iata !== "unknown"
  213.   ) {
  214.     route = trimText(result.airport_codes_iata);
  215.   }
  216.  
  217.   if (!route) {
  218.     return "";
  219.   }
  220.  
  221.   route = formatRoute(route);
  222.  
  223.   if (result.plausible === false) {
  224.     route += "?";
  225.   }
  226.  
  227.   return route;
  228. }
  229.  
  230. function fetchAdsbLolRoute(plane, callsign) {
  231.   try {
  232.     var requestBody = JSON.stringify({
  233.       planes: [
  234.         {
  235.           callsign: callsign,
  236.           lat: plane.lat,
  237.           lng: plane.lon
  238.         }
  239.       ]
  240.     });
  241.  
  242.     var response = wifi.httpFetch(ROUTE_URL, {
  243.       method: "POST",
  244.       body: requestBody,
  245.       responseType: "string",
  246.       headers: [
  247.         ["Content-Type", "application/json"],
  248.         ["Accept", "application/json"]
  249.       ]
  250.     });
  251.  
  252.     if (!response || !response.ok || !response.body) {
  253.       return "";
  254.     }
  255.  
  256.     return parseAdsbLolRoute(response.body);
  257.   } catch (error) {
  258.     return "";
  259.   }
  260. }
  261.  
  262. function fetchAdsbDbRoute(callsign) {
  263.   try {
  264.     var response = wifi.httpFetch(
  265.       ADSBDB_ROUTE_URL + encodeURIComponent(callsign)
  266.     );
  267.  
  268.     if (!response || !response.ok || !response.body) {
  269.       return "";
  270.     }
  271.  
  272.     var payload = JSON.parse(response.body);
  273.     var flightRoute = null;
  274.  
  275.     if (
  276.       payload &&
  277.       payload.response &&
  278.       payload.response.flightroute
  279.     ) {
  280.       flightRoute = payload.response.flightroute;
  281.     }
  282.  
  283.     if (
  284.       !flightRoute ||
  285.       !flightRoute.origin ||
  286.       !flightRoute.destination
  287.     ) {
  288.       return "";
  289.     }
  290.  
  291.     var origin =
  292.       trimText(flightRoute.origin.iata_code) ||
  293.       trimText(flightRoute.origin.icao_code);
  294.  
  295.     var destination =
  296.       trimText(flightRoute.destination.iata_code) ||
  297.       trimText(flightRoute.destination.icao_code);
  298.  
  299.     if (!origin || !destination) {
  300.       return "";
  301.     }
  302.  
  303.     return origin + " > " + destination;
  304.   } catch (error) {
  305.     return "";
  306.   }
  307. }
  308.  
  309. function fetchRouteForSelected() {
  310.   if (planes.length === 0 || selectedIndex >= planes.length) {
  311.     return;
  312.   }
  313.  
  314.   var plane = planes[selectedIndex];
  315.   var callsign = routeKey(plane);
  316.  
  317.   if (!callsign) {
  318.     plane.route = "NO CALLSIGN";
  319.     panelDirty = true;
  320.     return;
  321.   }
  322.  
  323.   if (routeCache[callsign] !== undefined) {
  324.     plane.route = routeCache[callsign];
  325.     panelDirty = true;
  326.     return;
  327.   }
  328.  
  329.   plane.route = "ROUTE...";
  330.   panelDirty = true;
  331.   drawPanel();
  332.  
  333.   var route = fetchAdsbLolRoute(plane, callsign);
  334.  
  335.   if (!route) {
  336.     plane.route = "TRY ADSBDB";
  337.     panelDirty = true;
  338.     drawPanel();
  339.  
  340.     route = fetchAdsbDbRoute(callsign);
  341.   }
  342.  
  343.   if (route) {
  344.     plane.route = route;
  345.   } else {
  346.     plane.route = "NO ROUTE";
  347.   }
  348.  
  349.   routeCache[callsign] = plane.route;
  350.   panelDirty = true;
  351. }
  352.  
  353. function fetchPlanes() {
  354.   lastStatus = "SYNC";
  355.   panelDirty = true;
  356.  
  357.   if (!wifi.connected()) {
  358.     wifi.connectDialog();
  359.   }
  360.  
  361.   if (!wifi.connected()) {
  362.     lastStatus = "NO WIFI";
  363.     lastRefresh = now();
  364.     panelDirty = true;
  365.     return;
  366.   }
  367.  
  368.   try {
  369.     /* Keep the request minimal for compatibility with older Bruce builds. */
  370.     var response = wifi.httpFetch(API_URL);
  371.  
  372.     if (!response) {
  373.       lastStatus = "NO RESPONSE";
  374.       lastRefresh = now();
  375.       panelDirty = true;
  376.       return;
  377.     }
  378.  
  379.     if (!response.ok) {
  380.       lastStatus = "HTTP " + response.status;
  381.       lastRefresh = now();
  382.       panelDirty = true;
  383.       return;
  384.     }
  385.  
  386.     if (!response.body || response.body.length === 0) {
  387.       lastStatus = "EMPTY DATA";
  388.       lastRefresh = now();
  389.       panelDirty = true;
  390.       return;
  391.     }
  392.  
  393.     var payload = JSON.parse(response.body);
  394.  
  395.     /*
  396.      * Airplanes.live v2 returns the aircraft array as `ac`.
  397.      * `aircraft` is retained as a fallback for compatible feeds.
  398.      */
  399.     var source = payload.ac || payload.aircraft || [];
  400.     var nextPlanes = [];
  401.     var i;
  402.  
  403.     for (i = 0; i < source.length && nextPlanes.length < MAX_PLANES; i++) {
  404.       var aircraft = source[i];
  405.  
  406.       if (
  407.         typeof aircraft.lat !== "number" ||
  408.         typeof aircraft.lon !== "number" ||
  409.         isNaN(aircraft.lat) ||
  410.         isNaN(aircraft.lon)
  411.       ) {
  412.         continue;
  413.       }
  414.  
  415.       var distance = distanceKm(
  416.         SOFIA_LAT,
  417.         SOFIA_LON,
  418.         aircraft.lat,
  419.         aircraft.lon
  420.       );
  421.  
  422.       if (distance > RANGE_KM) {
  423.         continue;
  424.       }
  425.  
  426.       nextPlanes.push({
  427.         hex: trimText(aircraft.hex),
  428.         flight: trimText(aircraft.flight),
  429.         registration: trimText(aircraft.r),
  430.         aircraftType: trimText(aircraft.t),
  431.         lat: aircraft.lat,
  432.         lon: aircraft.lon,
  433.         altitude: aircraft.alt_baro,
  434.         speed: aircraft.gs,
  435.         track: aircraft.track,
  436.         verticalRate: aircraft.baro_rate,
  437.         squawk: trimText(aircraft.squawk),
  438.         seen: aircraft.seen,
  439.         distance: distance,
  440.         route: routeCache[trimText(aircraft.flight)] || ""
  441.       });
  442.     }
  443.  
  444.     /* Sort nearest aircraft first. */
  445.     nextPlanes.sort(function (a, b) {
  446.       return a.distance - b.distance;
  447.     });
  448.  
  449.     planes = nextPlanes;
  450.  
  451.     if (planes.length === 0) {
  452.       selectedIndex = 0;
  453.       lastStatus = "NO TRAFFIC";
  454.     } else {
  455.       if (selectedIndex >= planes.length) {
  456.         selectedIndex = planes.length - 1;
  457.       }
  458.       if (selectedIndex < 0) {
  459.         selectedIndex = 0;
  460.       }
  461.       lastStatus = "ADSB.LOL";
  462.     }
  463.   } catch (error) {
  464.     /*
  465.      * Some Bruce builds do not provide the global println() function.
  466.      * Do not log here, because logging must never crash the app.
  467.      */
  468.     var message = String(error);
  469.     if (message.length > 12) {
  470.       message = message.substring(0, 12);
  471.     }
  472.     lastStatus = "ERR " + message;
  473.     panelDirty = true;
  474.   }
  475.  
  476.   lastRefresh = now();
  477.   panelDirty = true;
  478.  
  479.   if (planes.length > 0) {
  480.     fetchRouteForSelected();
  481.   }
  482. }
  483.  
  484. function drawRadarGrid() {
  485.   display.drawCircle(radarX, radarY, radarRadius, GRID_GREEN);
  486.   display.drawCircle(
  487.     radarX,
  488.     radarY,
  489.     Math.floor(radarRadius * 0.66),
  490.     DARK_GREEN
  491.   );
  492.   display.drawCircle(
  493.     radarX,
  494.     radarY,
  495.     Math.floor(radarRadius * 0.33),
  496.     DARK_GREEN
  497.   );
  498.  
  499.   display.drawLine(
  500.     radarX - radarRadius,
  501.     radarY,
  502.     radarX + radarRadius,
  503.     radarY,
  504.     DARK_GREEN
  505.   );
  506.   display.drawLine(
  507.     radarX,
  508.     radarY - radarRadius,
  509.     radarX,
  510.     radarY + radarRadius,
  511.     DARK_GREEN
  512.   );
  513.  
  514.   display.setTextSize(1);
  515.   display.setTextColor(DIM_GREEN);
  516.   display.drawText("N", radarX - 3, radarY - radarRadius + 2);
  517.   display.drawText(
  518.     String(RANGE_KM) + "km",
  519.     radarX - 8,
  520.     radarY + radarRadius - 10
  521.   );
  522.  
  523.   display.drawFillCircle(radarX, radarY, 2, GREEN);
  524. }
  525.  
  526. function drawSweep() {
  527.   var angle = (sweepAngle - 90) * Math.PI / 180;
  528.   var endX = Math.round(radarX + Math.cos(angle) * radarRadius);
  529.   var endY = Math.round(radarY + Math.sin(angle) * radarRadius);
  530.  
  531.   var trailAngle = (sweepAngle - 97) * Math.PI / 180;
  532.   var trailX = Math.round(radarX + Math.cos(trailAngle) * radarRadius);
  533.   var trailY = Math.round(radarY + Math.sin(trailAngle) * radarRadius);
  534.  
  535.   display.drawLine(radarX, radarY, trailX, trailY, DARK_GREEN);
  536.   display.drawLine(radarX, radarY, endX, endY, GREEN);
  537. }
  538.  
  539. function drawPlanes() {
  540.   var i;
  541.  
  542.   for (i = 0; i < planes.length; i++) {
  543.     var point = radarPoint(planes[i]);
  544.  
  545.     if (i === selectedIndex) {
  546.       display.drawCircle(point.x, point.y, 4, WHITE);
  547.       display.drawFillCircle(point.x, point.y, 2, YELLOW);
  548.     } else {
  549.       display.drawFillCircle(point.x, point.y, 2, GREEN);
  550.     }
  551.   }
  552. }
  553.  
  554. function drawPanelLine(text, y, color) {
  555.   display.setTextSize(1);
  556.   display.setTextColor(color || GREEN);
  557.   display.drawText(text, panelX, y);
  558. }
  559.  
  560. function drawInfoPanel() {
  561.   display.drawLine(panelX - 3, 0, panelX - 3, screenH, GRID_GREEN);
  562.  
  563.   drawPanelLine("LBSF ADS-B", 3, CYAN);
  564.   drawPanelLine(lastStatus + " " + planes.length, 14, lastStatus === "ADSB.LOL" ? GREEN : YELLOW);
  565.  
  566.   if (planes.length === 0) {
  567.     drawPanelLine("No aircraft", 34, WHITE);
  568.     drawPanelLine("SEL refresh", 48, DIM_GREEN);
  569.     drawPanelLine("ESC exit", 59, DIM_GREEN);
  570.     return;
  571.   }
  572.  
  573.   var plane = planes[selectedIndex];
  574.   var point = radarPoint(plane);
  575.   var callsign = valueOr(plane.flight, valueOr(plane.registration, plane.hex));
  576.  
  577.   if (callsign.length > 14) {
  578.     callsign = callsign.substring(0, 14);
  579.   }
  580.  
  581.   drawPanelLine(
  582.     (selectedIndex + 1) + "/" + planes.length + " " + callsign,
  583.     27,
  584.     YELLOW
  585.   );
  586.   var routeText = plane.route || "ROUTE...";
  587.   if (routeText.length > 15) {
  588.     routeText = routeText.substring(0, 15);
  589.   }
  590.  
  591.   drawPanelLine("RTE " + routeText, 39, WHITE);
  592.   drawPanelLine("ALT " + formatAltitude(plane.altitude), 51, GREEN);
  593.   drawPanelLine(
  594.     "SPD " +
  595.       formatNumber(
  596.         typeof plane.speed === "number" ? plane.speed * 1.852 : plane.speed,
  597.         0,
  598.         "--"
  599.       ) +
  600.       " km/h",
  601.     63,
  602.     GREEN
  603.   );
  604.   drawPanelLine(
  605.     "HDG " + formatNumber(plane.track, 0, "--") + " deg",
  606.     75,
  607.     GREEN
  608.   );
  609.   drawPanelLine(
  610.     "DST " + formatNumber(point.distance, 1, "--") + " km",
  611.     87,
  612.     GREEN
  613.   );
  614.  
  615.   var verticalText = "--";
  616.   if (typeof plane.verticalRate === "number" && !isNaN(plane.verticalRate)) {
  617.     var verticalMetric = plane.verticalRate * 0.00508;
  618.  
  619.     if (verticalMetric > 0) {
  620.       verticalText = "+" + verticalMetric.toFixed(1);
  621.     } else {
  622.       verticalText = verticalMetric.toFixed(1);
  623.     }
  624.   }
  625.  
  626.   drawPanelLine("V/S " + verticalText + " m/s", 99, GREEN);
  627.   drawPanelLine("PREV/NEXT select", 115, DIM_GREEN);
  628.   drawPanelLine("SEL refresh", 126, DIM_GREEN);
  629. }
  630.  
  631. function drawRadarFrame() {
  632.   /*
  633.    * Clear only the radar region. The information panel remains untouched,
  634.    * avoiding full-screen flashing on every animation frame.
  635.    */
  636.   display.drawFillRect(
  637.     0,
  638.     0,
  639.     panelX - 4,
  640.     screenH,
  641.     BLACK
  642.   );
  643.  
  644.   drawRadarGrid();
  645.   drawPlanes();
  646.   drawSweep();
  647. }
  648.  
  649. function drawPanel() {
  650.   display.drawFillRect(
  651.     panelX - 3,
  652.     0,
  653.     screenW - panelX + 3,
  654.     screenH,
  655.     BLACK
  656.   );
  657.   drawInfoPanel();
  658.   panelDirty = false;
  659. }
  660.  
  661. function selectPrevious() {
  662.   if (planes.length === 0) {
  663.     return;
  664.   }
  665.  
  666.   selectedIndex--;
  667.   if (selectedIndex < 0) {
  668.     selectedIndex = planes.length - 1;
  669.   }
  670.   panelDirty = true;
  671.   fetchRouteForSelected();
  672. }
  673.  
  674. function selectNext() {
  675.   if (planes.length === 0) {
  676.     return;
  677.   }
  678.  
  679.   selectedIndex++;
  680.   if (selectedIndex >= planes.length) {
  681.     selectedIndex = 0;
  682.   }
  683.   panelDirty = true;
  684.   fetchRouteForSelected();
  685. }
  686.  
  687. function handleOptionalKeyboardKeys() {
  688.   /*
  689.    * getKeysPressed() is useful on devices/keyboards that Bruce maps as keys.
  690.    * Stock Bruce may not expose the original I2C CardKB here, but these
  691.    * checks are harmless and provide compatibility if support is added.
  692.    */
  693.   var keys;
  694.  
  695.   try {
  696.     keys = keyboard.getKeysPressed();
  697.   } catch (error) {
  698.     return;
  699.   }
  700.  
  701.   if (!keys || keys.length === 0) {
  702.     return;
  703.   }
  704.  
  705.   var i;
  706.   for (i = 0; i < keys.length; i++) {
  707.     var key = String(keys[i]);
  708.  
  709.     if (key === "ArrowUp" || key === "Up" || key === "UP") {
  710.       selectPrevious();
  711.     } else if (
  712.       key === "ArrowDown" ||
  713.       key === "Down" ||
  714.       key === "DOWN"
  715.     ) {
  716.       selectNext();
  717.     } else if (key === "Enter") {
  718.       fetchPlanes();
  719.     } else if (key === "Escape" || key === "Esc") {
  720.       running = false;
  721.     }
  722.   }
  723. }
  724.  
  725. display.fill(BLACK);
  726. display.setTextColor(GREEN);
  727. display.setTextSize(2);
  728. display.drawText("LBSF RADAR", 12, 35);
  729. display.setTextSize(1);
  730. display.drawText("Connecting to ADS-B...", 12, 62);
  731.  
  732. display.fill(BLACK);
  733. fetchPlanes();
  734. drawPanel();
  735.  
  736. while (running) {
  737.   if (keyboard.getPrevPress()) {
  738.     selectPrevious();
  739.   }
  740.  
  741.   if (keyboard.getNextPress()) {
  742.     selectNext();
  743.   }
  744.  
  745.   if (keyboard.getSelPress()) {
  746.     fetchPlanes();
  747.   }
  748.  
  749.   if (keyboard.getEscPress()) {
  750.     running = false;
  751.   }
  752.  
  753.   handleOptionalKeyboardKeys();
  754.  
  755.   if (now() - lastRefresh >= REFRESH_MS) {
  756.     fetchPlanes();
  757.   }
  758.  
  759.   drawRadarFrame();
  760.  
  761.   if (panelDirty) {
  762.     drawPanel();
  763.   }
  764.  
  765.   sweepAngle += 12;
  766.   if (sweepAngle >= 360) {
  767.     sweepAngle -= 360;
  768.   }
  769.  
  770.   delay(FRAME_MS);
  771. }
  772.  
  773. display.fill(BLACK);
  774. display.setTextColor(GREEN);
  775. display.setTextSize(1);
  776. display.drawText("Radar closed", 10, 10);
  777. delay(500);
Advertisement
Add Comment
Please, Sign In to add comment