- [INDEX]
- Introduction and project description
- Sensor Node and Gateway Design
- How I use OpenHAB - audio notification and dashboards
- Design detail for wireless sensor nodes and gateway
- Uber Sensor - wireless sensor for fire, smoke, gas, barking, light, movement
- Diaper Box Sensor
- Laundry Room Sensor
I have my laundry room in the basement. I wanted to make a sensor that will tell me if the laundry is complete, and keep track of whether it's been picked up. I also wanted a water leak sensor in case something overflows. This laundry room sensor has the following elements. Check out the video demo at the very bottom.
- Sound sensor to detect Washer or Dryer running / idle state
- PIR sensor to detect laundry pickup
- Light sensor to detect laundry room light
- Water detection circuit to alarm if something overflows or floods.
It looks like this.
The idea is to move the audio notification from the basement to somewhere more optimal - like the living room or kitchen. Things like a water leak would of course merit an email notification. Status is displayed on the smart phone. I'm also keeping track of whether the laundry gets picked up. I only have one sound sensor right now, but eventually I'd like to equip both the washer and the dryer.
Demo Video:
First, here's the item definition. I'm defining both MQTT and display-only items to facilitate using rules to massage the graphics.
//--------------------------------------- //Item Definition //--------------------------------------- /*Laundry Room */ //in the <OpenHAB15\webapps\images> folder //create 3 pngs // myalarm.png // myalarm-off.png // myalarm-on.png // mylight.png // myalarm-0.png // myalarm-1.png Number itm_laun_dryer_mqtt "Dryer [MAP(laundry.map):%s]" (ALL) {mqtt="<[mymosquitto:3052:state:default]"} Number itm_laun_washer_mqtt "Washer [MAP(laundry.map):%s]" (ALL) {mqtt="<[mymosquitto:3042:state:default]"} Switch itm_laun_water_leak_alm_enb "Laundry Water Leak Notifier" Switch itm_laun_water_leak_alm_sta "Laundry Water Leak Status" <myalarm> Number itm_laun_water_leak_mqtt "Water Leak" (ALL) {mqtt="<[mymosquitto:3032:state:default]"} Number itm_laun_temp "Laundry Room Temp [%.1f °F]" <temperature> (All) {mqtt="<[mymosquitto:3022:state:default]"} Number itm_laun_hum "Laundry Room Humidity [%.1f %%]" <temperature> (All) {mqtt="<[mymosquitto:3023:state:default]"} Number itm_laun_light_mqtt "Light Sensor [%.1f]" (ALL) {mqtt="<[mymosquitto:3062:state:default]"} Number itm_laun_light_icon "Laundry Room Light" <mylight>
Next, I define the colors for the various status of the washer/dryer on the sitemap.
Text label="Laundry Room" icon="firstfloor" { Frame label="Laundry Room Status" { Text item=itm_laun_dryer_mqtt valuecolor=[<1="gray",==1="green",>1="red"] labelcolor=[<1="gray",==1="green",>1="red"] Text item=itm_laun_washer_mqtt valuecolor=[<1="gray",==1="green",>1="red"] labelcolor=[<1="gray",==1="green",>1="red"] Text item=itm_laun_temp Text item=itm_laun_hum Text item=itm_laun_light_icon Switch item=itm_laun_water_leak_alm_sta mappings=[OFF="Reset"] } Frame label="Alarm Enable" { Switch item=itm_laun_water_leak_alm_enb } }
Then I use the rules to define some of graphical states and when to play audio.
//--------------------------------------- //Rules //--------------------------------------- rule "Laundry Lights" when Item itm_laun_light_mqtt received update then if(itm_laun_light_mqtt.state < 350) { sendCommand(itm_laun_light_icon, 0) } else { sendCommand(itm_laun_light_icon, 1) } end rule "Washer Complete" when Item itm_laun_washer_mqtt received update then if(itm_laun_washer_mqtt.state == 2) { say("Washer Complete!") } end rule "Dryer Complete" when Item itm_laun_dryer_mqtt received update then if(itm_laun_dryer_mqtt.state == 2) { say("Dryer Complete!") } end /* --------- Laundry Water Leak ---------- */ rule "Laundry Water Leak" when Item itm_laun_water_leak_mqtt received update then sendCommand(itm_laun_water_leak_alm_sta, ON) end rule "Laundry Water Leak response" when Item itm_laun_water_leak_alm_sta changed from OFF to ON then if(itm_laun_water_leak_alm_enb.state == ON) { sendMail("ArduinoHomeAutomationOpenHAB@gmail.com", "laundry water leak" , "laundry water leak detected") } //playSound("alarm.mp3") end
The sketch does some counting to figure out if the washer or dryer is running. The counts would have to be massaged based on how loud your machine is or how sensitive you turn the sound pot.
/* Author: Eric Tsai License: CC-BY-SA, https://creativecommons.org/licenses/by-sa/2.0/ File: Laundry_room.ino This sketch is for a wired Arduino w/ RFM69 wireless transceiver Monitors dryer status, uses PIR sensor to indicate empty load. Notification for water leaks. Light sensor. to gateway. See OpenHAB configuration below. 1) Update encryption string "ENCRYPTKEY" 2) */ /* OpenHAB configuration Item Definition Number itm_dryer "Dryer is [MAP(laundry.map):%s]" (ALL) {mqtt="<[mymosquitto:3052:state:default]"} Sitemap Definition Text item=itm_dryer valuecolor=[<1="black",==1="green",>1="orange"] labelcolor=[2="orange", 1="green", 0="black"] Create a file <laundry.map> in /configuration/transform 0=Off & Empty 1=Running 2=Done Reference https://github.com/openhab/openhab/wiki/Explanation-of-Items */ /* MQTT topic addressing node = 30 device ID: 2 = 3022 = Temperature_F = 3023 = Humidity 3 = 3021 = Water Present 4 = 3042 = Washer status (not implemented) 5 = 3052 = Dryer status (implemented) 6 = 3062 = Light Pins A2 = light sensor 3 = water leak 5 = PIR sensor 6 = sound 1 7 = DHT11 sensor 8 = sound 2 */ //RFM69 -------------------------------------------------------------------------------------------------- #include <RFM69.h> #include <SPI.h> #define NODEID 30 //unique for each node on same network #define NETWORKID 101 //the same on all nodes that talk to each other #define GATEWAYID 1 //Match frequency to the hardware version of the radio on your Moteino (uncomment one): //#define FREQUENCY RF69_433MHZ //#define FREQUENCY RF69_868MHZ #define FREQUENCY RF69_915MHZ #define ENCRYPTKEY "xxxxxxxxxxxxxxxx" //exactly the same 16 characters/bytes on all nodes! #define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! #define ACK_TIME 30 // max # of ms to wait for an ack #define LED 9 // Moteinos have LEDs on D9 #define SERIAL_BAUD 9600 //must be 9600 for GPS, use whatever if no GPS boolean debug = 0; //struct for wireless data transmission typedef struct { int nodeID; //node ID (1xx, 2xx, 3xx); 1xx = basement, 2xx = main floor, 3xx = outside int deviceID; //sensor ID (2, 3, 4, 5) unsigned long var1_usl; //uptime in ms float var2_float; //sensor data? float var3_float; //battery condition? } Payload; Payload theData; char buff[20]; byte sendSize=0; boolean requestACK = false; RFM69 radio; //end RFM69 ------------------------------------------ /* // gas sensor================================================ int GasSmokeAnalogPin = 0; // potentiometer wiper (middle terminal) connected to analog pin int gas_sensor = -500; // gas sensor value, current int gas_sensor_previous = -500; //sensor value previously sent via RFM */ //temperature / humidity ===================================== #include "DHT.h" #define DHTPIN 7 // digital pin we're connected to #define DHTTYPE DHT11 // DHT 22 (AM2302) blue one //#define DHTTYPE DHT21 // DHT 21 (AM2301) white one // Initialize DHT sensor for normal 16mhz Arduino DHT dht(DHTPIN, DHTTYPE); // Light sensor =============================================== int lightAnalogInput = A2; //analog input for photo resistor int lightValue = -50; int lightValue_previous = -50; // PIR sensor ================================================ // used to clear a completed load (to know that it's been emptied) int PirInput = 5; int PIR_status = 0; int PIR_reading = 0; int PIR_reading_previous = 0; // sound sensor 1 (Dryer) ============================================== int soundInput1 = 6; //sound sensor digital input pin int sound_status1 = 0; int sound_reading1 = 0; //reading =1 mean no noise, 0=noise int sound_reading1_previous = 0; int sound_1_device_state = 0; //1 = running, 0 = empty, 2 = complete unsigned long sound_time_1 = 0; //millis of last reading int sound_count_1 = 0; //number of captures int sound_detected_count_1 = 0; //number of readings showing sound // water leak sensor ========================================== unsigned long water_time; int waterInput = 3; //pin for water detection int water_reading = 0; //for reading water pin // timings unsigned long pir_time; unsigned long sound_time; unsigned long temperature_time; unsigned long light_time; unsigned long light_time_send; void setup() { Serial.begin(9600); // setup serial //RFM69------------------------------------------- radio.initialize(FREQUENCY,NODEID,NETWORKID); #ifdef IS_RFM69HW radio.setHighPower(); //uncomment only for RFM69HW! #endif radio.encrypt(ENCRYPTKEY); char buff[50]; sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); Serial.println(buff); theData.nodeID = NODEID; //this node id should be the same for all devices in this node //end RFM-------------------------------------------- dht.begin(); //temperature & humidity sensor pinMode(soundInput1, INPUT); //sound sensor 1 (dryer) pinMode(waterInput, INPUT); //water detection //initialize times sound_time = millis(); temperature_time = millis(); pinMode(PirInput, INPUT); //PIR sensor } void loop() { unsigned long time_passed = 0; //=============================================================== //Water Leak sensor //Device #3 //=============================================================== water_reading = digitalRead(waterInput); //0 = no water, 1 = water (backwards) //if (water_reading) //Serial.println("no water"); //else //Serial.println("water det "); //delay(1000); if ((water_reading == 0) && ( ((millis() - water_time)>60000)||( (millis() - water_time)< 0)) ) //meaning there was sound { water_time = millis(); //update gas_time_send with when sensor value last transmitted theData.deviceID = 3; theData.var1_usl = millis(); theData.var2_float = 1; theData.var3_float = 101; //null value; radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)); Serial.print("water detected!!! "); } //=================================================================== //device PIR //note: this PIR sensor goes straight to I/O, no pull down resistor. // 1 = detected movement PIR_reading = digitalRead(PirInput); // if (PIR_reading == 1) //Serial.println("PIR = 1"); //else //Serial.println("PIR = 0"); // if someone is in the laundry room and washer/dryer is complete (not emptied) if ((PIR_reading == 1) && (sound_1_device_state == 2)) { sound_1_device_state = 0; theData.deviceID = 5; theData.var1_usl = millis(); theData.var2_float = 0; theData.var3_float = 11223; //null value; radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)); } //=================================================================== //device #5 //Dryer Running Sound Sensor //soundValue = analogRead(soundAnalogInput); //Serial.print("sound analog = "); //Serial.print(soundValue); //deal with millis rollover if (sound_time_1 > millis()) { sound_time_1 = millis(); } //capture sound as fast as we can. If there is sound, mark it as so. int sound = digitalRead(soundInput1); if (sound == 0) { sound_reading1 = 0; } //count sound as for real only every 1/2 second. if ((millis() - sound_time_1)>500) { sound_time_1 = millis(); //reset sound_time_1 to wait for next Xms //sound_reading1 = digitalRead(soundInput1); // 1 = no noise, 0 = noise!! Serial.print(sound_count_1); Serial.print(" out of "); Serial.print(sound_detected_count_1); Serial.print(" state= "); Serial.print(sound_1_device_state); if (!sound_reading1) Serial.println(" sound detected"); else Serial.println(" no sound detected"); sound_count_1 = sound_count_1 + 1; //count how many times we listened if (sound_reading1 == 0) //count how many times detected sound { sound_detected_count_1 = sound_detected_count_1 + 1; } sound_reading1 = 1; //reset back to no sound (1 = no sound) }//end reading every second //after X number of sound checks... if (sound_count_1 >= 40) { //sound_count_1 = number of times sensor listened //sound_detected_count_1 = number of times heard sound //sound_1_device_state: 0=emptied, 1=running, 2=cycle complete (but not emptied) // sound_1_device_state = 1 = running Serial.print("checking checking evaluating sound"); if ((sound_detected_count_1 >= 8) && ((sound_1_device_state == 0) || (sound_1_device_state == 2))) //number of times sensor registered sound { Serial.println("running!"); sound_1_device_state = 1; theData.deviceID = 5; theData.var1_usl = millis(); theData.var2_float = 1; //1 = device running theData.var3_float = 11223; //doesn't mean a thing radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)); } // sound_1_device_state = 2 = Cycle complete (but not emptied) else if ((sound_detected_count_1 < 3) && (sound_1_device_state == 1)) { Serial.println("not running!!"); sound_1_device_state = 2; theData.deviceID = 5; theData.var1_usl = millis(); theData.var2_float = 2; theData.var3_float = 11223; //null value; radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)); //reset how often sensor has listened and how many sound events detected } sound_count_1 = 0; sound_detected_count_1 = 0; } //end if count_count_1 //=================================================================== //device #2 //temperature / humidity time_passed = millis() - temperature_time; if (time_passed < 0) { temperature_time = millis(); } //only report temps/humidity after X seconds if (time_passed > 360000) { float h = dht.readHumidity(); // Read temperature as Celsius float t = dht.readTemperature(); // Read temperature as Fahrenheit float f = dht.readTemperature(true); // Check if any reads failed and exit early (to try again). if (isnan(h) || isnan(t) || isnan(f)) { Serial.println("Failed to read from DHT sensor!"); return; } Serial.print("Humidity="); Serial.print(h); Serial.print(" Temp="); Serial.println(f); temperature_time = millis(); //send data theData.deviceID = 2; theData.var1_usl = millis(); theData.var2_float = f; theData.var3_float = h; radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)); delay(1000); } //=================================================================== //device #6 //light time_passed = millis() - light_time; if (time_passed < 0) { light_time = millis(); light_time_send = -70000; } if (time_passed > 2000) //how often to examine the sensor analog value { light_time = millis(); //update time when sensor value last read lightValue = 0; //analog value: Less than 100 is dark. greater than 500 is room lighting lightValue = analogRead(lightAnalogInput); if ((lightValue < (lightValue_previous - 50)) || ((lightValue > (lightValue_previous + 100)) || (705000 < (millis() - light_time_send))) ) { light_time_send = millis(); theData.deviceID = 6; theData.var1_usl = millis(); theData.var2_float = lightValue; theData.var3_float = lightValue + 20; radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)); lightValue_previous = lightValue; Serial.print("light RFM ="); Serial.println(lightValue); } //start debug code if (debug) { Serial.print("light analog = "); Serial.println(lightValue); } }// end if millis time_passed > }//end loop