Node Red

2 November 2017

Using Node Red to control crawl space ventilation

On the full, desktop version of Raspian available for the Raspberry Pi, there is a link for a pre-installed version of Node Red. Node Red was created by IBM to more easily facilitate IOT, "The Internet of Things". I decided to incorporate Node Red into my environment to help tie together my sensors and provide some nice gauges and graphs to see trends and results. Node Red provides a graphical way to link logical blocks together in a "flow". 

Through research I learned that one way to control humidity in a crawl space is to monitor temperature and humidity inside and outside the crawl space. These readings are used to calculate dew point. When the dew point is higher inside the crawl space than outside, you can bring in air from the outside to displace the higher humidity inside when conditions are right. I have Node Red running on two Raspberry Pi machines. One Pi will be connected to a BME280 temperature, pressure and humidity sensor. This sensor will gather information inside my crawl space. Instead of using another sensor to measure conditions outside the crawl space, I use a Weather Underground node to fetch current conditions for my location from Weather Underground.

Each block in the image below is a "node". Nodes are available from the "palette" on the left side of the screen. Most of the various node types are installed by default and others, like the BME280 node, have to be installed. Each node receives and/or sends messages in various formats. If you need a function not provided by a standard node, you can use a "function" node and write some Java Script to do what you need. For example, the "Dew Point" nodes use temperature and humidity readings to calculate dew point.

Here is a screen capture of my first flow that gathers sensor information every 15 seconds, calculates dew points, sets a Raspberry Pi GPIO pin to control a ventilation fan, and stuff all the information into an on-board MQTT broker. The MQTT data is "bridged" (think mirrored) to a second Raspberry Pi that gathers this info and populates a MySQL database and generates web pages for graphical display of all the information.

This is the flow on the crawl space ventilation control Pi:


If you would like to create an identical flow in your Raspberry Pi, here is the text you can use. Just select all the text and copy it to the clipboard, then in Node Red, select "import" then "Clipboard" and import into your flow. The Weather Underground node requires an API key which you will need to get from Weather Underground. The API key is free and you can make 500 API calls per day before they cut you off.

Crawlspace Flow

[{"id":"40521289.45511c","type":"split","z":"b4d8ed2f.d9dd5","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":410,"y":110,"wires":[["d0fcf5d9.086ec8"]]},{"id":"25549ffb.1f148","type":"bme280rpi","z":"b4d8ed2f.d9dd5","name":"Inside Sensor","bus":"1","address":"0x76","interval":"15000","x":80,"y":110,"wires":[["1fe5d46f.da598c"]]},{"id":"1fe5d46f.da598c","type":"json","z":"b4d8ed2f.d9dd5","name":"","pretty":true,"x":240,"y":110,"wires":[["40521289.45511c","f556e5e0.9bca18"]]},{"id":"d0fcf5d9.086ec8","type":"switch","z":"b4d8ed2f.d9dd5","name":"","property":"parts.key","propertyType":"msg","rules":[{"t":"cont","v":"temperature","vt":"str"},{"t":"cont","v":"humid","vt":"str"}],"checkall":"true","outputs":2,"x":540,"y":110,"wires":[["b4627455.54be28"],["774d5df7.8b7954"]]},{"id":"f556e5e0.9bca18","type":"function","z":"b4d8ed2f.d9dd5","name":"Dew Point","func":" \nif(msg.temperature !== null)\n\t{\n\t\tvar temp2 = msg.temperature;\n\t\tvar r = msg.humidity;\n\t  \n\t\n\t\t    \n        var temp = -1.0*temp2;\n\t    es = 6.112*Math.exp(-1.0*17.67*temp/(243.5 - temp));\n\t    ed = r/100.0*es;\n\t    eln = Math.log(ed/6.112);\n\t    td = -243.5*eln/(eln - 17.67 );\n//\t    msg.payload = td.toFixed(2);\n        msg.payload = (td * 1.8)+32;\n\t}\nreturn msg;","outputs":1,"noerr":0,"x":458,"y":163,"wires":[["bf61ad0f.26e08"]]},{"id":"de1227f4.66c478","type":"mqtt out","z":"b4d8ed2f.d9dd5","name":"MQTT","topic":"cs/inside/temperature","qos":"0","retain":"false","broker":"142f2e82.533aa1","x":965,"y":76,"wires":[],"inputLabels":["temperature"]},{"id":"5b9bb471.643e3c","type":"smooth","z":"b4d8ed2f.d9dd5","name":"","action":"mean","count":"5","round":"1","mult":"single","x":809,"y":76,"wires":[["de1227f4.66c478"]]},{"id":"774d5df7.8b7954","type":"smooth","z":"b4d8ed2f.d9dd5","name":"","action":"mean","count":"5","round":"1","mult":"single","x":810,"y":120,"wires":[["41941793.fa9648"]]},{"id":"41941793.fa9648","type":"mqtt out","z":"b4d8ed2f.d9dd5","name":"MQTT","topic":"cs/inside/humidity","qos":"0","retain":"false","broker":"142f2e82.533aa1","x":967,"y":118,"wires":[],"inputLabels":["temperature"]},{"id":"bf61ad0f.26e08","type":"smooth","z":"b4d8ed2f.d9dd5","name":"","action":"mean","count":"5","round":"1","mult":"single","x":665,"y":162,"wires":[["a58367e9.fd3a78"]]},{"id":"5ea2cba1.ab7234","type":"mqtt out","z":"b4d8ed2f.d9dd5","name":"MQTT","topic":"cs/inside/dewpoint","qos":"0","retain":"false","broker":"142f2e82.533aa1","x":970,"y":164,"wires":[],"inputLabels":["temperature"]},{"id":"28ec8b9e.6c9bb4","type":"rpi-gpio out","z":"b4d8ed2f.d9dd5","name":"MPIO Fan Relay","pin":"12","set":true,"level":"0","freq":"","out":"out","x":940,"y":510,"wires":[]},{"id":"4cf9d8d0.3682a8","type":"mqtt out","z":"b4d8ed2f.d9dd5","name":"MQTT","topic":"cs/fanrelay","qos":"0","retain":"false","broker":"142f2e82.533aa1","x":914,"y":558,"wires":[],"inputLabels":["temperature"]},{"id":"b4627455.54be28","type":"function","z":"b4d8ed2f.d9dd5","name":"C to F","func":"msg.payload = (msg.payload * 1.8)+32;\nreturn msg;","outputs":1,"noerr":0,"x":675,"y":76,"wires":[["5b9bb471.643e3c"]]},{"id":"a21fc800.f8f8c8","type":"cpu","z":"b4d8ed2f.d9dd5","name":"","x":783,"y":616,"wires":[["15f8e5fb.594d4a"]]},{"id":"8a03e27a.6c748","type":"interval","z":"b4d8ed2f.d9dd5","name":"interval","interval":"5","onstart":false,"msg":"ping","showstatus":true,"unit":"seconds","statusformat":"YYYY-MM-D HH:mm:ss","x":588,"y":616,"wires":[["a21fc800.f8f8c8"]]},{"id":"15f8e5fb.594d4a","type":"ui_chart","z":"b4d8ed2f.d9dd5","name":"","group":"ab8853c.b9d01b","order":0,"width":"24","height":"6","label":"chart","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":978,"y":617,"wires":[[],[]]},{"id":"a58367e9.fd3a78","type":"function","z":"b4d8ed2f.d9dd5","name":"flow.set","func":"flow.set(\"isdewpoint\",msg.payload);\nreturn msg;","outputs":1,"noerr":0,"x":835,"y":163,"wires":[["5ea2cba1.ab7234"]]},{"id":"22f26389.03041c","type":"function","z":"b4d8ed2f.d9dd5","name":"flow.get","func":"var ostemp = flow.get(\"ostemperature\")|| \"\";\nvar isdp = flow.get(\"isdewpoint\")|| \"\";\nvar osdp = flow.get(\"osdewpoint\")|| \"\";\n\nif (ostemp >= 50) {    \n    if (isdp-1 >= osdp){\n        msg.payload = 1;\n        return [msg,null];\n    } else {\n        msg.payload = 0;\n        return [msg,null];\n    }\n} else {\n    msg.payload = 0;\n    return [msg,null];\n}","outputs":1,"noerr":0,"x":700,"y":508,"wires":[["28ec8b9e.6c9bb4","4cf9d8d0.3682a8"]]},{"id":"ee90662c.a386f8","type":"interval","z":"b4d8ed2f.d9dd5","name":"interval","interval":"5","onstart":false,"msg":"ping","showstatus":true,"unit":"seconds","statusformat":"YYYY-MM-D HH:mm:ss","x":522,"y":509,"wires":[["22f26389.03041c"]]},{"id":"571221b8.2a12f","type":"wunderground in","z":"b4d8ed2f.d9dd5","name":"Local Weather","lon":"","lat":"","city":"Nicholson","country":"GA","x":90,"y":320,"wires":[["b834b07e.21e4","ed2ae40a.766768"]]},{"id":"b834b07e.21e4","type":"function","z":"b4d8ed2f.d9dd5","name":"Outside Dew Point","func":"if (msg.payload[\"tempc\"] !== null)\n\t{\n\t\tvar temp2 = msg.payload[\"tempc\"];\n\t\tvar humstr = msg.payload[\"humidity\"];\n    \tvar r = Number(humstr.substring(0, humstr.length - 1));\n        var temp = -1.0*temp2;\n\t    es = 6.112*Math.exp(-1.0*17.67*temp/(243.5 - temp));\n\t    ed = r/100.0*es;\n\t    eln = Math.log(ed/6.112);\n\t    td = -243.5*eln/(eln - 17.67 );\n//\t    msg.payload = td.toFixed(2);\n        msg.payload = (td * 1.8)+32;\n\t}\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":410,"wires":[["e2843107.fc446"]]},{"id":"ed2ae40a.766768","type":"split","z":"b4d8ed2f.d9dd5","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":300,"y":320,"wires":[["dc6d4d76.3b9a8"]]},{"id":"dc6d4d76.3b9a8","type":"switch","z":"b4d8ed2f.d9dd5","name":"","property":"parts.key","propertyType":"msg","rules":[{"t":"cont","v":"tempc","vt":"str"},{"t":"cont","v":"humidity","vt":"str"}],"checkall":"true","outputs":2,"x":430,"y":320,"wires":[["4063b92b.10f9c8"],["59b0a823.98ca18"]]},{"id":"93b66f0b.f3041","type":"mqtt out","z":"b4d8ed2f.d9dd5","name":"MQTT","topic":"cs/outside/dewpoint","qos":"0","retain":"false","broker":"142f2e82.533aa1","x":970,"y":410,"wires":[],"inputLabels":["temperature"]},{"id":"a1a461ef.d1ba3","type":"function","z":"b4d8ed2f.d9dd5","name":"flow.set","func":"flow.set(\"osdewpoint\",msg.payload);\nreturn msg;","outputs":1,"noerr":0,"x":838,"y":409,"wires":[["93b66f0b.f3041"]]},{"id":"4dc99a1a.69d524","type":"mqtt out","z":"b4d8ed2f.d9dd5","name":"MQTT","topic":"cs/outside/temperature","qos":"0","retain":"false","broker":"142f2e82.533aa1","x":971,"y":269,"wires":[],"inputLabels":["temperature"]},{"id":"7b8cc416.f227ec","type":"mqtt out","z":"b4d8ed2f.d9dd5","name":"MQTT","topic":"cs/outside/humidity","qos":"0","retain":"false","broker":"142f2e82.533aa1","x":970,"y":320,"wires":[],"inputLabels":["temperature"]},{"id":"4063b92b.10f9c8","type":"function","z":"b4d8ed2f.d9dd5","name":"C to F","func":"msg.payload = (msg.payload * 1.8)+32;\nreturn msg;","outputs":1,"noerr":0,"x":680,"y":270,"wires":[["c403a4ea.b45478","a285b40b.fe3838"]]},{"id":"c403a4ea.b45478","type":"smooth","z":"b4d8ed2f.d9dd5","name":"","action":"mean","count":"2","round":"1","mult":"single","x":830,"y":270,"wires":[["4dc99a1a.69d524"]]},{"id":"e2d66b32.fdc098","type":"smooth","z":"b4d8ed2f.d9dd5","name":"","action":"mean","count":"2","round":"1","mult":"single","x":830,"y":320,"wires":[["7b8cc416.f227ec"]]},{"id":"e2843107.fc446","type":"smooth","z":"b4d8ed2f.d9dd5","name":"","action":"mean","count":"2","round":"1","mult":"single","x":670,"y":410,"wires":[["a1a461ef.d1ba3"]]},{"id":"59b0a823.98ca18","type":"function","z":"b4d8ed2f.d9dd5","name":"Str to Num","func":"if (msg.payload !== null)\n\t{\n\t\tvar humstr = msg.payload;\n    \tvar r = Number(humstr.substring(0, humstr.length - 1));\n\t    msg.payload = r;\n\t\treturn msg;\n\t}\nreturn msg;","outputs":1,"noerr":0,"x":660,"y":320,"wires":[["e2d66b32.fdc098"]]},{"id":"a285b40b.fe3838","type":"function","z":"b4d8ed2f.d9dd5","name":"flow.set","func":"flow.set(\"ostemperature\",msg.payload);\nreturn msg;","outputs":1,"noerr":0,"x":830,"y":220,"wires":[[]]},{"id":"142f2e82.533aa1","type":"mqtt-broker","z":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willPayload":"","birthTopic":"","birthQos":"0","birthPayload":""},{"id":"ab8853c.b9d01b","type":"ui_group","z":"","name":"Resources","tab":"179f5699.7e7c89","disp":true,"width":"24"},{"id":"179f5699.7e7c89","type":"ui_tab","z":"","name":"Resources","icon":"dashboard"}]

This dashboard is running on the second Raspberry Pi. The red light bulb icon indicates that the ventilation fan is engaged. The icon turns green when the fan is off. 

https://sites.google.com/a/wt4y.com/radio/node-red/DinPi.jpg
Here is the initial installation in the crawl space. At the moment, two sensors reside inside, but I will be using only one. The next step will be to mount the sensor in a proper enclosure. All the components are mounted on a standard DIN rail. From left to right the components are:
  • Perf board with the interface to the sensors, power supply and solid state relay
  • the Raspberry Pi with a 50mm fan to keep it cool
  • 5 volt power supply (from Amazon)
  • solid state relay (from Amazon)
  • DIN terminal blocks (from Amazon)
  • 3d printed mount for the PVC conduit (Carlon 3/4 inch conduit available at Home Depot or Lowes)
The fan on the Pi is rated for 12 volts, but I'm using 5 volts for power. This provides more than enough air flow to cool the Pi and it's completely silent.

I 3d printed the parts to mount the perf board, Raspberry Pi, power supply and conduit mount. If you are interested in the STL or step files for any of these components, just send me an email. I will eventually upload everything to Thingiverse.com.

For primary power, I wired up two switches. One switch controls input power to the 5 volt power supply. The other switch acts as a safety switch for the ventilation fan (not yet mounted). The duplex outlet is there because you can never have too many outlets.