/*
 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).

12-2017 - Adapted for AdaFruit Trinket M0
*/
 
#include <sam.h>
#include <power.h>
#include <Adafruit_SleepyDog.h>
#include <Adafruit_ASFcore.h>

// abs() is broken for the M0. Basically negative in yields negative out!
#undef abs()

//#define DEBUG_SERIAL // If enabled debug messages emitted over the serial port
//#define DEBUG_SLEEP  // If enabled the status LED is used to debug sleep periods

// Define I/O's
#define BUZZER1 0       // One pin of the piezo buzzer
#define BUZZER2 4       // Other pin of the piezo buzzer
#define ALARM_LED 1     // Alarm LED pin
#define STAT_LED 13     // Status LED pin
#define WATER_SENSOR A1 // Analog input pin for water sensor


// A difference of more than 100 on the analog input with respect to the average
// computed during startup will trigger the system
#define MAX_DIFFERENCE 100 // Assumes external 10K ohm pull-up to Vbat


// Two different sleep period are used to conserve power if the battery is left in circuit when the leak
// detector is not in use. A longer sleep of 8 seconds is employed during standby. Once triggered (e.g. test prior
// a sequence of dives) shifts the detector to a 2 second sleep period. After approximately two weeks where no leak
// is detected the detector shifts back to the standby sleep period.
// Sleep longer in standby
#define MAX_SHORT_SLEEP 0x200000  // About 2 weeks of 2 second sleep periods
#define ACTIVE_SLEEP_PERIOD 2000  // 2 seconds
#define STANDBY_SLEEP_PERIOD 8000 // 8 seconds

int waterAvg = 0; // Average analog value found during startup.

bool standbyState = true; // Initially the detector starts up in standby with a longer sleep period
long standbyDelay = MAX_SHORT_SLEEP; // Delay over which if no trigger occurs the detector shifts back to standby


// The ABS() define appears to be broken for the M0
int abs(int v) {
  if (v >= 0)
    return (v);
  else
    return (v*-1);
}


void setup() {
  delay(1000); // Ensure we can get into the boot loader before sleep

#ifdef DEBUG_SERIAL
  while (!Serial);
  Serial.begin(115200);
  Serial.println("H2Ohno!");
#endif

  // Setup I/O pins
  pinMode(BUZZER1, OUTPUT);
  pinMode(BUZZER2, OUTPUT);
  pinMode(ALARM_LED, OUTPUT);
  pinMode(STAT_LED, OUTPUT);
  pinMode(WATER_SENSOR, INPUT);
  
  //Take a series of readings from the water sensor and average them
  waterAvg = 0;
  for(int x = 0 ; x < 8 ; x++) {
    waterAvg += analogRead(WATER_SENSOR);
    delay(50);
  }
  waterAvg /= 8;

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

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

  // Set the I/O's to a low power state
  digitalWrite(BUZZER1, LOW);
  digitalWrite(BUZZER2, LOW);
  digitalWrite(STAT_LED, LOW);
  digitalWrite(ALARM_LED, LOW);

  // We don't use the temperature sensor, disable its reference
  system_voltage_reference_disable(SYSTEM_VOLTAGE_REFERENCE_TEMPSENSE);
}

void loop() {
  int waterDifference;
  long startTime;
  long timeSinceBlink;
  int actualSleep;
  
  if (standbyState == false)
    if (standbyDelay-- <= 0)
      standbyState = true;

  // Sleep with the ADC's reference off
  system_voltage_reference_disable(SYSTEM_VOLTAGE_REFERENCE_BANDGAP);
  if (standbyState == false)
    actualSleep = Watchdog.sleep(ACTIVE_SLEEP_PERIOD);
  else
    actualSleep = Watchdog.sleep(STANDBY_SLEEP_PERIOD);
  system_voltage_reference_enable(SYSTEM_VOLTAGE_REFERENCE_BANDGAP);
#ifdef DEBUG_SERIAL
  Serial.print("Actual sleep = ");
  Serial.println(actualSleep);
#endif

#ifdef DEBUG_SLEEP
  // Blip the status LED during water sensor sampling
  digitalWrite(statLED, HIGH);
#endif
  //Check for water
  waterDifference = abs(analogRead(WATER_SENSOR) - waterAvg);
#ifdef DEBUG_SLEEP
  digitalWrite(statLED, LOW);
#endif

#ifdef DEBUG_SERIAL
  Serial.print("Diff: ");
  Serial.println(waterDifference);
#endif

  if(waterDifference > MAX_DIFFERENCE) {
    if (standbyState == true) {
      standbyState = false;
      standbyDelay = MAX_SHORT_SLEEP;
    }
    
    startTime = millis(); // Record the current time for blinking
    timeSinceBlink = startTime;
    
    // Loop until we don't detect water AND 10 seconds of alarm has expired
    while(waterDifference > MAX_DIFFERENCE || (millis() - startTime) < 10000) {
      alarmSound();

      // Short on pulse, with 1 second period
      if ((digitalRead(ALARM_LED) == HIGH) && (millis() - timeSinceBlink > 10)) {
        timeSinceBlink = millis();
        digitalWrite(ALARM_LED, LOW);
      }
      if ((digitalRead(STAT_LED) == LOW) && (millis() - timeSinceBlink > 1000)) {
        timeSinceBlink = millis();
        digitalWrite(ALARM_LED, HIGH);
      }

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

#ifdef DEBUG_SERIAL
      Serial.print("Read: ");
      Serial.println(analogRead(WATER_SENSOR));
#endif
    }
    
    //No more alarm. Ensure LEDs and buzzer are off
    digitalWrite(BUZZER1, LOW); // Technical needs to transition to consume power
    digitalWrite(BUZZER2, LOW); // but set to low as the default off state
    digitalWrite(STAT_LED, LOW);
    digitalWrite(ALARM_LED, 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);
    }
  }
}

