Weather Prediction with Node-Red, forecast.io and a Raspberry Pi

A feature of Astro-Photography is the need to leave expensive equipment outside over night as it captures the hours of data necessary to create an image. You keep a careful eye on it but as none of this equipment is water-proof,  it leads to acute paranoia that the heavens may open the moment you take your eye off the sky. Also even if you do notice the rain it takes time to break the setup down and get it to safety.

So here is my solution to the problem which utilises a fantastic new technology built in IBM Hursley: Node – Red to take Ultra Local rain forcasting from the forecast.io webservice and trigger an alarm to wake me up should rain be predicted in the next 45 minutes.

NodeRed

Node-Red is an intuitive, easy to use tool based on Node.js that makes it incredibly simple to wire together services and devices . What’s more – it has recently been releasd to the open-source community so is free for all!

More information can be found at the following links:

http://www.nodered.org
Twitter:  @nodeRed

Node-Red also satisifed my other requirement in that it can be run on A Raspberry Pi – meaning that it can be unobtrusively left on 24×7.

Forecast.io

imageForecast.io is a great service that utilises radar feeds to make ultra local rainfall predictions. The site uses high resolution feeds in North America and from the Met Office in the UK and I have found it to be quite accurate. In other regions the without high resolution radar the prediction may be less accurate.

What makes the site awesome is that an api is provided that can be called 1000 times a day for free. All you need to do is sign up for an account and get an api key from here:

https://developer.forecast.io/

Putting it all together

The following screenshot shows the Node-Red flow used to :

  • Call the forecast.io api every 3 minutes with a lat / long location value.
  • Parse the returned result  to determine the number of minutes until the next rainfall. In this case I am only using predictions with a probability of over 25% as I have found lower values to prone to flase positives.
  • Write the number of minutes-to-rain to an mqtt topic.
  • I have seperate flows to drive actions based on the prediction. Firstly a flow to determine if the number of minutes is considered unsafe, and secondly a flow to create an RGB value that drives an ambient light. An mqtt connected arduino controlling a 433 Mhz plug socket turns on a bedside lamp if an unsafe condition is triggered.

Capture2

The Lat / Long location is passed into the flow from the Inject Node:

image

The forecast api key needs to be inserted into the url in the HTTP Get node.

image

Finally the function that does the parsing:

//parse forecast.io message

var weather = JSON.parse(msg.payload);

var data = weather.minutely.data;
var timeToRain = -1;

var i=1;
for (var i=0 ; i< data.length;i++) {
if (data[i].precipProbability > 0.25) { weather.nextRain = parseInt(data[i].time); break; }
}

//reduce size of object
delete weather.minutely;
delete weather.hourly;
delete weather.daily;
delete weather.flags;

if (weather.nextRain != null) {
var t =( weather.nextRain – parseInt(weather.currently.time) );

if (t<0){t=0}
else{t = t/60;}
timeToRain = t;
}
var msg2 = { payload:timeToRain};
return msg2;

As an idea of the actions triggered from the flow:

image

The devices doing the actual “home automation” are based on the JeeNode system – again controlled from Node-Red. More on this in the next edtion….

So go ahead and give it a go!  Use the Node-Red Import from Clipboard function to recreate this flow:

[{“id”:”ba386057.845d3″,”type”:”mqtt-broker”,”broker”:”localhost”,”port”:”1883″},{“id”:”5a66f76f.531448″,”type”:”inject”,”name”:”Burley”,”topic”:”Home”,”payload”:”51.0246,-1.3906″,”repeat”:”180″,”crontab”:””,”once”:true,”x”:103,”y”:114,”z”:”d41c69ca.0cff8″,”wires”:[[“bf4d1618.2a2518”]]},{“id”:”bf4d1618.2a2518″,”type”:”httpget”,”name”:”get forecast.io”,”baseurl”:”https://api.forecast.io/forecast/<api-key-goes-here>/”,”append”:”?units=uk”,”x”:264.9999237060547,”y”:115.00008201599121,”z”:”d41c69ca.0cff8″,”wires”:[[“6581eda0.195ccc”,”b4e2b9ee.055f6″]]},{“id”:”6581eda0.195ccc”,”type”:”function”,”name”:”make Weather object”,”func”:”//parse forecast.io message\n\nvar weather = JSON.parse(msg.payload); \n\nvar data = weather.minutely.data;\nvar timeToRain = -1;\n\nvar i=1;\nfor (var i=0 ; i< data.length;i++) {\n\tif (data[i].precipProbability > 0.25) { weather.nextRain = parseInt(data[i].time); break; }\n}\n\n//reduce size of object\ndelete weather.minutely;\ndelete weather.hourly;\ndelete weather.daily;\ndelete weather.flags;\n\nif (weather.nextRain != null) {\n\tvar t =( weather.nextRain – parseInt(weather.currently.time) );\n\nif (t<0){t=0}\nelse{t = t/60;}\ntimeToRain = t;\n}\nvar msg2 = { payload:timeToRain};\nreturn msg2;”,”outputs”:”1″,”x”:479.99993896484375,”y”:172,”z”:”d41c69ca.0cff8″,”wires”:[[“65c2b8ac.5d5c3″,”1537d2c3.e11a7d”]]},{“id”:”1537d2c3.e11a7d”,”type”:”debug”,”name”:””,”complete”:true,”x”:739.8333129882812,”y”:106.5,”z”:”d41c69ca.0cff8″,”wires”:[]},{“id”:”65c2b8ac.5d5c3″,”type”:”mqtt out”,”name”:”Minutes to Rain”,”topic”:”weather/forecast/minsToRain”,”broker”:”ba386057.845d3″,”x”:728,”y”:167,”z”:”d41c69ca.0cff8″,”wires”:[]},{“id”:”3a5c4bfb.2a358c”,”type”:”mqtt in”,”name”:”Minutes To Rain”,”topic”:”weather/forecast/minsToRain”,”broker”:”ba386057.845d3″,”x”:118,”y”:325,”z”:”d41c69ca.0cff8″,”wires”:[[“c3f1fcac.a78d48”]]},{“id”:”c3f1fcac.a78d48″,”type”:”function”,”name”:”Is weather unsafe?”,”func”:”// console.log(msg.topic, msg.payload, msg.qos, msg.retain);\n// context = {};\nvar unsafe=0;\nvar minsToRain = parseInt(msg.payload);\nif ((minsToRain >=0) && (minsToRain < 46) )\n{\n   unsafe=1;\n}\nmsg.payload=unsafe;\nreturn msg;”,”outputs”:”1″,”x”:366,”y”:325,”z”:”d41c69ca.0cff8″,”wires”:[[“6e3aa3cd.33d8bc”]]},{“id”:”6e3aa3cd.33d8bc”,”type”:”mqtt out”,”name”:”Weather unsafe”,”topic”:”weather/unsafe”,”broker”:”ba386057.845d3″,”x”:589,”y”:306,”z”:”d41c69ca.0cff8″,”wires”:[]},{“id”:”509b8e5.076f2f”,”type”:”function”,”name”:”Rain Warning Colour”,”func”:”// The received message is stored in ‘msg’\n// It will have at least a ‘payload’ property:\n//   console.log(msg.payload);\n// The ‘context’ object is available to store state\n// between invocations of the function\n//   context = {};\n\n//if weather status switched off end flow now\nif(context.global.weatherStatus == \”0\”)\n{\n   return null;\n}\nelse\n{\n\tvar red =0;\n\tvar blue =0;\n\tvar green =0;\n\t\n\tvar minsToRain = msg.payload;\n\t\n\tif (minsToRain == -1 || minsToRain > 45)\n\t{\n\t\tgreen=255;\n\t}\n\telse if (minsToRain == 0)\n\t{\n\t   red =255;\n\t}\n\telse\n\t{\n\t\t\tblue = (255 – (255 – (minsToRain*5)));\n\t        red = (255 – (minsToRain*5));\n\t        green =0;\n\t}\n\t\n\tmsg.payload = red + \”,\” + green + \”,\” + blue;\n}\nreturn msg;”,”outputs”:1,”x”:394.25,”y”:426.75,”z”:”d41c69ca.0cff8″,”wires”:[[“801b2e9.83a885″,”a96a35d2.dff4f”]]},{“id”:”801b2e9.83a885″,”type”:”debug”,”name”:”rgb out”,”active”:true,”complete”:”false”,”x”:674.25,”y”:474.75,”z”:”d41c69ca.0cff8″,”wires”:[]},{“id”:”55a50306.7be7f4″,”type”:”mqtt in”,”name”:”Minutes To Rain”,”topic”:”weather/forecast/minsToRain”,”broker”:”ba386057.845d3″,”x”:118.25,”y”:428.75,”z”:”d41c69ca.0cff8″,”wires”:[[“509b8e5.076f2f”]]},{“id”:”a96a35d2.dff4f”,”type”:”mqtt out”,”name”:”Ambient Light Lounge 1″,”topic”:”home/ambient/lounge/1″,”broker”:”ba386057.845d3″,”x”:727,”y”:384,”z”:”d41c69ca.0cff8″,”wires”:[]},{“id”:”b4e2b9ee.055f6″,”type”:”debug”,”name”:””,”active”:true,”complete”:”false”,”x”:450,”y”:111,”z”:”d41c69ca.0cff8″,”wires”:[]},{“id”:”7d3f1c52.6311ac”,”type”:”comment”,”name”:”Rain Forecaster”,”info”:””,”x”:93,”y”:67,”z”:”d41c69ca.0cff8″,”wires”:[]}]

This entry was posted in Uncategorized.