/*
  State change detection (edge detection)

  (with debouncing, LED toggling, and active-LOW input using INPUT_PULLUP)
  https://wokwi.com/projects/391482018625678337
  modified from https://wokwi.com/projects/391475222059330561

  Adapted to be a rate-limiting Skinnerian pigeon feeder by
  1) rate limiting only falling edge triggers to debounceInterval
  2) recoring the time of feeding

  Often, you don't need to know the state of a digital input all the time, but
  you just need to know when the input changes from one state to another.
  For example, you want to know when a button goes from OFF to ON. This is called
  state change detection, or edge detection.

  This example shows how to:
  1) detect when a button or button changes from off to on and on to off.
  2) debounce an input with millis()
  3) toggle a LED based on its state

  The circuit:
  - pushbutton attached to pin 2 and GND
  - LED attached from pin 13 to ground through 220 ohm resistor (or use the
    built-in LED on most Arduino boards)

  created  27 Sep 2005
  modified 30 Aug 2011
  by Tom Igoe
  modified 04 Mar 2024 by DaveX for https://forum.arduino.cc/t/state-change-detection-edge-detection-for-pushbuttons/1231632/10
  This example code is in the public domain.

  Based on:
  https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection/
*/

// this constant won't change:
const int buttonPin = 2;  // the pin that the pushbutton is attached to
const int ledPin = 13;    // the pin that the LED is attached to
const int feederPin = 9; // the pin that the feeder is attached to

// Variables will change:
int buttonState = 0;        // current state of the button
int lastButtonState = 0;   // previous state of the button
uint32_t interval = 10000; // interval between feedings
uint32_t feedInterval = 2000; // interval of feeding machine/LED energized
uint32_t lastChangeMillis = -interval; // previous time of change for debouncing
// negative interval rolls-over uint32_t to allow instant triggering

const int scheme = 5; // rate-limiting scheme

void setup() {
  // initialize the button pin as a input with an internal pullup resistor:
  pinMode(buttonPin, INPUT_PULLUP);
  lastButtonState = digitalRead(buttonPin); // Initial condition
  // initialize the LED as an output:
  pinMode(ledPin, OUTPUT);
  pinMode(feederPin,OUTPUT);
  // initialize serial communication:
  Serial.begin(115200);
  Serial.println("Skinnerian 10 second pigeon feeder");
  Serial.print("Scheme:");
  Serial.println(scheme);
}

void loop() {
  // read the pushbutton input pin:
  buttonState = digitalRead(buttonPin);
  uint32_t currentMillis = millis(); // save the current time

  switch (scheme) { // choose one of several rate-limiting schemes
    case 0: // no rate limiting
      if (buttonState != lastButtonState ) { // the state has changed
        if (buttonState == LOW) {
          // if the current state is LOW then the input is FALLING edge
          Serial.print("LOW ");
          toggleLed(ledPin);
          feeder(true);
        } else {
          Serial.print("HIGH ");
        }
        // save the current state as the last state, for next time through the loop
        lastButtonState = buttonState;
      }
      break;

    case 1: // delay like Arduino StateChange Example
      if (buttonState != lastButtonState ) { // the state has changed
        if (buttonState == LOW) {
          // if the current state is LOW then the input is FALLING edge
          Serial.print("LOW ");
          toggleLed(ledPin);
          feeder(true);
        } else {
          Serial.print("HIGH ");
        }
        delay(interval / 2);
        // save the current state as the last state, for next time through the loop
        lastButtonState = buttonState;
      }
      break;
    case 2: // delay falling
      if (buttonState != lastButtonState ) { // the state has changed
        if (buttonState == LOW) {
          // if the current state is LOW then the input is FALLING edge
          Serial.print("LOW ");
          toggleLed(ledPin);
          feeder(true);
          delay(interval);
        } else {
          Serial.print("HIGH ");
        }
        // save the current state as the last state, for next time through the loop
        lastButtonState = buttonState;
      }
      break;
    case 3: // millis inside mark any change
      if (buttonState != lastButtonState ) { // the state has changed
        if (currentMillis - lastChangeMillis >= interval / 2) { // limit rate using time stamps
          if (buttonState == LOW) {
            // if the current state is LOW then the input is FALLING edge
            Serial.print("LOW ");
            toggleLed(ledPin);
            feeder(true);
          } else {
            // if the current state is HIGH then the input is RISING edge
            Serial.print("HIGH ");
          }
          lastChangeMillis = currentMillis;
          // save the current state as the last state, for next time through the loop
          lastButtonState = buttonState;
        }
      }
      break;
    case 4: // millis inside, mark falling edge
      if (buttonState != lastButtonState ) { // the state has changed
        if (currentMillis - lastChangeMillis >= interval) { // limit rate using time stamps
          if (buttonState == LOW) {
            // if the current state is LOW then the input is FALLING edge
            Serial.print("LOW ");
            toggleLed(ledPin);
            feeder(true);
            lastChangeMillis = currentMillis;
          } else {
            // if the current state is HIGH then the input is RISING edge
            Serial.print("HIGH ");
          }
          // save the current state as the last state, for next time through the loop
          lastButtonState = buttonState;
        }
      }
      break;
    case 5: // millis outside, mark falling edge
      if (currentMillis - lastChangeMillis >= interval) { // limit rate using time stamps
        if (buttonState != lastButtonState ) { // the state has changed
          if (buttonState == LOW) {
            // if the current state is LOW then the input is FALLING edge
            Serial.print("LOW ");
            toggleLed(ledPin);
            feeder(true);
            lastChangeMillis = currentMillis;
          } else {
            // if the current state is HIGH then the input is RISING edge
            Serial.print("HIGH ");
          }
          // save the current state as the last state, for next time through the loop
          lastButtonState = buttonState;
        }
      }
      break;
    case 6: // millis outside, mark any change
      if (currentMillis - lastChangeMillis >= interval / 2) { // limit rate using time stamps
        if (buttonState != lastButtonState ) { // the state has changed
          if (buttonState == LOW) {
            // if the current state is LOW then the input is FALLING edge
            Serial.print("LOW ");
            toggleLed(ledPin);
            feeder(true);
            lastChangeMillis = currentMillis;
          } else {
            // if the current state is HIGH then the input is RISING edge
            Serial.print("HIGH ");
          }
          // save the current state as the last state, for next time through the loop
          lastButtonState = buttonState;
          lastChangeMillis = currentMillis;
        }
      }
      break;
    case 7: // millis outside, rate limiting
      // Bad: only works if you happen to be holding the button at the poll time
      if (currentMillis - lastChangeMillis >= interval / 2) { // limit rate using time stamps
        if (buttonState != lastButtonState ) { // the state has changed
          if (buttonState == LOW) {
            // if the current state is LOW then the input is FALLING edge
            Serial.print("LOW ");
            toggleLed(ledPin);
            feeder(true);
            lastChangeMillis = currentMillis;
          } else {
            // if the current state is HIGH then the input is RISING edge
            Serial.print("HIGH ");
          }
          // save the current state as the last state, for next time through the loop
          lastButtonState = buttonState;
        }
        lastChangeMillis = currentMillis;
      }
      break;
    case 8: // delay always
      // bad -- only works if you happen to hold the button while polling
      if (buttonState != lastButtonState ) { // the state has changed
        if (buttonState == LOW) {
          // if the current state is LOW then the input is FALLING edge
          Serial.print("LOW ");
          toggleLed(ledPin);
          feeder(true);
        } else {
          Serial.print("HIGH ");
        }
        // save the current state as the last state, for next time through the loop
        lastButtonState = buttonState;
      }
      delay(interval / 2);
      break;
  }
  feeder(false);
}


void toggleLed(byte pin) {
  if (digitalRead(pin) == LOW) { // toggle the LED as needed
    digitalWrite(pin, HIGH);
    Serial.print("on ");
  } else {
    digitalWrite(pin, LOW);
    Serial.print("off ");
  }
}

void feeder(boolean start) {
  // start a feeder on demand and turn it off after an interval
  static uint32_t lastStart = 0;;
  if (start) {
    digitalWrite(feederPin, HIGH);
    Serial.print("(feed) ");
    lastStart = millis();
  } else {
    if (digitalRead(feederPin) == HIGH && 
        millis() - lastStart >= feedInterval) {
      digitalWrite(feederPin, LOW);
    }
  }
}