/* 05/21/2020 METAR Reporting with LEDs and Local WEB SERVER In Memory of F. Hugh Magee, brother of John Magee author of poem HIGH FLIGHT. https://en.wikipedia.org/wiki/John_Gillespie_Magee_Jr. https://youtu.be/Yg61_kyG2zE HomebuiltHELP; The video that started me on this project. https://youtu.be/xPlN_Tk3VLQ Getting Started with ESP32 video from DroneBot Workshop. https://www.aviationweather.gov/docs/metar/Stations.txt List of all Stations Codes. REMARKS/COMMENTS: Created by John Pipe with help from David Bird. FULLY Configurable for YOUR application. (See the list of all Stations Codes, link above). No of Airports = 60 tested 5/24/19 (More possible with external power). When Error, successfuly recovers. If not, press "RESET" button on ESP32. The code is stored on the ESP32 Development Board 2.4GHz WiFi+Bluetooth Dual Mode, once software is downloaded, it will automatically restart, even after a power off. HOWEVER, a computer with FREE software is required to initially configure, download software and display any program messages (optional). (Watch Getting Started with ESP32 video from DroneBot Workshop, link above). Updates METARS aproximately every six minutes from AVIATIONWEATHER.GOV. A set of WS2812 LEDS show all station CATEGORIES (similar to the HomebuiltHELP video, link above) but cycles through all the stations and flashes individually for: Wind Gusts(Cyan), Precipitation(Green/White), Ice(Blue), Other(Yellow) and Info(Orange). Then displays "RAINBOW" for all stations, for Visibility [Red Orange Pink/White], Wind Speed[Cyan], Temperature Gradient [Blue Green Yellow Orange Red] and Altimeter Pressure Gradient [Blue Purple]. Also, VIEWABLE, with a cell phone or computer connected to the local network: SUMMARY html gives an overview and STATION html shows DETAILED information and MORE. (See Below for Improvements.) Makes a GREAT Christmas Tree Chain of Lights, TOO. MADE THINGS A LITTLE BETTER, BUG FIXES, IMPROVEMENTS, REPAIRS TO TIME-SPACE CONTINUUM, ETC, ETC. 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 Get_Time & loop 03/08 Added User 03/10 Added Cloud_base Change Arrows 03/13 Added Alt Pressure Change Arrows 03/13 Added Yellow Misc Weather 03/24 Added Observation Time 04/01 Added Orange Info Changes 04/05 Cleaned up Parse_Metar 04/14 Added Wind Changes 04/30 Changed Temperature Display Colors 05/06 Added Pressure Display 05/06 Modified Visability Display 05/15 Modified Variable Types 05/15 Tweaked Rainbow Dispalys 05/21 */ #include #include #include #include #include WiFiMulti wifiMulti; #include WiFiServer server(80); // Set web server port number to 80 // Configure Network //const char* ssid = "iPhone"; // your network SSID (name) [can use IPhone Hot Spot] //const char* password = "********"; // your network password const char* ssid = "*********"; // your network SSID (name) const char* password = "*********"; // your network password // Setup for Time Server const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 0; // UTC Time const int daylightOffset_sec = 0; // UTC Time struct tm timeinfo; // Time String "%A, %B %d %Y %H:%M:%S" // To get Station Name and Information // Test link: https://aviationweather.gov/adds/dataserver_current/httpparam?dataSource=Stations&requestType=retrieve&format=xml&stationString=KFLL String host = "https://aviationweather.gov"; String urls = "/adds/dataserver_current/httpparam?dataSource=Stations&requestType=retrieve&format=xml&stationString="; // To get Station METAR Data // Test link: https://www.aviationweather.gov/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString=KFLL String urlb = "/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString="; // Set Up LEDS #define No_Stations 52 // Also the number of LEDs CRGB leds[No_Stations]; #define DATA_PIN 5 // Connect to pin D5/P5 #define LED_TYPE WS2812 #define COLOR_ORDER GRB // WD2811 are RGB or WS2812 are GRB #define BRIGHTNESS 20 // LED Brightness max 36 tested // Define STATIONS & Global Variables std::vector Stations ({ // << Set Up - Do NOT delete this line "NULL, STATION NAME ", // 0 << Reserved - Do NOT delete this line "KCHA, CHATTANOOGA, TN ", // 1 << START modifing 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 and station name "KCSG, COLUBUS, GA ", // 7 and include the "quotes" and the "commas" "KMCN, MACON, GA ", // 8 "KCKF, CORDELLE, GA ", // 9 Note: SKYVECTOR.COM is good for locating METARs "KABY, ALBANY, GA ", // 10 "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 "KBQK, BRUNSWICK, GA ", // 55 "KSAV, SAVANNAH, GA ", // 56 "KTBR, STATESBORO, GA ", // 57 "KAGS, AUGUSTA, GA ", // 58 "KAHN, ATHENS, GA ", // 59 "KCEU, CLEMSON, GA ", // 60 }); // << Do NOT delete this line String updatetime[No_Stations + 1]; // Keeps previous time String rem[No_Stations + 1]; // Remarks String sig_wx[No_Stations + 1]; // Significant Weather float temp[No_Stations + 1]; // temperature deg C float dewpt[No_Stations + 1]; // dew point deg C String wind[No_Stations + 1]; // wind speed String wdir[No_Stations + 1]; // wind direction int old_wdir[No_Stations + 1]; // old wind direction float visab[No_Stations + 1]; // visibility String sky[No_Stations + 1]; // sky_cover & cloud_base int cloud_base[No_Stations + 1]; // cloud_base int old_cloud_base[No_Stations + 1]; // old cloud_base float seapres[No_Stations + 1]; // Sea Level Pressure float altim[No_Stations + 1]; // altimeter setting float old_altim[No_Stations + 1]; // old altimeter setting float elevation[No_Stations + 1]; // elevation setting String category[No_Stations + 1]; // NULL VFR MVFR IFR LIFR //.....................................Black Green Blue Red Magenta #define LED_BUILTIN 2 // ON Board LED GPIO 2 String metar; // Raw METAR data String Time; // Time "HH:MM" int Hour; // Lastest Hour int Minute; // Lastest Minute String Update_Time; // Last Update Time "HH:MM" int Update_Minute; // Last Update Minute int diff_in_clouds = 2750; // Significant CHANGE IN CLOUD BASE float diff_in_press = 0.045; // Significant CHANGE IN PRESSURE int station_num = 1; // Current Station for Server String header; // Header for Server int httpCode; // Error Code IPAddress ip; // WiFi local IP Address String local_ip; // WiFi local IP Address const char* FileName = "METAR_ESP32_05_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"); digitalWrite(LED_BUILTIN, HIGH); //ON Serial.print("WiFi Connecting to "); Serial.print(ssid); // Initializes the WiFi library's network settings. WiFi.begin(ssid, password); // CONNECT to Wifi network: while (WiFi.status() != WL_CONNECTED) { delay(300); // Wait a little bit Serial.print("."); } Serial.println(" Connected."); ip = WiFi.localIP(); local_ip = "http://" + WiFi.localIP().toString() + "/summary"; Serial.println("*******************************************"); Serial.println("To View Decoded Station METARs from a\nComputer or Cell Phone connected to the Local Network."); Serial.println("Enter this Url Address \t: " + local_ip); WiFi.mode(WIFI_STA); wifiMulti.addAP(ssid, password); WiFi.setHostname(FileName); Serial.println("File/Host Name \t\t: " + String(FileName)); configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Get Time from Server Get_Time(); // *********** RTC Current Time, Hour & Minute Serial.print("Date & Time \t\t: "); Serial.println(&timeinfo, "%A, %B %d %Y %H:%M - Zulu"); server.begin(); // Start the web server Serial.println("Web Server Started"); digitalWrite(LED_BUILTIN, LOW); //OFF Init_LEDS(); // *********** Initialize LEDs Serial.println("*******************************************"); } // *********** Main Loop void loop() { Update_Time = Time; Update_Minute = Minute; int Cycle = Update_Minute + 6; // Cycle time is 6 Minutes GetAllMetars(); // *********** GetAllMetars and Display First Loop DataPrint(); // *********** Print the Station Parameters (if set) Get_Time(); // *********** GET Current Time while (Cycle > Minute) { if ((Cycle - Minute) > 6 ) Cycle = -1; else DisplayMetar(); // ******** Display Station Metar/Show Loops 30s+ } } // *********** RTC Current Time, Hour & Minute -Zulu void Get_Time() { if (!getLocalTime(&timeinfo)) { Serial.println("*********** FAILED to Obtain Time"); return; } 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.print("Initializing LEDs for No_Stations = "); Serial.println(No_Stations); // Set up the strip configuration FastLED.addLeds(leds, No_Stations).setCorrection(TypicalLEDStrip); // Set master brightness control FastLED.setBrightness(BRIGHTNESS); // Set all leds to Black fill_solid(leds, No_Stations, CRGB::Black); FastLED.show(); delay(200); // Wait a little bit } // *********** GET All Metars in Chunks void GetAllMetars() { int Step = 20; // 20 Stations at a time for (int j = 0; j < No_Stations; j = j + 1 + Step) { int start = j; int finish = start + Step; if (finish > No_Stations) finish = No_Stations; String url = ""; for (int i = start; i <= finish; i++) { String station = Stations[i].substring(0, 5); url = url + String (station); } int len = url.length(); url = url.substring(0, len - 1); // remove last "comma" GetData(url); // *********** GET Some Metar Data 20 Stations at a time for (int i = start; i <= finish; i++) { ParseMetar(i); // *********** Parse Metar Data one Station at a time } } } // *********** GET Some Metar Data/Name void GetData(String url) { metar = ""; // Reset Metar Data if (url == "NAME") url = host + urls + Stations[0]; else url = host + urlb + url; //Serial.println("url = " + url); // Check for WiFi connection if ((wifiMulti.run() == WL_CONNECTED)) { digitalWrite(LED_BUILTIN, HIGH); // ON HTTPClient http; http.begin(url); // Start connection and send HTTP header httpCode = http.GET(); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled // file found at server if (httpCode == HTTP_CODE_OK) { metar = http.getString(); } http.end(); digitalWrite(LED_BUILTIN, LOW); //OFF } else { http.end(); digitalWrite(LED_BUILTIN, LOW); //OFF if (httpCode < 0) Serial.print(Time + " Communication Error - "); Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } } else { Serial.print("WiFi Connection : "); // WiFi Connection Failed Serial.print(WiFi.status()); if (WiFi.status() == 0 ) Serial.println(" : IDLE"); if (WiFi.status() == 3 ) Serial.println(" : CONNECTED"); if (WiFi.status() == 4 ) Serial.println(" : FAILED"); if (WiFi.status() == 5 ) Serial.println(" : LOST"); if (WiFi.status() == 6 ) Serial.println(" : DISCONNECTED"); } } // *********** Parse Metar Data void ParseMetar(int i) { String parsedmetar = ""; String station = Stations[i].substring(0, 4); if (station == "NULL") return; int data_start = metar.indexOf(station); // Search for station id int data_end = metar.indexOf("", data_start + 1); // Search for "data end" if (data_start > 0 && data_end > 0) { parsedmetar = metar.substring(data_start, data_end); // Parse Metar Data // Remove found data from metar metar = metar.substring(0, data_start) + metar.substring(data_end, metar.length()); Decodedata(i, station, parsedmetar); // *********** DECODE the Station DATA Routine } else { category[i] = "NF"; // Not Found sky[i] = "NF"; // Not Found rem[i] = "Station Not Found"; // Not Found sig_wx[i] = "NF"; // Not Found visab[i] = 0; // Not Found wdir[i] = "NA"; // Not Found wind[i] = "NA"; // Not Found temp[i] = 0; // Not Found dewpt[i] = 0; // Not Found altim[i] = 0; // Not Found old_altim[i] = 0; // Not Found updatetime[i] = "NF"; // Not Found Display_LED (i, 20); // *********** Update One Station LED Serial.println(Time + " " + station + " No:" + String(i) + "\tStation Not Found, skipping this one - ParseMetar"); } } // *********** DECODE the Station DATA Routine void Decodedata(int i, String station, String parsedmetar) { int pflag = 0; // Print Flag 0=NO Print 1=Print ALL 2= ALL + RAW DATA // Searching Remarks int search0 = parsedmetar.indexOf(station) + 7; // Start of Remark int search1 = parsedmetar.indexOf(" 0) search2 = parsedmetar.indexOf(" Q") + 6; // nonUS Airport if (search2 < 6) search2 = search1; rem[i] = parsedmetar.substring(search0, search2); int obsh = rem[i].substring(0, 2).toInt(); // Observation time int obsm = rem[i].substring(2, 4).toInt(); int ago = ((Hour - obsh) * 60) + Minute - obsm; if (ago < 0) ago = ((24 - obsh) * 60) + Minute - obsm; rem[i] = rem[i] + " (" + String(ago) + "m ago)"; // Add Observation dtime if (updatetime[i].substring(0, 4) != rem[i].substring(0, 4)) { // UPDATE Station updatetime[i] = rem[i].substring(0, 4); rem[i] = "new " + rem[i]; if (pflag != 0) { if (pflag == 2) Serial.println("\nParsed Data = " + parsedmetar + "\n"); // Search Failed Serial.println("\nStation " + Stations[i] + " No: " + String(i)); Serial.println("\tRemarks \t = " + rem[i]); } // Searching flight_category search0 = parsedmetar.indexOf(" 0 && search1 - search0 > 2) sig_wx[i] = sig_wx[i] + "Gusts to " + rem[i].substring(search1 - 2, search1 + 2) + "; "; // Variable Wind Dir search0 = rem[i].indexOf("0V"); search1 = rem[i].indexOf("FT"); if (search0 > 0 && search1 < 0) sig_wx[i] = sig_wx[i] + " Wind Dir Varies: " + rem[i].substring(search0 - 2, search0 + 5) + "; "; // Runway Visibility search0 = rem[i].indexOf("SM R"); search1 = rem[i].indexOf("FT"); if (search0 > 0 && search1 > 0) sig_wx[i] = sig_wx[i] + " Runway Vis: " + rem[i].substring(search0 + 3, search1 + 2) + "; "; // Significant Weather in Remarks and making readable Weather if (rem[i].indexOf(" +") > 0) sig_wx[i] = sig_wx[i] + " Heavy"; if (rem[i].indexOf(" -") > 0) sig_wx[i] = sig_wx[i] + " Light"; if (rem[i].indexOf("DR") > 0) sig_wx[i] = sig_wx[i] + " Drifting"; if (rem[i].indexOf("FZ") > 0) sig_wx[i] = sig_wx[i] + " Freezing"; if (rem[i].indexOf("RA") > 0) sig_wx[i] = sig_wx[i] + " Rain; "; if (rem[i].indexOf("DZ") > 0) sig_wx[i] = sig_wx[i] + " Drizzle "; if (rem[i].indexOf("SH") > 0) sig_wx[i] = sig_wx[i] + " Showers "; if (rem[i].indexOf("BR") > 0) sig_wx[i] = sig_wx[i] + " Mist; "; if (rem[i].indexOf("MI") > 0) sig_wx[i] = sig_wx[i] + " Shallow"; if (rem[i].indexOf("PR") > 0) sig_wx[i] = sig_wx[i] + " Partial"; if (rem[i].indexOf("BC") > 0) sig_wx[i] = sig_wx[i] + " Patches of"; if (rem[i].indexOf("FG") > 0) sig_wx[i] = sig_wx[i] + " Fog; "; if (rem[i].indexOf("GS") > 0) sig_wx[i] = sig_wx[i] + " Small Hail "; if (rem[i].indexOf("GR") > 0) sig_wx[i] = sig_wx[i] + " Large Hail "; if (rem[i].indexOf("IC") > 0) sig_wx[i] = sig_wx[i] + " Ice Crystals "; if (rem[i].indexOf("PL") > 0) sig_wx[i] = sig_wx[i] + " Ice Pellets "; if (rem[i].indexOf("BL") > 0) sig_wx[i] = sig_wx[i] + " Blowing"; if (rem[i].indexOf("SN") > 0) sig_wx[i] = sig_wx[i] + " Snow; "; if (rem[i].indexOf("SG") > 0) sig_wx[i] = sig_wx[i] + " Snow Grains "; if (rem[i].indexOf("DU") > 0) sig_wx[i] = sig_wx[i] + " Dust; "; if (rem[i].indexOf("FU") > 0) sig_wx[i] = sig_wx[i] + " Smoke; "; if (rem[i].indexOf("HZ") > 0) sig_wx[i] = sig_wx[i] + " Haze; "; if (rem[i].indexOf("FY") > 0) sig_wx[i] = sig_wx[i] + " Spray; "; if (rem[i].indexOf("SA") > 0) sig_wx[i] = sig_wx[i] + " Sand "; if (rem[i].indexOf("PO") > 0) sig_wx[i] = sig_wx[i] + " Dust/Sand "; if (rem[i].indexOf("SQ") > 0) sig_wx[i] = sig_wx[i] + " Squalls "; if (rem[i].indexOf("TS") > 0) sig_wx[i] = sig_wx[i] + " Thunderstorm "; if (rem[i].indexOf("VA") > 0) sig_wx[i] = sig_wx[i] + " Volcanic Ash "; if (rem[i].indexOf(" VC") > 0) sig_wx[i] = sig_wx[i] + "in Vicinity; "; if (rem[i].indexOf("-VC") > 0) sig_wx[i] = sig_wx[i] + "in Vicinity; "; if (rem[i].indexOf("+VC") > 0) sig_wx[i] = sig_wx[i] + "in Vicinity; "; if (rem[i].indexOf("TCU") > 0) sig_wx[i] = sig_wx[i] + " Towering Cumulus Clouds "; if (rem[i].indexOf("CB ") > 0) sig_wx[i] = sig_wx[i] + " Cumulonimbus 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 + " " + station + + " No:" + String(i) + "\tWind Not Found"); // 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) altim[i] = 0; else altim[i] = parsedmetar.substring(search0, search1).toFloat(); float Pressure = altim[i]; // in_hg if (pflag != 0) Serial.println("\tAltimeter \t = " + String(altim[i], 2) + " in hg"); // Searching ") + 23; if (search0 < 23) seapres[i] = 0; else seapres[i] = parsedmetar.substring(search0, search0 + 6).toFloat(); if (pflag != 0) Serial.println("\tSea Level Press\t = " + String(seapres[i]) + " in mb"); // Searching elevation_m search0 = parsedmetar.indexOf(" 0 && search2 > 0) Station_name = Station_name + " " + metar.substring(search1 + 6, search2) + ","; search1 = metar.indexOf(" 0) Station_name = Station_name + " " + metar.substring(search1 + 7, search2); } if (search1 > 0 && search2 > 0) Station_name = Station_name + " " + metar.substring(search1 + 9, search2); Stations[i] = Station_name; } // *********** Display Station Metar/Show Loops void DisplayMetar() { int Wait_Time = 6000; // Delay for Wait_Time 6 Seconds (x1000) Display_Weather_LEDS(Wait_Time); // Display Weather, Delay for Wait_Time Display_Vis_LEDS(Wait_Time); // Display Visabilities [Shades of Light Green], Delay for Wait_Time Display_Wind_LEDS(Wait_Time); // Display Winds [Shades of Aqua], Delay for Wait_Time Display_Temp_LEDS(Wait_Time); // Alt Display Temperatures [Blue Green Yellow Red], Delay for Wait_Time Display_Alt_LEDS(Wait_Time); // Display Alitmeter Pressure [Blue Purple], Delay for Wait_Time Get_Time(); // *********** GET Current Time } // *********** All Stations for Weather void Display_Weather_LEDS (int wait) { Display_Category_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 // Call Twinkle for 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, 0x00, 0xff, 0x00, 4, 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, 0x00, 0xff, 0x00, 3, 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].indexOf("Info") >= 0) Twinkle(i, 0xaf, 0x58, 0x00, 2, 200, 800); //Flash Orange } Set_Category_LED (i); FastLED.show(); } delay(wait); } // *********** Display Visability [Red Orange Pink/White] void Display_Vis_LEDS (int wait) { for (int index = 1; index < (No_Stations + 1); index++) { //int hue (Color) //int sat (white to solid color, <170 pastel) //int bright (black to color >200) int hue = visab[index] * 5; // (red yellow white) int sat = 168 - visab[index] * 6; int bright = 200 - visab[index] * 4; leds[index - 1] = CHSV(hue, sat, bright); if (category[index].substring(0, 1) == "N") leds[index - 1] = CHSV( 0, 0, 0); } FastLED.show(); Go_Server (1); //********* Go_Server HTML Routine *********** delay(wait); } // *********** Display Winds [Aqua] void Display_Wind_LEDS (int wait) { for (int index = 1; index < (No_Stations + 1); index++) { int Wind = wind[index].toInt(); leds[index - 1] = CRGB(0, Wind * 6, Wind * 6); if (category[index].substring(0, 1) == "N" || wind[index] == "NA") leds[index - 1] = CRGB(0, 0, 0); } FastLED.show(); Go_Server (1); //********* Go_Server HTML Routine *********** delay(wait); } // *********** Display Temperatures [Blue Green Yellow Orange Red] void Display_Temp_LEDS (int wait) { for (int index = 1; index < (No_Stations + 1); index++) { int hue = 162 - (temp[index] * 4.4); // purple blue green yellow orange red leds[index - 1] = CHSV( hue, 150, 200 - temp[index]); if (category[index].substring(0, 1) == "N") leds[index - 1] = CHSV( 0, 0, 0); } FastLED.show(); Go_Server (1); //********* Go_Server HTML Routine *********** delay(wait); } // *********** Display Alitmeter Pressure [Blue Purple] void Display_Alt_LEDS (int wait) { for (int index = 1; index < (No_Stations + 1); index++) { int Press = (altim[index] - 27.92) * 100; leds[index - 1] = CHSV( Press, 130, Press - 20); if (category[index].substring(0, 1) == "N") leds[index - 1] = CHSV( 0, 0, 0); } FastLED.show(); Go_Server (1); //********* Go_Server HTML Routine *********** delay(wait); } // *********** Display ALL Categories void Display_Category_LEDS() { for (int index = 1; index < (No_Stations + 1); index++) { Set_Category_LED (index); } FastLED.show(); delay(100); // Wait a little bit } // *********** Weather for Wind Gusts and Precipitation Only // Call for Weather (index, red,green,blue, pulses, on, off times) void Twinkle(int index, byte red, byte green, byte blue, int Count, 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 little bit for (int i = 0; i < Count; 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); } } // *********** Set Category for ONE Station LED void Set_Category_LED (int index) { leds[index - 1] = (0x102000); // NA or NF : Yellowish if (category[index] == "VFR" ) leds[index - 1] = CRGB::Green; if (category[index] == "MVFR") leds[index - 1] = CRGB::Blue; if (category[index] == "IFR" ) leds[index - 1] = CRGB::Red; if (category[index] == "LIFR") leds[index - 1] = CRGB::Magenta; Go_Server (index); //********* Go_Server HTML Routine *********** } // *********** Print the Station Parameters void DataPrint() { int pflag = 0; // Set Print Flag: 0=NO Print 1=Print ALL 2=Only UPDATES if (pflag != 0) { if (pflag == 1) { Serial.println("\nSummary of Weather Conditions; Time = " + Time + " - Zulu"); Serial.println("No\tID \tCAT \tSKY COVER\tVIS\tDIR\tWIND\tTEMP\tREMARKS"); } for (int i = 1; i < (No_Stations + 1); i++) { if (rem[i].substring(0, 3) == "new" || pflag == 1) { Serial.print(i); Serial.print("\t" + Stations[i].substring(0, 4)); Serial.print("\t" + category[i]); Serial.print("\t" + sky[i]); if (sky[i].length() < 11) Serial.print("\t"); Serial.print("\t" + String(visab[i])); Serial.print("\t" + wdir[i]); Serial.print("\t" + wind[i]); Serial.print("\t" + String(temp[i], 1)); Serial.println("\t" + rem[i].substring(0, rem[i].length() - 13)); } } } } // *********** Go_Server HTML *********** void Go_Server (int station_n) { // From Set_Category : station_n=index, else =1 WiFiClient client = server.available(); // Listen for incoMinuteg clients if (client) { // If a new client connects, String currentLine = ""; // make a String to hold incoMinuteg data from the client while (client.connected()) { // loop while the client's connected if (client.available()) { // if there's bytes to read from the client, char c = client.read(); // read a byte, then //Serial.write(c); // print it out the serial monitor header += c; if (c == '\n') { // if the byte is a newline character // if the current line is blank, you got two newline characters in a row. // that's the end of the client HTTP request, so send a response: if (currentLine.length() == 0) { // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) // and a content-type so the client knows what's coming, then a blank line: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); client.println(); // Serial.println(header); // VARIABLES int Cloud_flag; int wx_flag; int station_flag = 1; int summary_flag = 0; String User; String Browser; // Checking for favicon with CHROME int search = header.indexOf("GET /favicon.ico"); if (search >= 0) { // Do nothing station_flag = 0; summary_flag = 0; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (iPad;"); if (search >= 0) { User = "iPad User"; Browser = "Safari"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (iPhone"); if (search >= 0) { User = "iPhone User"; Browser = "Safari"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (X11; Linux armv7l)"); if (search >= 0) { User = "Linux User"; Browser = "Chromiun"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (Windows NT 10.0"); if (search >= 0) User = "Windows 10 User"; search = header.indexOf("User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0;"); if (search >= 0) Browser = "Microsoft Explorer"; search = header.indexOf("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); if (search >= 0) Browser = "Microsoft Edge"; search = header.indexOf("Accept-Language: en-US,en;q=0.9"); if (search >= 0) Browser = "Google Chrome"; } } } } // Checking if AIRPORT CODE was Entered search = header.indexOf("GET /get?Airport_Code="); if (search >= 0) { String Airport_Code = header.substring(search + 22, search + 26); Airport_Code.toUpperCase(); // changes all letters to UPPER CASE station_num = 0; for (int i = 1; i < (No_Stations + 1); i++) { // check if Airport_Code is in data base if (Airport_Code == Stations[i].substring(0, 4)) station_num = i; } if (station_num == 0) { GetData(Airport_Code); // *********** GET Some Metar /DATA Airport_Code = Airport_Code + ","; Stations[0] = Airport_Code.c_str(); updatetime[0] = ""; ParseMetar(0); // *********** Parse Metar DATA old_cloud_base[0] = 0; old_altim[0] = 0; old_wdir[0] = 0; GetData("NAME"); // *********** GET Some Metar /NAME DecodeStation(0); // *********** Decode Station NAME } station_flag = 1; } // Checking which BUTTON was Pressed search = header.indexOf("GET /back HTTP/1.1"); if (search >= 0) { station_num = station_num - 1; if (station_num < 0) station_num = No_Stations; if (station_num == 0 && Stations[0].substring(0, 4) == "NULL") station_num = No_Stations; station_n = station_num; station_flag = 1; } search = header.indexOf("GET /next HTTP/1.1"); if (search >= 0) { station_num = station_num + 1; if (station_num > No_Stations) station_num = 0; if (station_num == 0 && Stations[0].substring(0, 4) == "NULL") station_num = 1; station_n = station_num; station_flag = 1; } search = header.indexOf("GET /flash HTTP/1.1"); if (search >= 0) { station_num = station_n; station_flag = 1; } search = header.indexOf("GET /summary HTTP/1.1"); if (search >= 0) { station_n = 0; summary_flag = 1 ; station_flag = 0; } if (summary_flag == 1) { // *********** DISPLAY SUMMARY *********** client.print(""); // Display the HTML web page // Responsive in any web browser, Header client.print(""); client.print("METAR"); // TITLE client.print(""); // Closes Style & Header // Web Page Body client.print("

METAR Summary

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

METAR Station

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

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

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

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