// Rover Wakey v1.3.0 - Rover Sleep Controller
// Controls power supplies to shut down and wake up PC-104 stack.
// Runs on a PIC16F886 with a 4 MHz main crystal and a 32,768 Hz
// crystal on the Timer1 oscillator.
//
// Compiled with Microchip XC8 v1.33
// The following routines are unique to XC8 and must be replaced if a different
// compiler is used:
// __CONFIG()
// __delay_ms()
// eeprom_read()
// eeprom_write()
// CLRWDT()
// di()
// ei()
//
// Version History
// v1.4.0   Modified substantially for camera tripod controller (Sherman/Henthorn)
// v1.3.0   Added accessor functions to read and clear gAwakeSecs
// v1.2.0   Added timeout to sleep after too many minutes awake
// v1.1.0   Added code to wake from sleep when modem pulls RB0 low
// v1.0.0   Initial release based on SES Wakey code - prm


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <xc.h>		    // register definitions for this PIC
#include "intserial.h"	// hardware serial I/O routines
#include "adc.h"        // analog-to-digital converter routines

//__CONFIG(FOSC_HS & WDTE_ON & PWRTE_OFF & MCLRE_OFF & LVP_OFF & CPD_OFF & BOREN_OFF & DEBUG_ON);
#pragma config "FOSC=HS", "WDTE=ON", "PWRTE=OFF", "MCLRE=OFF"
#pragma config "LVP=OFF", "CP=OFF", "BOREN=OFF", "DEBUG=OFF"

#define _XTAL_FREQ 4000000  // must define for delay function to work
#define SER_BUFSIZE 32      // serial command buffer size
#define CR  0x0D            // carriage return
#define LF  0x0A            // line feed
#define BS  0x08            // back space

#define TMR1DLY 32768   // Timer1 delay value, 1 sec with 32,768 Hz crystal
                        //  and divide-by-1 prescaler
#define BATTV   0       // adc channel assignments
#define LHVMON  1
#define RHVMON  2




//#define INHIBIT5V   RB1 // digital output channel assignments

#define ENABLE7V    RB1 // digital output channel assignments
#define ENABLEDIV   RB2
#define RSTRBCHG    RB3
#define RSTRTRIG    RA3
#define LSTRTRIG    RA4
#define LSTRBCHG    RB4
#define FIRESHTR    RB5
#define NFIRESHTR   RA5 // Fire the Near camera shutter

#define SERNOADD    0   // EEPROM address of board serial number
#define VCALADD     1   // EEPROM address of voltage calibration

#define TRUE    1
#define FALSE   0
#define ON      TRUE
#define OFF     FALSE
#define LEFT    'L'
#define RIGHT   'R'
#define FFCAM  0
#define NFCAM  1

enum {RESETOK, RESETWD, WAKEOK, WAKEWD, MODEM, TIMEOUT};    // Cause for last reset or wake

#define MAIN_MENU 0
#define CMD_MENU  1
#define CFG_MENU  2
#define TEST_MENU 3

// Command codes
// 
#define FIRST_MENU    1              // Menu codes
#define M_CMD         FIRST_MENU
#define M_CONFIG      (M_CMD+1)
#define M_TEST        (M_CONFIG+1)
#define LAST_MENU     (M_TEST)       //////////

#define FIRST_CMD     (LAST_MENU+1)  // Command codes
#define CMD_SLEEP     (FIRST_CMD)
#define READWAKE      (CMD_SLEEP+1)
#define DEPLOY        (READWAKE+1)
#define LAST_CMD      (DEPLOY)       //////////

#define FIRST_CFG     (LAST_CMD+1)   // Config codes
#define SETSLEEP      (FIRST_CFG)
#define READSLEEP     (SETSLEEP+1)
#define SETDELAY      (READSLEEP+1)
#define READDELAY     (SETDELAY+1)
#define SETTIMEOUT    (READDELAY+1)
#define READTIMEOUT   (SETTIMEOUT+1)
#define SETBATT       (READTIMEOUT+1)
#define READBATT      (SETBATT+1)
#define SETSERNO      (READBATT+1)
#define READSERNO     (SETSERNO+1)
#define SETTIME       (READSERNO+1)
#define READTIME      (SETTIME+1)
#define LAST_CFG      (READTIME)      ///////////

#define FIRST_TEST    (LAST_CFG+1)    // Test codes
#define CAMERAON      (FIRST_TEST)
#define CAMERAOFF     (CAMERAON+1)
#define READ7V        (CAMERAOFF+1)
#define READLVOLT     (READ7V+1)
#define READRVOLT     (READLVOLT+1)
#define CHGLSTROBE    (READRVOLT+1)
#define CHGRSTROBE    (CHGLSTROBE+1)
#define LSTROBEON     (CHGRSTROBE+1)
#define LSTROBEOFF    (LSTROBEON+1)
#define RSTROBEON     (LSTROBEOFF+1)
#define RSTROBEOFF    (RSTROBEON+1)
#define TRIGLSTROBE   (RSTROBEOFF+1)
#define TRIGRSTROBE   (TRIGLSTROBE+1)
#define TRIGSHUTTER   (TRIGRSTROBE+1)
#define TRIGNFSHUTTER (TRIGSHUTTER+1)
#define SNAP          (TRIGNFSHUTTER+1)
#define SNAPNF        (SNAP+1)
#define LAST_TEST     (SNAPNF)        ///////////


#define CRLF    "\r\n"
#define FROWNY  ":("

// The order of these prompts must match the order of the
// menu codes above
//
static const char* menu_prompts[] =
{
    // Menu choices
    "Cmds",
    "Configs",
    "Tests"
};

// The order of these prompts must match the order of the
// config codes above
//
static const char* config_prompts[] =
{
    // Config choices
    "Set sleep",
    "Read sleep",
    "S delay",
    "R delay",
    "S timeout",
    "R timeout",
    "S batt cal",
    "R batt",  
    "S SN",
    "R SN",
    "S clk",
    "R clk"
};

// The order of these prompts must match the order of the
// command codes above
//
static const char* cmd_prompts[] =
{
    // Menu choices
    "Sleep",
    "Rd Wake",
    "Deploy"
};

// The order of these prompts must match the order of the
// test codes above
//
static const char* test_prompts[] =
{
    // Test choices
    "Cam |",
    "Cam O",
    "Cam ?",
    "L strb V?",
    "R V?",
    "Charge L strb",
    "Chg R",
    "L strb 1",
    "L 0",
    "R strb 1",
    "R 0",
    "Trg L strb",
    "Trg R",
    "Trg far shut",
    "Trg near",
    "Snap far cam",
    "Snap near"
};


void sleep(void);
void setSleep(void);
void setDelay(void);
void setTimeout(void);
void set7v(char state);
void setBatt(void);
unsigned int readBatt(void);
unsigned int readVmon(char strobe);
void chgStrobe(char strobe);
void strobeSwitch(char strobe, char state);
void TrigStrobe(char strobe);
void TrigShutter(char camera);
void readWake(void);
void setSerno(void);
unsigned int getAwakeSecs(void);
void clrAwakeSecs(void);
void takeTestImage(void);
void dispatch(int selection);

time_t getEpochSecs(void);
void setEpochSecs(char y, char mon, char d, char h, char min, char s, char dst);
void timer_setup(void);
void interrupt isr(void);

// Global vars
char            gMenu = 0;
volatile time_t gEpochSecs   = 0;    // Store epoch seconds from user-supplied time
unsigned int    gSleepMins = 1;      // number of minutes to sleep
unsigned int    gDelaySecs = 0;      // number of seconds to wait before sleeping
unsigned int    gIntervalMins = 60;  // number of minutes to sleep between each image
unsigned int    gChargeSecs = 30;    // seconds to wait for strobes to charge
unsigned int    gLVCharged, gLVSpent;
unsigned int    gRVCharged, gRVSpent;
time_t          gDeployStart = 0;    // deployment start time in epoch seconds

char            g7VOn;               // status of 7 V power supply
char            gWakeCause;          // reason for last reset or wakeup
unsigned int    gTimeoutMins = 0;    // max minutes awake before going back to sleep
volatile unsigned int    gAwakeSecs;     // number of seconds elapsed since wakeup
unsigned int    gGain;
unsigned char   cmdBuf[SER_BUFSIZE];     // command buffer

// A prompt is CRLF followed by the prompt string
// 
void prompt(const char *str)
{
    printf("%s%s", CRLF, str);
}

// A menu item is the string followed by a CRLF
// 
void menu_item(const char *str)
{
    printf("%s%s",str, CRLF);
}

// A report is a CRLF followed by the string amd another CRLLF
// 
void report(const char *str)
{
    printf("%s%s%s", CRLF, str, CRLF);
}

// Print the specified menu (main, command, test, or config)
// 
void print_menu(char menu) {
    short i;
    report("Enter #");
    switch (menu)
    {
        // Menu options
        case MAIN_MENU:
        for (i = FIRST_MENU; i <= LAST_MENU; i++)
        {
            sprintf(cmdBuf, "(%2d) %s", i, menu_prompts[i-FIRST_MENU]);
            menu_item(cmdBuf);
        }
        break;

        // Command options followed by an Exit
        case CMD_MENU:
        for (i = FIRST_CMD; i <= LAST_CMD; i++)
        {
            sprintf(cmdBuf, "(%2d) %s", i, cmd_prompts[i-FIRST_CMD]);
            menu_item(cmdBuf);
        }
        menu_item("( E) Exit");
        break;

        // Config options followed by an Exit
        case CFG_MENU:
        for (i = FIRST_CFG; i <= LAST_CFG; i++)
        {
            sprintf(cmdBuf, "(%2d) %s", i, config_prompts[i-FIRST_CFG]);
            menu_item(cmdBuf);
        }
        menu_item("( E) Exit");
        break;

        // Test options followed by an Exit
        case TEST_MENU:
        for (i = FIRST_TEST; i <= LAST_TEST; i++)
        {
            sprintf(cmdBuf, "(%2d) %s", i, test_prompts[i-FIRST_TEST]);
            menu_item(cmdBuf);
        }
        menu_item("( E) Exit");
        break;

        default:
        break;
    }

} // end help()

unsigned char spiXfer(unsigned char txByte) {

    SSPBUF = txByte;            // send byte of data
    while(!BF);                 // wait for send to finish
    return SSPBUF;              // return received byte
} // end spiXfer()


/*******************************************************************
* Function:        spiGetCh
* Gets a byte from the SPI buffer. Doesn't initiate any SPI transfers
* and will return whatever is currently in the SPI buffer.
********************************************************************/
unsigned char spiGetCh(void) {
    return SSPBUF;
} // end spiGetCh()

void spiSetUpMaster(void) {

    // set up master SPI port in mode 0,0

    SSPSTAT = 0b01000000;
    //          ||||||||_ buffer full status bit
    //          |||||||__ bit not used in SPI mode
    //          ||||||___ bit not used in SPI mode
    //          |||||____ bit not used in SPI mode
    //          ||||_____ bit not used in SPI mode
    //          |||______ bit not used in SPI mode
    //          ||_______ data transmitted on rising edge of SCK
    //          |________ input data sampled at middle of data output time

    SSPCON =  0b00100001;
    //          ||||\__/
    //          ||||  |__ SPI master mode, clock is Fosc/16
    //          ||||_____ idle state for clock is a low level
    //          |||______ enable SPI port
    //          ||_______ receive overflow indicator bit
    //          |________ write collision detect bit

    
    SSPIE = 0;  // disable SPI interrupts

} // end spiSetUpMaster()


// The main loop waits for serial input commands, parses them, and calls the
// appropriate routine.
void main(void) {
    
    extern bit __timeout;   // timeout bit in status reg saved by c startup code
    unsigned char   i;      // index for command buffer
    unsigned char   inChar; // input char for command buffer

    // The first thing we do after reset is save the reason for the reset.
    // In XC8, the __timeout bit is a shadow of the nTO bit in the
    // status register, and is only valid if the compiler option
    // --RUNTIME=resetbits is set. To set this in the MPLABX IDE, go to the menu
    // File -> Project Properties… -> Conf: [default] -> XC8 global options ->
    // XC8 Linker and check the "Backup reset condition flags" option.

    gWakeCause = (__timeout ? RESETOK : RESETWD); // save reason for reset
    CLRWDT();               // clear watchdog timer
    WDTCON = 0x17;          // set watchdog timer to timeout after 2.1 seconds
    OPTION_REG = 0xF8;      // don't use timer0 prescaler

    // All unused I/O port pins must be set as output to prevent floating
    // high-impedance inputs from drawing too much current in sleep mode.
    // Note that the serial setup routine will set SREN = 1 and make the
    // serial receive pin RC7 an input. The timer setup routine will set
    // T1OSCEN = 1 and make the timer1 oscillator bits RC0 and RC1 inputs.
    TRISA = ANSEL = 0x07;       // make all porta pins except RA0 outputs
    TRISB = 0x01;               // make all portb pins except RB0 outputs
    ANSELH = 0x00;              // make all portb pins digital
    TRISC = 0x00;               // make all portc pins outputs
    PORTA = PORTB = PORTC = 0;  // set all pins low

   // INHIBIT5V = FALSE;        // turn 5V power supply ON
    __delay_ms(100);            // wait for power supply to stabilize
  
    di();                   // disable all interrupts
    clrAwakeSecs();         // start counting number of seconds awake
    timer_setup();          // set up timer
    serial_setup();         // set up the USART - settings defined in intserial.h
    adc_setup();            // set up ADC
    ei();                   // enable all interrupts

    // retrieve the voltage calibration from EEPROM
    gGain = (eeprom_read(VCALADD) << 8) + eeprom_read(VCALADD+1);

    spiSetUpMaster();

    //set7v(OFF);
    //strobeSwitch(LEFT, OFF);
    //strobeSwitch(RIGHT, OFF);

    report("Wakey v1.4.0 Camera Tripod");
    while(1) { // main loop
        i = 0;
        inChar = NULL;
        prompt("> ");           // print prompt

        do {                        // read chars until carriage return
            if(kbhit()) {               // if char has been received
                inChar = getch();       // get the char
                if(inChar == BS) {      // if char is a backspace
                    if(i > 0) {         //  and not at beginning of line
                        i--;
                        putch(inChar);  // erase last char
                        putch(' ');
                        putch(inChar);
                    } // end if
                } else {                // otherwise put in buffer
                    cmdBuf[i++] = inChar;
                    putch(inChar);      // and echo char
                } // end if
            } // end if
            CLRWDT();                   // clear the watchdog timer while we wait
            // Check to see if we've been awake too long. If so, go to sleep
            // and when we wake back up, clear the command buffer and wait for
            // the next command.
            if(gTimeoutMins && (getAwakeSecs() >= gTimeoutMins * 60)) {
                report("TO!");
                sleep();                // go back to sleep
                // Continue here after we wake from sleep. We return to the
                // prompt by executing a null command.
                gWakeCause = TIMEOUT;   // remember reason for wakeup
                i = 1;                  // clear command buffer
                break;                  // break out of do-while loop
            } // end if
        } while((inChar != CR) && (i < SER_BUFSIZE - 1));
        cmdBuf[i-1] = NULL;         // get rid of CR

        // parse command
        if(!cmdBuf[0])
        {
            print_menu(gMenu);     // empty command, do nothing
        }
        else if('E' == cmdBuf[0] || 'e' == cmdBuf[0])
        {
            gMenu = 0;
            print_menu(gMenu);     // empty command, do nothing
        }
        else if('?' == cmdBuf[0])
        {
            print_menu(gMenu);     // empty command, do nothing
        }
        else
        {
            dispatch(atoi(cmdBuf));
        }
    } // end while
} // end main


// Sleep in a low-power state
void sleep(void) {
    unsigned int secsElapsed;

    sprintf(cmdBuf, "Sleep %d", gSleepMins*60);
    report(cmdBuf);

    // delay before sleeping
    for(secsElapsed = 0; secsElapsed < gDelaySecs; secsElapsed++) {
        CLRWDT();
        __delay_ms(1000);
    } // end for
    
    while(!TRMT);   // wait for last printf to finish sending
    RCIE = 0;       // disable serial receive interrupts
    SPEN = 0;       // turn off TX output so it won't power MAX3232
    PORTA = 0;      // set all pins low (except for power supply inhibits)
    PORTB = 0x0;   // this turns off both power supplies
    PORTC = 0x04;
    INTEDG = 0;     // look for falling edge on modem wakeup input RB0
    INTF = 0;       // clear RB0 interrupt flag
    g7VOn = FALSE; // update status of 7V power supply
    TMR1ON = 0;     // stop timer1
    TMR1 = TMR1DLY; // load the timeout value
    TMR1ON = 1;     // start timer1
    TMR1IE = 1;     // enable timer1 interrupts
    PEIE = 1;       // enable peripheral interrupts
    di();           // disable global interrupts
    // sleep until time elapsed, or modem wakeup on RB0, or watchdog times out
    while((secsElapsed++ <= gSleepMins*60) && !INTF && nTO) {
        SLEEP();
        // sleep here until timer1 interrupt after 1 second
        // entering sleep will clear the watchdog timer
        // as soon as awake, reload timer1 and start counting the next second
        TMR1ON = 0;     // stop timer1
        TMR1 = TMR1DLY; // load the timeout value
        TMR1ON = 1;     // start timer1
        TMR1IF = 0;     // clear the timer1 interrupt flag
        gEpochSecs++;
    }; // end while
    clrAwakeSecs(); // start counting number of seconds awake
    ei();           // enable global interrupts

    // save the reason for wakeup here before the next CLRWDT() erases it
    if(INTF) gWakeCause = MODEM;            // if modem triggered wakeup line RB0
    else if(!nPD && nTO) gWakeCause = WAKEOK;   // or power-down sleep flag but not WD time-out
    else if(!nPD && !nTO) gWakeCause = WAKEWD;  // or power-down sleep flag and WD time-out

  //  INHIBIT5V = FALSE;  // turn 5V power supply ON
    __delay_ms(100);    // wait for power supply to stabilize
    serial_setup();     // reinitialize the USART
    report("Up");
} // end sleep()

// Accessor function for non-atomic read of gEpochSecs. This prevents the
//  ISR from modifying the variable while the variable is being read.
time_t getEpochSecs(void) {
    time_t       epoch;
    static bit   intState;

    intState = TMR1IE;      // save current state of interrupt enable
    TMR1IE = 0;             // disable timer1 interrupts
    epoch = gEpochSecs;     // get the value
    TMR1IE = intState;      // restore state of interrupt enable
    return(epoch);
} // end getEpochSecs()

// Accessor function for non-atomic write gEpochSecs. This prevents the
//  ISR from modifying the variable while the variable is being modified.
void setEpochSecs(char y, char mon, char d, char h, char min, char s, char dst)
{
    static bit   intState;

    intState = TMR1IE;      // save current state of interrupt enable
    TMR1IE = 0;             // disable timer1 interrupts
    
    struct tm now;
    now.tm_sec   = s;
    now.tm_min   = min;
    now.tm_hour  = h;
    now.tm_mday  = d;
    now.tm_mon   = mon - 1  ;
    now.tm_year  = y + 100;
    now.tm_isdst = dst;
    gEpochSecs = mktime(&now);       // set the value
    
    TMR1IE = intState;      // restore state of interrupt enable
} // end setEpochSecs()

// Set the number of minutes to sleep
void setSleep(void) {
    unsigned int input;

    prompt("Sleep(1-1092 min):");
    gets(cmdBuf);
    input = atoi(cmdBuf);
    if(input > 0 && input <= 1092) {
        gSleepMins = input;
        sprintf(cmdBuf, "Sleep=%d", gSleepMins);
        report(cmdBuf);
    } else {
        report(FROWNY);
    } // end if
    
} // end setSleep()


// Set the number of seconds to delay before sleeping
void setDelay(void) {
    unsigned int input;

    prompt("Delay(0-60 sec):");
    gets(cmdBuf);
    input = atoi(cmdBuf);
    if(input >= 0 && input <= 60) {
        gDelaySecs = input;
        sprintf(cmdBuf, "Delay=%d", gDelaySecs);
        report(cmdBuf);
    } else {
        report(FROWNY);
    } // end if
} // end setDelay()


// Set the max number of minutes to stay awake before sleeping
void setTimeout(void) {
    unsigned int input;

    prompt("Timeout(1-1092 min,0==never): ");
    gets(cmdBuf);
    input = atoi(cmdBuf);
    if(input >= 0 && input <= 1092) {
        gTimeoutMins = input;
        sprintf(cmdBuf, "Timeout=%d", gTimeoutMins);
        report(cmdBuf);
        clrAwakeSecs(); // restart the timeout timer
    } else {
        report(FROWNY);
    } // end if
} // end setTimeout()

// Turn on/off the 7 V power supply
void set7v(char state) {
    ENABLE7V = state;        // turn 7V power supply ON/OFF
    g7VOn = state;
    sprintf(cmdBuf, "7V=%d", state);
    report(cmdBuf);
} // end set7vOn

// Set the calibration for reading battery voltage
void setBatt(void) {
    unsigned int adcVal;
    unsigned int volts;

    prompt("Input decivolts(200-320,0==abort): ");
    gets(cmdBuf);
    volts = atoi(cmdBuf);
    if(volts >= 200 && volts <= 320) {
        TRISA = ANSEL = 0x01;             // set up ADC input pin to read battery voltage
        ENABLEDIV = TRUE;                 // turn on voltage divider
        __delay_ms(100);                  // wait for divider capacitor to charge
        adcVal = adc_read(BATTV);         // read the ADC value
        ENABLEDIV = FALSE;                // turn off voltage divider
        gGain = volts * 1000L / adcVal;   // calculate the gain

        // save gain calibration to EEPROM
        eeprom_write(VCALADD, (unsigned char) (gGain >> 8));
        eeprom_write(VCALADD+1, (unsigned char) (gGain & 0x00ff));
        sprintf(cmdBuf, "Gain=%u", gGain);
        report(cmdBuf);
    } else {
        report(FROWNY);
    } // end if

} // end setBatt()


// Read the battery voltage in decivolts on Wakey's power input
unsigned int readBatt(void) {
    unsigned int adcVal;

    TRISA = ANSEL = 0x01;             // set up ADC input pin to read battery voltage
    ENABLEDIV = TRUE;                 // turn on voltage divider
    __delay_ms(100);                  // wait for divider capacitor to charge
    adcVal = adc_read(BATTV);         // read the ADC value
    ENABLEDIV = FALSE;                // turn off voltage divider
    
    // This calculation will result in decivolts, or volts x 10
    return (unsigned int) (((adcVal * gGain) + 500) / 1000); // convert to voltage
} // end readBatt()

unsigned int readVmon(char strobe){
    unsigned int adcVal;

    TRISA = ANSEL = 0x07;           // set up the ADC input pin
    adcVal = (strobe == LEFT ? adc_read(LHVMON) : adc_read(RHVMON)); // read the divider voltage
    
    // This calculation will result in decivolts, or volts x 10
    return (unsigned int) (((adcVal * gGain) + 50) / 100); // convert to voltage
}

void chgStrobe(char strobe)
{
   //declare charge time 
    unsigned int secsElapsed;
    
    strobe == LEFT ? LSTRBCHG = TRUE : RSTRBCHG = TRUE;      // charge strobe
    sprintf(cmdBuf, "%c charging%s", strobe);
    report(cmdBuf);
      
    for(secsElapsed = 0; secsElapsed < gChargeSecs; secsElapsed++) {
        CLRWDT();
        __delay_ms(1000);
    } // end for
    strobe == LEFT ? LSTRBCHG = FALSE : RSTRBCHG = FALSE;      // charge strobe
};

void strobeSwitch(char strobe, char state) {
    strobe == LEFT ? LSTRBCHG = state : RSTRBCHG = state;      // charge strobe
    sprintf(cmdBuf, "%c = %d", strobe, state);
    report(cmdBuf);
};

void TrigStrobe (char strobe){
    strobe == LEFT ? LSTRTRIG = TRUE : RSTRTRIG = TRUE;
    __delay_ms(50);
    strobe == LEFT ? LSTRTRIG = FALSE : RSTRTRIG = FALSE;
    sprintf(cmdBuf, "%c triggered", strobe);
    report(cmdBuf);
}

void TrigShutter(char camera) {
    if (camera == FFCAM)
    {
        FIRESHTR = TRUE;      // charge right strobe
        __delay_ms(100);        // wait 
        FIRESHTR = FALSE;
        report("FF trig");
    }
    if (camera == NFCAM)
    {
        NFIRESHTR = TRUE;      // charge right strobe
        __delay_ms(100);        // wait 
        NFIRESHTR = FALSE;
        report("NF trig");
    }
}

#define CAMERA_BOOT_TIME   7
#define CAMERA_WRITE_TIME  10

void takeTestImage(void){
    unsigned int secsElapsed;
    LSTRBCHG = TRUE;      // charge right strobe
    __delay_ms(5);
    RSTRBCHG = TRUE;

    report("Charging...");
    for(secsElapsed = 0; secsElapsed < gChargeSecs; secsElapsed++) {
        CLRWDT();
       __delay_ms(1000);
    } // end for
    LSTRBCHG = RSTRBCHG = FALSE;   // stop charging strobes

    gLVCharged = readVmon(LEFT);     
    __delay_ms(100);
    gRVCharged = readVmon(RIGHT);     
    sprintf(cmdBuf, "L:%uV R:%uV", gLVCharged, gRVCharged);
    report(cmdBuf);

    set7v(ON);
    report("Cam On");
    for(secsElapsed = 0; secsElapsed < CAMERA_BOOT_TIME; secsElapsed++) {
        CLRWDT();
        __delay_ms(1000);
    }
    report("Snap");
    TrigShutter(FFCAM);
         
    //   __delay_ms(100);
    for(secsElapsed = 0; secsElapsed < CAMERA_WRITE_TIME; secsElapsed++) {
        CLRWDT();
       __delay_ms(1000);
    }
    set7v(OFF);
       
    gLVSpent = readVmon(LEFT);     
    __delay_ms(100);
    gRVSpent = readVmon(RIGHT);     
    sprintf(cmdBuf, "L:%uV R:%uV", gLVSpent, gRVSpent);
    report(cmdBuf);
}

// Read the reason for the last wakeup
void readWake(void) {
    char *reason = 0;
    switch(gWakeCause) {
        case RESETOK:
            reason = (char*)"normal reset";
            break;
        case RESETWD:
            reason = (char*)"WD reset";
            break;
        case WAKEOK:
            reason = (char*)"normal wake";
            break;
        case WAKEWD:
            reason = (char*)"WD wake";
            break;
        case TIMEOUT:
            reason = (char*)"TO wake";
            break;
        default:
            reason = (char*)"unk";
            break;
    } // end switch
    sprintf(cmdBuf, "%s", reason);
    report(cmdBuf);
} // end readWake()


// Set the board serial number and save it to EEPROM memory
void setSerno(void) {
    unsigned char serno;

    prompt("SN (1 - 255, 0 to abort): ");
    gets(cmdBuf);
    serno = atoi(cmdBuf);
    if(serno > 0 && serno <= 255) {
        eeprom_write(SERNOADD, serno);
        sprintf(cmdBuf, "SN = %u", serno);
        report(cmdBuf);
    } else {
        report(FROWNY);
    } // end if

}; // end setSerno()

// Accessor function for non-atomic read of gAwakeSecs. This prevents the
//  ISR from modifying the variable while the variable is being read.
unsigned int getAwakeSecs(void) {
    unsigned int    awakeSecs;
    static bit      intState;

    intState = TMR1IE;      // save current state of interrupt enable
    TMR1IE = 0;             // disable timer1 interrupts
    awakeSecs = gAwakeSecs; // get the value
    TMR1IE = intState;      // restore state of interrupt enable
    return(awakeSecs);
} // end getAwakeSecs()


// Accessor function for non-atomic clear of gAwakeSecs. This prevents the
//  ISR from modifying the variable while the variable is being cleared.
void clrAwakeSecs(void) {
    static bit      intState;

    intState = TMR1IE;      // save current state of interrupt enable
    TMR1IE = 0;             // disable timer1 interrupts
    gAwakeSecs = 0;         // clear the value
    TMR1IE = intState;      // restore state of interrupt enable
} // end clrAwakeSecs()


// Set up Timer1 to be used as the sleep timer
void timer_setup(void) {

    // Timer1 initialization. Timer1 runs at 32,768 Hz.

    T1CON =     0b00001111;
    //            \/\/||||_ power on timer
    //             | ||||__ select external clock source
    //             | |||___ don't synchronize external clock input
    //             | ||____ power up oscillator
    //             | |_____ divide by 1 prescaler
    //             |_______ not used

    TMR1ON = 0;         // stop timer1
    TMR1 = TMR1DLY;     // load the timeout value
    TMR1ON = 1;         // start timer1
    TMR1IE = 1;         // enable Timer1 interrupt
    PEIE = 1;           // enable peripheral interrupts
} // end timer_setup()


// Interrupt service routine. Every interrupt will call this routine.
void interrupt isr(void) {

    ser_int();      // service serial port (this macro defined in intserial.h)

    // This timer1 code only executes when PIC is awake since global interrupts
    // are disabled when sleeping.
    if ((TMR1IE) && (TMR1IF)) {
        TMR1ON = 0;         // stop timer1
        TMR1 = TMR1DLY;     // load the timeout value
        TMR1ON = 1;         // start timer1
        gAwakeSecs++;       // count seconds since wakeup
        gEpochSecs++;       // increment wall-clock second hand
        TMR1IF = 0;         // clear timer1 interrupt flag
    } // end if

} // end isr()

void dispatch(int menu_num)
{
    char y, m, d, h, n, s, t;
    time_t et;

    switch (menu_num)
    {
        case M_CONFIG:
        gMenu = CFG_MENU;
        print_menu(gMenu);
        break;

        case M_TEST:
        gMenu = TEST_MENU;
        print_menu(gMenu);
        break;

        case M_CMD:
        gMenu = CMD_MENU;
        print_menu(gMenu);
        break;

        case CMD_SLEEP:
        sleep();
        break;

        case SETSLEEP:
        setSleep();
        break;

        case READSLEEP:
        sprintf(cmdBuf, "Sleep time min: %u", gSleepMins);
        report(cmdBuf);
        break;

        case READWAKE:
        readWake();
        break;

        case SETDELAY:
        setDelay();
        break;

        case READDELAY:
        sprintf(cmdBuf, "Delay time sec: %u", gDelaySecs);
        report(cmdBuf);
        break;

        case SETTIMEOUT:
        setTimeout();
        break;

        case READTIMEOUT:
        sprintf(cmdBuf, "Timeout min: %u", gTimeoutMins);
        report(cmdBuf);
        break;

        case CAMERAON:
        set7v(ON);
        break;

        case CAMERAOFF:
        set7v(OFF);
        break;
                
        case READ7V:
        sprintf(cmdBuf, "7V: %d", g7VOn);
        report(cmdBuf);
        break;

        case SETBATT:
        setBatt();
        break;

        case READBATT:
        sprintf(cmdBuf, "%u.%uV", readBatt()/10, readBatt() % 10);
        report(cmdBuf);
        break;

        case READLVOLT:
        sprintf(cmdBuf, "%uV", readVmon(LEFT));
        report(cmdBuf);
        break;

        case READRVOLT:
        sprintf(cmdBuf, "%uV", readVmon(RIGHT));
        report(cmdBuf);
        break;

        case CHGLSTROBE:
        chgStrobe (LEFT);
        break;

        case CHGRSTROBE:
        chgStrobe (RIGHT);
        break;

        case LSTROBEON:
        strobeSwitch(LEFT, ON);
        break;

        case LSTROBEOFF:
        strobeSwitch(LEFT, OFF);
        break;

        case RSTROBEON:
        strobeSwitch(RIGHT, ON);
        break;

        case RSTROBEOFF:
        strobeSwitch(RIGHT, OFF);
        break;

        case TRIGLSTROBE:
        TrigStrobe(LEFT);
        break;

        case TRIGRSTROBE:
        TrigStrobe(RIGHT);
        break;

        case TRIGSHUTTER:
        TrigShutter(FFCAM);
        break;

        case TRIGNFSHUTTER:
        TrigShutter(NFCAM);
        break;

        case SNAP:
        takeTestImage();
        break;

        case SETSERNO:
        setSerno();
        break;

        case READSERNO:
        sprintf(cmdBuf, "%u", eeprom_read(SERNOADD));
        report(cmdBuf);
        break;

        case SETTIME:
        prompt("YY>");
        y = atoi(gets(cmdBuf));
        prompt("MM(1-12)>");
        m = atoi(gets(cmdBuf));
        prompt("Day(1-31)>");
        d = atoi(gets(cmdBuf));
        prompt("HH(0-23)>");
        h = atoi(gets(cmdBuf));
        prompt("Min(0-59)>");
        n = atoi(gets(cmdBuf));
        prompt("SS(0-59)>");
        s = atoi(gets(cmdBuf));
        prompt("DST(0-1)>");
        t = atoi(gets(cmdBuf));
        setEpochSecs(y, m, d, h, n, s, t);
        break;

        case READTIME:
//        sprintf(cmdBuf, "%s", ctime(&gEpochSecs));
        et = getEpochSecs();
        report(ctime(&et));
        break;

//        case MENU:
//        help();
//        break;

        default:
        report(FROWNY);
        break;
    }
}

void deploy()
{
    char eight;
    short teen6;
    int thirty2;
    long sixty4;
    
    // Sleep the interval
    // Wakeup and take an image
    // Record data
}