// 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.3   made two mods to work with canon mark IV (longer shutter pulse) and cathyx strobes (power to strobes is on until after shutter trigger)) (Sherm)
// v1.4.2   Preparing for Pulse 67: Logging data, optimizing for space; etc. (Henthorn)
// v1.4.1   Removed RB0/INTF from sleep loop (McGill/Henthorn)
// 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

#define VERSION "v1.4.3"
#define FROWNY  VERSION

#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 SW_VERSION  0       // Camera Tripod SW version
                            // 0 == Integration test version
                            // 
                            // 
#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 (1 byte)
#define VCALADD     1   // EEPROM address of voltage calibration (2 bytes)
#define DEPLOYADD   3   // EEPROM address of active deployment flag (1 byte)
#define INTERVALADD 4   // EEPROM address of image interval (2 bytes)
#define TIMEOUTADD  6   // EEPROM address of timeout/max awake (2 bytes)
#define RUNDERVADD  8   // EEPROM address number of strobe charge undervoltages (2 bytes)
#define ROVERVADD   10  // EEPROM address number of strobe discharge overvoltages (2 bytes)
#define LUNDERVADD  12  // EEPROM address number of strobe charge undervoltages (2 bytes)
#define LOVERVADD   14  // EEPROM address number of strobe discharge overvoltages (2 bytes)
#define IMAGENUMADD 16  // EEPROM address number of deployment images (2 bytes)
#define WEEKLYSTATS 18  // EEPROM address of weekly voltage levels (1 bytes)

#define TRUE    1
#define FALSE   0
#define ON      TRUE
#define OFF     FALSE
#define LEFT    'L'
#define RIGHT   'R'
#define FFCAM  1
#define NFCAM  2
#define WRITE  27
#define READ   28

enum {RESETOK = 0, 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
// 
#if 0
#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
#endif

#define FIRST_CMD     1  // Command codes
#define CMD_SLEEP     (FIRST_CMD)
#define READWAKE      (CMD_SLEEP+1)
#define DEPLOY        (READWAKE+1)
#define CONTINUE      (DEPLOY+1)
#define HALT_DEPLOY   (CONTINUE+1)
#define NUMIMAGES     (HALT_DEPLOY+1)
#define LAST_CMD      (NUMIMAGES)  //////////

#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 GETSTATS      (SNAPNF+1)
#define LAST_TEST     (GETSTATS)        ///////////


#define CRLF      "\r\n"

#if 0
// The order of these prompts must match the order of the
// menu codes above
//
static const char* menu_prompts[] =
{
    // Menu choices
    "Cmd",
    "Config",
    "Test"
};

// The order of these prompts must match the order of the
// config codes above
//
static const char* config_prompts[] =
{
    // Config choices
    "Set sl",
    "Read sl",
    "S dly",
    "R dly",
    "S to",
    "R to",
    "S batt",
    "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
    "Slp",
    "Rd Wk",
    "Dply",
    "Rsume",
    "Halt",
    "Images"
};

// The order of these prompts must match the order of the
// test codes above
//
static const char* test_prompts[] =
{
    // Test choices
    "Cam 1",
    "Cam 0",
    "Cam ?",
    "L strb V?",
    "R V?",
    "Chg 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"
};
#endif

unsigned int  gItem;
unsigned long gAdd;
size_t gSize;

void sleep(void);
void setSleep(void);
void setDelay(void);
void setTimeout(void);
void setBatt(void);
void setEEPROMVal(unsigned int addr, int val);
int getEEPROMVal(unsigned int addr);
unsigned int readBatt(void);
unsigned int readVmon(char strobe);
void chgStrobe(char strobe);
void TrigStrobe(char strobe);
void TrigShutter(char camera);
void readWake(void);
void setSerno(void);
unsigned int getAwakeSecs(void);
void clrAwakeSecs(void);
void takeImage(void);
void dispatch(int selection);
void updateStats();
void getStats();
void deploy(char);
char countdown(char);

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 = 60;     // number of minutes to sleep
unsigned int    gDelaySecs = 0;      // number of seconds to wait before sleeping
unsigned int    gChargeSecs = 30;    // seconds to wait for strobes to charge
unsigned int    gTimeoutMins = 0;    // max minutes awake before going back to sleep
char            gDeployActive = 0;
time_t          gDeployStart = 0;    // deployment start time in epoch seconds
char            g7VOn;               // status of 7 V power supply

// Voltage check constants
// 
#define  LSUNDER  0x01
#define  LSOVER   0x02
#define  RSUNDER  0x04
#define  RSOVER   0x08

unsigned char gStrobeCheck;
unsigned int  gLStrobeCharged;
unsigned int  gLStrobeSpent;
unsigned int  gRStrobeCharged;
unsigned int  gRStrobeSpent;
unsigned int  gImageNumber;

char            gWakeCause;          // reason for last reset or wakeup
volatile unsigned int    gAwakeSecs;     // number of seconds elapsed since wakeup
unsigned int    gGain;
unsigned char   cmdBuf[SER_BUFSIZE];     // command buffer



// 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()


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

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

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

void ms_delay(unsigned int milliseconds)
{
    switch(milliseconds)
    {
        case 5:
            __delay_ms(5);
            break;

        case 25:
            __delay_ms(25);
            break;

        case 50:
            __delay_ms(50);
            break;

        case 100:
            __delay_ms(100);
            break;
            
        case 300:
            __delay_ms(300);
            break;

        case 1000:
            __delay_ms(1000);
            break;

        default:
            sprintf(cmdBuf, "No delay %d ms", milliseconds);
            report(cmdBuf);
            __delay_ms(100);
            break;
    }
}


// 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 = 0x00;               // make all portb pins outputs
    ANSELH = 0x00;              // make all portb pins digital
    TRISC = 0x00;               // make all portc pins outputs
    PORTA = PORTB = PORTC = 0;  // set all pins low
    FIRESHTR  = FALSE;          // Set shutter trigger lines low
    NFIRESHTR = FALSE;

    // INHIBIT5V = FALSE;        // turn 5V power supply ON
    /* __delay_ms */ ms_delay(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 = getEEPROMVal(VCALADD);
    gDeployActive = eeprom_read(DEPLOYADD);

    sprintf(cmdBuf, "Wakey %s Camera Tripod!", VERSION);
    report(cmdBuf);

    // Detect a power-cycle during a deployment.
    // Give the user 30 seconds to initiate any serial traffic.
    // If no traffic is detected, continue with the deployment.
    // 
    if (gDeployActive)
    {
        report ("Active Deployment!");
        gSleepMins = getEEPROMVal(INTERVALADD);
        gTimeoutMins = getEEPROMVal(TIMEOUTADD);
        gImageNumber = getEEPROMVal(IMAGENUMADD);

        if (!countdown(30)) deploy(FALSE);    // continue with the cached deployment parameters

    }

    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

        dispatch(atoi(cmdBuf));
#if 0
        // parse command
        if(!cmdBuf[0] || '?' == 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
        {
            dispatch(atoi(cmdBuf));
        }
#endif
    } // end while
} // end main


// Sleep in a low-power state
void sleep(void) {
    unsigned int secsElapsed;
    time_t et = getEpochSecs() + 60*gSleepMins;
    sprintf(cmdBuf, "Sleep %d min", gSleepMins);
    report(cmdBuf);

    // delay before sleeping
    for(secsElapsed = 0; secsElapsed < gDelaySecs; secsElapsed++) {
        CLRWDT();
        /* __delay_ms */ ms_delay(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 watchdog times out
    // 
    while(secsElapsed++ <= (gSleepMins*60) - 1 && nTO)   //!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
    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 */ ms_delay(100);      // wait for power supply to stabilize
    serial_setup();         // reinitialize the USART
    report("Up");
    et = getEpochSecs();
    report(ctime(&et));
} // 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("1-1092 min:");
    gets(cmdBuf);
    input = atoi(cmdBuf);
    if(input > 0 && input <= 1092) {
        gSleepMins = input;
        setEEPROMVal(INTERVALADD, gSleepMins);
        //eeprom_write(INTERVALADD,   (unsigned char) (gSleepMins >> 8));
        //eeprom_write(INTERVALADD+1, (unsigned char) (gSleepMins & 0x00ff));

        sprintf(cmdBuf, "=%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("0-60 sec:");
    gets(cmdBuf);
    input = atoi(cmdBuf);
    if(input >= 0 && input <= 60) {
        gDelaySecs = input;
        sprintf(cmdBuf, "=%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("1-1092 min,0==forever): ");
    gets(cmdBuf);
    input = atoi(cmdBuf);
    if(input >= 0 && input <= 1092) {
        gTimeoutMins = input;
        setEEPROMVal(TIMEOUTADD, gTimeoutMins);
        //eeprom_write(TIMEOUTADD,   (unsigned char) (gTimeoutMins >> 8));
        //eeprom_write(TIMEOUTADD+1, (unsigned char) (gTimeoutMins & 0x00ff));

        sprintf(cmdBuf, "=%d", gTimeoutMins);
        report(cmdBuf);
        clrAwakeSecs(); // restart the timeout timer
    } else {
        report(FROWNY);
    } // end if
} // end setTimeout()

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

    prompt("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 */ ms_delay(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
        setEEPROMVal(VCALADD, gGain);

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

} // end setBatt()

void setEEPROMVal(unsigned int addr, int val)
{
    // save value to addrerss in EEPROM
    eeprom_write(addr,   (unsigned char) (val >> 8));
    eeprom_write(addr+1, (unsigned char) (val & 0x00ff));
}

int getEEPROMVal(unsigned int addr)
{
    return (eeprom_read(addr) << 8) + eeprom_read(addr+1);
}

// 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 */ ms_delay(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/1000.) ) + .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
    
    return adcVal / 2;
    
    // This calculation determined to be wrong
    // 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 ...", strobe);
    report(cmdBuf);
      
    for(secsElapsed = 0; secsElapsed < gChargeSecs; secsElapsed++) {
        CLRWDT();
        /* __delay_ms */ ms_delay(1000);
    } // end for
    strobe == LEFT ? LSTRBCHG = FALSE : RSTRBCHG = FALSE;      // charge strobe
};

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

void TrigShutter(char camera) {
//    if (camera & NFCAM) {NFIRESHTR = TRUE; /*report("NF");*/}
//    if (camera & FFCAM) {FIRESHTR  = TRUE; /*report("FF");*/}
//    
//    Delay between triggers changed to 50 ms  (October 2016)
//    
    NFIRESHTR = TRUE;
    /* __delay_ms */ ms_delay(50);        // wait 
    FIRESHTR  = TRUE;
    /* __delay_ms */ ms_delay(300);        // wait 
    // FIRESHTR  = NFIRESHTR = FALSE;
    FIRESHTR  = FALSE;
    NFIRESHTR = FALSE;
}

#define CAMERA_BOOT_TIME   7
#define CAMERA_WRITE_TIME  10

// Take an image. This function handles all the steps in taking
// an image. Can be used to take a test image as well.
// 
void takeImage(void){
    unsigned int secsElapsed;
    LSTRBCHG = TRUE;      // charge strobes
    /* __delay_ms */ ms_delay(5);
    RSTRBCHG = TRUE;

    gStrobeCheck = 0;
    // report("Snap!"); return;

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

    // check strobe voltages, record under voltages of less than 300V
    // 
    gLStrobeCharged = readVmon(LEFT);
    if (gLStrobeCharged < 300) gStrobeCheck |= LSUNDER;

    /* __delay_ms */ ms_delay(100);
    gRStrobeCharged = readVmon(RIGHT);     
    if (gRStrobeCharged < 300) gStrobeCheck |= RSUNDER;
    
    sprintf(cmdBuf, "L:%uV R:%uV", gLStrobeCharged, gRStrobeCharged);
    report(cmdBuf);

    report("Cams+");
    g7VOn = ENABLE7V = ON;        // turn 7V power supply ON
    for(secsElapsed = 0; secsElapsed < CAMERA_BOOT_TIME; secsElapsed++) {
        CLRWDT();
        /* __delay_ms */ ms_delay(1000);
    }
    sprintf(cmdBuf, "Img %d", gImageNumber);
    report(cmdBuf);
    TrigShutter(FFCAM+NFCAM);
         
    //   /* __delay_ms */ ms_delay(100);
    for(secsElapsed = 0; secsElapsed < CAMERA_WRITE_TIME; secsElapsed++) {
        CLRWDT();
        /* __delay_ms */ ms_delay(1000);
    }
    report("Cams-");
    LSTRBCHG = RSTRBCHG = FALSE;   // stop charging strobes
    g7VOn = ENABLE7V = OFF;       // turn 7V power supply OFF
       
    // check strobe voltages, record over voltages of more than 50V
    // 
    gLStrobeSpent = readVmon(LEFT);
    if (gLStrobeSpent > 100) gStrobeCheck |= LSOVER;

    /* __delay_ms */ ms_delay(100);
    gRStrobeSpent = readVmon(RIGHT);
    if (gRStrobeSpent > 100) gStrobeCheck |= RSOVER;

    sprintf(cmdBuf, "L:%uV R:%uV", gLStrobeSpent, gRStrobeSpent);
    report(cmdBuf);
}

static const char* wake_events[] = {
    "RSTOK",
    "RSTWD",
    "WKOK",
    "WKWD",
    "",
    "TO"
};

// Read the reason for the last wakeup
void readWake(void) {
    if (gWakeCause <= TIMEOUT && gWakeCause >= 0)
        report(wake_events[gWakeCause]);
    else
        report("?");
} // end readWake()


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

    prompt("1-255,0 to abort:");
    gets(cmdBuf);
    serno = atoi(cmdBuf);
    if(serno > 0 && serno <= 255) {
        eeprom_write(SERNOADD, serno);
        sprintf(cmdBuf, "=%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()


// 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;
    time_t et;

    switch (menu_num)
    {
#if 0
        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;
#endif
        case CMD_SLEEP:
            sleep();
            readWake();
            break;

        case SETSLEEP:
            setSleep();
            break;

        case READSLEEP:
            sprintf(cmdBuf, "=%u", gSleepMins);
            report(cmdBuf);
            break;

        case READWAKE:
            readWake();
            break;

        case SETDELAY:
            setDelay();
            break;

        case READDELAY:
            sprintf(cmdBuf, "=%u", gDelaySecs);
            report(cmdBuf);
            break;

        case SETTIMEOUT:
            setTimeout();
            break;

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

        case CAMERAON:
            g7VOn = ENABLE7V = ON;        // turn 7V power supply ON/OFF
            report("1");
            break;

        case CAMERAOFF:
            g7VOn = ENABLE7V = OFF;        // turn 7V power supply ON/OFF
            report("0");
            break;
                
        case READ7V:
            sprintf(cmdBuf, "=%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:
            LSTRBCHG = ON;
            report("1");
            break;

        case LSTROBEOFF:
            LSTRBCHG = OFF;
            report("0");
            break;

        case RSTROBEON:
            RSTRBCHG = ON;
            report("1");
            break;

        case RSTROBEOFF:
            RSTRBCHG = OFF;
            report("0");
            break;

        case TRIGLSTROBE:
            TrigStrobe(LEFT);
            break;

        case TRIGRSTROBE:
            TrigStrobe(RIGHT);
            break;

        case TRIGSHUTTER:
            TrigShutter(FFCAM);
            break;

        case TRIGNFSHUTTER:
            TrigShutter(NFCAM);
            break;

        case SNAP:
            takeImage();
            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("DD(1-31)>");
            d = atoi(gets(cmdBuf));
            prompt("HH(0-23)>");
            h = atoi(gets(cmdBuf));
            prompt("MM(0-59)>");
            n = atoi(gets(cmdBuf));
            prompt("SS(0-59)>");
            s = atoi(gets(cmdBuf));
            prompt("DST(0-1)>");
            setEpochSecs(y, m, d, h, n, s, atoi(gets(cmdBuf)));
            break;

        case GETSTATS:
            getStats();
            break;

        case READTIME:
            et = getEpochSecs();
            report(ctime(&et));
            break;

        case DEPLOY:
            deploy(TRUE);
            break;

        case CONTINUE:
            deploy(FALSE);
            break;

        case HALT_DEPLOY:
            gDeployActive = 0;
            eeprom_write(DEPLOYADD, gDeployActive);
            report("Halt");
            break;

        case NUMIMAGES:
            sprintf(cmdBuf, "%u", gImageNumber);
            report(cmdBuf);
            break;

        default:
            report(FROWNY);
            break;
    }
}

char countdown(char seconds)
{
    char i;
    report("");
    for (i = seconds; i > 0; i--)
    {
        printf("\r%3d", i);
        if (kbhit())
        {
            return 1;   // break detected
        }
        CLRWDT();
        /* __delay_ms */ ms_delay(1000);
    }
    return 0;
}

#define IMAGE_INTERVAL  168      // 168 hours == 1 week

// Record updated statistics, if required.
// 
void updateStats()
{
    // Record battery voltage once a week (every 168 images) starting
    // at address WEEKLYSTATS
    // 
    char offset = 3 * (gImageNumber / IMAGE_INTERVAL);
    if (WEEKLYSTATS + offset > 253) return;

    if (gImageNumber % IMAGE_INTERVAL == 0)
    {
        unsigned char decivolts = 0;
        int v = readBatt() - 100;
        if (v > 255) decivolts = 255;
        else if (v < 0) decivolts = 0;
        else decivolts = v;
        //printf("\n\rStored %d to address %d\n", decivolts, WEEKLYSTATS+offset);
        eeprom_write(WEEKLYSTATS+offset, decivolts);

        // Inititalize the weekly faults counts
        // 
        eeprom_write(WEEKLYSTATS+offset+1, 0);
        eeprom_write(WEEKLYSTATS+offset+2, 0);
    }

    // Record any strobe voltage anomalies
    // 
    if (gStrobeCheck != 0)
    {
        // Increment the total fault counts
        // 
        if (gStrobeCheck & LSUNDER) {report("LS under"); setEEPROMVal(LUNDERVADD, getEEPROMVal(LUNDERVADD)+1);}
        if (gStrobeCheck &  LSOVER) {report("LS over" ); setEEPROMVal( LOVERVADD, getEEPROMVal( LOVERVADD)+1);}
        if (gStrobeCheck & RSUNDER) {report("RS under"); setEEPROMVal(RUNDERVADD, getEEPROMVal(RUNDERVADD)+1);}
        if (gStrobeCheck &  RSOVER) {report("RS over" ); setEEPROMVal( ROVERVADD, getEEPROMVal( ROVERVADD)+1);}

        // Increment the weekly fault counts
        // 
        if ((gStrobeCheck & LSUNDER) || (gStrobeCheck &  LSOVER))
            eeprom_write(WEEKLYSTATS+offset+1, eeprom_read(WEEKLYSTATS+offset+1)+1);
        if ((gStrobeCheck & RSUNDER) || (gStrobeCheck &  RSOVER));
            eeprom_write(WEEKLYSTATS+offset+2, eeprom_read(WEEKLYSTATS+offset+2)+1);
        gStrobeCheck = 0;
    }

}

// Report the stats accumulated so far
// 
void getStats()
{
    //report("SNAPPY DEPLOYMENT WEEKLY STATISTICS");
    report("Week No., Voltage (V), Left Strobe Faults, Right Strobe Faults");
    short i;
    for (i = 0; i <= ((gImageNumber-1) / IMAGE_INTERVAL); i++)
    {
        char offset = 3 * i;
        printf("\r   %2d,\t\t%3d,\t\t%3d,\t\t%3d\n",
            i, eeprom_read(WEEKLYSTATS+offset) + 100, eeprom_read(WEEKLYSTATS+offset+1), eeprom_read(WEEKLYSTATS+offset+2));
    }

    int left  = getEEPROMVal(LUNDERVADD);
    int right = getEEPROMVal(RUNDERVADD);
    printf("\n\rTOTAL FAULTS OUT OF %d IMAGES\n", gImageNumber);
    printf("\r Left Under-Charge: %3d\tRight Under-Charge: %3d\n", left, right);

    left  = getEEPROMVal(LOVERVADD);
    right = getEEPROMVal(ROVERVADD);
    printf("\r Left  Over-charge: %3d\tRight  Over-Charge: %3d\n", left, right);
}

// Deploy function. Keep taking images at the specified interval
// until the user interrupts in the 5-second window.
// 
void deploy(char beginDeployment)
{
    // Initialize deployment if this is a new one
    // 
    if (beginDeployment)
    {
        report("Begin");
        gImageNumber = 0;
        gDeployStart = getEpochSecs();
        setEEPROMVal(LUNDERVADD, 0);
        setEEPROMVal( LOVERVADD, 0);
        setEEPROMVal(RUNDERVADD, 0);
        setEEPROMVal( ROVERVADD, 0);
        eeprom_write(WEEKLYSTATS+0, 0);
        eeprom_write(WEEKLYSTATS+1, 0);
        eeprom_write(WEEKLYSTATS+2, 0);
    }
    else
    {
        report("Resume");
    }

    // The deployment is active
    // 
    gDeployActive = 1;
    eeprom_write(DEPLOYADD, gDeployActive);
    while (TRUE)
    {
        // Capture an image
        takeImage();

        // Record data
        //xferImageData(WRITE);
        updateStats();

        gImageNumber++;
        setEEPROMVal(IMAGENUMADD, gImageNumber);
        //eeprom_write(IMAGENUMADD,   (unsigned char) (gImageNumber >> 8));
        //eeprom_write(IMAGENUMADD+1, (unsigned char) (gImageNumber & 0x00ff));

        // Sleep the interval
        sleep();

        // Need to detect a user interrupt here upon wakeup
        // and break out of the loop. 5 seconds.
        // 
        if (countdown(5)) break;    // User wants to break from deployment loop

    }
}


#if 0
//void spi_mem_write();
//void spi_mem_read();
//void xferImageData(char);

void spi_mem_write()
{
    sprintf(cmdBuf, "W %d b @ %d: %d", gSize, gAdd, gItem);
    report(cmdBuf);
}

void spi_mem_read()
{
    sprintf(cmdBuf, "R %d b @ %d", gSize, gAdd);
    report(cmdBuf);
}

// Read or Write data to and from the auxilary EEPROM memory.
// Choose the image by setting the global gImageNumber variable
// before calling this function.
// 
void xferImageData(char direction)
{
    void (*spi_func)();
    unsigned int wv;
    unsigned int t;

    // Write new image data to storage location at the offset
    if (WRITE == direction)
    {
        spi_func = &spi_mem_write;
        wv = gWakeCause*1000 + readBatt();
        t  = (unsigned int)(getEpochSecs() - gDeployStart);  // Timestamp the write
    }
    else if (READ == direction)
    {
        spi_func = &spi_mem_read;
    }

    // Calculate the memory offset based on the image number.
    // The image number must be set before calling this function.
    // 
    gAdd = 16*gImageNumber;

    gItem = gImageNumber;
    gSize = sizeof(gImageNumber);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(gImageNumber);
    
    gItem = wv;
    gSize = sizeof(wv);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(wv);
    
    gItem = gLStrobeCharged;
    gSize = sizeof(gLStrobeCharged);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(gLStrobeCharged);
    
    gItem = gLStrobeSpent;
    gSize = sizeof(gLStrobeSpent);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(gLStrobeSpent);
    
    gItem = gRStrobeCharged;
    gSize = sizeof(gRStrobeCharged);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(gRStrobeCharged);
    
    gItem = gRStrobeSpent;
    gSize = sizeof(gRStrobeSpent);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(gRStrobeSpent);
    
    gItem = t;
    gSize = sizeof(t);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(t);

    // Fill the spare two bytes with something we can recognize
    // 
    gItem=&gImageNumber;
    gSize=sizeof(gImageNumber);
    spi_func(); //spi_mem_write();
    gAdd += sizeof(gImageNumber);

    // Write the data to stdout if we just read the data
    if (TRUE) //READ == direction)
    {
        time_t tt = t + gDeployStart;
        printf("Img %4d:\tWake=%s\tBatt=%d\tLChrg=%d\tLAfter=%d\tRchrg=%d\tRAfter=%d\t%s",
            gImageNumber,               // Image number
            wake_events[wv/1000],       // Wake event
            wv % 1000,                  // Battery voltage when image captured
            gLStrobeCharged,            // Left  strobe voltage when charged
            gLStrobeSpent,              // Left  strobe voltage when spent
            gRStrobeCharged,            // Right strobe voltage when charged
            gRStrobeSpent,              // Right strobe voltage when spent
            ctime(&tt)                  // Capture Time
        );
    }
#if 0
    }
    else
    {
        gItem=&gImageNumber;gSize=sizeof(gImageNumber);spi_mem_read(); gAdd += sizeof(gImageNumber);
        gItem=&wv;gSize=sizeof(wv);spi_mem_read(); gAdd += sizeof(wv);
        gItem=&gLStrobeCharged;gSize=sizeof(gLStrobeCharged);spi_mem_read(); gAdd += sizeof(gLStrobeCharged);
        gItem=&gLStrobeSpent;gSize=sizeof(gLStrobeSpent);spi_mem_read(); gAdd += sizeof(gLStrobeSpent);
        gItem=&gRStrobeCharged;gSize=sizeof(gRStrobeCharged);spi_mem_read(); gAdd += sizeof(gRStrobeCharged);
        gItem=&gRStrobeSpent;gSize=sizeof(gRStrobeSpent);spi_mem_read(); gAdd += sizeof(gRStrobeSpent);
        gItem=&t;gSize=sizeof(t);spi_mem_read(); gAdd += sizeof(t);
    }
#endif
}
#endif

#if 0
// Print the specified menu (main, command, test, or config)
// 
void print_menu(char menu) {

    printf("(%2d) Slp%s",CMD_SLEEP, CRLF);
    printf("(%2d) Rd Wk%s",READWAKE    ,CRLF);
    printf("(%2d) Dply%s",DEPLOY      ,CRLF);
    printf("(%2d) Rsume%s",CONTINUE    ,CRLF);
    printf("(%2d) Halt%s",HALT_DEPLOY ,CRLF);
    printf("(%2d) Images%s",NUMIMAGES   ,CRLF);
    printf("(%2d) Set sl%s", SETSLEEP    ,CRLF);
    printf("(%2d) Read sl%s",READSLEEP   ,CRLF);
    printf("(%2d) S dly%s",SETDELAY    ,CRLF);
    printf("(%2d) R dly%s",READDELAY   ,CRLF);
    printf("(%2d) S to%s",SETTIMEOUT  ,CRLF);
    printf("(%2d) R to%s",READTIMEOUT ,CRLF);
    printf("(%2d) S batt%s",SETBATT     ,CRLF);
    printf("(%2d) R batt%s",READBATT    ,CRLF);  
    printf("(%2d) S SN%s",SETSERNO    ,CRLF);
    printf("(%2d) R SN%s",READSERNO   ,CRLF);
    printf("(%2d) S clk%s",SETTIME     ,CRLF);
    printf("(%2d) R clk%s",READTIME    ,CRLF);
    printf("(%2d) Cam 1%s",CAMERAON    ,CRLF);
    printf("(%2d) Cam 0%s",CAMERAOFF   ,CRLF);
    printf("(%2d) Cam ?%s",READ7V      ,CRLF);
    printf("(%2d) L strb V?%s",READLVOLT   ,CRLF);
    printf("(%2d) R V?%s",READRVOLT   ,CRLF);
    printf("(%2d) Chg L strb%s",CHGLSTROBE  ,CRLF);
    printf("(%2d) Chg R%s",CHGRSTROBE  ,CRLF);
    printf("(%2d) L strb 1%s",LSTROBEON   ,CRLF);
    printf("(%2d) L 0%s",LSTROBEOFF  ,CRLF);
    printf("(%2d) R strb 1%s",RSTROBEON   ,CRLF);
    printf("(%2d) R 0%s",RSTROBEOFF  ,CRLF);
    printf("(%2d) Trg L strb%s",TRIGLSTROBE ,CRLF);
    printf("(%2d) Trg R%s",TRIGRSTROBE ,CRLF);
    printf("(%2d) Trg far shut%s",TRIGSHUTTER ,CRLF);
    printf("(%2d) Trg near%s",TRIGNFSHUTTER,CRLF);
    printf("(%2d) Snap far cam%s",SNAP   ,CRLF);
    printf("(%2d) Snap near%s",SNAPNF ,CRLF);
    char **p;
    short i, f, l;
    report("Enter #");
    switch (menu)
    {
        // Menu options
        case MAIN_MENU:
        f = FIRST_MENU; l = LAST_MENU; p = menu_prompts;
        break;

        // Command options followed by an Exit
        case CMD_MENU:
        f = FIRST_CMD; l = LAST_CMD; p = (char*)cmd_prompts;
        break;

        // Config options followed by an Exit
        case CFG_MENU:
        f = FIRST_CFG; l = LAST_CFG; p = config_prompts;
        break;

        // Test options followed by an Exit
        case TEST_MENU:
        f = FIRST_TEST; l = LAST_TEST; p = test_prompts;
        break;

        default:
        return;
    }
    for (i = f; i <= l; i++)
    {
        sprintf(cmdBuf, "(%2d) %s", i, p[i-f]);
        menu_item(cmdBuf);
    }
    if (menu != MAIN_MENU) menu_item("( E) Exit");
} // end help()
#endif
