// *************************************************************************************************************
//                                          MightyBoost control sample sketch
// *************************************************************************************************************
// Copyright (2015) Felix Rusu, http://lowpowerlab.com
// http://lowpowerlab.com/mightyboost
// MightyBoost is a smart backup PSU controllable by Moteino, and this sketch is a sample control sketch to run
// MightyBoost in this mode.
// Be sure to check back for code updates and patches
// *************************************************************************************************************
// This sketch will provide control over the essential features of MightyBoost:
//   - provide switched 5V power to a sensitive load like RaspberryPi which should not lose power instantly
//   - Control the "5V*" output via Moteino+PowerButton (momentary tactile)
//   - Monitor input supply and switch to battery backup when external power is lost
//   - Monitor battery voltage and issue a shutdown/reboot signal when battery runs low
// This sketch may be extended to include integration with other LowPowerLab automation products
// *************************************************************************************************************
// License
// *************************************************************************************************************
// This program is free software; you can redistribute it 
// and/or modify it under the terms of the GNU General    
// Public License as published by the Free Software       
// Foundation; either version 2 of the License, or        
// (at your option) any later version.                    
//                                                        
// This program is distributed in the hope that it will   
// be useful, but WITHOUT ANY WARRANTY; without even the  
// implied warranty of MERCHANTABILITY or FITNESS FOR A   
// PARTICULAR PURPOSE.  See the GNU General Public        
// License for more details.                              
//                                                        
// You should have received a copy of the GNU General    
// Public License along with this program; if not, write 
// to the Free Software Foundation, Inc.,                
// 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
//                                                        
// Licence can be viewed at                               
// http://www.fsf.org/licenses/gpl.txt                    
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// *************************************************************************************************************
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
#define LED                 5     // LED pin, should be analog for fading effect (PWM)
#define BUTTON              3     // Power button pin
#define SIG_SHUTOFF         6     // Signal to Pi to ask for a shutdown
#define SIG_BOOTOK         A0     // Signal from Pi that it's OK to cutoff power
                                  // !!NOTE!! Originally this was D7 but it was moved to A0 at least temporarily.
                                  // On MightyBoost R1 you need to connect D7 and A0 with a jumper wire.
                                  // The explanation for this is given here: http://lowpowerlab.com/mightyboost/#source
#define OUTPUT_5V           4     // HIGH on this pin will switch the "5V*" output ON
#define BATTERYSENSE       A7     // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 880 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
                                  // hence the actual input voltage = analogRead(A7) * 0.00322 (3.3v/1024) * 1.47 (10k+4.7k voltage divider ratio)
                                  // when plugged in this should be 4.80v, nothing to worry about
                                  // when on battery power this should decrease from 4.15v (fully charged Lipoly) to 3.3v (discharged Lipoly)
                                  // trigger a shutdown to the target device once voltage is around 3.4v to allow 30sec safe shutdown
#define LOWBATTERYTHRESHOLD  3.5  // a shutdown will be triggered to the target device when battery voltage drops below this (Volts)

#define RESETHOLDTIME         500 // Button must be hold this many mseconds before a reset is issued (should be much less than SHUTDOWNHOLDTIME)
#define SHUTDOWNHOLDTIME     2000 // Button must be hold this many mseconds before a shutdown sequence is started (should be much less than ForcedShutoffDelay)
#define ShutoffTriggerDelay  6000 // will start checking the SIG_BOOTOK line after this long
#define RecycleTime         50000 // window of time in which SIG_BOOTOK is expected to go HIGH
                                  // should be at least 3000 more than Min
                                  // if nothing happens after this window, if button is 
                                  // still pressed, force cutoff power, otherwise switch back to normal ON state
#define RESETPULSETIME        500 // When reset is issued, the SHUTOFF signal is held HIGH this many ms
#define ForcedShutoffDelay   7500 // when SIG_BOOTOK==0 (PI in unknown state): if button is held
                                  // for this long, force shutdown (this should be less than RecycleTime)
#define ShutdownFinalDelay   4500 // after shutdown signal is received, delay for this long
                                  // to allow all PI LEDs to stop activity (pulse LED faster)

#define PRINTPERIOD              10000

int lastValidReading = 1;
unsigned long lastValidReadingTime = 0;
unsigned long NOW=0;
int PowerState = 0;
long lastPeriod = -1;
float systemVoltage = 5;

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(SIG_BOOTOK, INPUT);
  pinMode(SIG_SHUTOFF, OUTPUT);
  pinMode(LED, OUTPUT);
  pinMode(OUTPUT_5V, OUTPUT);
  pinMode(A7, INPUT);
  digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
  digitalWrite(OUTPUT_5V, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
}

void loop() {
  //reduce MCU ON current  by ~10mA
  LowPower.idle(SLEEP_30MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_ON, SPI_OFF, USART0_OFF, TWI_OFF);

  int reading = digitalRead(BUTTON);
  NOW = millis();
  digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
  boolean batteryLow = systemVoltage < LOWBATTERYTHRESHOLD;

  if (batteryLow || reading != lastValidReading && NOW - lastValidReadingTime > 200)
  {
    lastValidReading = reading;
    lastValidReadingTime = NOW;
    
    if (batteryLow || reading == 0)
    {
      //make sure the button is held down for at least 'RESETHOLDTIME' before taking action (this is to avoid accidental button presses and consequently Pi shutdowns)
      NOW = millis();
      while (!batteryLow && (PowerState == 1 && millis()-NOW < RESETHOLDTIME)) { delay(10); if (digitalRead(BUTTON) != 0) return; }

      //RESETHOLDTIME is satisfied, now check if button still held until SHUTDOWNHOLDTIME is satisfied
      analogWrite(LED, 128); //dim the LED to show something's going on
      while (!batteryLow && (PowerState == 1 && millis()-NOW < SHUTDOWNHOLDTIME))
      {
        if (digitalRead(BUTTON) != 0)
        {
          if (BOOTOK())       //SIG_BOOTOK is HIGH so Pi is running the shutdowncheck.sh script, ready to intercept the RESET PULSE
          {
            digitalWrite(SIG_SHUTOFF, HIGH);
            delay(RESETPULSETIME);
            digitalWrite(SIG_SHUTOFF, LOW);

            NOW = millis();
            boolean recycleDetected=false;
            while (millis()-NOW < RecycleTime) //blink LED while waiting for BOOTOK to go high
            {
              //blink 3 times and pause
              digitalWrite(LED, LOW);
              delay(100);
              digitalWrite(LED, HIGH);
              delay(100);
              digitalWrite(LED, LOW);
              delay(100);
              digitalWrite(LED, HIGH);
              delay(100);
              digitalWrite(LED, LOW);
              delay(100);
              digitalWrite(LED, HIGH);
              delay(500);

              if (!BOOTOK()) recycleDetected = true;
              else if (BOOTOK() && recycleDetected)
                return;
            }
            return; //reboot pulse sent but it appears a reboot failed; exit all checks
          }
          else return; //ignore everything else (button was held for RESETHOLDTIME, but SIG_BOOTOK was LOW)
        }
      }
      
      //SIG_BOOTOK must be HIGH when Pi is ON. During boot, this will take a while to happen (till it executes the "shutdowncheck" script)
      //so I dont want to cutoff power before it had a chance to fully boot up
      if (batteryLow || (PowerState == 1 && BOOTOK()))
      {
        // signal Pi to shutdown
        digitalWrite(SIG_SHUTOFF, HIGH);

        //now wait for the Pi to signal back
        NOW = millis();
        float in, out;
        boolean forceShutdown = true;
        
        while (millis()-NOW < RecycleTime)
        {
          if (in > 6.283) in = 0;
          in += .00628;
          
          out = sin(in) * 127.5 + 127.5;
          analogWrite(LED,out);
          delayMicroseconds(1500);
          
          //account for force-shutdown action (if button held for ForcedShutoffDelay, then force shutdown regardless)
          if (millis()-NOW <= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && digitalRead(BUTTON) != 0)
            forceShutdown = false;
          if (millis()-NOW >= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && forceShutdown)
          {
            PowerState = 0;
            digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff
            digitalWrite(OUTPUT_5V, PowerState);
            break;
          }
          
          if (millis() - NOW > ShutoffTriggerDelay)
          {
            // Pi signaling OK to turn off
            if (!BOOTOK())
            {
              PowerState = 0;
              digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff
              NOW = millis();
              while (millis()-NOW < ShutdownFinalDelay)
              {
                if (in > 6.283) in = 0;
                in += .00628;
                
                out = sin(in) * 127.5 + 127.5;
                analogWrite(LED,out);
                delayMicroseconds(300);
              }
              
              digitalWrite(OUTPUT_5V, PowerState);
              break;
            }
          }
        }
        
        // last chance: if power still on but button still pressed, force cutoff power
        if (PowerState == 1 && digitalRead(BUTTON) == 0)
        {
          PowerState = 0;
          digitalWrite(OUTPUT_5V, PowerState);
        }
        
        digitalWrite(SIG_SHUTOFF, LOW);
      }
      else if (PowerState == 1 && !BOOTOK())
      {
        NOW = millis();
        unsigned long NOW2 = millis();
        int analogstep = 255 / ((ForcedShutoffDelay-SHUTDOWNHOLDTIME)/100); //every 500ms decrease LED intensity
        while (digitalRead(BUTTON) == 0)
        {
          if (millis()-NOW2 > 100)
          {
            analogWrite(LED, 255 - ((millis()-NOW)/100)*analogstep);
            NOW2 = millis();
          }
          if (millis()-NOW > ForcedShutoffDelay-SHUTDOWNHOLDTIME)
          {
            //TODO: add blinking here to signal final shutdown delay
            PowerState = 0;
            digitalWrite(OUTPUT_5V, PowerState);
            break;
          }
        }
      }
      else if (PowerState == 0)
      {
        PowerState = 1;
        digitalWrite(OUTPUT_5V, PowerState); //digitalWrite(LED, PowerState);
      }
    }

    digitalWrite(LED, PowerState);
  }

  int currPeriod = millis()/PRINTPERIOD;
  if (currPeriod != lastPeriod)
  {
    lastPeriod=currPeriod;
    Serial.print("VIN: ");
    systemVoltage = analogRead(BATTERYSENSE) * 0.00322 * 1.47;
    Serial.print(systemVoltage);
    if (systemVoltage > 4.3)
      Serial.println("  (plugged in)");
    else Serial.println("  (running from battery!)");
  }
}

boolean BOOTOK() {
  return analogRead(SIG_BOOTOK) > 800;
}