Visualize METAR Data
This flow downloads METAR weather data and displays the data with radial gauges and leds from the steelseries library (https://github.com/HanSolo/SteelSeries-Canvas).
The data is updated every 10 minutes and send to the web page via websocket connection.
Once deployed you can call the web page with
Default station is Hamburg (EDDH), but you can change with
http://your-server:1880/metar?station=LOWI
The station is set globally for all connected clients.
The flow uses some code from Paul-Reeds flow to get METAR data and calculate the feels-like temperature.
[{"id":"8a103fc6.75efc","type":"websocket-listener","path":"/ws/dashboard","wholemsg":"false"},{"id":"69ab454f.9654bc","type":"http request","name":"Call METAR Service","method":"GET","url":"http://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&stationString={{{payload}}}","x":148,"y":448,"z":"57584d97.a8a7b4","wires":[["907cdf5f.6f832"]]},{"id":"5873ed2f.a78c14","type":"inject","name":"Weather report","topic":"","payload":"/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&stationString=EDDH","payloadType":"none","repeat":"600","crontab":"","once":false,"x":106,"y":224,"z":"57584d97.a8a7b4","wires":[["a165de81.5e9a2"]]},{"id":"907cdf5f.6f832","type":"xml","name":"","x":315,"y":448,"z":"57584d97.a8a7b4","wires":[["b32637a5.4cd9c8","9d97e266.62682","d28f1f9b.2d70e","157b122.fea84ee","a5d3fe25.5a2c"]]},{"id":"159d93c2.ea626c","type":"comment","name":"NOAA Weather data","info":"Retrieving Finningly Airport METAR feed","x":95,"y":180,"z":"57584d97.a8a7b4","wires":[]},{"id":"b32637a5.4cd9c8","type":"function","name":"Parse METAR","func":"var METARdata = msg.payload.response.data[0].METAR[0];\nvar windkt = METARdata.wind_speed_kt[0];\nvar windkph = (windkt*1.852);\nvar winddir = METARdata.wind_dir_degrees[0]\nvar gusts = windkt; //if wind_gust_kt is not present, value defaults to wind_speed_kt\n if (typeof METARdata.wind_gust_kt != \"undefined\") {\n\t gusts = METARdata.wind_gust_kt[0];\n }\nvar visibility = METARdata.visibility_statute_mi[0]*1.852; \nvar altim = METARdata.altim_in_hg[0];\nvar pressure = Math.round(altim * 33.8637526); //convert hg to mb\nvar dew = METARdata.dewpoint_c[0];\ndew = Math.round(dew);\nvar tempC = METARdata.temp_c[0];\nvar tempF = ((tempC*1.8)+32);\n\n//tempC=5;\n//dew = 15;\n//windkt=1;\n\n//August-Roche-Magnus approximation to calculate rh_hum\nvar constA = 17.625;\nvar constB = 243.04;\nvar rh_numer = 10000.0*Math.exp((constA*eval(dew))/(eval(dew)+constB));\nvar rh_denom = Math.exp((constA*eval(tempC))/(eval(tempC)+constB));\nvar rh_hum = (rh_numer/rh_denom);\nvar humidityemon = Math.round(rh_hum);\nvar humidity = (humidityemon/100);\n\n//Wind Chill Index Calculations using the 'Joint Action Group for Temp Indices' (JAG TI) formula\nvar chill=(13.12+0.6215*tempC-11.37*Math.pow(windkph,0.16)+0.3965*tempC*Math.pow(windkph,0.16));\n\n//Calculate Apparent Temperature\n//firstly, Calculate Vapor pressure in kPa\nvar vaporp = ((6.11*Math.pow(10,(7.5*dew/(237.3 + dew))))/10);\n//apply Steadman's Apparent Temperature formula\nvar apptemp = -2.7+(1.04*tempC)+(2*vaporp)-(windkt*0.3343886);\n \n//Calculation of 'feels like' temperature by using the 'chill factor' if\n//the temp is below 10degC, or 'apparent temp' if over 20degC, or if between\n//10 and 20degC using a linear interpolation of both.\nvar feels;\nif (tempC < 10.0) {\n\tfeels=chill;\n\t}\n\telse if (tempC > 20.0) {\n\tfeels=apptemp;\n\t}\n\t\telse {\n\t\tvar calcA=((tempC-10)/10);\n\t\tvar calcB=1-calcA;\n\t\tfeels=((apptemp*calcA)+(chill*calcB));\n\t\t}\n\nmsg.payload = { \n wind: windkt,\n gusts: gusts,\n direction: winddir,\n humidity: humidity,\n dewpoint: dew,\n pressure: pressure,\n temperature: tempC,\n visibility: visibility,\n feels:feels\n };\nreturn msg;","outputs":1,"valid":true,"x":480,"y":518,"z":"57584d97.a8a7b4","wires":[["45cccff9.ba333","e852b626.17ad48","526f8ae7.ad9074","59c4f7f.fa63b08","d5bffc9a.2a4","1cfe6d9e.e30192","23f2644c.dc0d9c","a5f62102.5a09e"]]},{"id":"7885c297.877a3c","type":"http response","name":"","x":568,"y":1075,"z":"57584d97.a8a7b4","wires":[]},{"id":"586fdf52.a7902","type":"template","name":"HTML Page","field":"payload","format":"handlebars","template":"<!doctype html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>METAR Dashboard</title>\n <style>\n body {\n font-family: \"arial\";\n }\n table.led {\n width:1300px;\n }\n td.led {\n width: 60px;\n max-width: 60px;\n overflow: hidden;\n text-align: center;\n font-size: 70%\n }\n </style>\n</head>\n<body onload=\"init();\">\n<p>\n <div id=\"flash\"></div>\n</p>\n<canvas id=\"gaugeCanvas0\">No canvas in your browser...sorry...</canvas>\n<canvas id=\"gaugeCanvas1\"></canvas>\n<canvas id=\"gaugeCanvas2\"></canvas>\n<canvas id=\"gaugeCanvas3\"></canvas>\n<canvas id=\"gaugeCanvas4\"></canvas>\n<canvas id=\"gaugeCanvas5\"></canvas>\n<canvas id=\"gaugeCanvas6\"></canvas>\n<canvas id=\"gaugeCanvas7\"></canvas>\n<table class=\"led\">\n <tr id=\"leds\">\n <td class=\"led\"><canvas id=\"led0\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led1\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led2\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led3\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led4\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led5\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led6\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led7\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led8\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led9\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led10\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led11\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led12\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led13\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led14\" width=\"50\" height=\"50\"></canvas></td>\n <td class=\"led\"><canvas id=\"led15\" width=\"50\" height=\"50\"></canvas></td>\n </tr>\n <tr id=\"labels\">\n <td class=\"led\" id=\"lable0\"></td>\n <td class=\"led\" id=\"lable1\"></td>\n <td class=\"led\" id=\"lable2\"></td>\n <td class=\"led\" id=\"lable3\"></td>\n <td class=\"led\" id=\"lable4\"></td>\n <td class=\"led\" id=\"lable5\"></td>\n <td class=\"led\" id=\"lable6\"></td>\n <td class=\"led\" id=\"lable7\"></td>\n <td class=\"led\" id=\"lable8\"></td>\n <td class=\"led\" id=\"lable9\"></td>\n <td class=\"led\" id=\"lable10\"></td>\n <td class=\"led\" id=\"lable11\"></td>\n <td class=\"led\" id=\"lable12\"></td>\n <td class=\"led\" id=\"lable13\"></td>\n <td class=\"led\" id=\"lable14\"></td>\n <td class=\"led\" id=\"lable15\"></td>\n <td class=\"led\" id=\"lable16\"></td>\n </tr>\n</table>\n</body>\n<script type=text/javascript src=\"https://rawgit.com/HanSolo/SteelSeries-Canvas/master/tween.js\"></script>\n<script type=text/javascript src=\"https://rawgit.com/HanSolo/SteelSeries-Canvas/master/steelseries.js\"></script>\n<script>\n var radials = [];\n var leds = [];\n var connection;\n\n function init()\n {\n for (var i=0;i<7;i++) {\n radials[i] = new steelseries.Radial('gaugeCanvas'+i, {\n frameDesign: steelseries.FrameDesign.TILTED_GRAY,\n gaugeType: steelseries.GaugeType.TYPE4,\n backgroundColor : steelseries.BackgroundColor.WHITE,\n ledVisible: false,\n minValue:0,\n maxValue:100,\n size: 301,\n });\n radials[i].setThresholdVisible(false);\n radials[i].setValue(0);\n }\n radials[7] = new steelseries.WindDirection('gaugeCanvas7', {\n frameDesign: steelseries.FrameDesign.TILTED_GRAY,\n gaugeType: steelseries.GaugeType.TYPE4,\n backgroundColor : steelseries.BackgroundColor.WHITE,\n size: 301,\n lcdVisible: false\n });\n for (i=0;i<16;i++) {\n leds[i] = new steelseries.Led('led'+i, {size: 50});\n }\n }\n\n connect();\n\n function onConnect() {\n leds[0].setLedOnOff(true);\n console.log(\"connected\");\n connection.send(\"Request Data\");\n };\n\n function onClose() {\n leds[0].setLedOnOff(false);\n console.log(\"disconnected\");\n setTimeout(function() { connect()},5000);\n }\n\n function onMessage(msg) {\n flash(leds[1]);\n console.log(msg);\n var data=JSON.parse(msg.data);\n var radial = radials[data.radial];\n var led = leds[data.led];\n if (led && data.status) {\n if (data.status == 'flash')\n flash(led,data.duration || 400);\n else\n led.setLedOnOff(data.status.toUpperCase()==\"ON\");\n\n }\n if (led && data.color) {\n led.setLedColor(steelseries.LedColor[data.color.toUpperCase()+\"_LED\"]);\n }\n if (radial && data.max) {\n radial.setMaxValue(data.max);\n }\n if (radial && data.min) {\n radial.setMinValue(data.min);\n }\n if (radial && data.value) {\n if (radial.setValueAnimated) {\n radial.setValueAnimated(data.value);\n } else if (radial.setValueAnimatedLatest) {\n radial.setValueAnimatedLatest(data.value);\n radial.setValueAnimatedAverage(data.value);\n } else {\n radial.setValue(data.value);\n }\n }\n if (radial && data.odo) {\n radial.setOdoValue(data.odo);\n }\n if (data.title) {\n if (radial)\n radial.setTitleString(data.title);\n if (led)\n document.getElementById(\"lable\"+data.led).innerHTML = data.title;\n\n }\n if (radial && data.unit) {\n radial.setUnitString(data.unit)\n }\n if (data.flash) {\n document.getElementById(\"flash\").innerHTML = data.flash;\n }\n }\n\n function connect(cause) {\n if (cause)\n console.log(cause.errorMessage);\n const hostname = window.location.hostname || \"raspi2\"\n const port = window.location.port || 1880;\n const url = \"ws://\" + hostname + \":\" + port + \"/ws/dashboard\";\n connection=new WebSocket(url);\n connection.onopen=onConnect;\n connection.onclose=onClose;\n // Log errors\n connection.onerror = function (error) {\n flash(leds[2]);\n console.log('WebSocket Error ' + error);\n };\n\n // Log messages from the server\n connection.onmessage = onMessage;\n }\n\n function flash(led,duration) {\n led.setLedOnOff(true);\n setTimeout(function() { led.setLedOnOff(false)},duration || 400);\n }\n</script>","x":414,"y":1075,"z":"57584d97.a8a7b4","wires":[["7885c297.877a3c"]]},{"id":"7ada2666.8525d8","type":"http in","name":"","url":"/metar","method":"get","x":84,"y":1075,"z":"57584d97.a8a7b4","wires":[["6ca08ddb.935f74","154822ee.eab7dd"]]},{"id":"7e009dd0.81ff64","type":"websocket out","name":"","server":"8a103fc6.75efc","client":"","x":961,"y":956,"z":"57584d97.a8a7b4","wires":[]},{"id":"45cccff9.ba333","type":"template","name":"Temp2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 0,\n \"value\" : {{payload.temperature}},\n \"max\" : 50,\n \"unit\" : \"°C\",\n \"title\" : \"Temperature\"\n}\n","x":713,"y":520,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"e852b626.17ad48","type":"template","name":"Wind2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 6,\n \"value\" : {{payload.wind}},\n \"max\" : 80,\n \"unit\" : \"kt\",\n \"title\" : \"Wind\"\n}\n","x":710,"y":845,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"526f8ae7.ad9074","type":"template","name":"Hum2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 2,\n \"value\" : {{payload.humidity}},\n \"max\" : 100,\n \"unit\" : \"%\",\n \"title\" : \"Humidity\"\n}\n","x":714,"y":629,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"59c4f7f.fa63b08","type":"template","name":"Pres2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 4,\n \"value\" : {{payload.pressure}},\n \"min\" : 900,\n \"max\" : 1100,\n \"unit\" : \"hPa\",\n \"title\" : \"Pressure\"\n}\n","x":712,"y":734,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"27b65972.d849a6","type":"websocket in","name":"","server":"8a103fc6.75efc","client":"","x":107,"y":955,"z":"57584d97.a8a7b4","wires":[["e6a7ed4a.19581","a165de81.5e9a2"]]},{"id":"d5bffc9a.2a4","type":"template","name":"Dew2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 3,\n \"value\" : {{payload.dewpoint}},\n \"max\" : 50,\n \"unit\" : \"°C\",\n \"title\" : \"Dew\"\n}\n","x":712,"y":685,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"1cfe6d9e.e30192","type":"template","name":"Feels2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 1,\n \"value\" : {{payload.feels}},\n \"max\" : 50,\n \"unit\" : \"°C\",\n \"title\" : \"Feels\"\n}\n","x":715,"y":573,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"23f2644c.dc0d9c","type":"template","name":"Vis2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 5,\n \"value\" : {{payload.visibility}},\n \"max\" : 10,\n \"unit\" : \"km\",\n \"title\" : \"Visibility\"\n}\n","x":711,"y":786,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"a5f62102.5a09e","type":"template","name":"Dir2Radial","field":"payload","format":"handlebars","template":"{\n \"radial\" : 7,\n \"value\" : {{payload.direction}}\n}\n","x":711,"y":896,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"9d97e266.62682","type":"function","name":"to VFR LED","func":"var report=msg.payload.response.data[0].METAR[0];\nvar conditions=report.flight_category;\nmsg.payload = {\n \"led\" : 3,\n \"status\" : \"ON\",\n \"color\" : conditions == \"VFR\" ? \"green\" : conditions == \"MVFR\" ? \"yellow\" : \"red\",\n \"title\" : \"VFR\"\n}\nreturn msg;","outputs":1,"valid":true,"x":765,"y":449,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"2e8a4e74.d175b2","type":"websocket out","name":"","server":"8a103fc6.75efc","client":"","x":1054,"y":449,"z":"57584d97.a8a7b4","wires":[]},{"id":"e6a7ed4a.19581","type":"function","name":"Init Status LEDs","func":"msgs = [];\n\nmsgs.push({payload:{ \n \"title\" : \"Connect\",\n \"led\" : 0,\n \"color\" : \"blue\"\n}});\nmsgs.push({payload:{ \n \"title\" : \"Error\",\n \"led\" : 2,\n \"status\" : \"flash\",\n \"color\" : \"red\"\n}});\nmsgs.push({payload:{ \n \"title\" : \"Data\",\n \"led\" : 1,\n \"status\" : \"flash\",\n \"color\" : \"yellow\"\n}});\nreturn [msgs];","outputs":1,"valid":true,"x":391,"y":955,"z":"57584d97.a8a7b4","wires":[["7e009dd0.81ff64"]]},{"id":"d28f1f9b.2d70e","type":"function","name":"Extract raw METAR","func":"msg.payload=msg.payload.response.data[0].METAR[0].raw_text[0];\nreturn msg;","outputs":1,"valid":true,"x":514,"y":254,"z":"57584d97.a8a7b4","wires":[["d2e6ac6e.2d195","bcc1aaf2.433e58","1af39e86.e50c61"]]},{"id":"d2e6ac6e.2d195","type":"function","name":"to NOSIG LED","func":"msg.payload = {\n \"led\" : 4,\n \"status\" : msg.payload.indexOf(\"NOSIG\") > -1 ? \"ON\" : \"OFF\",\n \"color\" : \"green\",\n \"title\" : \"NOSIG\"\n}\nreturn msg;","outputs":1,"valid":true,"x":792,"y":135,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"bcc1aaf2.433e58","type":"function","name":"to CAVOK LED","func":"msg.payload = {\n \"led\" : 5,\n \"status\" : msg.payload.indexOf(\"CAVOK\") > -1 ? \"ON\" : \"OFF\",\n \"color\" : \"green\",\n \"title\" : \"CAVOK\"\n}\nreturn msg;","outputs":1,"valid":true,"x":790,"y":92,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"1af39e86.e50c61","type":"function","name":"to Flash Message","func":"msg.payload = {\n \"flash\" : msg.payload\n}\nreturn msg;","outputs":1,"valid":true,"x":798,"y":398,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"3a58c564.c5a73a","type":"comment","name":"Send HTML Page","info":"","x":103,"y":1027,"z":"57584d97.a8a7b4","wires":[]},{"id":"293a8f70.d6c57","type":"comment","name":"Respond to ping message","info":"","x":130,"y":905,"z":"57584d97.a8a7b4","wires":[]},{"id":"a2f94449.5d06b8","type":"function","name":"to DIZZELE LED","func":"msg.payload = {\n \"led\" : 6,\n \"status\" : msg.payload.indexOf(\"DZ\") > -1 ? \"ON\" : \"OFF\",\n \"color\" : \"yellow\",\n \"title\" : \"Dizzle\"\n}\nreturn msg;","outputs":1,"valid":true,"x":790,"y":176,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"285562db.d7aa9e","type":"function","name":"to RAIN LED","func":"msg.payload = {\n \"led\" : 7,\n \"status\" : msg.payload.indexOf(\"RA\") > -1 ? \"ON\" : \"OFF\",\n \"color\" : \"yellow\",\n \"title\" : \"Rain\"\n}\nreturn msg;","outputs":1,"valid":true,"x":793,"y":221,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"bf5ce258.40a32","type":"function","name":"to TS LED","func":"msg.payload = {\n \"led\" : 8,\n \"status\" : msg.payload.indexOf(\"TS\") > -1 ? \"ON\" : \"OFF\",\n \"color\" : \"red\",\n \"title\" : \"Thunderstorm\"\n}\nreturn msg;","outputs":1,"valid":true,"x":794,"y":270,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"6ca08ddb.935f74","type":"function","name":"Set Station","func":"if (msg.payload.station) {\n context.global.station=msg.payload.station;\n}\nreturn msg;","outputs":1,"valid":true,"x":245,"y":1075,"z":"57584d97.a8a7b4","wires":[["586fdf52.a7902"]]},{"id":"a165de81.5e9a2","type":"function","name":"Set Metar Station","func":"msg.payload=context.global.station || \"EDDH\";\nreturn msg;","outputs":1,"valid":true,"x":125,"y":323,"z":"57584d97.a8a7b4","wires":[["69ab454f.9654bc"]]},{"id":"154822ee.eab7dd","type":"debug","name":"","active":true,"console":"false","complete":"false","x":258,"y":1141,"z":"57584d97.a8a7b4","wires":[]},{"id":"157b122.fea84ee","type":"debug","name":"","active":true,"console":"false","complete":"payload.response.data","x":544,"y":473,"z":"57584d97.a8a7b4","wires":[]},{"id":"633ddaa6.9cc224","type":"function","name":"to FG LED","func":"msg.payload = {\n \"led\" : 9,\n \"status\" : msg.payload.indexOf(\"FG\") > -1 ? \"ON\" : \"OFF\",\n \"color\" : \"red\",\n \"title\" : \"Fog\"\n}\nreturn msg;","outputs":1,"valid":true,"x":795,"y":313,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"52947201.ad6b8c","type":"function","name":"to SN LED","func":"msg.payload = {\n \"led\" : 10,\n \"status\" : msg.payload.indexOf(\"SN\") > -1 ? \"ON\" : \"OFF\",\n \"color\" : \"red\",\n \"title\" : \"Snow\"\n}\nreturn msg;","outputs":1,"valid":true,"x":794,"y":355,"z":"57584d97.a8a7b4","wires":[["2e8a4e74.d175b2"]]},{"id":"a5d3fe25.5a2c","type":"function","name":"Extract WX String","func":"var wx=msg.payload.response.data[0].METAR[0].wx_string;\nmsg.payload=wx ? wx.join(): \"\";\nreturn msg;","outputs":1,"valid":true,"x":515,"y":140,"z":"57584d97.a8a7b4","wires":[["a2f94449.5d06b8","285562db.d7aa9e","bf5ce258.40a32","633ddaa6.9cc224","52947201.ad6b8c"]]}]