/* 10/03/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 = 100 tested 6/24/20 (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 https://www.arduino.cc/en/Main/Software , is needed to initially configure, download software and (optionally) display any program messages. (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 Update_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 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 Messing with memory storage 06/15 Modified to HTTPS 06/23 Removed "Welcome User" 07/11 Modified the URL address 10/03 Changed Temperature Display Colors 10/29 */ #include #include #include #include WiFiMulti wifiMulti; #include #include #include #include "ESPmDNS.h" WiFiServer server(80); // Set web server port number to 80 // Configure Network const char* ssid = "NETWORKNAME"; // your network SSID (name) const char* password = "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 PROGMEM 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 PROGMEM String rem[No_Stations + 1]; // Remarks PROGMEM String sig_wx[No_Stations + 1]; // Significant Weather PROGMEM float temp[No_Stations + 1]; // temperature deg C PROGMEM float dewpt[No_Stations + 1]; // dew point deg C PROGMEM String wind[No_Stations + 1]; // wind speed PROGMEM String wdir[No_Stations + 1]; // wind direction PROGMEM int old_wdir[No_Stations + 1]; // old wind direction PROGMEM float visab[No_Stations + 1]; // visibility PROGMEM String sky[No_Stations + 1]; // sky_cover & cloud_base PROGMEM int cloud_base[No_Stations + 1]; // cloud_base PROGMEM int old_cloud_base[No_Stations + 1]; // old cloud_base PROGMEM float seapres[No_Stations + 1]; // Sea Level Pressure PROGMEM float altim[No_Stations + 1]; // altimeter setting PROGMEM float old_altim[No_Stations + 1]; // old altimeter setting PROGMEM float elevation[No_Stations + 1]; // elevation setting PROGMEM 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; // Last Update Minute int Update_Data = 6; // Updates Data every 6 Minutes (Don't overload AVIATIONWEATHER.GOV) int station_num = 1; // Station # for Server - flash button int httpCode; // Error Code String local_hwaddr; // WiFi local hardware Address String local_swaddr; // WiFi local software Address TaskHandle_t Task1; // Main_Loop, Core 0 TaskHandle_t Task2; // Go_Server, Core 1 //const char* FileName = __FILE__; //FileName =(String(__FILE__).substring(String(__FILE__).lastIndexOf('\\')+1)); const char* ServerName = "metar"; // Address to the server with http://metar.local/ const char* FileName = "METAR_ESP32_MASTER_10_29"; 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"); Serial.println("File Name \t: " + String(FileName) + "\n"); Init_LEDS(); // Initialize LEDs if (ssid == "iPhone") Serial.println(F("** 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(F(" Connected.")); // Print the Signal Strength: long rssi = WiFi.RSSI() + 100; Serial.print("Signal Strength = " + String(rssi)); if (rssi > 50) Serial.println(F(" (>50 - Good)")); else Serial.println(F(" (Could be Better)")); digitalWrite(LED_BUILTIN, LOW); //OFF Serial.println(F("*******************************************")); Serial.println("To View Decoded Station METARs from a Computer or \nCell Phone connected to the " + String(ssid) + " WiFi Network."); WiFi.mode(WIFI_STA); wifiMulti.addAP(ssid, password); if (MDNS.begin(ServerName)) { // The name that will identify your device on the network local_hwaddr = "http://" + WiFi.localIP().toString() + "/summary"; Serial.println("Enter This Url Address \t: " + local_hwaddr); local_swaddr = "http://" + String(ServerName) + ".local/"; Serial.println(" Or This Url Address \t: " + local_swaddr); } else { Serial.println(F("ERROR setting up MDNS responder")); } digitalWrite(LED_BUILTIN, HIGH); //ON configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Get Zulu Time from Server Update_Time(); // Set Up Time,Hour,Minute Serial.print(F("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(F("Dual Core Initialized")); server.begin(); // Start the web server Serial.println(F("Web Server Started")); Serial.println(F("*******************************************")); } void loop() { } // *********** Main_Loop TASK1 void Main_Loop( void * pvParameters ) { for (;;) { Last_Up_Time = Time; Last_Up_Min = Minute; int NextCycle = Last_Up_Min + Update_Data; // Update Cycle time is 6 Minutes(Update_Data) GetAllMetars(); // Get All Metars and Display Categories Update_Time(); // Update Current Time while (NextCycle > Minute) { if ((NextCycle - Minute) > Update_Data) NextCycle = -1; else Display_Metar_LEDS (); // Display Station Metar/Show Loops } } } // *********** Set/Get Current Time, Hour, Minute void Update_Time() { if (!getLocalTime(&timeinfo)) { Serial.print(F("\t\t\t** FAILED to Get Time **\n")); Network_Status (); // 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, start); // 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, int i) { 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 https; https.begin(url); // Start connection and send HTTP header httpCode = https.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 = https.getString(); } https.end(); digitalWrite(LED_BUILTIN, LOW); //OFF } else { https.end(); // Communication HTTP Error digitalWrite(LED_BUILTIN, LOW); //OFF Serial.println(Time + "\tNo:" + String(i) + + "\tCommunication Error = " + String(httpCode) + " : " + https.errorToString(httpCode).c_str()); } } else { Network_Status (); // WiFi Network Error } } // *********** WiFi Network Error void Network_Status () { Serial.print(Time + "\t\t\tWiFi Network : "); if (WiFi.status() == 0 ) Serial.println(F("Idle")); if (WiFi.status() == 1 ) Serial.println(F("Not Available, SSID can not be reached")); if (WiFi.status() == 2 ) Serial.println(F("Scan Completed")); if (WiFi.status() == 3 ) Serial.println(F("Connected")); if (WiFi.status() == 4 ) Serial.println(F("Failed, password incorrect")); if (WiFi.status() == 5 ) Serial.println(F("Connection Lost")); if (WiFi.status() == 6 ) Serial.println(F("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 Reporting"; // Not Found if (httpCode < 0) rem[i] = "Connection Error"; 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 if (WiFi.status() == 3 && httpCode > 0) // Not Network or HTTP Error Serial.println(Time + "\tNo:" + String(i) + "\t" + station + "\tStation Not Reporting, 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 String old_obs_time = rem[i].substring(0, 4); // Last Observation Time if (old_obs_time == "new ") old_obs_time = rem[i].substring(4, 8); // 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(); // METAR Obs Time - Hour int obsm = rem[i].substring(2, 4).toInt(); // METAR Obs Time - Minute int ago = ((Hour - obsh) * 60) + Minute - obsm; // Minutes ago if (ago < 0) ago = ((24 - obsh) * 60) + Minute - obsm; rem[i] = rem[i] + " (" + String(ago) + "m ago)"; // Append Minutes ago if (old_obs_time != rem[i].substring(0, 4)) { // New Time : UPDATE Station 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) + " KTS ; "; // 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("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("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("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.c_str(); } // *********** Display Metar/Show Loops void Display_Metar_LEDS () { int Wait_Time = 5000; // Delay after Loop (Seconds x 1000) Display_Weather_LEDS (8000); // Display Weather Display_Vis_LEDS (Wait_Time); // Display Visibility [Red-Orange-White] Display_Wind_LEDS (Wait_Time); // Display Winds [Shades of Aqua] Display_Temp_LEDS (Wait_Time); // Alt Display Temperatures [Blue-Green-Yellow-Red] Display_Alt_LEDS (Wait_Time); // Display Altimeter Pressure [Blue-Purple] Update_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; // Station # for 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, 1, 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 = 145 - temp[i] * 4.2; // purple blue green yellow orange red leds[i - 1] = CHSV( hue, 180, 150); if (temp[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 diff_in_clouds = 2750; // Significant CHANGE IN CLOUD BASE float diff_in_press = 0.045; // Significant CHANGE IN PRESSURE String header; // Header for Server int sta_n = 1; int Cloud_flag; int wx_flag; int station_flag = 1; int summary_flag = 1; float TempF; 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(F("HTTP/1.1 200 OK")); client.println(F("Content-type:text/html")); client.println(F("Connection: close")); //client.println(F("Refresh: 60")); // refresh the page automatically every 60 sec client.println(); // Serial.println(header); // Checking if AIRPORT CODE was Entered int 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 = -1; for (int i = 0; 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 == -1) { // Airport_Code NOT in Data base, Add as Station[0] sta_n = 0; Stations[sta_n] = Airport_Code.c_str(); GetData("NAME", 0); // GET Some Metar /NAME Decode_Name(sta_n); // Decode Station NAME GetData(Airport_Code, 0); // GET Some Metar /DATA ParseMetar(sta_n); // Parse Metar DATA old_cloud_base[sta_n] = 0; old_altim[sta_n] = 0; old_wdir[sta_n] = 0; } 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(F("")); // Display the HTML web page // Responsive in any web browser, Header client.print(F("")); client.print(F("METAR")); // TITLE client.print(F("")); // Closes Style & Header // Web Page Body client.print(F("

METAR Summary

")); client.print("Summary of Weather Conditions - Last Update : " + Last_Up_Time + " Zulu    Next Update in " + String(Update_Data) + " Minutes
"); // Display SUMMARY Table *********** client.print(F("")); String Deg = "Deg F"; 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(F("")); client.print(color + String(visab[i]) + ""); client.print(color + wdir[i] + ""); client.print(color + wind[i] + ""); TempF = temp[i] * 1.8 + 32; // deg F if (temp[i] == 0 && dewpt[i] == 0) client.print(color + "NA"); else if (Deg == "Deg C") client.print(color + String(temp[i], 1) + ""); else 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(F(""); } } client.print(F("
No:IDCATSKY
COVER
VIS
Miles
WIND
from
WIND
Speed
TEMP
" + Deg + "
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(F("⇑")); //up arrow if (cloud_base[i] < old_cloud_base[i]) client.print(F("⇓")); //down arrow if (cloud_base[i] == old_cloud_base[i]) client.print(F("⇒")); //right arrow } } client.print(F("
" + String(altim[i])); else client.print(color + String(altim[i])); if (altim[i] > old_altim[i]) client.print(F("
⇑")); //up arrow if (altim[i] < old_altim[i]) client.print(F("
⇓")); //down arrow if (altim[i] == old_altim[i]) client.print(F("
⇒")); //right arrow } else { client.print(color + String(altim[i])); } client.print(F("
")); else client.print(color); client.print(rem[i] + "
")); } if (station_flag == 1) { // *********** DISPLAY STATION *********** client.print(F("")); // Display the HTML web page // Responsive in any web browser. client.print(F("")); client.print(F("METAR")); // TITLE client.print(F("")); // Closes Style & Header // Web Page Body client.print(F("

METAR Station

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

")); 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(F("")); client.print(F("")); client.print(F("")); client.print(F("    ")); client.print(F("
    Press 'Flash BUTTON', when LED is Flashing
")); // Display TABLE/FORM: the ESP32 receives a request in the header ("GET /get?Airport_Code=") client.print(F("")); client.print(F("
ENTER AIRPORT ID CODE:")); client.print(F("
")); client.print(F("")); client.print(F("
")); Display_LED (sta_n, 300); // Display One Station LED Update_Time(); // GET Current Time String C_Time = "Current Time : " + Time + " Zulu      Next Update in less than 0 Minutes
"; int upd = (Last_Up_Min + Update_Data) - Minute; if (Last_Up_Min + Update_Data > 60) upd = 60 - Minute; if (upd > 0 && upd < Update_Data + 1) { C_Time.replace("than 0", "than " + String(upd)); if (upd == 1) C_Time.replace("Minutes", "Minute"); } client.print(C_Time); // 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(F("")); 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(F("
Significant Increase in Cloud Base")); if (cloud_base[sta_n] <= old_cloud_base[sta_n] - diff_in_clouds) // DECREASE client.print(F("
Significant Decrease in Cloud Base")); if (cloud_base[sta_n] > old_cloud_base[sta_n] && altim[sta_n] > old_altim[sta_n]) client.print(F("
Weather is Getting Better")); if (cloud_base[sta_n] < old_cloud_base[sta_n] && altim[sta_n] < old_altim[sta_n]) client.print(F("
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(F("⇑ from ")); //up arrow client.print(old_cloud_base[sta_n]); } if (cloud_base[sta_n] < old_cloud_base[sta_n]) { client.print(F("⇓ 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(F("⇒ no change")); //right arrow } } } else client.print(sky[sta_n]); client.print(F("")); client.print("" + color + visab[sta_n] + " Statute miles"); client.print(F("")); TempF = temp[sta_n] * 1.8 + 32; // deg F client.print(F(""); 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(F(""); client.print(F("
Flight Category
Remarks" + rem[sta_n] + ""); else client.print("" + rem[sta_n]); client.print(F("
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(F("
Temperature")); if (temp[sta_n] <= 0) client.print(F("")); else client.print(F("")); if (temp[sta_n] == 0 && dewpt[sta_n] == 0) client.print(F("NA")); else client.print(String(temp[sta_n], 1) + " Deg C   :   " + String(TempF, 1) + " Deg F"); if (TempF >= 95.0) client.print(F(" and Too HOT")); client.print(F("
Dew Point")); if (dewpt[sta_n] <= 0) client.print(F("")); else client.print(F("")); if (dewpt[sta_n] == 0) client.print(F("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(F("Significant ")); client.print(F("⇑ 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(F("Significant ")); client.print(F("⇓ from ")); //down arrow client.print(old_altim[sta_n], 2); } if (altim[sta_n] == old_altim[sta_n]) client.print(F("⇒ Steady")); //right arrow } client.print(F("
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(F("NA")); else client.print(String(DensityAlt, 1) + " Ft
")); client.print("
File Name: " + String(FileName)); client.print("
H/w Address : " + local_hwaddr); client.print("
Url Address : " + local_swaddr); client.print(F("
Dedicated to: F. Hugh Magee")); client.print(F("
")); } 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 } } }