/* 04/14/2021_PRINT 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.) NOTE: To view these, you need the http address which is shown at start up if the serial monitor is swiched on. 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 from remark "Welcome User" 07/11 Modified the URL address 10/03 Changed Temperature Display Colors 10/29 Address to the server with http://metar.local/ 10/29 Added Comments 02/11/21 Modified Dictinary 04/14/21 */ //#include #include "FastLED.h" #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 = "Network"; // your network SSID (name) //const char* password = "Password"; // your network password const char* ssid = "Network"; // your network SSID (name) const char* password = "Password"; // your network password // Setup from 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 urls = "/adds/dataserver_current/httpparam?dataSource=Stations&requestType=retrieve&format=xml&stationString="; // To get Station DATA // Test link: https://www.aviationweather.gov/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString=KFLL,KFXE String urlb = "/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString="; String host = "https://aviationweather.gov"; // Set Up LEDS #define No_Stations 52 // Number of Stations also Number of LEDs #define DATA_PIN 4 // Connect to pin D5/P5 with 330 to 500 Ohm Resistor #define LED_TYPE WS2812 // WD2811 or WS2812 or NEOPIXEL #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) #define FRAMES_PER_SECOND 120 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 (my home town), GA", // 21 "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 comment[No_Stations + 1]; // Comments 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* ServerName = "metar"; // Address to the server with http://metar.local/ const char* FileName = "METAR_ESP32_04_14_21"; 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 (String(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 } // configTime(0, 0, "pool.ntp.org"); // get UTC time via NTP // time_t now = time(nullptr); 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()); //Serial.println("\nProcessing Station " + String(i) + " " + station + " in ParseMetar"); Decodedata(i, station, parsedmetar); // DECODE the Station DATA } else { //Serial.println("Station " + String(i) + " " + station + " Not Found"); 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 comment[i] = ""; // 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; 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 search_Strt = parsedmetar.indexOf(station, 0) + 7; // Start of Remark int search_Raw_End = parsedmetar.indexOf("= 0 && search3 >= 0) { rem_data_FLAG = search3; // Flag to skip the rest text = comment[i].substring(search2, search3); rem_data = rem_data - search3 + search2; comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REMOVE SLPnnn Sea Level Pressure 12-hour mean search2 = comment[i].indexOf(" SLP", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search3 - search2 == 6) search2 = -1; // If SLPNO if (search2 >= 0 && search3 >= 0) { rem_data_FLAG = search3; // Flag to skip the rest text = comment[i].substring(search2, search3); rem_data = rem_data - search3 + search2; comment[i].replace(comment[i].substring(search2, search3), ""); search_From = search2; } // **** REMOVE TsTTTTTTT Current Temperature // s= 0 Positive s= 1 Negative T= Temp in Deg C search2 = comment[i].indexOf(" T0", 0); // Positive if (search2 < 0) search2 = comment[i].indexOf(" T1", 0); // Negative search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2, search3); if (text.charAt(2) == '0') text.setCharAt(2, '+'); else text.setCharAt(2, '-'); if (text.charAt(6) == '0') text.setCharAt(6, '+'); else text.setCharAt(6, '-'); text = text.substring(2, 5) + "." + text.substring(5, 6) + " DewPt " + text.substring(6, 9) + "." + text.substring(9, 10); rem_data = rem_data - search3 + search2; comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REPLACE Ppppp Hourly Precipition Amount // p=precip amount in Hundredths of Inch search2 = comment[i].indexOf(" P0", 0); search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2, search3); mesg = " Hourly Precip=" + text.substring(3, 4) + "." + text.substring(4, 6) + '"'; mesg.replace("0.00", "Trace"); rem_data = rem_data - search3 + search2; comment[i].replace(comment[i].substring(search2, search3), mesg); search_From = search2; } // **** SKIP THE REST if (rem_data_FLAG != 0 && rem_data > 3) { // CONTINUE or skip to THE REST // **** THE REST // **** REMOVE 6RRRR 3Hr Precipitation Amount in Inches // R=precip amount in Hundredths of Inch ?? search2 = comment[i].indexOf(" 6", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search3 - search2 < 5) search2 = -1; if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); mesg = " 3Hr Precip=" + text.substring(2, 3) + "." + text.substring(3, 6) + '"'; comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REMOVE 7RRRR 24Hr Precipitation Amount in Inches // R=precip amount in Hundredths of Inch ?? search2 = comment[i].indexOf(" 7", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search3 - search2 < 5) search2 = -1; if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); mesg = " 24Hr Precip=" + text.substring(2, 3) + "." + text.substring(3, 5) + '"'; comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REMOVE 3PPPP Air Pressure at station level in hPa search2 = comment[i].indexOf(" 3", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search3 - search2 < 5) search2 = -1; if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 2, search3); comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REPLACE ITnnn Hourly Ice Accretion in Hundredths of Inch // T = 1 or 3 or 6 hourly nnn= Icing amount search2 = comment[i].indexOf(" I", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2, search3); mesg = " ICG=" + text.substring(3, 4) + "." + text.substring(4, 5) + '"'; mesg.replace("0.00", "Trace"); comment[i].replace(comment[i].substring(search2, search3), mesg); } // **** REMOVE 933RRR Water Equivalent of Snow on Ground in tenths in the 0800 UTC report search2 = comment[i].indexOf(" 933", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); mesg = text.substring(3, 6); comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REMOVE 1sTTT 6 Hr MAX Temperature //3-hourly report: A METAR taken at 0300, 0900, 1500, or 2100 UTC. //6-hourly report: A METAR taken at 0000, 0600, 1200, or 1800 UTC // at 0000, 0600, 1200, and 1800 UTC observations // s= 0 Positive s= 1 Negative T= Temp in Deg C search2 = comment[i].indexOf(" 10", search_From); // Positive if (search2 < 0) search2 = comment[i].indexOf(" 11", search_From); // Negative search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2, search3); if (text.charAt(2) == '0') text.setCharAt(2, '+'); else text.setCharAt(2, '-'); text = text.substring(2, 5) + "." + text.substring(5, 6); comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REMOVE 2sTTT 6 Hr MIN Temperature // s= 0 Positive s= 1 Negative T=Temp in Deg C search2 = comment[i].indexOf(" 20", search_From); // Positive if (search2 < 0) search2 = comment[i].indexOf(" 21", search_From); // Negative search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2, search3); if (text.charAt(2) == '0') text.setCharAt(2, '+'); else text.setCharAt(2, '-'); text = text.substring(2, 5) + "." + text.substring(5, 6); comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REMOVE 4sTTTsTTT 24 Hr MAX/MIN Temperature // s= 0 Positive s= 1 Negative T=Temp in Deg C search2 = comment[i].indexOf(" 40", search_From); // Positive if (search2 < 0) search2 = comment[i].indexOf(" 41", search_From); // Negative search3 = comment[i].indexOf(" ", search2 + 1); if (search2 < 0) search2 = comment[i].indexOf(" 41", search_From); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REPLACE 5aPPP Pressure Tendency 3- and 6-hourly report // a=4 Steady a=0123 Incr a=5678 Decr PPP=tenths hPa/mb search2 = comment[i].indexOf(" 5", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search3 - search2 < 5) search2 = -1; int press_change = 0; if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); press_change = comment[i].substring(search2 + 2, search2 + 3).toInt(); if (press_change < 4) mesg = " 3Hr Pressure tend-" + comment[i].substring(search2 + 4, search2 + 5) + "." + comment[i].substring(search2 + 5, search2 + 6) + " mb"; if (press_change > 4) mesg = " 3Hr Pressure tend+" + comment[i].substring(search2 + 4, search2 + 5) + "." + comment[i].substring(search2 + 5, search2 + 6) + " mb"; if (press_change == 0) mesg = " 3Hr Pressure NA"; if (press_change == 4) mesg = " 3Hr Pressure Steady"; comment[i].replace(comment[i].substring(search2, search3), mesg); } // **** REPLACE 4/SSS Snow Depth 0000, 0600, 1200, and 1800 UTC // SSS Inches on the Ground search2 = comment[i].indexOf("4/", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 3, search3); mesg = "Snow Depth on the Ground=" + text + '"'; comment[i].replace(comment[i].substring(search2, search3), mesg); } // **** REMOVE 8/LMH Cloud Types // /=above overcast 3= dense cirrus 6=stratus 9-CB search2 = comment[i].indexOf("8/", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); mesg = text.substring(2, 5); comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REMOVE 8/CLCMCH Cloud Cover // 8/CLCMCH, shall be reported and coded in 3- and 6-hourly reports search2 = comment[i].indexOf(" 8/", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search3 - search2 > 3 ) search3 = -1; // Don't change for now if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); mesg = text.substring(2, 6); comment[i].replace(comment[i].substring(search2, search3), ""); } // **** REPLACE 98MMM Duration of Sunshine in Minutes search2 = comment[i].indexOf(" 98", search_From); search3 = comment[i].indexOf(" ", search2 + 1); if (search2 >= 0 && search3 >= 0) { text = comment[i].substring(search2 + 1, search3); float sun = (float)text.substring(2, 5).toInt() / 60.0; mesg = " Sunshine=" + String(sun) + " Hrs"; comment[i].replace(comment[i].substring(search2, search3), mesg); } } // Decode Weather and make readable in Dictionary Function String weather = Decode_Weather(i, comment[i]); comment[i] = weather; // **** DECODING REMARKS 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 int 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("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("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("BR") > 0) sig_wx[i] = sig_wx[i] + " Mist; "; 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("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("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("2500ft Visibility>8km "); weather.replace("wht", "WHITE: Cloud Base>1500ft Visibility>5km "); weather.replace("grn", "GREEN: Cloud Base>700ft Visibility>3.7km "); weather.replace("ylo", "YELLOW: Cloud Base>300ft Visibility>1600m "); weather.replace("amb", "AMBER: Cloud Base>200ft Visibility>800m "); weather.replace("tend-", "Change -"); // 3 Hr Pressure Tendancy weather.replace("tend+", "Change +"); // 3 Hr Pressure Tendancy weather.replace(" ", " "); // Fixing an error // Peak Wind int search0 = weather.indexOf("Peak Wind"); if (search0 >= 0) { int search1 = weather.indexOf("/", search0 + 9); String Dir = weather.substring(search1 - 5, search1 - 2) + "Deg"; String Wind = weather.substring(search1 - 2, search1) + "KT at "; String Time = weather.substring(search1 + 1, search1 + 5); weather.replace(weather.substring(search1 - 5, search1 + 5), Dir + Wind + Time); } weather.replace("PWINO", "Peak Wind Sensor NA"); return weather; } // *********** Display One Station LED void Display_LED(int index, int wait) { if (index == 0) return; leds[index - 1] = 0xaf5800; // Orange Decoding Data FastLED.show(); delay(wait); Set_Cat_LED(index); // Set Category for This Station LED FastLED.show(); } // *********** DECODE the Station NAME void Decode_Name(int i) { String Station_name = Stations[i].substring(0, 4); int search0 = metar.indexOf(Station_name) + 1; // Start Search from Here int search1 = metar.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, 0x20, 0x50, 0x00, 3, 500, 500); //Mist L Green if (rem[i].indexOf("DZ") > 0) Twinkle(i, 0x22, 0x50, 0x22, 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, 4, 100, 800); //S Hail Yellow if (rem[i].indexOf("GR") > 0) Twinkle(i, 0x88, 0x88, 0x00, 5, 100, 800); //L Hail Yellow if (rem[i].indexOf("IC") > 0) Twinkle(i, 0x00, 0x00, 0x80, 3, 300, 400); //Ice C Blue if (rem[i].indexOf("PL") > 0) Twinkle(i, 0x00, 0x00, 0xa0, 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, 0x30, 0x30, 0x30, 2, 300, 300); //CU Grey if (rem[i].indexOf("CB") > 0) Twinkle(i, 0x30, 0x30, 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 = 176 - temp[i] * 4.2; // purple blue green yellow orange red int hue = 160 - temp[i] * 4; // purple blue green yellow orange red //Serial.println("Station=" + Stations[i].substring(0, 4) + " Temp=" + String(temp[i]) + " Hue=" + String(hue)); 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 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(); cloud_base[sta_n] = 0; altim[sta_n] = 0; old_cloud_base[sta_n] = 0; old_altim[sta_n] = 0; //old_wdir[sta_n] = 0.0; wdir[sta_n] = ""; rem[sta_n] = ""; 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 } 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 = ""); client.print(""); } else { client.print(""); // Display Station Number client.print(color + Stations[i].substring(0, 4) + ""); // Display Station Id } client.print(color + category[i] + ""); // Display Category if (sky[i].length() < 11) { client.print(color + sky[i]); // Display Sky Cover } else { if (old_cloud_base[i] > 0 && sig_wx[i].substring(0, 4) == "None") { if (cloud_base[i] >= old_cloud_base[i] + diff_in_clouds) // Significant INCREASE in Cloud Base sig_wx[i] = "Info:"; // Triggers Orange Flashing & MistyRose if (cloud_base[i] <= old_cloud_base[i] - diff_in_clouds) // Significant DECREASE in Cloud Base sig_wx[i] = "Info:"; // Triggers Orange Flashing & MistyRose } if (sig_wx[i] == "Info:") client.print(F("")); client.print(color + String(visab[i]) + ""); // Display Visibility client.print(color + wdir[i] + ""); // Display Wind Direction client.print(color + wind[i] + ""); // Display Wind Speed 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.1) { 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("")); // Display Remarks 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) + "" + color.substring(4, color.length()) + Stations[i].substring(0, 4) + "
" + String(i) + "")); 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 (comment[i].length() > 1) client.print("
" + comment[i]); // Display Comment client.print("
")); } 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 'BUTTON' above, 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 } if (comment[sta_n].length() > 1) // Comment client.print("
" + comment[sta_n] + ""); 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 } } }