My Goal - Make a sort of "hat" thing that sits above the stove to bring the stinky stinks to the outside world
(i.e. a smart range hood which can run itself, text the home owners, summon the husband for dinner with images of dinner, and do other cool things too!)
See blog post #1 for links to all posts in the project.
Reading the Data
My last post got very long so I wanted to do one in more detail relating to the actual control of the system. So far, I have most the hardware running on a breadboard and controlled through Node Red.
In particular, I will focus on the DHT 22 temperature / humidity sensor. At this stage in the process, I have the DHT being read once per second. It pushes the current reading into an algorithm to decide IF the fan should run and HOW FAST the fan should run. It also allows manual control so that any user can push a button on the UI either locally or through the OpenHAB from their phone. Additionally, the temperature and humidity data is pushed into OpenHAB via MQTT once every 5 minutes.
I plan to do similar posts related to the other sensors as I get the controls finalized for them. As you'll see in the video, this one seems to be pretty rock solid for what it does in controlling the system, reporting status, and working with the different user interfaces.
As I noted in my previous post, this is the "flow" for the DHT sensor:
The "timestamp" node at the left is the main "tick" that runs my code. It fires once per second and since it is linked to the DHT sensor, this will trigger a reading of the sensor data. The DHT22 node comes from a package add-on to Node Red called "node-red-contrib-dht-sensor". This add-on handles all of the 1-wire communication protocol to the sensor and simplifies use to just defining the GPIO pin required. The sensor data is then passed to the following node labeled "ConvertToF". This is a "function" node allowing me to type basically any javascript compatible script. I'll expand the detail of the function node below.
At this stage, the flow splits into multiple paths. One path drops off the bottom of the image a feeds further nodes for automation. The graphic above also shows two paths that run through a rate limiter then post to an MQTT server. The rate limiter is a configurable "out of the box" timer node. In just a few simple clicks, I can set the timer node to allow only a single message every five minutes. This is perfect for this application since I want to read the sensor basically constantly so I can control the fan; but the home automation server doesn't need to be flooded with all that data; it just needs periodic updates. The final node is an MQTT publish node. It takes incoming data and publishes it to an MQTT broker as configured. Again, with Node Red this comes as an "out of the box" feature that the programmer can just drag out from the tool palette, configure a few options, link the node & deploy.
The raw sensor data is passed into the function node as a json message as follows:
{"topic":"DHT22","payload":"21.90","_msgid":"3572bca8.7bf4e4","humidity":"33.90","location":"DHT22","sensorid":"dht22"}
To use the temperature reading, I can simply read msg.payload in my javascript. In this case, I need to convert from Celsius to American, so I use the 'ole conversion rule - F = (5/3) C + 32. I also round this to two decimal places with the .toFixed(2) function. To access the humidity in the json message, I use msg.humidity. These values are then sent out of the function node each on their own path. I also save out the temperature and humidity to global variables so they can be accessed from other nodes anywhere in the program. This is done through the global.set and global.get functions.
var tmpF = ((5/3) * msg.payload + 32).toFixed(2); // read temperature value, convert to F, and round to two digits. global.set ("Temperature", tmpF); //save global variable for temperature var humidity = msg.humidity; //read humidity value global.set ("Humidity", humidity); // save global variable for humidity return [{payload: tmpF}, {payload: humidity}]; //write data out to two different nodes so they can follow different paths
So the final set of steps to getting the DHT 22 running in Node Red on the raspberry Pi are as follows:
1. Install the DHT sensor library. (see blog post # 6)
2. Drag & drop all nodes as show above into a flow
3. Link all nodes in order
4. Update Function node as shown with code
5. Minor settings updates in the other nodes.
This entire process if done correctly should be quite fast. I did run into some issues initially getting the DHT library installed and since I don't use javascript on a daily basis there was a few syntax things that I ran into like rounding to two decimal places.
Doing cool things with the data
The difference between having lots of data and doing cool things with the data is the same difference as between being a hoarder and a smarty-pants.
Once I have the sensor data, I feed a main controller for the fan. I use a function block like I did above, but modify it to have four outputs. I trigger it from the end of the main "loop".
This Fan Controller node holds most the core logic for controlling the fan. It has two input nodes - one feeding from MQTT for manual override control and one triggering it from the end of the main loop. The node needs to look at all the sensor data (humidity, methane, LPG, CO, hydrogen) and decide if the fan needs to run. It also must accommodate any user just walking up and pressing the "run" button. To actually run the fan, I am triggering a set of relays. I need ONE and ONLY ONE relay to be on at a time. The logic block must decide firstly IF the fan should run, then HOW FAST to make it run, then trigger the appropriate relays. It additionally must also report out to MQTT to the rest of the home automation system what it is up to.
The logic ended up being pretty simple. I have a set of IF statements for each sensor. They compare the sensor reading against setpoints for each fan level. For the humidity sensor, I used 95%, 87%, and 80% as HIGH, MED, and LOW setpoints. The humidity must exceed 80% to turn ON. It will not turn OFF until the humidity is below 70%. This creates a hysteresis of at least 10%RH where we don't short-cycle the fan if the humidity hovers around a single ON/OFF setpoint value.
// Humidity sensor logic if(global.get("Humidity") > 95){ fanReqHumidity = 3;} // request high speed else if(global.get("Humidity") > 87){ fanReqHumidity = 2;} // request high speed else if(global.get("Humidity") > 80){ fanReqHumidity = 1;} // request low speed else if(global.get("Humidity") < 70){ fanReqHumidity = 0;} //turn off need from humidity sensor
In this example, I am setting a "request" variable ("fanReqHumidity") for the humidity. It goes from 0 to 3 for off, low, med, high. Each other sensor has its own "request" variable. Farther down in the code, all I need to do is find the maximum request and accommodate that request. This means that any sensor exceeding the threshold can set a request and the fan will run the maximum requested speed. Humidity may not require the fan to run, but if CO gets too high, then it will come on. This uses the Math.max() function in line 3 below.
To accommodate manual fan control in this logic block, I can simply compare against the global variable "manualFanControl" to see which mode the system is running.
// Final control logic - find the highest required value if(global.get("manualFanControl") === "FALSE"){ global.set("FanSpeed", Math.max(fanReqHumidity, fanReqMethane, fanReqHydrogen, fanReqCO, fanReqLPG)); } else{ global.set("FanSpeed", parseInt(global.get("manualFanSetting"))); }
Then to actually fire the correct outputs, there is a section which translates the speed of 0-3 (off/low/med/high) into the correct discrete outputs for the attached relays. I found out that the RaspberryPi seems to assert the pins HIGH by default instead of LOW which I am used to dealing with. Additionally, the relay board I purchased uses inverse logic - 0V means turn ON the relay and 3.3V means turn OFF the relay. So to get the a relay to clik ON, I have to write a 0 to the GPIO on the board. I use an IF/Else statement for the four different combinations of fan speed and write either a 1 or a 0 to each of the three fan relays. I additionally write out current fan speed to an MQTT node as the fourth output from the logic block.
if(global.get("FanSpeed") === 0) return [{payload: 1},{payload: 1},{payload: 1},{payload: 0}]; // Fan off - all off else if(global.get("FanSpeed") === 1) return [{payload: 0},{payload: 1},{payload: 1},{payload: 1}]; // low speed else if(global.get("FanSpeed") === 2) return [{payload: 1},{payload: 0},{payload: 1},{payload: 2}]; // Med speed else if(global.get("FanSpeed") === 3) return [{payload: 1},{payload: 1},{payload: 0},{payload: 3}]; // High speed
Writing data out to specific outputs of NodeRed is done through an array. Each element of the array is written to the corresponding output from the node. I also needed to cast the values of 0 or 1 as a type called 'payload' to make this work correctly.
Finally, here is the complete (CURRENT) function block for the fan controller.
var fanReqHumidity; // number from 0-3 if humidity requests the fan var fanReqMethane; // fan request methane var fanReqHydrogen; // fan request hydrogen var fanReqCO; // fan request CO var fanReqLPG; // fan request LPG // Humidity sensor logic if(global.get("Humidity") > 95){ fanReqHumidity = 3;} // request high speed else if(global.get("Humidity") > 87){ fanReqHumidity = 2;} // request med speed else if(global.get("Humidity") > 80){ fanReqHumidity = 1;} // request low speed else if(global.get("Humidity") < 70){ fanReqHumidity = 0;} //turn off from humidity sensor // Methane sensor logic if(global.get("Methane") > 1000){ fanReqMethane = 3;} // request high speed else if(global.get("Methane") > 850){ fanReqMethane = 2;} // request med speed else if(global.get("Methane") > 750){ fanReqMethane = 1;} // request low speed else if(global.get("Methane") < 500){ fanReqMethane = 0;} //turn off need from methane sensor // Hydrogen sensor logic if(global.get("Hydrogen") > 2000){ fanReqHydrogen = 3;} // request high speed else if(global.get("Hydrogen") > 1900){ fanReqHydrogen = 2;} // request med speed else if(global.get("Hydrogen") > 1750){ fanReqHydrogen = 1;} // request low speed else if(global.get("Hydrogen") < 1650){ fanReqHydrogen = 0;} //turn off need from hydrogen sensor // CO sensor logic if(global.get("CO") > 12){ fanReqCO = 3;} // request high speed else if(global.get("CO") > 9){ fanReqCO = 2;} // request med speed else if(global.get("CO") > 8){ fanReqCO = 1;} // request low speed else if(global.get("CO") < 7){ fanReqCO = 0;} // turn off need from CO sensor // LPG sensor logic if(global.get("LPG") > 1000){ fanReqLPG = 3;} // request high speed else if(global.get("LPG") > 850){ fanReqLPG = 2;} // request med speed else if(global.get("LPG") > 750){ fanReqLPG = 1;} // request low speed else if(global.get("LPG") < 500){ fanReqLPG = 0;} // turn off need from LPG sensor // Final control logic - find the highest required value if(global.get("manualFanControl") === "FALSE"){ global.set("FanSpeed", Math.max(fanReqHumidity, fanReqMethane, fanReqHydrogen, fanReqCO, fanReqLPG)); } else{ global.set("FanSpeed", parseInt(global.get("manualFanSetting"))); } if(global.get("FanSpeed") === 0) return [{payload: 1},{payload: 1},{payload: 1},{payload: 0}]; // Fan off - all off else if(global.get("FanSpeed") === 1) return [{payload: 0},{payload: 1},{payload: 1},{payload: 1}]; // low speed else if(global.get("FanSpeed") === 2) return [{payload: 1},{payload: 0},{payload: 1},{payload: 2}]; // Med speed else if(global.get("FanSpeed") === 3) return [{payload: 1},{payload: 1},{payload: 0},{payload: 3}]; // High speed
Manual Control
Most users still expect to be able to just walk up to a device and turn it on. They generally don't want to be required to use their smartphone to do simple tasks like turn on the lights in the room or the coffee pot. The UI and the manual buttons will allow local control of the device. The current implementation of this uses OpenHAB as the mobile user interface and connects to the range hood over MQTT.
In general, manual control just involves setting a global variable called "manualFanControl" to TRUE and providing a requested speed. A timer node is used to give an appropriate timeout after which automatic mode will be enabled again.
Here is what the flow looks like:
So we are using an MQTT "listen" node under the topic called "home/kitchen/rangehood/fan/command" for any newly issued commands. There are also four UI button nodes with the little hand icon for the NodeRed user interface for manual control This is subject to change before the final design, but here is the current implementation accessible through a web browser:
So all of these nodes feed into a function node called "Enable Manual Control." This simply sets a global variable for manual control. The next node is a timer - once the timer expires then control reverts to automatic. For testing, I have left the timer at 2 seconds. In production I think I will use 30 minutes. Once automatic control takes back over, the fan speed will be set based on the sensor data as explained above.
One more little bit - I am using OpenHAB as my main home automation controller. The Range Hood will function autonomously of OpenHAB; but will still offer status info and take external data from the OpenHAB server. I am not going to give a lot of detail on how OpenHAB is set up, but I will post here the relevant .items file contents and .sitemap section that is shown in the video.
Sitemap file which controls the user interface -
Group item=Kitchen label="Range Hood" { Switch item=RangeHoodFan mappings=[0="Off", 1="Low", 2="Med", 3="High"] Switch item=RangeHoodLight icon=light Text item=RangeHoodTemperature Text item=RangeHoodHumidity Text item=RangeHoodMethane Text item=RangeHoodLPG Text item=RangeHoodCO Text item=RangeHoodHydrogen Switch item=RangeHoodBuzzer Chart item=rangeHoodGasses period=h refresh=300000 Chart item=RangeHoodTemperature period=d refresh=300000 Chart item=RangeHoodHumidity period=d refresh=300000 }
.items file which defines the variables available to the system -
Group rangeHoodGasses Number RangeHoodFan "Fan Speed" { mqtt=">[mosquitto:home/kitchen/rangehood/fan/command:command:*:default], <[mosquitto:home/kitchen/rangehood/fan/status:state:default]"} Switch RangeHoodLight "Light" { mqtt=">[mosquitto:home/kitchen/rangehood/light/state:command:*:default], <[mosquitto:home/kitchen/rangehood/light/status:state:default]"} Switch RangeHoodBuzzer "Buzzer" { mqtt=">[mosquitto:home/kitchen/rangehood/buzzer/state:command:*:default], <[mosquitto:home/kitchen/rangehood/buzzer/status:state:default]"} Number RangeHoodHumidity "Humidity [%.01f %%]" {mqtt="<[mosquitto:home/kitchen/rangehood/humidity:state:default]"} Number RangeHoodTemperature "Temperature [%.01f °F]" {mqtt="<[mosquitto:home/kitchen/rangehood/temperature:state:default]"} Number RangeHoodMethane "Methane [%s ppm]" (rangeHoodGasses) {mqtt="<[mosquitto:home/kitchen/rangehood/methane:state:default]"} Number RangeHoodLPG "LPG [%s ppm]" (rangeHoodGasses) {mqtt="<[mosquitto:home/kitchen/rangehood/LPG:state:default]"} Number RangeHoodCO "CO [%s ppm]" (rangeHoodGasses) {mqtt="<[mosquitto:home/kitchen/rangehood/CO:state:default]"} Number RangeHoodHydrogen "Hydrogen [%s ppm]" (rangeHoodGasses) {mqtt="<[mosquitto:home/kitchen/rangehood/hydrogen:state:default]"}
Finally - here is a video demonstration showing the final controls inaction - err - I mean "in action." I also walk through the Node Red flows and you can catch glimpses of some of the other portions of the project. I also cover the power consumption of the complete system. If you only have a few minutes, check this one out. It is very neat since it show automatic control of the system based on sensor data.
Node Red flow overview - this one is a little long, but covers all the stuff in this blog post + some extras. It explains in additional information the control system.
Before we end - here is a preview of where the sheet metal design ended up -
SSSSUUUUPPPPERRRR excited that this came in. Stay tuned for more info on all the details...
Top Comments