Tag Archives: ha

The future’s bright, the future’s RED, Node-RED

It’s been a while since I posted – unfortunately it’s been a pretty busy time lately with so many projects on the go both at work and at home 🙂  With a few hours to spare, I thought I’d share how my Home Automation system is shaping up nowadays as I seek to further reduce my reliance on Windows and the remaining Virtual Machines that are still playing a big part in running our home.

Regular readers will remember that last year I completed a “move to the Cloud” for some of my less critical systems and this allowed me to turn off one of the two remaining VMWare vSphere host machines that I was running at the time.  Since then, I’ve slowly been chipping away at the core Home Automation system that is still running on the xAP protocol using the excellent xAP Floorplan for most of the logic and smarts.

I’ve nothing against xAP or xAP Floorplan you understand, but I’ve been moving more towards low powered embedded Linux based devices for the past few years and want to keep heading in that direction.  One of the problems I’ve been having is that I still use a whole bunch of xAP applications that are only available on Windows – xAP Switchboard, xAP TV, xAP Weather, xAP News – pretty much all of James from Mi4‘s creations!

Of course, my love affair with my MiCasaVerde Vera Z-Wave controller continues, so some stuff I’ve been migrating across to Vera whenever that’s been possible – the recently completed Stairway Lighting Project is a good example of that.  In fact, most of the lighting control is in the capable hands of Vera now and the integration with my Visonic alarm panel and Logitech SqueezeCenter have also successfully moved across.

About a year or so ago I started experimenting with MQTT and Node.js amongst other IOT (Internet Of Things) protocols, with a view that possibly MQTT could become my transport of choice in the future and that Node.js might play an important role in some sort of central control. xAP has been great but most development around it has stalled and many of the original champions have long since disappeared (the same can be said for xPL too).  MQTT has some massive interest and looks set to be the next big thing – if it isn’t already!

Robert over at “Digits Domotica Blog” has recently completed a similar move and I have been following his progress regularly for insight into how he’s been resolving issues along the way.  His blog is awesome and he’s clearly been doing this stuff for a long time so there’s plenty to learn from his efforts.

Roll forward to a few months ago and I stumbled across an interesting piece of software called “Node-RED” – a so called “Visual tool for wiring the Internet of Things”.  A quick read of the home page and flick through some of the docs and I was intrigued – based on Node.js and written with MQTT in mind, it seemed like it might be a good fit.

There looked to be plenty of functionality “out-of-the-box” and additional “nodes” could be downloaded to add various inputs and outputs and support other bits of hardware or other systems.  Scripting or “wiring” together the building blocks could be carried out using JavaScript, not a million miles away from the VBScript of xAP Floorplan and certainly simpler than the C#, Java and Objective C that has been filling my head for the past few years at work!  Time to have a play then 🙂


Since I already had a recently set-up Raspberry Pi that was running some of my xAP perl scripts and the Mosquitto MQTT broker already (installed by following this post), I decided that would be a good home for a Node-RED install!  Fortunately when I set this Pi up I’d already installed a bunch of software development and build tools, so I was able to hit the ground running and not get caught up with installing prerequisites and dependencies.

First up I needed to get a working Node.js install on the Pi.  I shunned the usual apt-get methods and decided to build Node.js from source – no pain no gain!  I also ignored the warnings on the Node-RED site that the latest versions of Node.js had problems on the Pi and that I should use v0.8.x and went straight for the latest stable version 0.10.2 instead – so far I’ve not had any problems.

The build was a pretty simple process:

root@ahsrpi1:/home/pi# screen
root@ahsrpi1:/home/pi# wget http://nodejs.org/dist/v0.10.2/node-v0.10.2.tar.gz
root@ahsrpi1:/home/pi# tar -xzf node-v0.10.2.tar.gz
root@ahsrpi1:/home/pi# cd node-v0.10.2
root@ahsrpi1:/home/pi# ./configure
root@ahsrpi1:/home/pi# make
root@ahsrpi1:/home/pi# make install

I knew it was going to take a good few hours to build on the Pi, so I left it building in a Screen session and went to bed! If you want to go for a pre-built version of Node.js, there’s a great tutorial here that also goes into installing Node-RED too.

The next day it was done and a quick:

root@ahsrpi1:/home/pi# node -v


root@ahsrpi1:/usr/src# npm -v

…..seemed to confirm all was ok, so on to installing Node-RED itself.

Again I decided to ignore the pre-built Pi stuff and instead went for the latest bleeding edge, perhaps not a wise choice but the project seems incredibly active so I figured that anything that was broken in a major way would be fixed pretty quick.

I cloned the git repo and ran the install commands:

root@ahsrpi1:/usr/src# git clone https://github.com/node-red/node-red.git
root@ahsrpi1:/usr/src# cd node-red
root@ahsrpi1:/usr/src/node-red# npm install --production

…..then added the node-red-nodes repo too…..

root@ahsrpi1:/usr/src/node-red# cd nodes/
root@ahsrpi1:/usr/src/node-red/nodes# git clone https://github.com/node-red/node-red-nodes.git

…..after that it was just a case of running Node-RED and seeing what happened…..

root@ahsrpi1:/usr/src/node-red/nodes# cd ..
root@ahsrpi1:/usr/src/node-red# node red.js

Unfortunately the next part became pretty tedious as the the first time Node-RED starts it will attempt to load all the nodes it finds in the nodes/ directory and will likely fail since those nodes have dependencies that won’t have been met yet.  So I spent the next few hours in a cycle of watching the log file for missing dependencies and then running an “npm install…..” command to install the dependencies…..which then installed more dependencies…..that required other dependencies….you get the picture….

Luckily you only need to install the dependencies if you actually want to use a particular node, so I just stuck to installing those nodes that sounded like they would be useful in a Home Automation set-up, for example MySQL, Prowl, Growl, ping, wol and a few others.  I figured I could always add more at a later point if and when I needed them.

Once sorted I just fired up another Screen session, left Node-RED running and headed over to the web UI at http://raspberryPi:1880 – nothing quite like being dropped in at the deep end!

Fortunately there’s some pretty good docs over on the Node-RED site so it didn’t take too long to start being productive.  One of the many great features of Node-RED is the ability to export and then import your logic – known as “flows” in Node-RED.  This makes it incredibly simple to not only move stuff around different Node-RED instances, but to also share your flows with others.  A “Flow Library” has recently appeared on the Node-RED site which is a great place to plagiarise work submitted by other people 🙂

Since I was hoping to be able to replace great chunks of functionality in my current Home Automation system with Node-RED equivalents, I decided to rush straight in and look at replicating the features of xAP Weather. This application downloads weather information from the BBC web site in the form of RSS feeds for both the current and 3-day forecast data.

xAP Weather then sends xAP messages using the xAP Weather Schema and displays a nice icon thing on a xAP Floorplan map.  I also use a script in xAP Floorplan to react to the xAP messages and send OSD (On Screen Display) messages with the current and forecasted Weather.  These are shown on SqueezeBoxes via the xAP plugin in the SqueezeCenter server software running on one of my NAS.

So a pretty tall order for a first foray into Node-RED, but taking a look at what was available “on the menu” in the Node-RED web UI I was pleasantly surprised – it seemed that many of the building blocks that I’d require were already present:

  • inject node – to schedule stuff
  • httpget node – to download the RSS feed xml files
  • xml2js node – automagically parse the xml into a JavaScript Object
  • mqtt out node – to send an MQTT message that could be subscribed to elsewhere
  • function node – to write your own JavaScript bits to glue it all together

The only thing that was missing was a way to output a xAP message, but since Node-RED is built on Node.js that was soon resolved too as bigkevmcd has already written a simple xAP broadcaster and since it’s in the official Node.js package manager it’s a simple case of doing an “npm install xap” in the Node-RED directory.

In order to use 3rd party packages like this, it’s possible to create your own “node” that would sit in the Node-RED menu palette on the left side of the web UI, but unfortunately the documentation is still pretty sparse in that area.  I did find a small mention in the “writing functions” section on the Node-RED web site that said you could access Node.js modules from within functions by making them available in a “Global Context” so I elected to go down this route for the time being and look at making a proper xAP Node at a later date.

Another great feature of Node-RED is the ability to add “debug” nodes throughout your flows that can dump the contents of the “msg” object in a handy debug pane in the browser.  Incidentally, the “msg” object is a JavaScript object that is passed between nodes, typically containing information that one node needs to send to the next in the flow.  Since I was running Node-RED on the Pi in a Screen session I could also keep an eye on the console log to see any errors or other debug output from my coding efforts.

It wasn’t long before my JavaScript-Fu was flow-ing (see what I did there?) and the various pieces began to fall into place.  I was sceptical at first that the Node-RED UI was going to make a decent editor code-wise, but it proved OK.  Until I had a few browser “incidents” and kept losing my work – until you actually “Deploy” the code to the Node-RED “server”, it only exists in your browser, so that’s something to watch out for!

I quickly found a decent rhythm, the key thing was to write small chunks of code and to deploy the flow often, even if it wasn’t finished completely.  This approach also lent itself to testing along the way as any output from a section of the flow could be dumped to the console log or to a debug node as required to make sure it was achieving the desired result.

Eventually the flow was complete:

Node-RED BBC Weather Feed Flow
Node-RED BBC Weather Feed Flow

Another benefit of scripting languages like JavaScript (and my all time favourite, perl) is that they lend themselves really easily to processing text of all shapes and sizes.  All in all it only took a couple of hours of experimenting and the final flow ran like this:

  1. An inject node prods httpget nodes periodically
  2. The httpget nodes grab the RSS from the BBC site (either the Observations or 3-Day Forecast Schedule)
  3. A function node takes care of a small issue I discovered along the way
  4. An xml2js node parses the XML from the RSS feed into a JavaScript Object
  5. A function node then makes a “Weather” Object
  6. A bunch of function nodes then take care of sending the xAP messages mentioned earlier to replicate the functionality I already had
  7. I also added another function node to output the weather data over MQTT as well – figured I might as well do that since it looks like MQTT is where I’m eventually heading 🙂

Node-RED has now been running for six weeks or so and the BBC Weather flow has been rock solid.  I was so impressed with how it all hung together that it was only a few days after I first set it all up that I retired the old xAP Weather application and the xAP Floorplan scripts.  One down then, but unfortunately many more to go – although at this rate it won’t take too long!

Oh, and remember I said how Node-RED makes it easy to rob share other peoples work?  Well here’s my BBC Weather flow, feel free to make use of it in your own Node-RED set-up – but if you make any improvements please let me know!
Node-RED BBC Weather flow

[{"id":"b0b8bcb8.4f474","type":"mqtt-broker","broker":"localhost","port":"1883","clientid":"ahs-nodered-ahsrpi1"},{"id":"5a652c04.a59ad4","type":"function","name":"Parse 3-Day Forecast To Weather Object","func":"//console.log(msg.payload);\n\nvar weatherChannel = msg.payload.rss.channel[0];\n\nvar forecasts = [];\n\nvar forecastslength = weatherChannel.item.length;\n\nfor (var forecast = 0; forecast < forecastslength; forecast++) {\n\n\tvar pubDate = weatherChannel.item[forecast].pubDate[0];\n\n\tvar title = weatherChannel.item[forecast].title[0];\n\t\n\t//console.log(title);\n\n\tvar feed = weatherChannel.item[forecast].description[0].toLowerCase();\n\n\t//console.log(feed);\n\t\n\tfeed = feed.split(\",\");\n\n\tvar weather = {};\n\t\n\tvar forecastday = forecast + 1;\n\t\t\n\tweather.type = \"bbcforecastday\" + forecastday;\n\t\n\tvar outlook = title.split(\"day: \");\n\n\toutlook = outlook[1].split(\",\");\n\n\tweather.outlook = outlook[0].toLowerCase();\n\t\n\tvar days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];\n\tvar d=new Date(pubDate);\n\tvar y=d.getFullYear();\n\tvar m=d.getMonth()+1; if(m<10){m='0'+m;};\n\tvar day=d.getDate(); if(day<10){day='0'+day;};\n\tvar h=d.getHours(); if(h<10){h='0'+h;};\n\tvar mm=d.getMinutes(); if(mm<10){mm='0'+mm;};\n\tvar s=d.getSeconds(); if(s<10){s='0'+s;};\n\tvar dow=days[d.getDay() + forecast];\n\t\n\tweather.updated = y+m+day+h+mm+s;\n\tweather.date = y+m+day;\n\tweather.time = h+':'+mm;\n\tweather.day = dow.toLowerCase();\n\t\n\tvar feedlength = feed.length;\n\t\n\tfor (var feednumber = 0; feednumber < feedlength; feednumber++) {\n\t\n\t\t//console.log(feed[feednumber]);\n\t\t\n\t\tvar item = feed[feednumber].split(\": \");\n\t\t\n\t\t//console.log(item[0] + \" ---- \" + item[1]);\n\t\t\n\t\tswitch(item[0].trim())\n\t\t{\n\t\tcase \"maximum temperature\":\n\t\t\tvar temp = item[1].trim();\n\t\t\ttemp = temp.split(\" (\");\t\t\n\t\t\tweather.maximumtemperaturec = temp[0].replace(/\\D/g,'');\n\t\t\tweather.maximumtemperaturef = temp[1].replace(/\\D/g,'');\n\t\t  \tbreak;\n\t\tcase \"minimum temperature\":\n\t\t  \tvar temp = item[1].trim();\n\t\t\ttemp = temp.split(\" (\");\t\t\n\t\t\tweather.minimumtemperaturec = temp[0].replace(/\\D/g,'');\n\t\t\tweather.minimumtemperaturef = temp[1].replace(/\\D/g,'');\t \n\t\t  \tbreak;\n\t\tcase \"wind direction\":\n\t\t  \tswitch(item[1].trim())\n\t\t\t{\n\t\t\tcase \"northerly\":\n\t\t\t\tweather.winddirection = \"n\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"southerly\":\n\t\t\t\tweather.winddirection = \"s\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"easterly\":\n\t\t\t\tweather.winddirection = \"e\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"westerly\":\n\t\t\t\tweather.winddirection = \"w\";\t \n\t\t\t\tbreak;\t\t\t\n\t\t\tcase \"south south easterly\":\n\t\t\t\tweather.winddirection = \"sse\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"south south westerly\":\n\t\t\t\tweather.winddirection = \"ssw\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"north north easterly\":\n\t\t\t\tweather.winddirection = \"nne\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"north north westerly\":\n\t\t\t\tweather.winddirection = \"nnw\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"south easterly\":\n\t\t\t\tweather.winddirection = \"se\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"south westerly\":\n\t\t\t\tweather.winddirection = \"sw\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"north easterly\":\n\t\t\t\tweather.winddirection = \"ne\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"north westerly\":\n\t\t\t\tweather.winddirection = \"nw\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"west south easterly\":\n\t\t\t\tweather.winddirection = \"wse\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"west south westerly\":\n\t\t\t\tweather.winddirection = \"wsw\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"west north easterly\":\n\t\t\t\tweather.winddirection = \"wne\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"west north westerly\":\n\t\t\t\tweather.winddirection = \"wnw\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"east south easterly\":\n\t\t\t\tweather.winddirection = \"ese\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"east south westerly\":\n\t\t\t\tweather.winddirection = \"esw\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"east north easterly\":\n\t\t\t\tweather.winddirection = \"ene\";\t \n\t\t\t\tbreak;\n\t\t\tcase \"east north westerly\":\n\t\t\t\tweather.winddirection = \"enw\";\t \n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tconsole.log(\"dafuq is dis shit: \" + item[1].trim());\n\t\t\t}\t  \t\n\t\t  \tbreak;\n\t\tcase \"wind speed\":\n\t\t  \tweather.windspeed = item[1].trim().replace(/\\D/g,'');\t \n\t\t  \tbreak;\n\t\tcase \"visibility\":\n\t\t  \tweather.visibility = item[1].trim();\t \n\t\t  \tbreak;\n\t\tcase \"pressure\":\n\t\t  \tweather.pressure = item[1].trim().replace(/\\D/g,'');\n\t\t  \tbreak;\t \n\t\tcase \"humidity\":\n\t\t  \tweather.humidity = item[1].trim().replace(/\\D/g,'');\t \n\t\t  \tbreak;\n\t\tcase \"uv risk\":\n\t\t  \tweather.uvrisk = item[1].trim();\t \n\t\t  \tbreak;\n\t\tcase \"pollution\":\n\t\t  \tweather.pollution = item[1].trim();\t \n\t\t  \tbreak;\n\t\tcase \"sunrise\":\n\t\t  \tweather.sunrise = item[1].trim().replace(/\\D/g,'') + \"00\";\t \n\t\t  \tbreak;\n\t\tcase \"sunset\":\n\t\t  \tweather.sunset = item[1].trim().replace(/\\D/g,'') + \"00\";\t \n\t\t  \tbreak;\n\t\tdefault:\n\t\t\tconsole.log(\"dafuq is dis shit: \" + item[0] + \" ---- \" + item[1]);\n\t\t}\n\t}\n\t\n\t//Maximum Temperature: 8°C (46°F), Minimum Temperature: 4°C (39°F), \n\t//Wind Direction: South South Westerly, Wind Speed: 27mph, Visibility: Good, \n\t//Pressure: 971mb, Humidity: 76%, UV Risk: 1, Pollution: Low, Sunrise: 07:33 GMT, Sunset: 17:03 GMT\n\t\n\t//console.log(weather);\n\t\n\tforecasts[forecast] = weather;\n}\n\n//console.log(forecasts);\n\nmsg.weather = forecasts;\n\nreturn msg;","outputs":1,"x":1142.8893127441406,"y":242.8888702392578,"z":"87374e96.78c8b","wires":[["71deb4bd.8e214c","567972e.fa9868c","7036fa4.f8fc904"]]},{"id":"87a33a07.785cc8","type":"function","name":"Parse Observations To Weather Object","func":"//console.log(msg);\n\nvar weatherChannel = msg.payload.rss.channel[0];\n\nvar pubDate = weatherChannel.item[0].pubDate[0];\n\nvar title = weatherChannel.item[0].title[0];\n\n//console.log(title);\n\nvar feed = weatherChannel.item[0].description[0].toLowerCase();\n\nfeed = feed.replace(\"mb, falling\", \"mb falling\");\n\nfeed = feed.replace(\"mb, rising\", \"mb rising\");\n\n//console.log(feed);\n\nfeed = feed.split(\",\");\n\nvar forecasts = [];\n\nvar weather = {};\n\nweather.type = \"bbcobservation\";\n\nvar outlook = title.split(\": \");\n\noutlook = outlook[1].split(\",\");\n\nweather.outlook = outlook[0].toLowerCase();\n\nvar days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];\nvar d=new Date(pubDate);\nvar y=d.getFullYear();\nvar m=d.getMonth()+1; if(m<10){m='0'+m;};\nvar day=d.getDate(); if(day<10){day='0'+day;};\nvar h=d.getHours(); if(h<10){h='0'+h;};\nvar mm=d.getMinutes(); if(mm<10){mm='0'+mm;};\nvar s=d.getSeconds(); if(s<10){s='0'+s;};\nvar dow=days[d.getDay()];\n\nweather.updated = y+m+day+h+mm+s;\nweather.date = y+m+day;\nweather.time = h+':'+mm;\nweather.day = dow.toLowerCase();\n\nvar feedlength = feed.length;\n\nfor (var feednumber = 0; feednumber < feedlength; feednumber++) {\n\n\t//console.log(feed[feednumber]);\n\t\n\tvar item = feed[feednumber].split(\": \");\n\t\n\t//console.log(item[0] + \" ---- \" + item[1]);\n\t\n\tswitch(item[0].trim())\n\t{\n\tcase \"temperature\":\n\t\tvar temp = item[1].trim();\n\t\ttemp = temp.split(\" (\");\t\t\n\t\tweather.temperaturec = temp[0].replace(/\\D/g,'');\n\t\tweather.temperaturef = temp[1].replace(/\\D/g,'');\n\t\tbreak;\n\tcase \"wind direction\":\n\t\tswitch(item[1].trim())\n\t\t{\n\t\tcase \"northerly\":\n\t\t\tweather.winddirection = \"n\";\t \n\t\t\tbreak;\n\t\tcase \"southerly\":\n\t\t\tweather.winddirection = \"s\";\t \n\t\t\tbreak;\n\t\tcase \"easterly\":\n\t\t\tweather.winddirection = \"e\";\t \n\t\t\tbreak;\n\t\tcase \"westerly\":\n\t\t\tweather.winddirection = \"w\";\t \n\t\t\tbreak;\t\t\t\n\t\tcase \"south south easterly\":\n\t\t\tweather.winddirection = \"sse\";\t \n\t\t\tbreak;\n\t\tcase \"south south westerly\":\n\t\t\tweather.winddirection = \"ssw\";\t \n\t\t\tbreak;\n\t\tcase \"north north easterly\":\n\t\t\tweather.winddirection = \"nne\";\t \n\t\t\tbreak;\n\t\tcase \"north north westerly\":\n\t\t\tweather.winddirection = \"nnw\";\t \n\t\t\tbreak;\n\t\tcase \"south easterly\":\n\t\t\tweather.winddirection = \"se\";\t \n\t\t\tbreak;\n\t\tcase \"south westerly\":\n\t\t\tweather.winddirection = \"sw\";\t \n\t\t\tbreak;\n\t\tcase \"north easterly\":\n\t\t\tweather.winddirection = \"ne\";\t \n\t\t\tbreak;\n\t\tcase \"north westerly\":\n\t\t\tweather.winddirection = \"nw\";\t \n\t\t\tbreak;\n\t\tcase \"west south easterly\":\n\t\t\tweather.winddirection = \"wse\";\t \n\t\t\tbreak;\n\t\tcase \"west south westerly\":\n\t\t\tweather.winddirection = \"wsw\";\t \n\t\t\tbreak;\n\t\tcase \"west north easterly\":\n\t\t\tweather.winddirection = \"wne\";\t \n\t\t\tbreak;\n\t\tcase \"west north westerly\":\n\t\t\tweather.winddirection = \"wnw\";\t \n\t\t\tbreak;\n\t\tcase \"east south easterly\":\n\t\t\tweather.winddirection = \"ese\";\t \n\t\t\tbreak;\n\t\tcase \"east south westerly\":\n\t\t\tweather.winddirection = \"esw\";\t \n\t\t\tbreak;\n\t\tcase \"east north easterly\":\n\t\t\tweather.winddirection = \"ene\";\t \n\t\t\tbreak;\n\t\tcase \"east north westerly\":\n\t\t\tweather.winddirection = \"enw\";\t \n\t\t\tbreak;\n\t\tdefault:\n\t\t\tconsole.log(\"dafuq is dis shit: \" + item[1].trim());\n\t\t}\t  \t\n\t  \tbreak;\n\tcase \"wind speed\":\n\t  \tweather.windspeed = item[1].trim().replace(/\\D/g,'');\t \n\t  \tbreak;\n\tcase \"humidity\":\n\t  \tweather.humidity = item[1].trim().replace(/\\D/g,'');\t \n\t  \tbreak;\n\tcase \"pressure\":\n\t\tvar press = item[1].trim();\n\t\tpress = press.split(\"mb \");\n\t\tweather.pressure = press[0];\n\t  \tweather.pressuretype = press[1];\t \n\t  \tbreak;\t\n\tcase \"visibility\":\n\t  \tweather.visibility = item[1].trim();\t \n\t  \tbreak;\t\n\tdefault:\n\t  \tconsole.log(\"dafuq is dis shit: \" + item[0] + \" ---- \" + item[1]);\n\t}\n}\n\n//Temperature: 4°C (39°F), Wind Direction: East South Easterly, Wind Speed: 14mph, \n//Humidity: 95%, Pressure: 986mb, Falling, Visibility: Moderate\n\n//console.log(weather);\n\nforecasts[0] = weather;\n\n//console.log(forecasts);\n\nmsg.weather = forecasts;\n\nreturn msg;","outputs":1,"x":1133.8890686035156,"y":97.88888549804688,"z":"87374e96.78c8b","wires":[["71deb4bd.8e214c","2414ff1.fdbeb","794b3bdf.86b4c4"]]},{"id":"71deb4bd.8e214c","type":"function","name":"BBC Weather Feeds Prepare MQTT Messages","func":"var msgs = [];\n\n//console.log(msg.weather);\n\nfor (var id = 0, len = msg.weather.length; id < len; id++) {\n\n\tvar weather = msg.weather[id];\n\t\n\tfor (var key in weather) {\n\t\t\n\t\tif (key == \"type\" || key == \"updated\") {\n\t\t\tcontinue;\n\t\t}\n\t\t\n\t\tvar payload = \"informationweather\" + weather.type + \"\" + key + \n\t\t\"\" + weather[key] + \"\" + weather.updated + \"\";\n\t\t\n\t\tmsgs.push({'retain': true, 'qos': '1', 'topic': '/ha/value/floorplan/information/weather/' + weather.type + '/' + key + '/xml', 'payload': payload});\t\n\t}\n}\n\n//console.log(msgs);\n\nreturn [msgs];","outputs":1,"x":1413.8890686035156,"y":163.88888549804688,"z":"87374e96.78c8b","wires":[["793f1a75.86c0e4"]]},{"id":"793f1a75.86c0e4","type":"mqtt out","name":"Weather","topic":"","broker":"b0b8bcb8.4f474","x":1674.8890686035156,"y":165.88885498046875,"z":"87374e96.78c8b","wires":[]},{"id":"2414ff1.fdbeb","type":"function","name":"spoofxAPWeatherReport","func":"var weather = msg.weather[0];\n\nvar xAPBroadcaster = new context.global.xap.XAPBroadcaster( \n    {\n    class: 'weather.report', \n    source: 'mi4.weather.345', \n    uid: 'FF400100', \n    }\n    );\n\nxAPBroadcaster.send( \n    'weather.report', \n    {\n    'utc': weather.time, \n    'date': weather.date, \n    'tempc': weather.temperaturec,    \n    'tempf': weather.temperaturef,\n    'windm': weather.windspeed,\n    'windk': weather.windspeed,\n    'winddirc': weather.winddirection,\n    'airpressure': weather.pressure,\n    'humidity': weather.humidity,\n    'outlook': weather.outlook,\n    'icon': weather.outlook.replace(/ /gi, \"\"),\n    }\n    );","outputs":"0","x":1474.8889465332031,"y":39.88887023925781,"z":"87374e96.78c8b","wires":[]},{"id":"8e26cf19.71d93","type":"inject","name":"BBC Weather Observations Schedule","topic":"","payload":"1","payloadType":"string","repeat":"","crontab":"*/60 7-22 * * *","once":false,"x":175.88888549804688,"y":31.33331298828125,"z":"87374e96.78c8b","wires":[["c9b7e666.364818"]]},{"id":"c9b7e666.364818","type":"http request","name":"BBC Weather Observations","method":"GET","url":"http://open.live.bbc.co.uk/weather/feeds/en/2641430/observations.rss","x":380.8888854980469,"y":90.33331298828125,"z":"87374e96.78c8b","wires":[["f141cab6.0ebe38","8c9eb6d5.736148"]]},{"id":"e486cc74.1b793","type":"xml2js","useEyes":false,"name":"Observations RSS To JS Object","x":819.8890075683594,"y":99.33331298828125,"z":"87374e96.78c8b","wires":[["75e40ad9.8a1bf4","87a33a07.785cc8"]]},{"id":"9d042572.62fbd8","type":"http request","name":"BBC Weather 3-Day Forecast","method":"GET","url":"http://open.live.bbc.co.uk/weather/feeds/en/2641430/3dayforecast.rss","x":380.888916015625,"y":241.33330535888672,"z":"87374e96.78c8b","wires":[["6a4fc796.95b038","33ebc598.cc143a"]]},{"id":"bf5878bb.40a788","type":"inject","name":"BBC Weather 3-Day Forecast Schedule","topic":"","payload":"1","payloadType":"string","repeat":"","crontab":"*/120 7-22 * * *","once":false,"x":183.888916015625,"y":171.33331298828125,"z":"87374e96.78c8b","wires":[["9d042572.62fbd8"]]},{"id":"70719207.8f8e6c","type":"xml2js","useEyes":false,"name":"3-Day Forecast RSS To JS Object","x":817.8890686035156,"y":243.33331298828125,"z":"87374e96.78c8b","wires":[["e8abd0d7.17543","5a652c04.a59ad4"]]},{"id":"75e40ad9.8a1bf4","type":"debug","name":"","active":false,"complete":"true","x":1049.8889465332031,"y":33,"z":"87374e96.78c8b","wires":[]},{"id":"e8abd0d7.17543","type":"debug","name":"","active":false,"complete":"true","x":1061.8890686035156,"y":315.888916015625,"z":"87374e96.78c8b","wires":[]},{"id":"6a4fc796.95b038","type":"debug","name":"","active":false,"complete":false,"x":575.888916015625,"y":318.8888854980469,"z":"87374e96.78c8b","wires":[]},{"id":"f141cab6.0ebe38","type":"debug","name":"","active":false,"complete":false,"x":573.8889770507812,"y":33.888885498046875,"z":"87374e96.78c8b","wires":[]},{"id":"567972e.fa9868c","type":"function","name":"spoofxAPWeatherForecast","func":"for (var id = 0, len = msg.weather.length; id < len; id++) {\n\n\tvar weather = msg.weather[id];\n\t\n\tvar day = id + 1;\n\t\n\tvar xAPBroadcaster = new context.global.xap.XAPBroadcaster( \n\t    {\n\t    class: 'weather.forecast', \n\t    source: 'mi4.weather.345', \n\t    uid: 'FF400100', \n\t    }\n\t    );\n\n\txAPBroadcaster.send( \n\t    'forecast.day' + day, \n\t    {\n\t    'day': weather.day,\n\t    'icon': weather.outlook.replace(/ /gi, \"\"),\n\t    'maxtempc': weather.maximumtemperaturec,\n\t    'mintempc': weather.minimumtemperaturec,\n\t    'maxtempf': weather.maximumtemperaturef,\n\t    'mintempf': weather.minimumtemperaturef,\n\t    'winddir': weather.winddirection,\n\t    'windspeedm': weather.windspeed,\n\t    'windspeedk': weather.windspeed,\n\t    'airpressure': weather.pressure,\n\t    'humidity': weather.humidity,\n\t    'outlook': weather.outlook,\n\t    }\n\t    );\t\n}","outputs":"0","x":1481.8891296386719,"y":246.8888702392578,"z":"87374e96.78c8b","wires":[]},{"id":"794b3bdf.86b4c4","type":"function","name":"BBC Weather Feeds xAPSqueezeBoxOSD","func":"var weather = msg.weather[0];\n\nvar xAPBroadcaster = new context.global.xap.XAPBroadcaster( \n    {\n    class: 'message.display', \n    source: 'ahs.node-red.ahsrpi1', \n    uid: 'FF969100', \n    target: 'ersp.slimserver.ahscctvserver:*'\n    }\n    );\n\nxAPBroadcaster.send( \n    'display.text', \n    {\n    'line1': 'Weather: [Report] - ' + weather.date.substring(6,8) + '/' + weather.date.substring(4,6) + '/' + weather.date.substring(0,4) + \n    ' - ' + weather.time + ':00', \n    'line2': \"Temp: \" + weather.temperaturec + \" °C - Outlook: \" + weather.outlook + \" -  Wind: \" + weather.winddirection + \"@\" +\n    weather.windspeed + \" mph - Humidity: \" + weather.humidity + \" % Pressure: \" + weather.pressure + \" mb\", \n    'duration': '45',    \n    'priority': '2',\n    'type': 'Queue'\n    }\n    );","outputs":"0","x":1504.8889465332031,"y":99.88888549804688,"z":"87374e96.78c8b","wires":[]},{"id":"7036fa4.f8fc904","type":"function","name":"BBC Weather Feeds xAPSqueezeBoxOSD","func":"for (var id = 0, len = msg.weather.length; id < len; id++) {\n\n\tvar weather = msg.weather[id];\n\n\tvar xAPBroadcaster = new context.global.xap.XAPBroadcaster( \n\t    {\n\t    class: 'message.display', \n\t    source: 'ahs.node-red.ahsrpi1', \n\t    uid: 'FF969100', \n\t    target: 'ersp.slimserver.ahscctvserver:*'\n\t    }\n\t    );\n\t\n\txAPBroadcaster.send( \n\t    'display.text', \n\t    {\n\t    'line1': 'Weather: [Forecast] - ' + weather.date.substring(6,8) + '/' + weather.date.substring(4,6) + '/' + weather.date.substring(0,4) + \n\t    ' - ' + weather.time + ':00 - ' + weather.day, \n\t    'line2': \"Temp Min / Max: \" + weather.minimumtemperaturec + \"°C / \" + weather.maximumtemperaturec + \" °C - Outlook: \" + weather.outlook + \n\t    \" -  Wind: \" + weather.winddirection + \"@\" + weather.windspeed + \" mph - Humidity: \" + weather.humidity + \" % Pressure: \" + \n\t    weather.pressure + \" mb Visibility: \" + weather.visibility + \" Pollution: \" + weather.pollution + \" UV Risk: \" + weather.uvrisk +\n\t    \" Sunrise: \" + weather.sunrise.substring(0,2) + ':' + weather.sunrise.substring(2,4) + ':' + weather.sunrise.substring(4,6) + \n\t    \" Sunset: \" + weather.sunset.substring(0,2) + ':' + weather.sunset.substring(2,4) + ':' + weather.sunset.substring(4,6), \n\t    'duration': '45',    \n\t    'priority': '2',\n\t    'type': 'Queue'\n\t    }\n\t    );\n}","outputs":"0","x":1470.8890075683594,"y":306.8888854980469,"z":"87374e96.78c8b","wires":[]},{"id":"33ebc598.cc143a","type":"function","name":"UTF8 Fixup","func":"msg.payload = msg.payload.replace(\"\\ufeff\",\"\");\n\nreturn msg;","outputs":1,"x":592.8889465332031,"y":241.88888549804688,"z":"87374e96.78c8b","wires":[["70719207.8f8e6c"]]},{"id":"8c9eb6d5.736148","type":"function","name":"UTF8 Fixup","func":"msg.payload = msg.payload.replace(\"\\ufeff\",\"\");\n\nreturn msg;","outputs":1,"x":604.8888854980469,"y":98.88888549804688,"z":"87374e96.78c8b","wires":[["e486cc74.1b793"]]}]
Thanks for reading,

Martyn Wendon