/*
 1-14-2013
 Spark Fun Electronics
 Nathan Seidle
  
 When the ATtiny senses water between two pins, go crazy. Make noise, blink LED.
 
 To use this code you must configure the ATtiny to run at 8MHz so that serial and other parts of the code work correctly.
 
 We take a series of readings of the water sensor at power up. We then wait for a deviation of more than
 100 from the average before triggering the alarm.
 
 The alarm will run for a minimum of 2 seconds before shutting down.
 
 This firmware uses some power saving to get the average consumption down to ~50uA average. With a CR2032 @ 200mAh 
 this should allow it to run for 4,000hrs or 166 days. This is easily extended by increasing the amount of time
 between water checks (currently 1 per second).

 4-2017
 Adapted for AdaFruit Trinket
 */

#include <avr/sleep.h> //Needed for sleep_mode
#include <avr/wdt.h> //Needed to enable/disable watch dog timer

/*
 * Pick a hardware platform: UNO (e.g. development) or TRINKET
 */
 
//#define UNO
#define TRINKET

//Pin definitions for regular Arduino Uno (used during development)
#ifdef UNO
// Pin definitions for Uno
//const byte buzzer1 = 8;
//const byte buzzer2 = 9;
const byte buzzer1 = 12;
const byte buzzer2 = 13;
const byte statLED = 10;
const byte waterSensor = A0;
#endif

#ifdef TRINKET
//Pin definitions for ATtiny
const byte buzzer1 = PB0;
const byte buzzer2 = PB4;
const byte statLED = PB1;
const byte waterSensor = 1; // Note: for the ATTiny, 1 must be used to define an analog input
#endif

//This is the average analog value found during startup. Usually ~995
//When hit with water, the analog value will drop to ~400. A diff of 100 is good.
int waterAvg = 0; 
int maxDifference = 100; //A diff of more than 100 in the analog value will trigger the system.

//This runs each time the watch dog wakes us up from sleep
ISR(WDT_vect) {
  //Don't do anything. This is just here so that we wake up.
}

void setup()
{
  pinMode(buzzer1, OUTPUT);
  pinMode(buzzer2, OUTPUT);
  pinMode(statLED, OUTPUT);

  //pinMode(waterSensor, INPUT_PULLUP);
  pinMode(2, INPUT); //When setting the pin mode we have to use 2 instead of A1
  digitalWrite(2, HIGH); //Hack for getting around INPUT_PULLUP

#ifdef UNO
  Serial.begin(9600);
  Serial.println("H2Ohno!");
#endif

  //Take a series of readings from the water sensor and average them
  waterAvg = 0;
  for(int x = 0 ; x < 8 ; x++)
  {
    waterAvg += analogRead(waterSensor);

    //During power up, blink the LED to let the world know we're alive
    if(digitalRead(statLED) == LOW)
      digitalWrite(statLED, HIGH);
    else
      digitalWrite(statLED, LOW);

    delay(50);
  }
  waterAvg /= 8;

#ifdef UNO
  Serial.print("Avg: ");
  Serial.println(waterAvg);
#endif

  //During power up, beep the buzzer to verify its function
  alarmSound();
  delay(100);
  alarmSound();

  digitalWrite(buzzer1, LOW);
  digitalWrite(buzzer2, LOW);
  digitalWrite(statLED, LOW);

  //Power down various bits of hardware to lower power usage  
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); //Power down everything, wake up from WDT
  sleep_enable();
}

void loop() 
{
  int waterDifference;
  long startTime;
  long timeSinceBlink;
  
  ADCSRA &= ~(1<<ADEN); //Disable ADC, saves ~230uA
  setup_watchdog(6); //Setup watchdog to go off after 1 sec
  sleep_mode(); //Go to sleep, and wake up 1 sec later and check water

  //Check for water
  ADCSRA |= (1<<ADEN); //Enable ADC
  waterDifference = abs(analogRead(waterSensor) - waterAvg);
  
  #ifdef UNO
  Serial.print("Diff: ");
  Serial.println(waterDifference);
  #endif

  if(waterDifference > maxDifference) {
    wdt_disable(); //Turn off the WDT!!
    
    startTime = millis(); //Record the current time
    timeSinceBlink = millis(); //Record the current time for blinking
    digitalWrite(statLED, HIGH); //Start out with the uh-oh LED on
    
    //Loop until we don't detect water AND 2 seconds of alarm have completed
    while(waterDifference > maxDifference || (millis() - startTime) < 10000)
    {
      alarmSound(); //Make noise!!

      if ((digitalRead(statLED) == HIGH) && (millis() - timeSinceBlink > 50)) {
        timeSinceBlink = millis();
        digitalWrite(statLED, LOW);
      }
      
      if ((digitalRead(statLED) == LOW) && (millis() - timeSinceBlink > 1000)) {
        timeSinceBlink = millis();
        digitalWrite(statLED, HIGH);
      }

      waterDifference = abs(analogRead(waterSensor) - waterAvg); //Take a new reading

#ifdef UNO
      Serial.print("Read: ");
      Serial.println(analogRead(waterSensor));
#endif

    } //Loop until we don't detect water AND 2 seconds of alarm have completed
    
    //No more alarm. Turn off LED and buzzer   
    digitalWrite(buzzer1, LOW);
    digitalWrite(buzzer2, LOW);
    digitalWrite(statLED, LOW);
  }
}

//This is just a unique (annoying) sound we came up with, there is no magic to it
//Comes from the Simon Says game/kit actually: https://www.sparkfun.com/products/10547
//250us to 79us

void alarmSound(void)
{
  // Toggle the buzzer at various speeds
  for (byte x = 200 ; x > 70; x--)
  {
    for (byte y = 0 ; y < 3 ; y++)
    {
      digitalWrite(buzzer2, HIGH);
      digitalWrite(buzzer1, LOW);
      
      delayMicroseconds(x);

      digitalWrite(buzzer2, LOW);
      digitalWrite(buzzer1, HIGH);
      delayMicroseconds(x);
    }
  }
}


//Sets the watchdog timer to wake us up, but not reset
//0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
//6=1sec, 7=2sec, 8=4sec, 9=8sec
//From: http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
#ifdef TRINKET
void setup_watchdog(int timerPrescaler) {
  if (timerPrescaler > 9 )
    timerPrescaler = 9; //Limit incoming amount to legal settings

  byte bb = timerPrescaler & 7; 
  if (timerPrescaler > 7)
    bb |= (1<<5); //Set the special 5th bit if necessary

  //This order of commands is important and cannot be combined
  MCUSR &= ~(1<<WDRF); //Clear the watch dog reset
  WDTCR |= (1<<WDCE) | (1<<WDE); //Set WD_change enable, set WD enable
  WDTCR = bb; //Set new watchdog timeout value
  WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int
}
#endif

#ifdef UNO
void setup_watchdog(int timerPrescaler) {
  if (timerPrescaler > 9 )
    timerPrescaler = 9; //Limit incoming amount to legal settings

  byte bb = timerPrescaler & 7; 
  if (timerPrescaler > 7)
    bb |= (1<<5); //Set the special 5th bit if necessary

  //This order of commands is important and cannot be combined
  MCUSR &= ~(1<<WDRF); //Clear the watch dog reset
  WDTCSR |= (1<<WDCE) | (1<<WDE); //Set WD_change enable, set WD enable
  WDTCSR = bb; //Set new watchdog timeout value
  WDTCSR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int
}
#endif
