I've been recently diving into Arduino and am having a great time. What a great community, too. I have an initial project for my whole home automation that I would love some discussion / education around. My goal is to do this with my teenage kids to team them programming while we all learn basic electronics -- I'm a computer scientist by education (a.k.a. more programming background). First, I'll lay out the initial project and then the larger vision.
Wine bar control:
I have (2) refrigerators - one beverage center and another wine fridge
Cabinets where I store liquor (one cabinet of primary concern)
Onkyo amp installed inside cabinet
Under counter and in-cabinet LED light strip (white only)
Goal:
Alarm on refrigerator doors left ajar after 30 seconds.
Fingerprint authentication for liquor cabinet
Control LED strip
Basic speaker for alarm generation
IR repeater for Onkyo so I can control the unit while cabinet door is shut
Basic temp and humidity reporting
Long term:
I have a long list of ideas to make the house much more automated. I have installed openHAB on my QNAP NAS that I want to tie the control for everything in together. New version of openHAB has remote control (i.e. when I'm not home) capabilities now. Here is the list of projects:
Pool pumps and light controller
Pool temperature monitoring...and whatever else I can monitor i.e. chemical levels
Home theater and audio zone controller (main zone and backyard zone)
GPS dog collar - I have a beagle that loves to run away!
LCD touch screen and control
Basic weather forecast and status
Sprinkler system control
Thermostat control
Garage door sensing and control
Some light switch controls
Breathalyzer
Motion detector for front door w/ camera image capture
Humidity sensors in bathrooms
Flood alarm sensor for washing machine room
Landscape lighting control
I purchased most of the main components already. My goal is to start with the basic sensors and start integrating them slowly into openHAB with the RFM69HW 900MHz radios I bought (wanted range, battery savings and to avoid complexity of Wi-Fi authentication, roaming, etc.)
Wine Bar
Ok, so now you know my long-term goals, my question is more architectural / approach related for the wine bar. I decided to use interrupts as a central approach to the wine bar system. Reason being that I didn't want to miss events while the unit is processing other lines of code. Polling the sensors seemed silly, but I open this up for criticism and feedback. If a fridge door opened and I started some type of a countdown timer for, say, 30 seconds before sounding a piezo speaker (for now). In that time, you could do a fingerprint scan to open liquor cabinet and/or also open the wine fridge. I want to catch each sensor event as absolutely reliably as I can.
I have the reed switches working now with the temp/humidity. I'm using interrupts for the reed switches and I'm concerned already how reliable they will be. My initial tests show imperfect results, but other variables are in the mix and once I solder them and have predictable swing motion and mounting it may increase. ...more experimenting to be done.
I'm stuck on the timer function. I have been looking into using the timer interrupts, but I'm not finding any closely applicable example after over an hour of searching. Have any of you done this where the Arduino (Uno in my case, but hopefully a Nano in final build) will be processing other tasks?
I very much appreciate the feedback and contributing my design and code back to the community!
Instead of using interrupts for switches, just avoid use of delay (use blink-without-delay methods - there are oodles of threads on this) and check the switches during loop()...
If you are used to write event/message driven multi-threaded code, adjust your mind a bit. Microcontrollers don't support easily a message queue, instead they poll inputs over and over again - they have to do nothing else so far. Next come stateful automatons, which allow to structure and coordinate multiple (possibly long time) parallel actions. Forget about threads and - for now - about interrupts, polling is the right and easy way to go.
Also forget about cooked messages, you'll have to deal with bouncing keys and other raw (disturbed analog...) input, which has to be filtered before further processing or triggering actions. Such filtering typically requires multiple readings of the same signal, over time, that's where interrupts will not help a lot. Later you may use interrupts to wake up the controller, when it was sent to sleep due to lack of any ongoing actions. But even then an interrupt will not do much more than resume the polling loop().
Also forget about timer interrupts, as a special case of events. The controller "kernel" will use one hardware timer to track the current time, and all time-based actions are assumed to compare the current time against their schedule time (interval - see blink-without-delay). More timers may be used for really time-critical library actions (communication clocks...), so that neither hardware timers nor counters are available in application code.
If you think that polling is deprecated since long, then welcome back to the reality of small yet powerful machines
Okay, you made me think very different about this. And, WOW, this was soooo much easier! I did this in like 30m and I got the timer to work as well.
Yes, my code isn't optimized yet, so forgive that. I'm using LEDs for visual feedback for now.
#include <DHT.h>
//PIN CONFIGURATION
#define DHTPIN 3 //analog pin for temperature and humidity sensor
#define BevCenter 6 //digital pin for beverage center door sensor
#define WineFridge 5 //digital pin for wine fridge door sensor
//INSTANTIATING DHT11 SENSOR
#define DHTTYPE DHT11 //telling the DHT library which sensor model we're using
DHT dht(DHTPIN, DHTTYPE);
//FUNCTIONS
//VARIABLES
boolean BevCenterState, WineFridgeState = false;
long unsigned prevFridgeMillis, curFridgeMillis, fridgeAlarmTime;
const int fridgePollInterval = 1000; //polling interval in (ms) for fridge reed switchs (magnets)
const int fridgeAlarmThreshold = 10000; //alarm trigger time in ms
//volatile long unsigned eventTime, prevEventTime;
boolean fridgeEvent = false;
void setup() {
Serial.begin(9600);
Serial.println(">>>> Barduino Test Program <<<<");
Serial.println(); //blank line
dht.begin(); //launch the temperature and humidity control
pinMode(BevCenter, INPUT_PULLUP);
pinMode(WineFridge, INPUT_PULLUP);
pinMode(8, OUTPUT); //LED
pinMode(9, OUTPUT); //LED
pinMode(10, OUTPUT); //LED
}
void loop() {
//DIGITAL THERMOMETER AND HUMIDITY SENSOR
//Serial.print("Read sensor: ");
//float h = dht.readHumidity(); // Reading temperature or humidity takes about 250 milliseconds!
//float f = dht.readTemperature(true); // Read temperature as Fahrenheit (isFahrenheit = true)
// Check if any reads failed and exit early (to try again).
/*
if (isnan(h) || isnan(f)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
Serial.print("Humidity: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(f);
Serial.println(" *F\t");
*/
//REFRIGERATOR DOOR ALARMS
curFridgeMillis = millis();
if (curFridgeMillis - prevFridgeMillis > fridgePollInterval) { //determines when to check fridge sensors
//check fridge sensor
BevCenterState = digitalRead(BevCenter);
WineFridgeState = digitalRead(WineFridge);
//Serial.print("BevCenter = "); Serial.print(BevCenterState); Serial.print("WineFridge = "); Serial.println(WineFridgeState); //for testing only
if (BevCenterState | WineFridgeState) { //if either is open: 1=open, 0=closed
//debugging only - delete after testing
if (BevCenterState) digitalWrite(8, HIGH); else digitalWrite(8, LOW);
if (WineFridgeState) digitalWrite(9, HIGH); else digitalWrite(9, LOW);
//end debug
if (!fridgeEvent) { //check to see if it has been previously detected or not
fridgeAlarmTime = curFridgeMillis;
fridgeEvent = 1;
}
//how long is it open? Look to see if alarm threshold is exceeded
if (curFridgeMillis - fridgeAlarmTime > fridgeAlarmThreshold) {
//SOUND PIEZO!!
digitalWrite(10, HIGH);
}
}
else {
fridgeEvent = 0;
digitalWrite(8, LOW);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
}
prevFridgeMillis = curFridgeMillis;
}
To keep the loop code cleaner, what about throwing the refrigerator logic into a function?
I am messing with the fingerprint sensor now. That was super easy to get working. Darn light isn't turning off, so I need to figure out how to keep it from staying on.
if (BevCenterState) digitalWrite(8, HIGH); else digitalWrite(8, LOW);
Not so obvious but a bit smaller and faster:
digitalWrite(8, BevCenterState); //or BevCenterState != 0, to be sure
And yes, you can put actions into subroutines, in detail those with much code.
The use of | or || is a matter of taste with simple variables. The binary version uses one OR and branch, the logical version one or two comparisons and branches. The logical version is mandatory when the first condition prevents bogus (NULL pointers...) in the evaluation of the following conditions.
What's your problem with the light? I'd simply copy the door open/close state to the lamp output.
Your polling logic looks incorrect. When you update prevFridgeMillis = curFridgeMillis at the end of loop(), you'll never reach the trheshold. Update prevFridgeMillis only when the event has occured (at the end of the event handler code).
I love the optimization removing the if statement logic. That saves compute cycles. Thank you for that, DrDietrich. I'll review the logic on that prevFridgeMillis statement, too.
In terms of the fridges, everything is working very, very well so far. Foregoing the interrupts was perfect. The problem I'm having with the light is with the fingerprint scanner. I bought this one: http://www.ebay.com/itm/261212895682 The red light isn't turning off, it just continues to pulse. It is working very well, but geez, that red light would annoy me if it stayed on. I just need to dig into the example code further. I don't see anyone posting about how to optimize the unit; I looked for an hour or so last night. I could do a button to trigger it, but that would stink for usability reasons. ...I can hear my wife now.
Oh, I did use an external power supply for the fingerprint sensor because it looks like it peaks at 250mA. I'm going to get the solenoid working tonight...have to pick up a few parts to complete the circuit.
Ok, I have integrated most of what I need now. Here is what I have now:
refrigerator doors working
fingerprint scanner w/ solenoid working
temp and humidity working
To do:
integrated to the LED strip that I installed under the inside bar counter
make the fingerprint scanner not on full time - I'm thinking of a proximity sensor or perhaps HC-SR04 ultrasound unit to trigger it when somebody walks by.
add a RGM69HW to get the data back to openHAB to report and control solenoid via phone
Here is my working code thus far:
/* Barduino Project
Project will have the following functionality:
- detection when refigerator door is left open, sound alarm
- temperature and humidity sensor
- LED light control for under cabinet lights
- IR repeater
- fingerprint sensor foor cabinet door lock control
*/
#include <DHT.h>
#include <Adafruit_Fingerprint.h> //for fingerprint sensor
#include <SoftwareSerial.h> //for fingerprint sensor
int getFingerprintIDez();
// pin #2 is IN from sensor (GREEN wire)
// pin #3 is OUT from arduino (WHITE wire)
SoftwareSerial mySerial(2, 3);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);
//PIN CONFIGURATION
#define BuzzerPin 11 //digital pin for piezo speaker
#define Solenoid 4 //digital pin for solenoid
#define DHTPIN 5 //digital pin for temperature and humidity sensor
#define BevCenter 6 //digital pin for beverage center door sensor
#define WineFridge 7 //digital pin for wine fridge door sensor
#define greenLED 8 //green LED for feedback while coding DELETE LATER
//INSTANTIATING DHT11 SENSOR
#define DHTTYPE DHT11 //telling the DHT library which sensor model we're using
DHT dht(DHTPIN, DHTTYPE);
//FUNCTIONS
//VARIABLES
boolean BevCenterState, WineFridgeState = false;
long unsigned int curMillis, prevFridgeMillis, prevTempMillis, fridgeAlarmTime = 0;
const long unsigned int fridgePollInterval = 1000; //polling interval in (ms) for fridge reed switchs (magnets)
const long unsigned int tempPollInterval = 60000; //polling interval in (ms) for temperature and humidity
const int fridgeAlarmThreshold = 20000; //alarm trigger time in ms
//volatile long unsigned eventTime, prevEventTime;
boolean fridgeEvent = false;
void setup() {
Serial.begin(9600);
Serial.println(">>>> Barduino Test Program <<<<");
Serial.println(); //blank line
dht.begin(); //launch the temperature and humidity control
pinMode(BevCenter, INPUT_PULLUP);
pinMode(WineFridge, INPUT_PULLUP);
pinMode(Solenoid, OUTPUT);
pinMode(8, OUTPUT); //LED
pinMode(9, OUTPUT); //LED
pinMode(10, OUTPUT); //LED
pinMode(BuzzerPin, OUTPUT); //Piezo speaker
//fingerprint sensor
// set the data rate for the fingerprint sensor serial port
finger.begin(57600);
if (finger.verifyPassword()) {
Serial.println("Found fingerprint sensor!");
} else {
Serial.println("Did not find fingerprint sensor :(");
while (1);
}
//Serial.println("Waiting for valid finger...");
}
void loop() {
curMillis = millis();
//TEMP AND HUMIDITY STATUS
if (curMillis - prevTempMillis > tempPollInterval) { //determines when to check temp and humidity sensor
//check sensor
Serial.print("Read sensor: ");
float h = dht.readHumidity(); // Reading temperature or humidity takes about 250 milliseconds!
float f = dht.readTemperature(true); // Read temperature as Fahrenheit (isFahrenheit = true)
// Check if any reads failed and exit early (to try again).
if (isnan(h) || isnan(f)) { Serial.println("Failed to read from DHT sensor!"); return; }
Serial.print("Humidity: "); Serial.print(h); Serial.print(" %\t"); Serial.print("Temperature: "); Serial.print(f); Serial.println(" *F\t");
prevTempMillis = curMillis;
}
//REFRIGERATOR DOOR ALARMS
if (curMillis - prevFridgeMillis > fridgePollInterval) { //determines when to check fridge sensors
//check fridge sensor
BevCenterState = digitalRead(BevCenter);
WineFridgeState = digitalRead(WineFridge);
//Serial.print("BevCenter = "); Serial.print(BevCenterState); Serial.print("WineFridge = "); Serial.println(WineFridgeState); //for testing only
if (BevCenterState || WineFridgeState) { //if either is open: 1=open, 0=closed
//debugging only - delete after testing
if (BevCenterState) digitalWrite(8, HIGH); else digitalWrite(8, LOW);
if (WineFridgeState) digitalWrite(9, HIGH); else digitalWrite(9, LOW);
//end debug
if (!fridgeEvent) { //check to see if it has been previously detected or not
fridgeAlarmTime = curMillis;
fridgeEvent = 1;
}
//how long is it open? Look to see if alarm threshold is exceeded
if (curMillis - fridgeAlarmTime > fridgeAlarmThreshold) {
//SOUND PIEZO!!
tone(BuzzerPin, 540);
digitalWrite(10, HIGH);
}
}
else {
fridgeEvent = 0;
digitalWrite(8, LOW);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
noTone(BuzzerPin);
}
prevFridgeMillis = curMillis;
}
//look for fingerprint scan
getFingerprintIDez();
}
uint8_t getFingerprintID() {
uint8_t p = finger.getImage();
switch (p) {
case FINGERPRINT_OK:
Serial.println("Image taken");
break;
case FINGERPRINT_NOFINGER:
Serial.println("No finger detected");
return p;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.println("Communication error");
return p;
case FINGERPRINT_IMAGEFAIL:
Serial.println("Imaging error");
return p;
default:
Serial.println("Unknown error");
return p;
}
// OK success!
p = finger.image2Tz();
switch (p) {
case FINGERPRINT_OK:
Serial.println("Image converted");
break;
case FINGERPRINT_IMAGEMESS:
Serial.println("Image too messy");
return p;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.println("Communication error");
return p;
case FINGERPRINT_FEATUREFAIL:
Serial.println("Could not find fingerprint features");
return p;
case FINGERPRINT_INVALIDIMAGE:
Serial.println("Could not find fingerprint features");
return p;
default:
Serial.println("Unknown error");
return p;
}
// OK converted!
p = finger.fingerFastSearch();
if (p == FINGERPRINT_OK) {
Serial.println("Found a print match!");
} else if (p == FINGERPRINT_PACKETRECIEVEERR) {
Serial.println("Communication error");
return p;
} else if (p == FINGERPRINT_NOTFOUND) {
Serial.println("Did not find a match");
return p;
} else {
Serial.println("Unknown error");
return p;
}
// found a match!
Serial.print("Found ID #"); Serial.print(finger.fingerID);
Serial.print(" with confidence of "); Serial.println(finger.confidence);
}
int getFingerprintIDez() {
// returns -1 if failed, otherwise returns ID #
uint8_t p = finger.getImage();
if (p != FINGERPRINT_OK) {
return -1;
}
p = finger.image2Tz();
if (p != FINGERPRINT_OK) {
return -1;
}
p = finger.fingerFastSearch();
if (p != FINGERPRINT_OK) {
return -1;
}
// found a match!
//Turn on LED for test
digitalWrite(Solenoid, HIGH); //solenoid
digitalWrite(greenLED, HIGH); //green
delay(1500);
digitalWrite(Solenoid, LOW); //solenoid
digitalWrite(greenLED, LOW); //green
Serial.print("Found ID #"); Serial.print(finger.fingerID);
Serial.print(" with confidence of "); Serial.println(finger.confidence);
return finger.fingerID;
}
I am going to do a sketch in Fritzing. ...coming soon.
Well, I lied. Doing the Fritzing sketch is a little arduous. I decided to take a video and upload it to YouTube. I figured that would give you a better feel for the project regardless.