/**************************************************************************

septic_07.c

This program is timed-discharge adapter for a Vericom panel used with Advantex
septic treatment system.  It turns on the final discharge pump by manipulating
the Vericom's PUMP-ON and PUMP-OFF float inputs on a timed basis, using the
state of the actual float switches as boundary conditions, i.e. don't turn on
the pump at all if the PUMP-OFF float says that there's no water to pump, and
turn on the pump anytime the PUMP-ON float says that the water level is high.

This version adds:  eeprom storage of pump-on time
                    activity LED shows 10% of pump-on time duration
                    skip out of delays immediately if put into BYPASS mode

Makes the high level float pump until it goes down, no interrupts.  Logs
on-time in both bypass and timed modes.

Still has issues because it confuses the Vericom panel, which then
goes into an alarm condition, and phones out with an alarm.  I haven't
figured out exactly what the Vericom panels is confused about, other
than that the low float is on for a very short period before the high
float is activated.

                         +5
                          |
                         14
                     ----------
                    |      RA0 |-17-- PUMP ON RELAY
     HIGH FLOAT---6-| RB0      |
                    |      RA1 |-18-- PUMP OFF RELAY
     LOW FLOAT----7-| RB1      |
                    |      RB5 |-11-- PUMP ON LED
     BYPASS-------8-| RB2      |
                    |      RB4 |-10-- PUMP OFF LED
                    |          |
                    |      RB3 |-9--- ACTIVITY LED
                    |          |
                    |  16F628  |
                    |          |
                    |      RB6 |-12-- PGD
                    |      RB7 |-13-- PGC
       6MHz XTAL-15-|     MCLR |-4--- MCLR
            XTAL-16-|          |
                     ----------
                          5
                          |
                         Gnd

***************************************************************************/

#include <16F628A.h>
#fuses HS, NOPROTECT, PUT, NOWDT, BROWNOUT, NOMCLR, NOLVP
#include 

#use standard_io ( A )
#use standard_io ( B )
#use delay ( clock = 6000000 )
// one cycle = 0.66uS @ 6MHz

#define PUMP_ON_RELAY            PIN_A0
#define PUMP_OFF_RELAY           PIN_A1
#define PUMP_ON_FLOAT_LED        PIN_B5
#define PUMP_OFF_FLOAT_LED       PIN_B4
#define ACTIVITY_LED             PIN_B3
#define HIGH_FLOAT               PIN_B0
#define LOW_FLOAT                PIN_B1
#define BYPASS_SWITCH            PIN_B2
#define RAISED                   0
#define LOWERED                  1
#define NORMAL_WATER_LEVEL       0
#define HIGH_WATER_LEVEL         1
#define TIMED_MODE               0
#define VOLUME_MODE              1
#define INTERRUPTS_PER_SECOND    46

//macros
#define LEDON   output_high ( ACTIVITY_LED )
#define LEDOFF  output_low ( ACTIVITY_LED )

// TIMES IN SECONDS
#define INITIAL_PUMP_ON_TIME     10
#define PUMP_ON_HIGH_WATER_TIME  30
#define VERICOM_RESPONSE_DELAY   6
#define PUMP_CYCLE_INTERVAL      3600
#define DELAY_TO_FIRST_PUMP      15
#define FLOAT_DEBOUNCE_DELAY     3

// EEPROM STORAGE AT DEVICE PROGRAMMING TIME
#rom 0x2100 = { INITIAL_PUMP_ON_TIME }
// EEPROM ADDRESS MAP
#define EEPROM_PUMP_ON_TIME_ADR    0

// PROTOTYPE STATEMENTS
ActivateDischargePump ( char cLevelMode );
char DelaySeconds ( char cSeconds );
void RecalculateTimes ( void );

// VARIABLES
static char cPumpTimeFlag, cInterruptCount, cElapsedHours, cPumpOnTime, cMode, cBypassLedCount;
static char cPumpOnFloatCount, cPumpOffFloatCount, cPumpOnFloatFlag, cPumpOffFloatFlag, cSkip;
static char cRecalculateTimesFlag, cAccumulatePumpTimeFlag, cPumpOnTimeSkipCounter, cLedSkipFlag;
static long iPumpIntervalCount, iElapsedSeconds, iAccumulatedPumpTime, iLedCounter, iPumpLedCount;

// INTERRUPT CODE
#int_rtcc
void TimerInterrupt ( void )
    {
    // at 6MHz clock, gets here every 21.760mS
    cInterruptCount++;                 // increment counter
    // ACTIVITY LED
    if ( cMode == TIMED_MODE )
        {
        // flash green LED (on for 10% of the present pump-on duration)
        if ( ++iLedCounter < iPumpLedCount )
            {
            output_high ( ACTIVITY_LED );
            }
        else
            {
            output_low ( ACTIVITY_LED );
            }
        // wait one second before turning LED on again
        if ( iLedCounter > ( iPumpLedCount + INTERRUPTS_PER_SECOND ) )
            {
            iLedCounter = 0;        // reset count
            }
        }
    else
        {
        if ( ++cBypassLedCount < 3 )
            {
            output_high ( ACTIVITY_LED );    // ~130mS flash
            }
        else
            {
            output_low ( ACTIVITY_LED );
            }
        if ( cBypassLedCount >= 6 )    // fast rep rate
            {
            cBypassLedCount = 0;        // reset count
            }
        }
    // ONE-SECOND TIMER TICK
    if ( cInterruptCount >= INTERRUPTS_PER_SECOND )      // one second yet?
        {
        // gets here every 1 second  (measures 0.998775 sec)
        if ( iPumpIntervalCount++ >= PUMP_CYCLE_INTERVAL )
            {
            cPumpTimeFlag = YES;        // signal to turn on pump, this flag turned off in the main loop
            iPumpIntervalCount = 0;     // reset interval
            }
        // display pump-on seconds by LED blink count, skip one second between sets of blinks
        cPumpOnTimeSkipCounter++;
        if ( cPumpOnTimeSkipCounter == cPumpOnTime )
            {
            cLedSkipFlag = YES;         // skip LED blink for this one second period
            }
        if ( cPumpOnTimeSkipCounter > cPumpOnTime )
            {
            cLedSkipFlag = NO;          // reset skip flag
            cPumpOnTimeSkipCounter = 0; // reset counter
            }
        // time accumulation
        if ( cAccumulatePumpTimeFlag == ON )
            {
            iAccumulatedPumpTime++;     // accumulate the pump seconds, reset after recalculation in main loop
            }
        // recalculation trigger, actual recalculation done in main loop
        if ( ++iElapsedSeconds >= 3599 )       // if one hour has passed
            {
            if ( ++cElapsedHours >= 23 )       // if 24 hours has passed
                {
                cRecalculateTimesFlag = ON;    // time to recalculate based on the previous 24 hour logging
                cElapsedHours = 0;      // reset count
                }
            iElapsedSeconds = 0;       // reset count
            }
        cInterruptCount = 0;        // reset count
        }
    // DEBOUNCE PUMP-ON FLOAT SWITCH
    if ( input ( HIGH_FLOAT ) == RAISED )
        {
        if ( cPumpOnFloatCount++ >= ( FLOAT_DEBOUNCE_DELAY * INTERRUPTS_PER_SECOND ) )
            {
            cPumpOnFloatFlag = ON;      // set flag if float was continuously on for three seconds
            }
        }
    else
        {
        cPumpOnFloatCount = 0;      // restart count
        cPumpOnFloatFlag = OFF;     // reset flag
        }
    // DEBOUNCE PUMP-OFF FLOAT SWITCH
    if ( input ( LOW_FLOAT ) == RAISED )
        {
        if ( cPumpOffFloatCount++ >= ( FLOAT_DEBOUNCE_DELAY * INTERRUPTS_PER_SECOND ) )
            {
            cPumpOffFloatFlag = ON;      // set flag if float was continuously on for three seconds
            }
        }
    else
        {
        cPumpOffFloatCount = 0;      // restart count
        cPumpOffFloatFlag = OFF;     // reset flag
        }
    // PRE-ADJUST TIMER FOR ACCURACY BEFORE LEAVING INTERRUPT
    // RTCC(0) = 1.006567 sec
    // RTCC(1) = 1.002676 sec
    // RTCC(2) = 0.998775 sec
    set_rtcc ( 2 );
    }

// MAIN CODE
void main ( void )
    {
    delay_ms ( 200 );

    setup_counters ( RTCC_INTERNAL, RTCC_DIV_128 );   // 256 * 4uS = 1.024mS timer wrap

    output_high ( ACTIVITY_LED );
    output_high ( PUMP_ON_FLOAT_LED );
    output_high ( PUMP_OFF_FLOAT_LED );
    delay_ms ( 1000 );
    output_low ( ACTIVITY_LED );
    output_low ( PUMP_ON_FLOAT_LED );
    output_low ( PUMP_OFF_FLOAT_LED );
    delay_ms ( 1000 );

    // initialize all variables
    cPumpTimeFlag = OFF;
    iPumpIntervalCount = PUMP_CYCLE_INTERVAL - DELAY_TO_FIRST_PUMP;
    cInterruptCount = 0;
    iAccumulatedPumpTime = 0;
    iElapsedSeconds = 0;
    cElapsedHours =  0;
    cPumpOffFloatCount = 0;
    cPumpOffFloatFlag = OFF;
    cPumpOnFloatCount = 0;
    cPumpOnFloatFlag = OFF;
    cPumpOnTimeSkipCounter = 0;
    cLedSkipFlag = NO;
    cBypassLedCount = 0;
    iLedCounter = 0;

    // get stored pump time from EEPROM
    cPumpOnTime = read_eeprom ( EEPROM_PUMP_ON_TIME_ADR );      // get stored value
    if ( ( cPumpOnTime < 10 ) || ( cPumpOnTime > 120 ) )        // if stored value seems outlandish
        {
        cPumpOnTime = INITIAL_PUMP_ON_TIME;       // correct the time to a reasonable starting value
        }
    iPumpLedCount = ( long ) cPumpOnTime * INTERRUPTS_PER_SECOND / 10;          // calculate the number of interrupts for 1/10 of the pump-on time

    enable_interrupts ( INT_RTCC );     // turn on timer interrupt
    enable_interrupts ( GLOBAL );       // enable interrupts

    while ( TRUE )
        {
        while ( input ( BYPASS_SWITCH ) == HIGH )   // if NOT in bypass mode
            {
            // TIMED DISCHARGE MODE
            // pump timing is activated by interrupt
            cMode = TIMED_MODE;
            // Pump at timed intervals
            if ( cPumpTimeFlag == ON )
                {
                if ( cPumpOffFloatFlag == ON )          // only if PUMP-OFF float is raised
                    {
                    ActivateDischargePump ( NORMAL_WATER_LEVEL );   // activate pump for pump-on time
                    }
                cPumpTimeFlag = OFF;        // reset flag
                }
            // Pump immediately if pump-on float switch is raised (shouldn't occur if timed pumping is keeping up.)
            while ( cPumpOnFloatFlag == ON )      // until high float is lowered, regardless of low float setting
                {
                ActivateDischargePump ( HIGH_WATER_LEVEL );   // long time, indefinite time
                if ( cSkip )
                    {
                    break;
                    }
                }
            // If time to recalculate pump-on times
            if ( cRecalculateTimesFlag == ON )
                {
                RecalculateTimes();
                }
            }

        while ( input ( BYPASS_SWITCH ) == LOW )        // if in bypass mode
            {
            // VOLUME DISCHARGE MODE (normal Vericom mode)
            // simply pass float switches through to their relays
            cMode = VOLUME_MODE;
            // PUMP-ON FLOAT SWITCH
            if ( input ( HIGH_FLOAT ) == RAISED )
                {
                output_high ( PUMP_ON_FLOAT_LED );
                output_high ( PUMP_ON_RELAY );
                cAccumulatePumpTimeFlag = ON;       // set flag to enable time logging
                }
            else
                {
                output_low ( PUMP_ON_FLOAT_LED );
                output_low ( PUMP_ON_RELAY );
                }
            // PUMP-OFF FLOAT SWITCH
            if ( input ( LOW_FLOAT ) == RAISED )
                {
                output_high ( PUMP_OFF_FLOAT_LED );
                output_high ( PUMP_OFF_RELAY );
                }
            else
                {
                output_low ( PUMP_OFF_FLOAT_LED );
                output_low ( PUMP_OFF_RELAY );
                cAccumulatePumpTimeFlag = OFF;      // reset flag to stop time logging
                }
            // If time to recalculate pump-on times
            if ( cRecalculateTimesFlag == ON )
                {
                RecalculateTimes();
                }
            }

        // RESET RELAYS AND LEDs
        output_low ( PUMP_ON_FLOAT_LED );
        output_low ( PUMP_ON_RELAY );
        output_low ( PUMP_OFF_FLOAT_LED );
        output_low ( PUMP_OFF_RELAY );
        output_low ( ACTIVITY_LED );
        }
    }

ActivateDischargePump ( char cLevelMode )
    {
    char cCnt;

    output_high ( PUMP_OFF_RELAY );     // simulate PUMP-OFF float raised to Vericom
    output_high ( PUMP_OFF_FLOAT_LED );
    cSkip = DelaySeconds ( VERICOM_RESPONSE_DELAY );   // Vericom response delay
    if ( !cSkip )
        {
        output_high ( PUMP_ON_RELAY );      // simulate PUMP-ON float raised to Vericom
        output_high ( PUMP_ON_FLOAT_LED );
        cSkip = DelaySeconds ( VERICOM_RESPONSE_DELAY );   // Vericom response delay before turning pump on
        if ( !cSkip )
            {
            // PUMP TURNS ON
            cAccumulatePumpTimeFlag = ON;       // set flag to enable time logging
            output_low ( PUMP_ON_RELAY );       // simulate PUMP-ON float lowered, water level dropping
            output_low ( PUMP_ON_FLOAT_LED );
            // Time the PUMP-ON state
            if ( cLevelMode == NORMAL_WATER_LEVEL )
                {
                // don't know if Vericom debounces low-going float like high-going float
                for ( cCnt = 0; cCnt < ( cPumpOnTime - VERICOM_RESPONSE_DELAY ); cCnt++ )   // delay before simulating low float drop
                    {
                    DelaySeconds ( 1 );              // delay one second
                    if ( cSkip )
                        {
                        break;
                        }
                    if ( cPumpOffFloatFlag == OFF )   // check PUMP-OFF float, stop pumping if float drops
                        {
                        break;                      // high float went low, skip out, done pumping
                        }
                    }
                }
            else            // else HIGH_WATER_LEVEL
                {
                while ( cPumpOnFloatFlag == ON )     // do until PUMP-ON float drops
                    {
                    DelaySeconds ( 1 );              // delay one second
                    // skip out if mode switch changed or low float drops (just in case)
                    if ( ( cSkip ) || ( cPumpOffFloatFlag == OFF ) )
                        {
                        break;
                        }
                    }
                }
            if ( !cSkip )
                {
                output_low ( PUMP_OFF_RELAY);       // simulate PUMP-OFF float lowered to Vericom
                output_low ( PUMP_OFF_FLOAT_LED );
                cSkip = DelaySeconds ( VERICOM_RESPONSE_DELAY );   // Vericom response delay before turning pump off
                }
            // PUMP TURNS OFF
            cAccumulatePumpTimeFlag = OFF;      // reset flag to stop time logging
            }
        }
    }

char DelaySeconds ( char cSeconds )
    {
    char cCnt;

    // checks if MODE switch went into BYPASS mode, and returns 1 for skip out
    for ( cCnt = 0; cCnt < cSeconds; cCnt++ )
        {
        if ( input ( BYPASS_SWITCH ) == LOW )       // if in BYPASS MODE
            {
            return ( YES );     // skip out
            }
        delay_ms ( 998 );   // allow for interrupt duration that expands delays in main loop
        }
    return ( NO );          // don't skip out
    }

void RecalculateTimes ( void )
        {
        cPumpOnTime = (char) ( iAccumulatedPumpTime / 24 );     // seconds per hour based on logging
        iAccumulatedPumpTime = 0;                               // reset count
        if ( cPumpOnTime < 10 )                                 // prevent going too low
            {
            cPumpOnTime = 10;
            }
        // save new calculated time to EEPROM in case power is ever dropped
        write_eeprom ( EEPROM_PUMP_ON_TIME_ADR, cPumpOnTime );  // save it in EEPROM
        // calculate the number of interrupts for 1/10 of the pump-on time for LED display
        iPumpLedCount = ( ( long ) cPumpOnTime * INTERRUPTS_PER_SECOND ) / 10;
        cRecalculateTimesFlag = OFF;                            // reset flag
        }