diff --git a/MMM-aviationwx.js b/MMM-aviationwx.js index e79304d..63c7a15 100644 --- a/MMM-aviationwx.js +++ b/MMM-aviationwx.js @@ -24,11 +24,15 @@ Module.register("MMM-aviationwx", { // Initialize var for storing data wxdata: [], + airportList:[], + // Default module configuration variables defaults: { - airports: "KSFO,PAO,HAF,JFK", // continental U.S. airports only - updateInterval: 10, // in minutes + airports: "KSFO ,JFK ,LFBD ,ORY", // airports list + US_country: "Y ,Y ,N ,N",//if the airport is in the US put Y (yes) otherwise put N (no) + updateInterval: 10, // in minutes + legend:1 // show legend for VFR, MVFR, IFR, MIFR }, getScripts: function() { @@ -41,6 +45,13 @@ Module.register("MMM-aviationwx", { // Entry point for module start: function() { + + // remove white space and tab + this.config.airports = this.config.airports.replace(/\s/g, ''); + this.config.airports = this.config.airports.replace(/\t+/g, ""); + this.config.US_country = this.config.US_country.replace(/\s/g, ''); + this.config.US_country = this.config.US_country.replace('\t',''); + this.getWX(); this.scheduleUpdate(); }, @@ -74,12 +85,15 @@ Module.register("MMM-aviationwx", { // Format data for each airport var airportList = this.config.airports.split(","); airportList = airportList.map(function (ap) { return ap.trim(); }); + var airportContry = this.config.US_country.split(","); + airportContry = airportContry.map(function (ap) { return ap.trim(); }); var notFound = ""; + var airportKey =""; for (var i = 0; i < airportList.length; i++) { - var airportKey = (airportList[i].length === 3) ? "K" + airportList[i] : airportList[i]; - - if (!(airportKey in this.wxdata)) { + airportKey = airportList[i]; + + if (!(this.wxdata[airportKey].METAR)) { if (airportKey) notFound += airportKey + " "; console.log("Error: " + airportKey + " data not found. " + "Check correct code, or airport has stopped reporting METAR for the day."); @@ -88,43 +102,46 @@ Module.register("MMM-aviationwx", { // Pull out variables for display var airport = this.wxdata[airportKey]; - var icao = airport.id; - var iata = airport.id.substr(1); - var name = airport.site; - var fltcat = airport.fltcat; - var temp = parseInt(airport.temp); - var dewpoint = parseInt(airport.dewp); - var winddir = this.padZeroes(airport.wdir, 3); - var windspeed = this.padZeroes(airport.wspd, 2); + var icao = airport.METAR.id; + var iata = airport.IATA; + var name = airport.METAR.site; + var fltcat = airport.METAR.fltcat; + var temp = parseInt(airport.METAR.temp); + var dewpoint = parseInt(airport.METAR.dewp); + var winddir = this.padZeroes(airport.METAR.wdir, 3); + var windspeed = this.padZeroes(airport.METAR.wspd, 2); var wind = (windspeed > 0) ? winddir + "@" + windspeed + "kt" : "CALM"; - var visibility = airport.visib; - var wx = airport.wx || ""; - var cover = airport.cover; - var ceiling = (airport.ceil) ? cover + " " + airport.ceil : cover; - var obsTime = airport.obsTime; // yyyy-mm-ddThh:mm:ssZ + var visibility = airport.METAR.visib; + var wx = airport.METAR.wx || ""; + var cover = airport.METAR.cover; + var ceiling = (airport.METAR.ceil) ? cover + " " + airport.METAR.ceil : cover; + var obsTime = airport.METAR.obsTime; // yyyy-mm-ddThh:mm:ssZ obsTime = obsTime.replace("Z", " +0000").replace("T", " "); var obsTimeMoment = moment(obsTime, "YYYY-MM-DD HH:mm:ss Z").local(); var minsSinceObs = moment().diff(obsTimeMoment, "minutes"); - var rawMETAR = airport.rawOb; + var rawMETAR = airport.METAR.rawOb; var delay = 0; // 0 = no data, 1 = delay, 2 = no delay - // Check if FAA delay data for airport exists - if ("FAA" in airport) { - if (airport.FAA.delay === "true") { - // See http://www.fly.faa.gov/Products/Glossary_of_Terms/glossary_of_terms.html - // for common FAA delay abbreviations - var delay = 1; - var delayReason = airport.FAA.status.reason; - var delayType = airport.FAA.status.type; - var minDelay = airport.FAA.status.minDelay; - var delayAvg = airport.FAA.status.avgDelay; - var maxDelay = airport.FAA.status.maxDelay; - var delayTime; - if (minDelay) delayTime = "Min delays of " + minDelay; - if (delayAvg) delayTime = "Avg delays of " + delayAvg; - if (maxDelay) delayTime = "Max delays of " + maxDelay; - } else { - var delay = 2; + if (airportContry[i] === "Y") //check for US only airport + { + // Check if FAA delay data for airport exists + if ("FAA" in airport) { + if (airport.FAA.delay === "true") { + // See http://www.fly.faa.gov/Products/Glossary_of_Terms/glossary_of_terms.html + // for common FAA delay abbreviations + var delay = 1; + var delayReason = airport.FAA.status.reason; + var delayType = airport.FAA.status.type; + var minDelay = airport.FAA.status.minDelay; + var delayAvg = airport.FAA.status.avgDelay; + var maxDelay = airport.FAA.status.maxDelay; + var delayTime; + if (minDelay) delayTime = "Min delays of " + minDelay; + if (delayAvg) delayTime = "Avg delays of " + delayAvg; + if (maxDelay) delayTime = "Max delays of " + maxDelay; + } else { + var delay = 2; + } } } @@ -146,7 +163,11 @@ Module.register("MMM-aviationwx", { var nameCell = document.createElement("td"); nameCell.className = "bright nodec left-align"; var tafUrl = "https://aviationweather.gov/taf/data?ids=" + icao + "&format=decoded&metars=on&layout=on"; - nameCell.innerHTML = this.wrapInLink(iata, tafUrl) + " "; + if ( iata === "" || iata ==="999"){ // put ICAO code if IATA doesn't exist + nameCell.innerHTML = this.wrapInLink(icao, tafUrl) + " "; + }else{ + nameCell.innerHTML = this.wrapInLink(iata, tafUrl) + " "; + } nameCell.setAttribute("title", name + " Airport"); if (delay === 1) { var delaySpan = document.createElement("span"); @@ -186,6 +207,72 @@ Module.register("MMM-aviationwx", { // Append row table.appendChild(row); + + } + + // Show Legend Flight Category (VFR, MVFR, IFR, LIFR) + if(this.config.legend === 1){ + var legendRow = document.createElement("tr"); + var Legend_container=document.createElement("th"); + Legend_container.setAttribute("colSpan", "3"); + var statusH = document.createElement("a"); + statusH.innerHTML = "Legend: "; + Legend_container.appendChild(statusH); + + //VFR + var statusCell_L_vfr = document.createElement("a"); + var statusSpan_L_vfr = document.createElement("span"); + statusSpan_L_vfr.className = "vfr"; + statusSpan_L_vfr.setAttribute("title", "VFR"); + statusSpan_L_vfr.innerHTML = " ◉" + statusCell_L_vfr.appendChild(statusSpan_L_vfr); + Legend_container.appendChild(statusCell_L_vfr); + var Legend_vfr = document.createElement("a"); + Legend_vfr.className = "left-align"; + Legend_vfr.innerHTML = " VFR"; + Legend_container.appendChild(Legend_vfr); + + //MVFR + var statusCell_L_mvfr = document.createElement("a"); + var statusSpan_L_mvfr = document.createElement("span"); + statusSpan_L_mvfr.className = "mvfr"; + statusSpan_L_mvfr.setAttribute("title", "MVFR"); + statusSpan_L_mvfr.innerHTML = " ◉" + statusCell_L_mvfr.appendChild(statusSpan_L_mvfr); + Legend_container.appendChild(statusCell_L_mvfr); + var Legend_mvfr = document.createElement("a"); + Legend_mvfr.className = "left-align"; + Legend_mvfr.innerHTML = " MVFR"; + Legend_container.appendChild(Legend_mvfr); + + //IFR + var statusCell_L_ifr = document.createElement("a"); + var statusSpan_L_ifr = document.createElement("span"); + statusSpan_L_ifr.className = "ifr"; + statusSpan_L_ifr.setAttribute("title", "IFR"); + statusSpan_L_ifr.innerHTML = " ◉" + statusCell_L_ifr.appendChild(statusSpan_L_ifr); + Legend_container.appendChild(statusCell_L_ifr); + var Legend_ifr = document.createElement("a"); + Legend_ifr.className = "left-align"; + Legend_ifr.innerHTML = " IFR"; + Legend_container.appendChild(Legend_ifr); + + //MIFR + var statusCell_L_mifr = document.createElement("a"); + var statusSpan_L_mifr = document.createElement("span"); + statusSpan_L_mifr.className = "mifr"; + statusSpan_L_mifr.setAttribute("title", "MIFR"); + statusSpan_L_mifr.innerHTML = " ◉" + statusCell_L_mifr.appendChild(statusSpan_L_mifr); + Legend_container.appendChild(statusCell_L_mifr); + var Legend_mifr = document.createElement("a"); + Legend_mifr.className = "left-align"; + Legend_mifr.innerHTML = " MIFR"; + Legend_container.appendChild(Legend_mifr); + + legendRow.appendChild(Legend_container); + table.appendChild(legendRow); } // Identify any airports for which data was not retrieved @@ -229,9 +316,9 @@ Module.register("MMM-aviationwx", { // Data Handling Functions getWX: function () { - var metarUrl = "https://aviationweather.gov/gis/scripts/MetarJSON.php?density=all"; + var metarUrl = "https://aviationweather.gov/gis/scripts/MetarJSON.php?density=all&bbox=-180,-90,180,90"; var FAAUrl = "http://services.faa.gov/airport/status/?format=application/json" - var payload = [this.config.airports, metarUrl, FAAUrl]; + var payload = [this.config.airports, this.config.US_country, metarUrl, FAAUrl]; this.sendSocketNotification("GET_WX", payload); }, @@ -241,4 +328,4 @@ Module.register("MMM-aviationwx", { this.updateDom(self.config.fadeSpeed); } }, -}); +}); \ No newline at end of file diff --git a/README.md b/README.md index 446b2b9..71a092b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is a module for MagicMirror. -`MMM-aviationwx` displays aviation weather information (METAR data) for continental U.S. airports on your MagicMirror. If +`MMM-aviationwx` displays aviation weather information (METAR data) for airports on your MagicMirror. If the FAA tracks delay information for that airport, it will also note if there are any current delays and the reason for the delay if the airport has provided one. The module includes hyperlinks and tooltips that are generally not accessible with a Raspberry PI installation, but will work if you have mouse control (which is more often the case if you run Magic Mirror on another OS, such as macOS). @@ -29,8 +29,10 @@ modules: [ header: 'Aviation Weather', position: 'top_left', config: { - airports: 'KSFO,PAO,HAF,JFK', // 3-char or 4-char codes, U.S. only - updateInterval: 10, // update interval in minutes + airports: "KSFO ,JFK ,LFBD ,ORY", // airports list + US_country: "Y ,Y ,N ,N",//if the airport is in the US put Y (yes) otherwise put N (no) + updateInterval: 10, // in minutes + legend:1 // show legend for VFR, MVFR, IFR, MIFR } }, ... @@ -40,8 +42,10 @@ modules: [ * `position`: See MagicMirror documentation for usage. * `header`: Optional. See MagicMirror documentation for usage. -* `config.airports`: Comma delimited list of continental U.S. airport codes (no spaces). Supports 3-character FAA codes (which are mostly the same as 3-character IATA codes) or 4-character ICAO codes. +* `config.airports`: Comma delimited list of airport codes (spaces and tab allowed). Supports 3-character IATA codes or 4-character ICAO codes. +* `config.US_country`: Comma delimited to specify if the airports upward is in the US or not(spaces and tab allowed). * `config.updateInterval`: How often the module will pull new data in minutes. Airports generally only update every 60 minutes. Additionally, because of how the Aviation Weather Center provides METAR data, each update pulls down the data for most airports in the continental U.S., so I suggest going easy on the update frequency. +* `config.legend`: to print the color legend for flight categories. ## Notes @@ -56,7 +60,6 @@ modules: [ * Decoding of wx (`+RA`, `BR`, etc.) to English or icons * Decoding of delay abbreviations (`VOL`, etc.) -* Support for AK, HI and Canadian airports * General code and technique clean up ## License diff --git a/node_helper.js b/node_helper.js index 2d0cce0..1c1eb35 100644 --- a/node_helper.js +++ b/node_helper.js @@ -19,16 +19,68 @@ module.exports = NodeHelper.create({ // [airports, metarUrl, FAAUrl] = payload; var airports = payload[0].split(","); - var metarUrl = payload[1]; - var FAAUrl = payload[2]; + var US_country=payload[1].split(","); + var metarUrl = payload[2]; + var FAAUrl = payload[3]; var airportData = new Object(); + var airportICAO = new Object(); + var iata_to_icao_url="https://ae.roplan.es/api/IATA-ICAO.php?iata="; //url api to get ICAO with IATA + var icao_to_iata_url="https://ae.roplan.es/api/ICAO-IATA.php?icao="; //url api to get IATA with ICAO - // Convert to US ICAO codes - airports = airports.map(function(airport) { - airport = airport.trim(); - return (airport.length < 4) ? "K" + airport : airport; - }); - + // Convert codes + airports.forEach(function(airport, index) { + + airportData[airport]= new Object(); + + if (airport.length < 4){ //IATA code + if (US_country[index]==="Y"){ + + airportICAO[airport]="K" + airport; + + }else{ + + var ICAOCheckUrl = iata_to_icao_url + airport; + + request({url: ICAOCheckUrl, method: "GET"}, function (err, rsp, bod) { + if (!err && rsp.statusCode == 200) { + console.log("ICAO found for " + airport); + airportICAO[airport] = bod; + } else { + if (rsp.statusCode == 404) { + console.log("ICAO not found for " + airport + " (HTTP status: 404)"); + } else { + console.log("Error fetching ICAO for " + airport + ": " + error + + " (HTTP status: " + rsp.statusCode + ")"); + } + } + }); + } + airportData[airport]["IATA"] =airport; + + }else{ //ICAO code + + if (US_country[index]==="Y"){ + airportData[airport]["IATA"] = airport.substr(1); + }else{ + var IATACheckUrl = icao_to_iata_url + airport; + + request({url: IATACheckUrl, method: "GET"}, function (err, rsp, bod) { + if (!err && rsp.statusCode == 200) { + console.log("No error IATA search for " + airport); + airportData[airport]["IATA"] = bod; + } else { + if (rsp.statusCode == 404) { + console.log("IATA not found for " + airport + " (HTTP status: 404)"); + } else { + console.log("Error fetching IATA for " + airport + ": " + error + + " (HTTP status: " + rsp.statusCode + ")"); + } + } + }); + } + airportICAO[airport] =airport; + } + }); // Track number of HTTP requests to be made var numAirports = airports.length; @@ -37,9 +89,9 @@ module.exports = NodeHelper.create({ var metarData = JSON.parse(body).features; airports.forEach(function(airport) { metarData.forEach(function(metar) { - if (airport === metar.properties.id) { + if (airportICAO[airport] === metar.properties.id) { console.log("METAR data found for " + airport); - airportData[airport] = metar.properties; + airportData[airport]["METAR"] = metar.properties; return; // check next airport in list } }); @@ -52,25 +104,27 @@ module.exports = NodeHelper.create({ // Now check for FAA data airports.forEach(function(airport, index) { - var FAACheckUrl = FAAUrl.replace("", airport.substr(1)); // FAA takes IATA codes + if (US_country[index]==="Y"){ + var FAACheckUrl = FAAUrl.replace("", airportICAO[airport].substr(1)); // FAA takes IATA codes - request({url: FAACheckUrl, method: "GET"}, function (err, rsp, bod) { - if (!error && rsp.statusCode == 200) { - console.log("FAA data found for " + airport); - airportData[airport]["FAA"] = JSON.parse(bod); - } else { - if (rsp.statusCode == 404) { - console.log("FAA data not found for " + airport + " (HTTP status: 404)"); + request({url: FAACheckUrl, method: "GET"}, function (err, rsp, bod) { + if (!err && rsp.statusCode == 200) { + console.log("FAA data found for " + airport); + airportData[airport]["FAA"] = JSON.parse(bod); } else { - console.log("Error fetching FAA data for " + airport + ": " + error + - " (HTTP status: " + rsp.statusCode + ")"); + if (rsp.statusCode == 404) { + console.log("FAA data not found for " + airport + " (HTTP status: 404)"); + } else { + console.log("Error fetching FAA data for " + airport + ": " + error + + " (HTTP status: " + rsp.statusCode + ")"); + } } - } // This async call has completed, decrement number of HTTP requests remaining // There may be a better technique of doing this - numAirports--; - if (numAirports == 0) self.sendSocketNotification("WX_RESULT", airportData); - }); + }); + } + numAirports--; + if (numAirports == 0) self.sendSocketNotification("WX_RESULT", airportData); }); }); }, @@ -83,4 +137,3 @@ module.exports = NodeHelper.create({ } }); - diff --git a/preview.png b/preview.png index f470bb1..4a1cd97 100644 Binary files a/preview.png and b/preview.png differ