/* 03/24/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. REMARKS: Gets All METARS on every first pass 5/24 Created by John Pipe with help from David Bird Made things a little better and squished some bugs No of Stations = 60 tested 5/24 (100 possible with extenal power) When Error, Successfuly restarts mostly. If not, press "RESET" button on ESP32. Modified Significant Weather to include Cloud Cover & Visibility 12/31 Changed to a TIMED 6 Minute METAR read and update 01/07 Added Diplay for ice and hail 01/30 Added Capability to select any Airport 02/02 Added Summary to HTML 03/04 Cleaned up Get_Time & loop 03/08 Added User 03/10 Added Cloud_base Changes 03/13 Added Alt Pressure Changes 03/13 Added Arrows 03/13 Added Yellow Misc Weather 03/24 */ #include #include #include #include #include WiFiMulti wifiMulti; #include WiFiServer server(80); // Set web server port number to 80 //const char* ssid = "iPhone"; // your network SSID (name) //const char* password = "*********"; // your network password const char* ssid = "NETGEAR46"; // your network SSID (name) const char* password = "*********"; // your network password const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 0; // UTC Time const int daylightOffset_sec = 0; // UTC Time struct tm timeinfo; // Test link: https://www.aviationweather.gov/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString=KFLL String host = "https://aviationweather.gov"; String urlb = "/adds/dataserver_current/httpparam?datasource=metars&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString="; String tafs = "/adds/dataserver_current/httpparam?datasource=tafs&requestType=retrieve&format=xml&mostRecentForEachStation=constraint&hoursBeforeNow=1.25&stationString="; // Set Up LEDS #define NUM_AIRPORTS 52 // Also the number of LEDs CRGB leds[NUM_AIRPORTS]; #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 // Set Up STATIONS std::vector stations ({ // << Do NOT delete this line "NULL, STATION NAME ", // 0 << 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 stations and station names "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[NUM_AIRPORTS + 1]; // Keeps previous time String rem[NUM_AIRPORTS + 1]; // Remarks String sig_wx[NUM_AIRPORTS + 1]; // Significant Weather String temp[NUM_AIRPORTS + 1]; // temperature deg C String dewpt[NUM_AIRPORTS + 1]; // dew point deg C String wind[NUM_AIRPORTS + 1]; // wind speed String wdir[NUM_AIRPORTS + 1]; // wind direction String visab[NUM_AIRPORTS + 1]; // visibility String sky[NUM_AIRPORTS + 1]; // sky_cover & cloud_base int cloud_base[NUM_AIRPORTS + 1]; // cloud_base int old_cloud_base[NUM_AIRPORTS + 1]; // old cloud_base String altim[NUM_AIRPORTS + 1]; // altimeter setting float old_altim[NUM_AIRPORTS + 1]; // old altimeter setting String elevation[NUM_AIRPORTS + 1]; // elevation setting String category[NUM_AIRPORTS + 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" String Old_Time; // Last Update Time "HH:MM" int Hour; // Lastest Hour int Minute; // Lastest Minute int Old_Minute; // Last Update Minute int station_num = 1; // Current Station for Server String header; // Header for Metar Server int httpCode; // Error Code const char* FileName = "METAR_ESP32_03/24"; void setup() { pinMode(LED_BUILTIN, OUTPUT); // the onboard LED Serial.begin(115200); 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(200); Serial.print("."); } Serial.println(" Connected."); digitalWrite(LED_BUILTIN, LOW); //OFF Serial.println("*******************************************"); Serial.println("To View a decoded Station METAR \non a Computer or Cell Phone connected to the Local Network."); Serial.print("Enter this Url Address : http://"); Serial.print(WiFi.localIP()); Serial.println("/"); WiFi.mode(WIFI_STA); wifiMulti.addAP(ssid, password); WiFi.setHostname(FileName); Serial.print("File/Host Name : "); Serial.println(WiFi.getHostname()); configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); //init and get the time Get_Time(); // *********** GET Current Time Serial.println(&timeinfo, "%A, %B %d %Y %H:%M - Zulu"); server.begin(); // Start the webserver Serial.println("Webserver started..."); Startleds(); // *********** Initialize LEDs Serial.println("*******************************************"); delay(800); } // *********** Main Loop void loop() { Old_Time = Time; Old_Minute = Minute; int Trip = Old_Minute + 6; // Cycle time is 6 Minutes GetAllMetars(); // *********** GetAllMetars and Display First Loop DataPrint(); // *********** Print the Station Parameters Get_Time(); // *********** GET Current Time while (Trip > Minute) { if ((Trip - Minute) > 6 ) Trip = -1; else DisplayMetar(); // ******** Display Station Metar/Show Loops 30s } } // *********** GET Current Time -Zulu void Get_Time() { if (!getLocalTime(&timeinfo)) { Serial.println("*********** FAILED to Obtain Time"); return; } char TimeChar[10]; strftime(TimeChar, 10, "%H:%M:%S", &timeinfo); Time = String(TimeChar); Time = Time.substring(0, 5); Hour = (Time.substring(1, 2).toInt()); Minute = (Time.substring(3, 5).toInt()); } // *********** Initialize LEDs void Startleds() { Serial.print("Initializing LEDs for NUM_AIRPORTS = "); Serial.println(NUM_AIRPORTS); // tell FastLED about the LED strip configuration FastLED.addLeds(leds, NUM_AIRPORTS).setCorrection(TypicalLEDStrip); // Set all leds to Black fill_solid(leds, NUM_AIRPORTS, CRGB::Black); FastLED.show(); delay(200); // Wait a little bit // Set master brightness control FastLED.setBrightness(BRIGHTNESS); } // *********** GET All Metars in Chunks void GetAllMetars() { int Step = 20; // 20 Stations at a time for (int j = 0; j < NUM_AIRPORTS; j = j + 1 + Step) { int start = j; int finish = start + Step; if (finish > NUM_AIRPORTS) finish = NUM_AIRPORTS; 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 for (int i = start; i <= finish; i++) { ParseMetar(i); // *********** Parse Metar Data } } } // *********** GET Some Metar Data void GetData(String url) { metar = ""; // Reset Metar Data 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 Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); if (httpCode < 0) Serial.println("Communication Error"); if (httpCode == -1) Serial.println("Server Connection-Refused"); if (httpCode == -11) Serial.println("Server Connection-Time out"); } } else { Serial.print("WiFi Connection"); // WiFi Connection Failed Serial.println(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 if (data_start > 0) { int data_end = data_start + 1420; // 1420 is station data + some parsedmetar = metar.substring(data_start, data_end); // First Pass data_end = parsedmetar.indexOf(" 0) search2 = parsedmetar.indexOf(" Q") + 6; // nonUS Airport if (search2 < 0) Serial.println(Time + " Station " + station + " - Search2 Failed"); if (search2 < 6) search2 = search1; rem[i] = (parsedmetar.substring(search0, search2)); /* Serial.print(i); Serial.println("\tRemarks = " + rem[i]); Serial.print("\tUpdatetime = " + updatetime[i]); if (updatetime[i].substring(0, 4) != rem[i].substring(0, 4)) Serial.println(".dif"); else Serial.println(""); */ 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 (search1 < 0) pflag = 1; // - Search Failed if (pflag != 0) { Serial.print("\nStation " + stations[i] + "\tNo: "); Serial.println(i); Serial.println("\tRemarks = " + rem[i]); } // Reading flight_category category[i] = "NA"; search0 = parsedmetar.indexOf(" 0 && search0 < search1) { sig_wx[i] = "Gusts to " + (rem[i].substring(search1 - 2, search1 + 3)) + "; "; } // Variable Wind Dir or Runway Vis search0 = rem[i].indexOf("0V"); if (search0 > 0) { String Variable=rem[i].substring(search0 - 3, search0 + 5); if (Variable.substring(0, 1)== " ") sig_wx[i] = sig_wx[i] + " Variable Wind Dir" + Variable + "; "; search0 = rem[i].indexOf("SM"); search1 = rem[i].indexOf("FT"); if (search0 > 0 && search1 > 0) { Variable=rem[i].substring(search0 + 3, search1 + 2); sig_wx[i] = sig_wx[i] + " Runway Vis=" + Variable + "; "; } } // 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("BL") > 0) sig_wx[i] = sig_wx[i] + " Blowing"; 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("SN") > 0) sig_wx[i] = sig_wx[i] + " Snow "; 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("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("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 (sig_wx[i] == "") sig_wx[i] = "None"; if (pflag != 0) Serial.println("\tSignificant Weather = " + sig_wx[i]); // Reading temp_c search0 = parsedmetar.indexOf(" 100.0) sig_wx[i] = sig_wx[i] + " but Too HOT "; if (pflag != 0) { Serial.print("\tTemperature degC = " + temp[i] + " : degF = "); Serial.println(TempF, 2); } // Reading dewpoint_c search0 = parsedmetar.indexOf(" 0) wdir[i] = "VRB"; // Reading wind_speed_kt search0 = parsedmetar.indexOf("wind_speed_kt") + 14; search1 = parsedmetar.indexOf(" 11) sky[i] = (parsedmetar.substring(search0, search0 + 3)); { search0 = parsedmetar.indexOf("_ft_agl=") + 9; search1 = parsedmetar.indexOf(" />") - 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(search0, search1)) + " Ft"; if (sky[i] == "FEW") sky[i] = sky[i] + " at " + (parsedmetar.substring(search0, search1)) + " Ft"; if (sky[i] == "SCT") sky[i] = sky[i] + " at " + (parsedmetar.substring(search0, search1)) + " Ft"; if (sky[i] == "OVC") sky[i] = sky[i] + " at " + (parsedmetar.substring(search0, search1)) + " Ft"; //String Cloud_base = (parsedmetar.substring(search0, search1)); //cloud_base[i] = Cloud_base.toInt(); cloud_base[i] = (parsedmetar.substring(search0, search1)).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; } // 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(); } if (pflag != 0) { Serial.println("\tSky Cover \t = " + sky[i]); Serial.print("\tCloud Base \t = "); Serial.println(cloud_base[i]); } // Reading ") + 13; if (search0 > 13) altim[i] = (parsedmetar.substring(search0, search0 + 5)); float Pressure = altim[i].toFloat(); // in_hg if (pflag != 0) { Serial.print("\tAltimeter \t = "); Serial.print(Pressure, 2); Serial.println(" in hg"); } // Reading elevation_m elevation[i] = "NA"; search0 = parsedmetar.indexOf(" 13) elevation[i] = (parsedmetar.substring(search0, search1)); float Elevation = elevation[i].toFloat() * 3.28; // feet if (pflag != 0) { Serial.print("\tElevation m = " + elevation[i] + " : Ft = "); Serial.println(Elevation, 1); } // Calculating Pressure Altitude float PressAlt = Elevation + (1000 * (29.92 - Pressure)); // feet // Calculating Density Altitude float DensityAlt = PressAlt + (120 * (TempC - (15 - abs(2 * Elevation / 1000)))); // feet if (pflag != 0) { Serial.print("\tEstimated Density Altitude Ft = "); Serial.println(DensityAlt, 0); } } if (i != 0) UpdateLED (i, 20); // *********** Update One Station LED } // *********** Update One Station LED void UpdateLED(int index, int wait) { int lednum = index - 1; // LED Number leds[lednum] = (0xFF8000); // Orange Decoding Data FastLED.show(); delay(wait); Set_Category_LED(index, lednum); FastLED.show(); } // *********** Display Station Metar/Show Loops void DisplayMetar() { int wait_Time = 4800; UpdateWeather_LEDS(wait_Time); // Display All Weather, Delay for wait_Time UpdateWind_LEDS(wait_Time); // Display All Winds [Shades of Aqua], Delay for wait_Time UpdateTemp_LEDS(wait_Time); // Display All Temperatues [Shades of Yellow] Delay for wait_Time //CalcTemp_LEDS(wait_Time); // Calculate Temperatures [Blue Green Red] Delay for wait_Time UpdateVis_LEDS(wait_Time); // Display All Visabilities [Shades of Light Green], Delay for wait_Time Get_Time(); // *********** GET Current Time } // *********** All Stations for Weather void UpdateWeather_LEDS(int wait) { // digitalWrite(LED_BUILTIN, HIGH); //ON UpdateCategory_LEDS(); // Display All Categories for (int i = 1; i < (NUM_AIRPORTS + 1); i++) { int n = i - 1; // LED Number if (sig_wx[i].substring(0, 4) != "None") { // IF NOT "None" in Sig Wx, Display Weather // Call Twinkle for Weather (index, red,green,blue, pulses, on, off times, led no) if (sig_wx[i].indexOf("KT") > 0) Twinkle(i, 0x00, 0xff, 0xff, 1, 500, 500, n); //Gusts Aqua if (rem[i].indexOf("FZ") > 0) Twinkle(i, 0x00, 0x00, 0x70, 2, 300, 400, n); //Freezing Blue if (rem[i].indexOf("FG") > 0) Twinkle(i, 0x30, 0x40, 0x26, 2, 400, 500, n); //Fog L Yellow if (rem[i].indexOf("BR") > 0) Twinkle(i, 0x22, 0x70, 0x22, 2, 500, 500, n); //Mist L Green if (rem[i].indexOf("DZ") > 0) Twinkle(i, 0x20, 0x50, 0x00, 2, 300, 300, n); //Drizzle L Green if (rem[i].indexOf("RA") > 0) Twinkle(i, 0x00, 0xff, 0x00, 4, 300, 300, n); //Rain Green if (rem[i].indexOf("HZ") > 0) Twinkle(i, 0x20, 0x20, 0x20, 2, 400, 400, n); //Haze White/purple if (rem[i].indexOf("SN") > 0) Twinkle(i, 0xaf, 0xaf, 0xaf, 4, 300, 600, n); //Snow White if (rem[i].indexOf("SG") > 0) Twinkle(i, 0xaf, 0xaf, 0xaf, 4, 300, 600, n); //Snow White if (rem[i].indexOf("TS") > 0) Twinkle(i, 0xff, 0xff, 0xff, 4, 10, 900, n); //Thunder White if (rem[i].indexOf("GS") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //S Hail Yellow if (rem[i].indexOf("GR") > 0) Twinkle(i, 0x88, 0x88, 0x00, 4, 100, 800, n); //L Hail Yellow if (rem[i].indexOf("IC") > 0) Twinkle(i, 0x00, 0x00, 0x40, 3, 300, 400, n); //Ice C Blue if (rem[i].indexOf("PL") > 0) Twinkle(i, 0x00, 0x00, 0x50, 3, 300, 400, n); //Ice P Blue if (rem[i].indexOf("VCSH") > 0) Twinkle(i, 0x00, 0xff, 0x00, 2, 300, 300, n); //Showers Green if (rem[i].indexOf("TCU") > 0) Twinkle(i, 0x40, 0x40, 0x40, 2, 300, 300, n); //CU Grey if (rem[i].indexOf("CB") > 0) Twinkle(i, 0x40, 0x40, 0x40, 2, 300, 300, n); //CB Grey // Rest of Weather = YELLOW if (rem[i].indexOf("DU") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //Dust Yellow if (rem[i].indexOf("FU") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //Smoke Yellow if (rem[i].indexOf("FY") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //Spray Yellow if (rem[i].indexOf("SA") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //Sand Yellow if (rem[i].indexOf("PO") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //Dust&Sand Yellow if (rem[i].indexOf("SQ") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //Squalls Yellow if (rem[i].indexOf("VA") > 0) Twinkle(i, 0x88, 0x88, 0x00, 2, 100, 800, n); //Volcanic Ash Yellow } FastLED.show(); delay(10); } // digitalWrite(LED_BUILTIN, LOW); //OFF delay(wait); } // *********** Display All Stations for Winds [Aqua] void UpdateWind_LEDS(int wait) { for (int index = 1; index < (NUM_AIRPORTS + 1); index++) { int lednum = index - 1; // LED Number int Wind = wind[index].toInt(); if (category[index] == "NF" || wind[index] == "NA") { leds[lednum] = CRGB(0, 0, 0); } else { leds[lednum] = CRGB(0, Wind * 6, Wind * 6); } } FastLED.show(); Handle_Server (1); //********* Handle_Server HTML Routine *********** delay(wait); } // *********** Display All Stations for Temperatures [Yellow/Orange] void UpdateTemp_LEDS(int wait) { for (int index = 1; index < (NUM_AIRPORTS + 1); index++) { int lednum = index - 1; // LED Number int Temp = temp[index].toInt(); // deg C if (category[index] == "NF" || temp[index] == "NA") { leds[lednum] = CRGB(0, 0, 0); } else { //leds[lednum] = CRGB(Temp * 4 + 12 , Temp * 4 + 20 , 0); leds[lednum] = CRGB(Temp * 4 + 6, Temp * 4 + 10, 0); if (Temp > 36) leds[lednum] = CRGB(Temp * 6 + 12 , Temp * 4, 0); // Hot-Red if (Temp < 0) leds[lednum] = CRGB(0 , 0, Temp * -30); // Cold-Blue } } FastLED.show(); Handle_Server (1); //********* Handle_Server HTML Routine *********** delay(wait); } // *********** Calculate Temperatures [Blue Green Red] void CalcTemp_LEDS(int wait) { int maxtemp = 40; for (int index = 1; index < (NUM_AIRPORTS + 1); index++) { int lednum = index - 1; // LED Number float TempC = temp[index].toFloat() / 10; // deg C double scale_blue = sin(TempC * 1.3 + 1.8); double scale_green = sin(TempC * 0.8); double scale_yellow = sin(TempC * 1.3); double scale_red = sin(TempC * 0.8 + 4.6); int Temp = temp[index].toInt(); int b = (maxtemp - Temp) * scale_blue * 2; if (b < 0) b = 0; int g = Temp * scale_yellow; if (g < 0) g = 0; int r = Temp * scale_red + scale_yellow; if (r < 0) r = 0; if (category[index] == "NF" || temp[index] == "NA") leds[lednum] = CRGB(0, 0, 0); else leds[lednum] = CRGB(r, g, b); } FastLED.show(); Handle_Server (1); //********* Handle_Server HTML Routine *********** delay(wait); } // *********** Display All Stations for Visability [Light Green] void UpdateVis_LEDS(int wait) { for (int index = 1; index < (NUM_AIRPORTS + 1); index++) { int lednum = index - 1; // LED Number int Vis = visab[index].toInt(); if (category[index] == "NF" || visab[index] == "NA") { leds[lednum] = CRGB(0, 0, 0); } else { leds[lednum] = CRGB(Vis * 3 + 6, Vis * 6 + 6, Vis * 3 + 6); } } FastLED.show(); Handle_Server (1); //********* Handle_Server HTML Routine *********** delay(wait); } // *********** Display all Categories void UpdateCategory_LEDS() { for (int index = 1; index < (NUM_AIRPORTS + 1); index++) { int lednum = index - 1; // LED Number Set_Category_LED(index, lednum); } FastLED.show(); delay(500); } // *********** Weather for Wind Gusts and Precipitation Only // Call for Weather (index,red,green,blue, pulses, on, off times, led no) void Twinkle(int index, byte red, byte green, byte blue, int Count, int on_Time, int off_Time, int lednum) { leds[lednum].r = 0x00; // Red Off leds[lednum].g = 0x00; // Green Off leds[lednum].b = 0x00; // Blue Off FastLED.show(); delay(600); for (int i = 0; i < Count; i++) { leds[lednum].r = red; // Red On leds[lednum].g = green; // Green On leds[lednum].b = blue; // Blue On FastLED.show(); delay(on_Time); leds[lednum].r = 0x00; // Red Off leds[lednum].g = 0x00; // Green Off leds[lednum].b = 0x00; // Blue Off FastLED.show(); delay(off_Time); } delay(100); Set_Category_LED(index, lednum); } // *********** Set Category for one Station LED void Set_Category_LED (int index, int lednum) { if (category[index] == "VFR" ) leds[lednum] = CRGB::Green; if (category[index] == "MVFR") leds[lednum] = CRGB::Blue; if (category[index] == "IFR" ) leds[lednum] = CRGB::Red; if (category[index] == "LIFR") leds[lednum] = CRGB::Magenta; if (category[index] == "NA") leds[lednum] = (0x102000); // Not Available : Yellowish if (category[index] == "NF") leds[lednum] = (0x102000); // Not Found : Yellowish Handle_Server (index); //********* Handle_Server HTML Routine *********** } // *********** Print the Station Parameters void DataPrint() { int pflag = 0; // Print Flag: 0=NO Print: 1=Print ALL: 2=UPDATES Only: if (pflag != 0) { if (pflag == 1) { Serial.println("\nUpdated, Displaying Stations Show/About 6 Minute Delay"); 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 < (NUM_AIRPORTS + 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" + visab[i]); Serial.print("\t" + wdir[i]); Serial.print("\t" + wind[i]); Serial.print("\t" + temp[i]); Serial.println("\t" + rem[i]); } } } } // *********** Handle_Server HTML *********** void Handle_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 diff_in_clouds = 750; // CHANGE IN CLOUD BASE 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"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (iPhone"); if (search >= 0) { User = "iPhone User"; } else { search = header.indexOf("User-Agent: Mozilla/5.0 (X11; Linux armv7l)"); if (search >= 0) { User = "Linux User"; } 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 < (NUM_AIRPORTS + 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(); ParseMetar(0); // *********** Parse Metar Data } 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 = NUM_AIRPORTS; if (station_num == 0 && stations[0].substring(0, 4) == "NULL") station_num = NUM_AIRPORTS; station_flag = 1; } search = header.indexOf("GET /next HTTP/1.1"); if (search >= 0) { station_num = station_num + 1; if (station_num > NUM_AIRPORTS) station_num = 0; if (station_num == 0 && stations[0].substring(0, 4) == "NULL") station_num = 1; 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) { 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 :" + Old_Time + " - Zulu    Next Update in Six Minutes
"); // Display Table SUMMARY *********** client.print(""); client.print(""); for (int i = 0; i < (NUM_AIRPORTS + 1); i++) { String color = ""); client.print(color + stations[i].substring(0, 4) + ""); client.print(color + category[i] + ""); int Cloud_flag = 0; // Cloud Base int Cloud_flag0 = 0; if (cloud_base[i] >= old_cloud_base[i] + diff_in_clouds || // INCREASE cloud_base[i] <= old_cloud_base[i] - diff_in_clouds) Cloud_flag = 1; // DECREASE if (old_cloud_base[i] > 0 && Cloud_flag == 1) Cloud_flag0 = 1; else Cloud_flag0 = 0; if (Cloud_flag0 == 1) client.print(""); client.print(color + visab[i] + ""); client.print(color + wdir[i] + ""); client.print(color + wind[i] + ""); client.print(color + temp[i] + ""); float Pressure = altim[i].toFloat(); //in_Hg int press_flag = 0; if (old_altim[i] > 0) { if (cloud_base[i] > old_cloud_base[i] && Pressure > old_altim[i]) press_flag = 1; if (cloud_base[i] < old_cloud_base[i] && Pressure < old_altim[i]) press_flag = 1; } if (press_flag == 1) client.print(""); if (rem[i].substring(0, 3) == "new") client.print(""); } } client.print("
No:IDCATSKY COVERVISDIRWINDTEMPALTREMARKS
"; if (category[i] == "MVFR") color = color + "'Blue'>"; if (category[i] == "IFR" ) color = color + "'Red'>"; if (category[i] == "LIFR") color = color + "'Magenta'>"; if (category[i] == "ERR") color = color + "'Black'>"; 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); if (sky[i].length() < 11) { client.print(sky[i]); } else { 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("
"); else client.print(color); client.print(Pressure, 2); if (old_altim[i] > 0) { if (Pressure > old_altim[i]) client.print("
⇑"); //up arrow if (Pressure < old_altim[i]) client.print("
⇓"); //down arrow if (Pressure == old_altim[i]) client.print("
⇒"); //right arrow } 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] + "  #  "); client.print(station_num); client.print("
"); // 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 CODE:"); client.print("
"); client.print(""); client.print("
"); client.print("Current Zulu Time = " + Time + " - Zulu    Next Update in "); if (Old_Minute + 6 > 60) client.print(60 - Minute); else client.print(Old_Minute + 6 - Minute); client.print(" Minutes
"); // Display Table DISPLAY STATION *********** 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] == "ERR") Bcol = Bcol + "'Black'"; 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]) + ""; float Pressure = altim[station_num].toFloat(); //in_Hg // Comments for Weather and Cloud Base if (old_cloud_base[station_num] > 0) { if (cloud_base[station_num] > old_cloud_base[station_num] && Pressure > old_altim[station_num]) client.print("
Weather is Getting Better"); if (cloud_base[station_num] < old_cloud_base[station_num] && Pressure < old_altim[station_num]) client.print("
Weather is Getting Worse"); 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"); } 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 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 TempC = temp[station_num].toFloat(); // deg C float TempF = temp[station_num].toFloat() * 1.8 + 32; // deg F client.print(""); client.print(""); client.print(""); float Elevation = elevation[station_num].toFloat() * 3.28; //feet client.print(""); float PressAlt = Elevation + (1000 * (29.92 - Pressure)); //feet float DensityAlt = PressAlt + (120 * (TempC - (15 - abs(2 * Elevation / 1000)))); // feet client.print("
Flight Category
Remarks" + rem[station_num] + ""); else client.print("" + rem[station_num]); client.print("
Significant Weather
Sky Cover
Visibility
Wind from"); if (wind[station_num] == "CALM") client.println(wind[station_num]); else client.println(wdir[station_num] + " Deg at " + wind[station_num]); client.print("
Temperature" + temp[station_num] + " Deg C :   "); client.print(TempF, 1); client.print(" Deg F
Dewpoint" + dewpt[station_num] + " Deg C
Altimeter"); client.print(Pressure, 2); client.print(" in Hg    "); if (old_altim[station_num] > 0) { if (Pressure > old_altim[station_num]) { if (Pressure > old_altim[station_num] + 0.025) client.print("Significant "); client.print("⇑ from "); //up arrow client.print(old_altim[station_num], 2); } if (Pressure < old_altim[station_num]) { if (Pressure < old_altim[station_num] - 0.025) client.print("Significant "); client.print("⇓ from "); //down arrow client.print(old_altim[station_num], 2); } if (Pressure == old_altim[station_num]) client.print("⇒ Steady"); //right arrow } client.print("
Elevation" + elevation[station_num] + " m    :   "); client.print(Elevation, 1); client.print(" Ft
Estimated Density Altitude"); if (TempC == 0) client.println("NA"); else client.print(DensityAlt, 1); // feet client.print(" 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 Edge

"); client.print("File/Host Name: " + String(FileName) + "
"); 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 } }