/* 06/07/2020 METAR Reporting with LEDs and Local WEB SERVER In Memory of F. Hugh Magee, brother of John Magee author of poem HIGH FLIGHT. https://en.wikipedia.org/wiki/John_Gillespie_Magee_Jr. https://youtu.be/Yg61_kyG2zE HomebuiltHELP; The video that started me on this project. https://youtu.be/xPlN_Tk3VLQ Getting Started with ESP32 video from DroneBot Workshop. https://www.aviationweather.gov/docs/metar/Stations.txt List of ALL Stations Codes. REMARKS/COMMENTS: Created by John Pipe with help from David Bird. FULLY Configurable for YOUR application. (See the list of all Stations Codes, link above). No of Airports = 60 tested 5/24/19 (60+ possible with external power). When Error, successfully recovers. If not, press "RESET" button on ESP32. The code is stored on the ESP32 Development Board 2.4GHz WiFi+Bluetooth Dual Mode, once the code is downloaded, it will run by it's self and will automatically restart, even after a power off. ESP32 requires internet access (Network or Hot Spot) to download METARS. A computer, with FREE software, is needed to initially configure, download software and display any program messages (optional). (Watch Getting Started with ESP32 video from DroneBot Workshop, link above). Updates METARS approximately every six minutes, so nearly REAL TIME data, from AVIATIONWEATHER.GOV. A set of WS2812 LEDS show all station CATEGORIES (similar to the HomebuiltHELP video, link above). Then cycles through all the stations and flashes individually for: Wind Gusts(Cyan)[suspendable], Precipitation(Green/White), Ice(Blue), Other(Yellow) and Info(Orange). Then displays "RAINBOW" for all stations, for Visibility [Red Orange Pink/White], Wind Speed Gradient [Cyan], Temperature Gradient [Blue Green Yellow Orange Red] and Altimeter Pressure Gradient [Blue Purple]. Recommended, VIEWABLE with a cell phone or computer connected to the SAME network: SUMMARY html gives a colorful overview and STATION html shows DECODED METAR information and much MORE. (See Below for Improvements.) Makes a GREAT Christmas Tree Chain of Lights, TOO (and a Good Conversation Piece). MADE THINGS A LITTLE BETTER, BUG FIXES, IMPROVEMENTS, REPAIRS TO TIME-SPACE CONTINUUM, ETC, ETC. Includes: Decoded Metar, Current Zulu Time, Temperature in Deg F, Elevation Ft, Estimated Density Altitude Ft Modified Significant Weather to include Cloud Cover, RVR & Weather 12/31 Changed to a TIMED 6 Minute METAR read and update 01/07 Added for Ice and Hail (Blue) 01/30 Added Capability to select ANY Airport Code 02/02 Added Summary to HTML 03/04 Cleaned up Set_Time & loop 03/08 Added User 03/10 Added Cloud_base Change Arrows 03/13 Added Alt Pressure Change Arrows 03/13 Added Yellow Misc Weather 03/24 Added Observation Time 04/01 Added Orange Info Changes 04/05 Cleaned up Parse_Metar 04/14 Added Wind Changes 04/30 Changed Temperature Display Colors 05/06 Added Pressure Display 05/06 Modified Visibility Display 05/15 Modified Variable Types 05/15 Tweaked Rainbow Displays 05/21 Dual Core: Main_Loop Task1 Core0; Go_Server Task2 Core1 05/24 Modified Server Update Time 05/30 More little tweaks 06/01 */ #include #include #include #include #include WiFiMulti wifiMulti; #include WiFiServer server(80); // Set web server port number to 80 // Configure Network //const char* ssid = "iPhone"; // your network SSID (name) //const char* password = "********"; // your network password const char* ssid = "NETGEAR46"; // your network SSID (name) const char* password = "*********"; // your network password // Setup for Time Server const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 0; // UTC Time const int daylightOffset_sec = 0; // UTC Time struct tm timeinfo; // Time String "%A, %B %d %Y %H:%M:%S" // To get Station Name and Information // Test link: https://aviationweather.gov/adds/dataserver_current/httpparam?dataSource=Stations&requestType=retrieve&format=xml&stationString=KFLL String host = "https://aviationweather.gov"; String urls = "/adds/dataserver_current/httpparam?dataSource=Stations&requestType=retrieve&format=xml&stationString="; // To get Station METAR Data // Test link: https://www.aviationweather.gov/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString=KFLL String urlb = "/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString="; // Set Up LEDS #define No_Stations 52 // Number of Stations also Number of LEDs #define DATA_PIN 5 // Connect to pin D5/P5 #define LED_TYPE WS2812 // WD2811 or WS2812 #define COLOR_ORDER GRB // WD2811 are RGB or WS2812 are GRB #define BRIGHTNESS 20 // Master LED Brightness (<12=Dim 20=ok >20=Too Bright/Much Power) CRGB leds[No_Stations]; // Number of LEDs // Define STATIONS & Global Variables std::vector Stations ({ // << Set Up - Do NOT change this line "NULL, STATION NAME ", // 0 << Reserved - Do NOT change this line "KCHA, CHATTANOOGA, TN ", // 1 << START modifying from here "KRMG, ROME, GA ", // 2 Order of LEDs; NULL for no airport "KVPC, CARTERSVILLE, GA ", // 3 "KATL, ATLANTA, GA ", // 4 First FIVE Characters are IMPORTANT !! "KCTJ, CARROLTON, GA ", // 5 "KLGC, LA GRANGE, GA ", // 6 Over type your Station Code and Station name "KCSG, COLUMBUS, GA ", // 7 and include the "quotes" and the "commas" "KMCN, MACON, GA ", // 8 Padding after station name are not necessary. "KCKF, CORDELLE, GA ", // 9 "KABY, ALBANY, GA ", // 10 Note: SKYVECTOR.COM is good for locating METARs "KTLH, TALLAHASSEE, FL ", // 11 "KVLD, VALDOSTA, GA ", // 12 "KAYS, WAYCROSS, GA ", // 13 "KJAX, JACKSONVILLE, FL ", // 14 "KBQK, BRUNSWICK, GA ", // 15 "KSAV, SAVANNAH, GA ", // 16 "KTBR, STATESBORO, GA ", // 17 "KAGS, AUGUSTA, GA ", // 18 "KAHN, ATHENS, GA ", // 19 "KCEU, CLEMSON, GA ", // 20 "KJES, JESUP, GA ", // 21 (my home town) "KBHC, BAXLEY, GA ", // 22 "KAZE, HAZLEHURST, GA ", // 23 "KCAE, COLUMBIA, SC ", // 24 "KVDI, VIDALIA RGNL, GA ", // 25 "KAFP, ANSON CO, NC ", // 26 "KBDU, BOULDER, CO ", // 27 "KCVG, CINCINNATI, OH ", // 28 "KBWI, WASHINGTON, DC ", // 29 "KORD, CHICAGO O'HARE, IL ", // 30 "KMEM, MEMPHIS, TN ", // 31 "KMSY, NEW ORLEANS, LA ", // 32 "KSDF, LOUISVILLE, KY ", // 33 "KBOS, BOSTON, MA ", // 34 "KCLT, CHARLOTTE, NC ", // 35 "KBNA, NASHVILLE, TN ", // 36 "EGPN, DUNDEE, SCOTLAND ", // 37 "EGPH, EDINBURGH, SCOTLAND ", // 38 "EGPE, INVERNESS, SCOTLAND ", // 39 "EGLL, LONDON HEATHROW, UK ", // 40 "EGKK, LONDON GATWICK, UK ", // 41 "EGCC, MANCHESTER, UK ", // 42 "EGDM, BOSCOMBE DOWN, UK ", // 43 "EBCV, CHIEVRES, BELGIUM ", // 44 "EHAM, AMSTERDAM SCHIPHOL ", // 45 "LFSB, BASEL, SWITZERLAND ", // 46 "KORL, ORLANDO, FL ", // 47 "KPBI, WEST PALM BEACH, FL ", // 48 "KFXE, FT LAUDERDALE EXE, FL", // 49 "KFLL, FT LAUDERDALE INT, FL", // 50 "KHWO, NTH PERRY, FL ", // 51 "KMIA, MIAMI, FL ", // 52 (last airport) "KEYW, KEY WEST, FL ", // 53 (extras) "KJAX, JACKSONVILLE, FL ", // 54 "KTIX, TITUSVILLE, FL ", // 55 "KDAB, DAYTONA BEACH, FL ", // 56 "KTBR, STATESBORO, GA ", // 57 "KAGS, AUGUSTA, GA ", // 58 "KAHN, ATHENS, GA ", // 59 "KCEU, CLEMSON, GA ", // 60 }); // << Do NOT change this line String updatetime[No_Stations + 1]; // Keeps previous time String rem[No_Stations + 1]; // Remarks String sig_wx[No_Stations + 1]; // Significant Weather float temp[No_Stations + 1]; // temperature deg C float dewpt[No_Stations + 1]; // dew point deg C String wind[No_Stations + 1]; // wind speed String wdir[No_Stations + 1]; // wind direction int old_wdir[No_Stations + 1]; // old wind direction float visab[No_Stations + 1]; // visibility String sky[No_Stations + 1]; // sky_cover & cloud_base int cloud_base[No_Stations + 1]; // cloud_base int old_cloud_base[No_Stations + 1]; // old cloud_base float seapres[No_Stations + 1]; // Sea Level Pressure float altim[No_Stations + 1]; // altimeter setting float old_altim[No_Stations + 1]; // old altimeter setting float elevation[No_Stations + 1]; // elevation setting String category[No_Stations + 1]; // NULL VFR MVFR IFR LIFR //.....................................Black Green Blue Red Magenta #define LED_BUILTIN 2 // ON Board LED GPIO 2 String metar; // Raw METAR data String Time; // Time "HH:MM" int Hour; // Latest Hour int Minute; // Latest Minute String Last_Up_Time; // Last Update Time "HH:MM" int Last_Up_Min = 0; // Last Update Minute int diff_in_clouds = 2750; // Significant CHANGE IN CLOUD BASE float diff_in_press = 0.045; // Significant CHANGE IN PRESSURE int station_num = 1; // Station for Server - flash String header; // Header for Server int httpCode; // Error Code IPAddress ip; // WiFi local IP Address String local_ip; // WiFi local IP Address TaskHandle_t Task1; // Main_Loop, Core 0 TaskHandle_t Task2; // Go_Server, Core 1 const char* FileName = "METAR_ESP32_06_07"; void setup() { pinMode(LED_BUILTIN, OUTPUT); // the onboard LED Serial.begin(115200); delay(4000); // time to press "Clear output" in Serial Monitor Serial.println("\nMETAR Reporting with LEDs and Local WEB SERVER\n"); if (ssid == "iPhone") Serial.println("** If iPhone DOESN'T CONNECT: Select HOT SPOT on iPhone **"); digitalWrite(LED_BUILTIN, HIGH); //ON Serial.print("WiFi Connecting to " + String(ssid) + " "); // Initializes the WiFi library's network settings. WiFi.begin(ssid, password); // CONNECT to WiFi network: while (WiFi.status() != WL_CONNECTED) { delay(300); // Wait a little bit Serial.print("."); } Serial.println(" Connected."); // Print the Signal Strength: long rssi = WiFi.RSSI() + 100; Serial.print("Signal Strength = " + String(rssi)); if (rssi > 50) Serial.println(" (>50 - Good)"); else Serial.println(" (Could be Better)"); digitalWrite(LED_BUILTIN, LOW); //OFF ip = WiFi.localIP(); local_ip = "http://" + WiFi.localIP().toString() + "/summary"; Serial.println("*******************************************"); Serial.println("To View Decoded Station METARs from a Computer or \nCell Phone connected to the " + String(ssid) + " WiFi Network."); Serial.println("Enter This Url Address \t: " + local_ip); WiFi.mode(WIFI_STA); wifiMulti.addAP(ssid, password); WiFi.setHostname(FileName); Serial.println("File/Host Name \t\t: " + String(FileName)); digitalWrite(LED_BUILTIN, HIGH); //ON configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Get Zulu Time from Server Set_Time(); // Set Up Time,Hour,Minute Serial.print("Date & Time \t\t: "); Serial.println(&timeinfo, "%A, %B %d %Y %H:%M - Zulu"); digitalWrite(LED_BUILTIN, LOW); //OFF //Task1 = Main_Loop() function, with priority 1 and executed on core 0 xTaskCreatePinnedToCore( Main_Loop, "Task1", 10000, NULL, 1, &Task1, 0); delay(200); // Wait a smidgen //Task2 = Go_Server() function, with priority 1 and executed on core 1 xTaskCreatePinnedToCore( Go_Server, "Task2", 10000, NULL, 1, &Task2, 1); delay(200); // Wait a smidgen Serial.println("Dual Core Initialized"); server.begin(); // Start the web server Serial.println("Web Server Started"); Init_LEDS(); // Initialize LEDs Serial.println("*******************************************"); } void loop() { } // *********** Main_Loop TASK1 void Main_Loop( void * pvParameters ) { for (;;) { Last_Up_Time = Time; Last_Up_Min = Minute; int Cycle = Last_Up_Min + 6; // Cycle time is 6 Minutes GetAllMetars(); // Get All Metars and Display Categories Set_Time(); // Update Current Time while (Cycle > Minute) { if ((Cycle - Minute) > 6 ) Cycle = -1; else DisplayMetar(); // Display Station Metar/Show Loops } } } // *********** Set Current Time, Hour, Minute void Set_Time() { if (!getLocalTime(&timeinfo)) { Serial.print("\t\t\t** FAILED to Get Time **\n\t"); Network_Error (); // WiFi Network Error } char TimeChar[9]; strftime(TimeChar, 9, "%H:%M:%S", &timeinfo); Time = String(TimeChar); Hour = Time.substring(0, 2).toInt(); Minute = Time.substring(3, 5).toInt(); Time = Time.substring(0, 5); } // *********** Initialize LEDs void Init_LEDS() { Serial.println("Initializing LEDs for No_Stations = " + String(No_Stations)); // Set up the strip configuration FastLED.addLeds(leds, No_Stations).setCorrection(TypicalLEDStrip); // Set master brightness control (<12=Dim 20=ok >20=Too Bright) FastLED.setBrightness(BRIGHTNESS); // Set all leds to Black fill_solid(leds, No_Stations, CRGB::Black); FastLED.show(); delay(100); // Wait a smidgen } // *********** GET All Metars in Chunks void GetAllMetars() { int Step = 20; // 20 Stations at a time for (int j = 0; j < No_Stations; j = j + 1 + Step) { int start = j; int finish = start + Step; if (finish > No_Stations) finish = No_Stations; String url = ""; for (int i = start; i <= finish; i++) { String station = Stations[i].substring(0, 5); url = url + String (station); } int len = url.length(); url = url.substring(0, len - 1); // remove last "comma" GetData(url); // GET Some Metar Data 20 Stations at a time for (int i = start; i <= finish; i++) { ParseMetar(i); // Parse Metar Data one Station at a time } } } // *********** GET Some Metar Data/Name void GetData(String url) { metar = ""; // Reset Metar Data if (url == "NAME") url = host + urls + Stations[0]; else url = host + urlb + url; //Serial.println("url = " + url); // Check for WiFi connection if ((wifiMulti.run() == WL_CONNECTED)) { digitalWrite(LED_BUILTIN, HIGH); // ON HTTPClient http; http.begin(url); // Start connection and send HTTP header httpCode = http.GET(); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled // and File found at server if (httpCode == HTTP_CODE_OK) { metar = http.getString(); } http.end(); digitalWrite(LED_BUILTIN, LOW); //OFF } else { http.end(); // Communication HTTP Error digitalWrite(LED_BUILTIN, LOW); //OFF Serial.println(Time + "\tCommunication Error = " + String(httpCode) + " : " + http.errorToString(httpCode).c_str()); } } else { Network_Error (); // WiFi Network Error } } // *********** WiFi Network Error void Network_Error () { Serial.print(Time + "\t\tWiFi Network : "); if (WiFi.status() == 0 ) Serial.println("Idle"); if (WiFi.status() == 1 ) Serial.println("Not Available"); if (WiFi.status() == 2 ) Serial.println("Scan Completed"); if (WiFi.status() == 3 ) Serial.println("Connected"); if (WiFi.status() == 4 ) Serial.println("Failed"); if (WiFi.status() == 5 ) Serial.println("Connection Lost"); if (WiFi.status() == 6 ) Serial.println("Disconnected"); } // *********** Parse Metar Data void ParseMetar(int i) { String parsedmetar = ""; String station = Stations[i].substring(0, 4); if (station == "NULL") return; int data_start = metar.indexOf(station); // Search for station id int data_end = metar.indexOf("", data_start + 1); // Search for "data end" if (data_start > 0 && data_end > 0) { parsedmetar = metar.substring(data_start, data_end); // Parse Metar Data // Remove found data from metar metar = metar.substring(0, data_start) + metar.substring(data_end, metar.length()); Decodedata(i, station, parsedmetar); // DECODE the Station DATA } else { category[i] = "NF"; // Not Found sky[i] = "NF"; // Not Found rem[i] = "Station Not Found"; // Not Found sig_wx[i] = "NF"; // Not Found visab[i] = 0; // Not Found wdir[i] = "NA"; // Not Found wind[i] = "NA"; // Not Found temp[i] = 0; // Not Found dewpt[i] = 0; // Not Found altim[i] = 0; // Not Found old_altim[i] = 0; // Not Found updatetime[i] = "NF"; // Not Found if (WiFi.status() == 3 && httpCode > 0) // Not Network or HTTP Error Serial.println(Time + "\tNo:" + String(i) + "\t" + station + "\tStation Not Found, skipping this one in ParseMetar"); Display_LED (i, 20); // Display Station LED } } // *********** DECODE the Station DATA void Decodedata(int i, String station, String parsedmetar) { int pflag = 0; // Print Flag 0=NO Print 1=Print ALL 2= ALL + RAW DATA // Searching Remarks int search0 = parsedmetar.indexOf(station) + 7; // Start of Remark int search1 = parsedmetar.indexOf(" 0) search2 = parsedmetar.indexOf(" Q") + 6; // nonUS Airport if (search2 < 6) search2 = search1; rem[i] = parsedmetar.substring(search0, search2); int obsh = rem[i].substring(0, 2).toInt(); // Observation time int obsm = rem[i].substring(2, 4).toInt(); int ago = ((Hour - obsh) * 60) + Minute - obsm; if (ago < 0) ago = ((24 - obsh) * 60) + Minute - obsm; rem[i] = rem[i] + " (" + String(ago) + "m ago)"; // Add Observation dtime if (updatetime[i].substring(0, 4) != rem[i].substring(0, 4)) { // UPDATE Station updatetime[i] = rem[i].substring(0, 4); rem[i] = "new " + rem[i]; if (pflag != 0) { if (pflag == 2) Serial.println("\nParsed Data = " + parsedmetar + "\n"); // Search Failed Serial.println("\nStation No: " + String(i)) + "\t" + Stations[i]; Serial.println("\tRemarks \t = " + rem[i]); } // Searching flight_category search0 = parsedmetar.indexOf(" 0 && search1 - search0 > 2) sig_wx[i] = sig_wx[i] + "Gusts to " + rem[i].substring(search1 - 2, search1 + 2) + "; "; // Variable Wind Dir search0 = rem[i].indexOf("0V"); search1 = rem[i].indexOf("FT"); if (search0 > 0 && search1 < 0) sig_wx[i] = sig_wx[i] + " Wind Dir Varies: " + rem[i].substring(search0 - 2, search0 + 5) + "; "; // Runway Visibility search0 = rem[i].indexOf("SM R"); search1 = rem[i].indexOf("FT"); if (search0 > 0 && search1 > 0) sig_wx[i] = sig_wx[i] + " Runway Vis: " + rem[i].substring(search0 + 3, search1 + 2) + "; "; // Significant Weather in Remarks and making readable Weather if (rem[i].indexOf(" +") > 0) sig_wx[i] = sig_wx[i] + " Heavy"; if (rem[i].indexOf(" -") > 0) sig_wx[i] = sig_wx[i] + " Light"; if (rem[i].indexOf("DR") > 0) sig_wx[i] = sig_wx[i] + " Drifting"; if (rem[i].indexOf("FZ") > 0) sig_wx[i] = sig_wx[i] + " Freezing"; if (rem[i].indexOf("RA") > 0) sig_wx[i] = sig_wx[i] + " Rain "; if (rem[i].indexOf("DZ") > 0) sig_wx[i] = sig_wx[i] + " Drizzle "; if (rem[i].indexOf("SH") > 0) sig_wx[i] = sig_wx[i] + " Showers "; if (rem[i].indexOf("BR") > 0) sig_wx[i] = sig_wx[i] + " Mist; "; if (rem[i].indexOf("MI") > 0) sig_wx[i] = sig_wx[i] + " Shallow"; if (rem[i].indexOf("PR") > 0) sig_wx[i] = sig_wx[i] + " Partial"; if (rem[i].indexOf("BC") > 0) sig_wx[i] = sig_wx[i] + " Patches of"; if (rem[i].indexOf("FG") > 0) sig_wx[i] = sig_wx[i] + " Fog "; if (rem[i].indexOf("GS") > 0) sig_wx[i] = sig_wx[i] + " Small Hail "; if (rem[i].indexOf("GR") > 0) sig_wx[i] = sig_wx[i] + " Large Hail "; if (rem[i].indexOf("IC") > 0) sig_wx[i] = sig_wx[i] + " Ice Crystals "; if (rem[i].indexOf("PL") > 0) sig_wx[i] = sig_wx[i] + " Ice Pellets "; if (rem[i].indexOf("BL") > 0) sig_wx[i] = sig_wx[i] + " Blowing"; if (rem[i].indexOf("SN") > 0) sig_wx[i] = sig_wx[i] + " Snow; "; if (rem[i].indexOf("SG") > 0) sig_wx[i] = sig_wx[i] + " Snow Grains "; if (rem[i].indexOf("DU") > 0) sig_wx[i] = sig_wx[i] + " Dust; "; if (rem[i].indexOf("FU") > 0) sig_wx[i] = sig_wx[i] + " Smoke; "; if (rem[i].indexOf("HZ") > 0) sig_wx[i] = sig_wx[i] + " Haze; "; if (rem[i].indexOf("FY") > 0) sig_wx[i] = sig_wx[i] + " Spray; "; if (rem[i].indexOf("SA") > 0) sig_wx[i] = sig_wx[i] + " Sand "; if (rem[i].indexOf("PO") > 0) sig_wx[i] = sig_wx[i] + " Dust/Sand "; if (rem[i].indexOf("SQ") > 0) sig_wx[i] = sig_wx[i] + " Squalls; "; if (rem[i].indexOf("TS") > 0) sig_wx[i] = sig_wx[i] + " Thunderstorm "; if (rem[i].indexOf("VA") > 0) sig_wx[i] = sig_wx[i] + " Volcanic Ash "; if (rem[i].indexOf(" VC") > 0) sig_wx[i] = sig_wx[i] + "in Vicinity; "; if (rem[i].indexOf("-VC") > 0) sig_wx[i] = sig_wx[i] + "in Vicinity; "; if (rem[i].indexOf("+VC") > 0) sig_wx[i] = sig_wx[i] + "in Vicinity; "; if (rem[i].indexOf("CB ") > 0) sig_wx[i] = sig_wx[i] + " Cumulonimbus Clouds "; if (rem[i].indexOf("TCU") > 0) sig_wx[i] = sig_wx[i] + " Towering Cumulus Clouds "; if (rem[i].indexOf("CBMAM") > 0) sig_wx[i] = sig_wx[i] + " CB Mammatus Clouds "; if (rem[i].indexOf("UP ") > 0) sig_wx[i] = sig_wx[i] + " Unknown Precipitation "; if (sig_wx[i] == "") sig_wx[i] = "None"; if (pflag != 0) Serial.println("\tSignificant Weather = " + sig_wx[i]); // Searching temp_c search0 = parsedmetar.indexOf(" 0) wdir[i] = "VRB"; if (wdir[i] == "NA") Serial.println(Time + "\tNo:" + String(i) + "\t" + station + "\tWind Dir Not Found, in Decodedata"); // Searching wind_speed_kt search0 = parsedmetar.indexOf("wind_speed_kt") + 14; search1 = parsedmetar.indexOf("", search0) - 1; if (sky[i] == "OVX") sky[i] = "OBSECURED"; if (sky[i] == "CAV") sky[i] = "CLEAR"; if (sky[i] == "CLR") sky[i] = "CLEAR"; if (sky[i] == "SKC") sky[i] = "CLEAR"; if (sky[i] == "BKN") sky[i] = sky[i] + " at " + parsedmetar.substring(search1, search2) + " Ft"; if (sky[i] == "FEW") sky[i] = sky[i] + " at " + parsedmetar.substring(search1, search2) + " Ft"; if (sky[i] == "SCT") sky[i] = sky[i] + " at " + parsedmetar.substring(search1, search2) + " Ft"; if (sky[i] == "OVC") sky[i] = sky[i] + " at " + parsedmetar.substring(search1, search2) + " Ft"; cloud_base[i] = parsedmetar.substring(search1, search2).toInt(); if (sky[i] == "OBSECURED") cloud_base[i] = 0; if (sky[i] == "CLEAR") cloud_base[i] = 99999; } if (sky[i] == "NA") cloud_base[i] = 0; if (pflag != 0) Serial.println("\tSky Cover \t = " + sky[i] + "\n\tCloud Base \t = " + String(cloud_base[i])); // Vertical Visibility search0 = parsedmetar.indexOf("") + 13; if (search0 > 13) { sig_wx[i] = sig_wx[i] + " Vertical Visibility = " + parsedmetar.substring(search0, search0 + 3) + " Ft; "; cloud_base[i] = parsedmetar.substring(search0, search0 + 3).toInt(); } // Searching ") + 13; search1 = parsedmetar.indexOf(""); if (search0 < 13) Serial.println(Time + "\tNo:" + String(i) + "\t" + station + "\tAltimeter Not Found, in Decodedata"); if (search0 < 13) altim[i] = 0; else altim[i] = parsedmetar.substring(search0, search1).toFloat(); float Pressure = altim[i]; // in_hg if (pflag != 0) Serial.println("\tAltimeter \t = " + String(altim[i], 2) + " in hg"); // Searching ") + 23; if (search0 < 23) seapres[i] = 0; else seapres[i] = parsedmetar.substring(search0, search0 + 6).toFloat(); if (pflag != 0) Serial.println("\tSea Level Press\t = " + String(seapres[i]) + " in mb"); // Searching elevation_m search0 = parsedmetar.indexOf(" 0 && search2 > 0) Station_name = Station_name + " " + metar.substring(search1 + 6, search2) + ","; search1 = metar.indexOf(" 0) Station_name = Station_name + " " + metar.substring(search1 + 7, search2); } if (search1 > 0 && search2 > 0) Station_name = Station_name + " " + metar.substring(search1 + 9, search2); Stations[i] = Station_name; } // *********** Display Metar/Show Loops void DisplayMetar() { int Wait_Time = 3000; // Delay for Wait_Time (Seconds x 1000) Display_Weather_LEDS (Wait_Time); // Display Weather, Delay for Wait_Time Display_Vis_LEDS (Wait_Time); // Display Visibility [Red-Orange-White], Delay for Wait_Time Display_Wind_LEDS (Wait_Time); // Display Winds [Shades of Aqua], Delay for Wait_Time Display_Temp_LEDS (Wait_Time); // Alt Display Temperatures [Blue-Green-Yellow-Red], Delay for Wait_Time Display_Alt_LEDS (Wait_Time); // Display Altimeter Pressure [Blue-Purple], Delay for Wait_Time Set_Time (); // Update Current Time } // *********** Display Weather void Display_Weather_LEDS (int wait) { Display_Cat_LEDS (); // Display All Categories for (int i = 1; i < (No_Stations + 1); i++) { if (sig_wx[i].length() > 4) { // IF NOT "None" in Sig Wx, Display Weather station_num = i; // for Go_Server - Flash Button // Twinkle for Flashing Weather (index, red,green,blue, pulses, on, off time) if (sig_wx[i].indexOf("KT") > 0) Twinkle(i, 0x00, 0xff, 0xff, 1, 500, 500); //Gusts Aqua if (rem[i].indexOf("FZ") > 0) Twinkle(i, 0x00, 0x00, 0x70, 3, 300, 400); //Freezing Blue if (rem[i].indexOf("FG") > 0) Twinkle(i, 0x30, 0x40, 0x26, 3, 400, 500); //Fog L Yellow if (rem[i].indexOf("BR") > 0) Twinkle(i, 0x22, 0x70, 0x22, 3, 500, 500); //Mist L Green if (rem[i].indexOf("DZ") > 0) Twinkle(i, 0x20, 0x50, 0x00, 3, 300, 300); //Drizzle L Green if (rem[i].indexOf("RA") > 0) Twinkle(i, 0x22, 0xFC, 0x00, 5, 300, 300); //Rain Green if (rem[i].indexOf("HZ") > 0) Twinkle(i, 0x20, 0x20, 0x20, 3, 400, 400); //Haze White/purple if (rem[i].indexOf("SN") > 0) Twinkle(i, 0xaf, 0xaf, 0xaf, 4, 300, 600); //Snow White if (rem[i].indexOf("SG") > 0) Twinkle(i, 0xaf, 0xaf, 0xaf, 4, 300, 600); //Snow White if (rem[i].indexOf("TS") > 0) Twinkle(i, 0xff, 0xff, 0xff, 4, 10, 900); //Thunder White if (rem[i].indexOf("GS") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 100, 800); //S Hail Yellow if (rem[i].indexOf("GR") > 0) Twinkle(i, 0x88, 0x88, 0x00, 4, 100, 800); //L Hail Yellow if (rem[i].indexOf("IC") > 0) Twinkle(i, 0x00, 0x00, 0x40, 3, 300, 400); //Ice C Blue if (rem[i].indexOf("PL") > 0) Twinkle(i, 0x00, 0x00, 0x50, 3, 300, 400); //Ice P Blue if (rem[i].indexOf("VCSH") > 0) Twinkle(i, 0x22, 0xfc, 0x00, 2, 300, 300); //Showers Green if (rem[i].indexOf("TCU") > 0) Twinkle(i, 0x40, 0x40, 0x40, 2, 300, 300); //CU Grey if (rem[i].indexOf("CB") > 0) Twinkle(i, 0x40, 0x40, 0x40, 2, 300, 300); //CB Grey // Rest of Weather = YELLOW if (rem[i].indexOf("DU") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Dust Yellow if (rem[i].indexOf("FU") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Smoke Yellow if (rem[i].indexOf("FY") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Spray Yellow if (rem[i].indexOf("SA") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Sand Yellow if (rem[i].indexOf("PO") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Dust&Sand Yellow if (rem[i].indexOf("SQ") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Squalls Yellow if (rem[i].indexOf("VA") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Volcanic Ash Yellow if (rem[i].indexOf("UP") > 0) Twinkle(i, 0x88, 0x88, 0x00, 3, 500, 500); //Unknown Yellow // Notification of changes = ORANGE if (sig_wx[i] == "Info:") Twinkle(i, 0xaf, 0x58, 0x00, 2, 200, 800); //Flash Orange Set_Cat_LED (i); // Set Category for This Station LED FastLED.show(); } } delay(wait); } // *********** Twinkle for Flashing Weather void Twinkle (int index, byte red, byte green, byte blue, int pulses, int on_time, int off_time) { leds[index - 1].r = 0x00; // Red Off leds[index - 1].g = 0x00; // Green Off leds[index - 1].b = 0x00; // Blue Off FastLED.show(); delay(100); // Wait a smidgen for (int i = 0; i < pulses; i++) { leds[index - 1].r = red; // Red On leds[index - 1].g = green; // Green On leds[index - 1].b = blue; // Blue On FastLED.show(); delay(on_time); leds[index - 1].r = 0x00; // Red Off leds[index - 1].g = 0x00; // Green Off leds[index - 1].b = 0x00; // Blue Off FastLED.show(); delay(off_time); } delay(300); // Wait a little bit for flash button } // *********** Display ALL Categories void Display_Cat_LEDS () { for (int i = 1; i < (No_Stations + 1); i++) { Set_Cat_LED (i); // Set Category for This Station LED } FastLED.show(); delay(200); // Wait a smidgen } // *********** Set Category for One Station LED void Set_Cat_LED (int i) { if (category[i] == "NF" ) leds[i - 1] = CRGB(20, 20, 0); // DIM Yellowish if (category[i] == "NA" ) leds[i - 1] = CRGB(20, 20, 0); // DIM Yellowish if (category[i] == "VFR" ) leds[i - 1] = CRGB::DarkGreen; if (category[i] == "MVFR") leds[i - 1] = CRGB::DarkBlue; if (category[i] == "IFR" ) leds[i - 1] = CRGB::DarkRed; if (category[i] == "LIFR") leds[i - 1] = CRGB::DarkMagenta; } // *********** Display Visibility [Red Orange Pink/White] void Display_Vis_LEDS (int wait) { for (int i = 1; i < (No_Stations + 1); i++) { int hue = visab[i] * 5; // (red yellow white) int sat = 170 - visab[i] * 6; int bright = 200 - visab[i] * 4; leds[i - 1] = CHSV(hue, sat, bright); if (category[i].substring(0, 1) == "N") leds[i - 1] = CHSV( 0, 0, 0); } FastLED.show(); delay(wait); } // *********** Display Winds [Aqua] void Display_Wind_LEDS (int wait) { for (int i = 1; i < (No_Stations + 1); i++) { int Wind = wind[i].toInt(); leds[i - 1] = CRGB(0, Wind * 7, Wind * 7); if (category[i].substring(0, 1) == "N" || wind[i] == "NA") leds[i - 1] = CRGB(0, 0, 0); } FastLED.show(); delay(wait); } // *********** Display Temperatures [Blue Green Yellow Orange Red] void Display_Temp_LEDS (int wait) { for (int i = 1; i < (No_Stations + 1); i++) { int hue = 162 - temp[i] * 4.4; // purple blue green yellow orange red leds[i - 1] = CHSV( hue, 180, 200 - hue / 2); if (temp[i] == 0 && dewpt[i] == 0) leds[i - 1] = CHSV( 0, 0, 0); } FastLED.show(); delay(wait); } // *********** Display Altimeter Pressure [Blue Purple] void Display_Alt_LEDS (int wait) { for (int i = 1; i < (No_Stations + 1); i++) { int hue = (altim[i] - 27.92) * 100; // blue purple leds[i - 1] = CHSV( hue - 30, 130, 150); if (category[i].substring(0, 1) == "N") leds[i - 1] = CHSV( 0, 0, 0); } FastLED.show(); delay(wait); } // *********** Go_Server HTML TASK 2 void Go_Server ( void * pvParameters ) { int sta_n = 1; int Cloud_flag; int wx_flag; int station_flag = 1; int summary_flag = 0; String User; String Browser; for (;;) { WiFiClient client = server.available(); // Listen for incoming clients if (client) { // If a new client connects, String currentLine = ""; // make a String to hold incoming data from the client while (client.connected()) { // loop while the client's connected if (client.available()) { // if there's bytes to read from the client, char c = client.read(); // read a byte, then //Serial.write(c); // print it out the serial monitor header += c; if (c == '\n') { // if the byte is a newline character // if the current line is blank, you got two newline characters in a row. // that's the end of the client HTTP request, so send a response: if (currentLine.length() == 0) { // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) // and a content-type so the client knows what's coming, then a blank line: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); //client.println("Refresh: 60"); // refresh the page automatically every 60 sec client.println(); // Serial.println(header); // Checking for favicon with CHROME int search = header.indexOf("GET /favicon.ico"); if (search >= 0) { // Do nothing station_flag = 0; summary_flag = 0; } else { // Checking for User/Browser search = header.indexOf("User-Agent: Mozilla/5.0 (iPad;"); if (search >= 0) { User = "iPad User"; Browser = "Safari"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (iPhone"); if (search >= 0) { User = "iPhone User"; Browser = "Safari"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (X11; Linux armv7l)"); if (search >= 0) { User = "Linux User"; Browser = "Chromium"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (Windows NT 10.0"); if (search >= 0) User = "Windows 10 User"; search = header.indexOf("User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0;"); if (search >= 0) Browser = "Microsoft Explorer"; search = header.indexOf("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); if (search >= 0) Browser = "Microsoft Edge"; search = header.indexOf("Accept-Language: en-US,en;q=0.9"); if (search >= 0) Browser = "Google Chrome"; } } } } // Checking if AIRPORT CODE was Entered search = header.indexOf("GET /get?Airport_Code="); if (search >= 0) { String Airport_Code = header.substring(search + 22, search + 26); Airport_Code.toUpperCase(); // changes all letters to UPPER CASE sta_n = 0; for (int i = 1; i < (No_Stations + 1); i++) { // check if Airport_Code is in data base if (Airport_Code == Stations[i].substring(0, 4)) sta_n = i; } if (sta_n == 0) { GetData(Airport_Code); // GET Some Metar /DATA Airport_Code = Airport_Code + ","; Stations[0] = Airport_Code.c_str(); updatetime[0] = ""; ParseMetar(0); // Parse Metar DATA old_cloud_base[0] = 0; old_altim[0] = 0; old_wdir[0] = 0; GetData("NAME"); // GET Some Metar /NAME Decode_Name(0); // Decode Station NAME } summary_flag = 0; station_flag = 1; } // Checking which BUTTON was Pressed search = header.indexOf("GET /back HTTP/1.1"); if (search >= 0) { sta_n = sta_n - 1; if (sta_n < 0) sta_n = No_Stations; if (sta_n == 0 && Stations[0].substring(0, 4) == "NULL") sta_n = No_Stations; summary_flag = 0; station_flag = 1; } search = header.indexOf("GET /next HTTP/1.1"); if (search >= 0) { sta_n = sta_n + 1; if (sta_n > No_Stations) sta_n = 0; if (sta_n == 0 && Stations[0].substring(0, 4) == "NULL") sta_n = 1; summary_flag = 0; station_flag = 1; } search = header.indexOf("GET /flash HTTP/1.1"); if (search >= 0) { sta_n = station_num; // From Display Weather if (sta_n < 1 || sta_n > No_Stations) sta_n = 1; summary_flag = 0; station_flag = 1; } search = header.indexOf("GET /summary HTTP/1.1"); if (search >= 0) { summary_flag = 1; station_flag = 1; } if (summary_flag == 1) { // *********** DISPLAY SUMMARY *********** client.print(""); // Display the HTML web page // Responsive in any web browser, Header client.print(""); client.print("METAR"); // TITLE client.print(""); // Closes Style & Header // Web Page Body client.print("

METAR Summary

"); client.print("Summary of Weather Conditions - Last Update : " + Last_Up_Time + " - Zulu    Next Update in Six Minutes
"); // Display SUMMARY Table *********** client.print(""); client.print(""); for (int i = 0; i < (No_Stations + 1); i++) { String color = ""); else client.print(""); if (i == sta_n ) client.print(""); else client.print(color + Stations[i].substring(0, 4) + ""); client.print(color + category[i] + ""); if (sky[i].length() < 11) { client.print(color + sky[i]); } else { Cloud_flag = 0; if (old_cloud_base[i] > 0) { //if (cloud_base[i] >= old_cloud_base[i] + diff_in_clouds) Cloud_flag = 1; // Significant INCREASE in Cloud Base if (cloud_base[i] <= old_cloud_base[i] - diff_in_clouds) Cloud_flag = 1; // Significant DECREASE in Cloud Base if (cloud_base[i] <= old_cloud_base[i] - diff_in_clouds && sig_wx[i].substring(0, 4) == "None") sig_wx[i] = "Info:"; // Triggers Orange Flashing & MistyRose } if (sig_wx[i] == "Info:") client.print(""); client.print(color + String(visab[i]) + ""); client.print(color + wdir[i] + ""); client.print(color + wind[i] + ""); if (temp[i] == 0 && dewpt[i] == 0) client.print(color + "NA"); else client.print(color + String(temp[i], 1) + ""); float TempF = temp[i] * 1.8 + 32; // deg F //client.print(color + String(TempF, 1) + ""); wx_flag = 0; if (old_altim[i] > 0) { if (altim[i] >= old_altim[i] + diff_in_press) wx_flag = 1; // Significant INCREASE in Pressure if (altim[i] <= old_altim[i] - diff_in_press) wx_flag = 1; // Significant DECREASE in Pressure if (wx_flag == 1) client.print(""); if (rem[i].substring(0, 3) == "new") client.print(""); } } client.print("
No:IDCATSKY
COVER
VIS
Miles
DIR
from
WIND TEMP
Deg C
ALT
in Hg
REMARKS
"; if (category[i] == "MVFR") color = color + "'Blue'>"; if (category[i] == "IFR" ) color = color + "'Red'>"; if (category[i] == "LIFR") color = color + "'Magenta'>"; if (category[i] == "NA") color = color + "'Black'>"; if (category[i] == "NF") color = color + "'Orange'>"; if (Stations[i].substring(0, 4) != "NULL") { // Display Station if (i == sta_n ) client.print("
" + String(i) + "
" + String(i) + "" + color.substring(4, color.length()) + Stations[i].substring(0, 4) + ""); else client.print(color); client.print(sky[i].substring(0, 3) + " at
" + cloud_base[i] + " Ft "); if (old_cloud_base[i] > 0) { if (cloud_base[i] > old_cloud_base[i]) client.print("⇑"); //up arrow if (cloud_base[i] < old_cloud_base[i]) client.print("⇓"); //down arrow if (cloud_base[i] == old_cloud_base[i]) client.print("⇒"); //right arrow } } client.print("
" + String(altim[i])); else client.print(color + String(altim[i])); if (altim[i] > old_altim[i]) client.print("
⇑"); //up arrow if (altim[i] < old_altim[i]) client.print("
⇓"); //down arrow if (altim[i] == old_altim[i]) client.print("
⇒"); //right arrow } else { client.print(color + String(altim[i])); } client.print("
"); else client.print(color); client.print(rem[i] + "
"); } if (station_flag == 1) { // *********** DISPLAY STATION *********** client.print(""); // Display the HTML web page // Responsive in any web browser. client.print(""); client.print("METAR"); // TITLE client.print(""); // Closes Style & Header // Web Page Body client.print("

METAR Station

"); } if (station_flag == 1 || summary_flag == 1) { client.print("

"); client.print("For : " + Stations[sta_n] + "  #  " + String(sta_n) + "
"); // Display BUTTONS: the ESP32 receives a request in the header ("GET /back HTTP/1.1") client.print(""); client.print(""); client.print(""); client.print("    "); client.print("
    Press 'Flash BUTTON', when LED is Flashing
"); // Display TABLE/FORM: the ESP32 receives a request in the header ("GET /get?Airport_Code=") client.print(""); client.print("
ENTER AIRPORT ID CODE:"); client.print("
"); client.print(""); client.print("
"); Display_LED (sta_n, 300); // Display One Station LED Set_Time(); // GET Current Time String Zulu_T = "Current Zulu Time : " + Time + "      Next Update in less than 0 Minutes
"; int upd = (Last_Up_Min + 6) - Minute; if (Last_Up_Min + 6 > 60) upd = 60 - Minute; if (upd > 0 && upd < 7 ) { Zulu_T.replace("than 0", "than " + String(upd)); if (upd == 1) Zulu_T.replace("Minutes", "Minute"); } client.print(Zulu_T); // Display STATION Table *********** String Bcol = "BORDERCOLOR="; if (category[sta_n] == "VFR" ) Bcol = Bcol + "'Green'"; if (category[sta_n] == "MVFR") Bcol = Bcol + "'Blue'"; if (category[sta_n] == "IFR" ) Bcol = Bcol + "'Red'"; if (category[sta_n] == "LIFR") Bcol = Bcol + "'Magenta'"; if (category[sta_n] == "NA") Bcol = Bcol + "'Black'"; if (category[sta_n] == "NF") Bcol = Bcol + "'Orange'"; String color = ""; client.print(""); client.print("" + color + "" + category[sta_n] + " for " + Stations[sta_n] + ""); client.print(""); if (rem[sta_n].substring(0, 3) == "new" ) client.print(""); client.print("" + color + sig_wx[sta_n]) + ""; // Comments for Weather and Cloud Base if (sky[sta_n].substring(0, 3) == "OVC") client.print("
Overcast Cloud Layer"); if (cloud_base[sta_n] > 0 && cloud_base[sta_n] <= 1200) client.print("
Low Cloud Base"); if (old_cloud_base[sta_n] > 0 && old_altim[sta_n] > 0) { if (cloud_base[sta_n] >= old_cloud_base[sta_n] + diff_in_clouds) // INCREASE client.print("
Significant Increase in Cloud Base"); if (cloud_base[sta_n] <= old_cloud_base[sta_n] - diff_in_clouds) // DECREASE client.print("
Significant Decrease in Cloud Base"); if (cloud_base[sta_n] > old_cloud_base[sta_n] && altim[sta_n] > old_altim[sta_n]) client.print("
Weather is Getting Better"); if (cloud_base[sta_n] < old_cloud_base[sta_n] && altim[sta_n] < old_altim[sta_n]) client.print("
Weather is Getting Worse"); if (sig_wx[sta_n] == "Info:") sig_wx[sta_n] = "Info"; // Reset Orange Flashing } client.print("" + color); if (sky[sta_n].length() > 11) { client.print(sky[sta_n].substring(0, 3) + " Clouds " + sky[sta_n].substring(4, sky[sta_n].length()) + "   "); if (old_cloud_base[sta_n] > 0) { if (cloud_base[sta_n] > old_cloud_base[sta_n]) { client.print("⇑ from "); //up arrow client.print(old_cloud_base[sta_n]); } if (cloud_base[sta_n] < old_cloud_base[sta_n]) { client.print("⇓ from "); //down arrow if (old_cloud_base[sta_n] > 99998) client.print("CLEAR"); else client.print(old_cloud_base[sta_n]); } if (cloud_base[sta_n] == old_cloud_base[sta_n]) { client.print("⇒ no change"); //right arrow } } } else client.print(sky[sta_n]); client.print(""); client.print("" + color + visab[sta_n] + " Statute miles"); client.print(""); float TempF = temp[sta_n] * 1.8 + 32; // deg F client.print(""); client.print(""); float Elevation = elevation[sta_n] * 3.28; //feet client.print(""); float PressAlt = Elevation + (1000 * (29.92 - altim[sta_n])); //feet float DensityAlt = PressAlt + (120 * (temp[sta_n] - (15 - abs(2 * Elevation / 1000)))); // feet client.print(""); client.print("
Flight Category
Remarks" + rem[sta_n] + ""); else client.print("" + rem[sta_n]); client.print("
Significant Weather
Sky Cover
Visibility
Wind from"); int newdir = wdir[sta_n].toInt(); int olddir = old_wdir[sta_n]; if (wind[sta_n] == "CALM") client.print(wind[sta_n]); else client.print(wdir[sta_n] + " Deg at " + wind[sta_n]); if (newdir != 0 && olddir != 0) { if (newdir == olddir) client.print(" : no change"); if (newdir > olddir + 30 || newdir < olddir - 30) { client.print(" : Significant Change from " + String(olddir) + " Deg"); } else if (newdir != olddir) client.print(" : previously " + String(olddir) + " Deg"); } client.print("
Temperature"); if (temp[sta_n] <= 0) client.print(""); else client.print(""); if (temp[sta_n] == 0 && dewpt[sta_n] == 0) client.print("NA"); else client.print(String(temp[sta_n], 1) + " Deg C   :   " + String(TempF, 1) + " Deg F"); if (TempF >= 95.0) client.print(" and Too HOT"); client.print("
Dew Point"); if (dewpt[sta_n] == 0) client.print("NA"); else client.print(String(dewpt[sta_n], 1) + " Deg C
Altimeter" + String(altim[sta_n]) + " in Hg  "); if (old_altim[sta_n] > 0) { if (altim[sta_n] > old_altim[sta_n]) { if (altim[sta_n] > old_altim[sta_n] + diff_in_press) client.print("Significant "); client.print("⇑ from "); //up arrow client.print(old_altim[sta_n], 2); } if (altim[sta_n] < old_altim[sta_n]) { if (altim[sta_n] < old_altim[sta_n] - diff_in_press) client.print("Significant "); client.print("⇓ from "); //down arrow client.print(old_altim[sta_n], 2); } if (altim[sta_n] == old_altim[sta_n]) client.print("⇒ Steady"); //right arrow } client.print("
Elevation" + String(elevation[sta_n], 1) + " m   :   " + String(Elevation, 1) + " Ft
Estimated Density Altitude"); if (temp[sta_n] == 0 || altim[sta_n] == 0) client.print("NA"); else client.print(String(DensityAlt, 1) + " Ft
"); client.print("
Welcome " + User + " with " + Browser + "
"); if (Browser.substring(0, 9) == "Microsoft") client.print("Works best with Google Chrome

"); else client.print("Works best with Microsoft Explorer

"); client.print("File/Host Name: " + String(FileName)); client.print("
Url Address : " + local_ip); client.print("
Dedicated to: F. Hugh Magee"); client.print("
"); } client.println(); // The HTTP response ends with another blank line break; // Break out of the while loop } else { // if you got a newline, then clear currentLine currentLine = ""; } } else if (c != '\r') { // if you got anything else but a carriage return character, currentLine += c; // add it to the end of the currentLine } } // Client Not Available } // Client Disconnected header = ""; // Clear the header variable client.stop(); // Close the connection } } }