Fun project for my new wine bar - need advice please

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!

Shawn

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()...

Thank you for the suggestion. I will research tonight.

It your recommendation more reliable? Trying to gain some insight why. Thanks!

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 :-)

Wow, that is giving me a lot of pause on my current approach.

So, am I to assume that we have the voting:

blink-without-delay = 2 votes interrupts = 0 votes

:)

I have a lot of homework to do tonight!

Great feedback!

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. :slight_smile: 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?

Progress!!

jackman_fam:
Wow, that is giving me a lot of pause on my current approach.

So, am I to assume that we have the voting:

blink-without-delay = 2 votes
interrupts = 0 votes

Not quite.
I think you’ll find the voting (unless you’re Superman and can repeatedly press a contact switch faster than 50KHz :slight_smile: ) is:

Blink-without-delay = 100%
interrupts 0%

Also

if (BevCenterState | WineFridgeState) { //if either is open: 1=open, 0=closed

should be:

if (BevCenterState || WineFridgeState) { //if either is open: 1=open, 0=closed

Logical (not bitwise) OR.

I missed that, thank you. Good catch.

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.

Great discussion. You guys are awesome.

Thank you,

Shawn

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.

Here it is. Work in progress, of course.

https://youtu.be/w-j6j8_R3A4