
#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>
#include <avr/sleep.h>


// for the tiny85 I choose pin0
#define PINSTRIP       0
#define N_LEDS    5


Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, PINSTRIP, NEO_GRB + NEO_KHZ800);
SoftwareSerial mySerial = SoftwareSerial(4, 3); // Rx, Tx


const float INTERN = 1.1; // IC dependent(!), for our application not important
float vcc = 5.0; // measured later
boolean vccOk = true; // Vcc sufficient assumed

uint16_t prog = 0; // neopixel program to be called
const int PINPROG0 = 2;
const int PINPROG1 = 1;


void setup() {
  uint16_t digit0;
  uint16_t digit1;

  vcc = readVCC(); // read internal reference
  
  // read the jumper switches
  pinMode(PINPROG1, INPUT_PULLUP);
  pinMode(PINPROG0, INPUT_PULLUP);
  if (digitalRead(PINPROG1) == 0)
    digit1 = 1;
  else
    digit1 = 0;
    if (digitalRead(PINPROG0) == 0)
    digit0 = 1;
  else
    digit0 = 0;

  // input of the jumper switches is finished
  // we "remove" the internal pullups to save power, although this is minimal
  pinMode(PINPROG1, INPUT);
  pinMode(PINPROG0, INPUT);

  prog = (2 * digit1 + digit0);  // time in s
  //mySerial.print("Prog=");    // for test
  //mySerial.println(prog);       // for test

  // for controller version 1 only!!!
  // prog = 3;
  
  strip.begin();
  mySerial.begin(9600);
}


void loop() {
  if (vccOk) {
    vcc = readVCC(); // read internal reference
    float measuredVcc = vcc / 1023 * analogRead(A2); // measure battery voltage 
    mySerial.print("VCC=");
    mySerial.println(measuredVcc);

    if (measuredVcc > 3.4)        // if battery voltage sufficient, then power the LEDs
      vccOk = true;
      else {
        erase();                // otherwise we erase pixels (once!)to save power
        vccOk = false;            // and do not talk any longer with the pixel strip
        
        // for sleep modes and various tips see  http://www.gammon.com.au/forum/?id=11497
        // first we disable ADC (analog to digital converter)
        ADCSRA = 0;  
        
        // then we disable brown-out detection and go to bed
        set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
        noInterrupts ();           // timed sequence follows
        sleep_enable();
 
        // turn off brown-out enable in software
        MCUCR = bit (BODS) | bit (BODSE);  // turn on brown-out enable select
        MCUCR = bit (BODS);         // this must be done within 4 clock cycles of above
        interrupts ();              // guarantees next instruction executed
        sleep_cpu ();               // sleep within 3 clock cycles of above                           

        // Note: neopixels still may draw some current!
        
      }
  }

  if (vccOk)                      // do it only if power sufficient
  {
    switch(prog){
      case 0:
       // shift all 3 basic colours 
        chase(strip.Color(255, 0, 0)); // Red
        chase(strip.Color(0, 255, 0)); // Green
        chase(strip.Color(0, 0, 255)); // Blue
        chase(strip.Color(255, 255, 255)); // White
        break;
 
      case 1:
        // switch on pixels one after another, for all 3 basic colours
        hop(strip.Color(255, 0, 0)); // Red
        hop(strip.Color(0, 255, 0)); // Green
        hop(strip.Color(0, 0, 255)); // Blue
        hop(strip.Color(255, 255, 255)); // White
        break;
 
      case 2:
        // switch on pixels one after another, waiting some time at end, 3 basic colors and white
        up(strip.Color(255, 0, 0)); // Red
        up(strip.Color(0, 255, 0)); // Green
        up(strip.Color(0, 0, 255)); // Blue
        up(strip.Color(255, 255, 255)); // White
        break;
   
      case 3:
         // switch on pixels one after another, waiting some time at end, white only
        up(strip.Color(255, 255, 255)); // Red, Green, Blue is White
        break;
     }
  }
}


static void chase(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels()+4; i++) {
      strip.setPixelColor(i  , c); // Draw new pixel
      strip.setPixelColor(i-4, 0); // Erase pixel a few steps back
      strip.show();
      delay(25);
  }
}


static void hop(uint32_t c) {
  for(uint16_t j=0; j<strip.numPixels(); j++) {
    for(uint16_t i=0; i<strip.numPixels(); i++) {
        strip.setPixelColor(i, 0); // Erase all pixels
    }
    strip.setPixelColor(j, c); // Draw new pixel
    strip.show();
    delay(200);
  }
}


static void up(uint32_t c) {
  for(uint16_t j=0; j<strip.numPixels(); j++) {
    for(uint16_t i=0; i<strip.numPixels(); i++) {
        strip.setPixelColor(i, 0); // Erase all pixels
    }
    strip.setPixelColor(j, c); // Draw new pixel
    strip.show();
    delay(50);
  }
  delay(300);
}


static void down(uint32_t c) {
  for(uint16_t j=strip.numPixels()-1; j>=0; j--) {
    for(uint16_t i=0; i<strip.numPixels(); i++) {
      if (j == i)
        strip.setPixelColor(j, c); // Draw new pixel
      else
        strip.setPixelColor(i, 0); // Erase pixel
    }
    strip.show();
    delay(50);
  }
  delay(300);
}

static void erase(void) {
  for(uint16_t i=0; i<strip.numPixels()+4; i++) {
    strip.setPixelColor(i, 0); // Erase pixel
    strip.show();
    delay(25);
  }
}

float readVCC() {
  // read 1.1 V reference as input with reference voltage vcc
  // Referenz Vcc und analog Input = interne Referenz 1,1V
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  

  delay(10); // wait until reference stabile
  ADCSRA |= _BV(ADSC); // start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measure

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;
  float rvcc = INTERN * 1023L / result;
  analogReference(DEFAULT);  // back to Vcc as reference
  delay(10); // wait until reference stabile
  return rvcc;
}

