// ------------------------------------------------------------------
//
// TRAIN TIMER
// -----------
//
// Firmware for TRAIN02A board. It changes state of relay outputs
// as defined in configuration. Default configuration is in .h file
// and definitions may be changed online via USB port and if needed
// stored into EEPROM.
// 
// Uses ATmega8 at 8MHz (internal RC or external XTAL)
//
// (c) miho WWW.MLAB.CZ/PermaLink/TRAIN
//
// ------------------------------------------------------------------
//
// History
//
// 1.00 First version


// Standard header files
#include <avr/io.h>                                     // Device Specific Defines
#include <avr/interrupt.h>                      // For Timer and USART Interrupt
#include <stdlib.h>                                     // For rand()
#include <avr/eeprom.h>                         // For EEPROM access (for configuration)
#include <stdio.h>                                      // For printf() etc
#include <string.h>                                     // For strcmp() etc
#include <avr/pgmspace.h>                       // Data in Program Memory (strings)

#include "TRAIN.h"                                      // Hardware Definitions
#include "TRAIN_TIMER.h"                        // Project Definitions - default configuration is here


// Set Relay Outputs
void SetOutput(const unsigned char Number, const unsigned char State)
{
        // Check
        if(Number>7)
                return;

        // Set DDR (for output)
        (*RE_DDR_Table[Number]) |= RE_BIT_MASK[Number];

        // Set/Clear data
        if(State)
                (*RE_PORT_Table[Number]) |= RE_BIT_MASK[Number];
        else
                (*RE_PORT_Table[Number]) &= ~RE_BIT_MASK[Number];
}


// Get Switch Input State
unsigned char GetInput(const unsigned char Number)
{
        // Check
        if(Number>7)
                return 0;
        // PullUp
        (*SW_PORT_Table[Number]) |= SW_BIT_MASK[Number];
        // Get Value
        return ((*SW_PIN_Table[Number]) & SW_BIT_MASK[Number]) == 0 ? 1 : 0;
}


// EEPROM Data
unsigned int EEMEM EE_TimesOn[8]     = DEFAULT_TIMES_ON ;
unsigned int EEMEM EE_TimesOff[8]    = DEFAULT_TIMES_OFF ;
unsigned int EEMEM EE_TimesOnRnd[8]  = DEFAULT_TIMES_ON_RND ;
unsigned int EEMEM EE_TimesOffRnd[8] = DEFAULT_TIMES_OFF_RND ;


// Runtime configuration
static volatile unsigned int TimesOn[8];
static volatile unsigned int TimesOff[8];
static volatile unsigned int TimesOnRnd[8];
static volatile unsigned int TimesOffRnd[8];


// Load configuration from EEPROM to RAM
void EE_Load()
{
        // Copy data from EEPROM to RAM
        eeprom_read_block(&TimesOn,     &EE_TimesOn,     sizeof(TimesOn)     );
        eeprom_read_block(&TimesOff,    &EE_TimesOff,    sizeof(TimesOff)    );
        eeprom_read_block(&TimesOnRnd,  &EE_TimesOnRnd,  sizeof(TimesOnRnd)  );
        eeprom_read_block(&TimesOffRnd, &EE_TimesOffRnd, sizeof(TimesOffRnd) );
}


// Store configuration to EEPROM from RAM
void EE_Store()
{
        // Copy data from RAM to EEPROM
        eeprom_write_block(&TimesOn,     &EE_TimesOn,     sizeof(TimesOn)     );
        eeprom_write_block(&TimesOff,    &EE_TimesOff,    sizeof(TimesOff)    );
        eeprom_write_block(&TimesOnRnd,  &EE_TimesOnRnd,  sizeof(TimesOnRnd)  );
        eeprom_write_block(&TimesOffRnd, &EE_TimesOffRnd, sizeof(TimesOffRnd) );
}


// Init Timer 1
void TimerInit()
{
        TCCR1B |= (1<<WGM12);                   // Configure timer 1 for CTC mode
        TIMSK  |= (1<<OCIE1A);                  // Enable CTC interrupt (Output Compare)
        OCR1A   = F_CPU/F_TIME_GRAIN/8; // Peridic interrupt
        TCCR1B |= (1 << CS11);                  // Start timer at Fcpu/8
}


// Global State of Realays
static volatile unsigned char RelayState[8];
static volatile unsigned int Timers[8];


// ISR - TIMER - 10ms
ISR(TIMER1_COMPA_vect)
{
        // Time granularity to 1s counter
        static volatile unsigned int Timer;

        Timer++;
        if(Timer==F_TIME_GRAIN)
        {
                // 1s
                for(unsigned char i=0; i<8; i++)
                {
                        // Decrement all timers
                        if(Timers[i])
                        {
                                Timers[i]--;
                        }
                        else
                        {
                                // Switch state
                                if(RelayState[i])
                                {
                                        Timers[i] = TimesOff[i]-1 + (rand()%(TimesOffRnd[i]+1));
                                        RelayState[i] = 0;
                                }
                                else
                                {
                                        Timers[i] = TimesOn[i]-1 + (rand()%(TimesOnRnd[i]+1));
                                        RelayState[i] = 1;
                                }
                                // Send new state to realy output
                                SetOutput(i, RelayState[i]);
                        }
                }
                // Restart granularity counter
                Timer=0;
        }

        // Inputs state
        static volatile unsigned char ButtonState[8];

        // Read all inputs
        for(unsigned char i=0; i<8; i++)
        {
                // Read buttons to shift register (each button has its own)
                ButtonState[i] = (ButtonState[i] << 1) + GetInput(i);
                if(ButtonState[i]==1)
                {
                        // Just activated - direct action!
                        /*
                        RelayState[i] = !RelayState[i];
                        SetOutput(i, RelayState[i]);
                        */
                        // Just activated - change relay and exspire timer
                        SetOutput(i, !RelayState[i]);
                        Timers[i] = 0;
                }
        }
}


// USART TX buffer
static volatile uint8_t UART_TxBuf[UART_TX0_BUFFER_SIZE];
                
#if UART_TX0_BUFFER_SIZE > 256
        static volatile uint16_t UART_TxHead;
        static volatile uint16_t UART_TxTail;
#else
        static volatile uint8_t UART_TxHead;
        static volatile uint8_t UART_TxTail;
#endif

// USART RX buffer
static volatile uint8_t UART_RxBuf[UART_RX0_BUFFER_SIZE];

#if UART_RX0_BUFFER_SIZE > 256
        static volatile uint16_t UART_RxHead;
        static volatile uint16_t UART_RxTail;
#else
        static volatile uint8_t UART_RxHead;
        static volatile uint8_t UART_RxTail;
#endif


// USART Init
void USART_Init()
{
        // Init RX and TX FIFO
        UART_TxHead = 0;
        UART_TxTail = 0;
        UART_RxHead = 0;
        UART_RxTail = 0;

        // Set baud rate
        #define MYUBRR (F_CPU/16/BAUD-1)
        UBRRH = (unsigned char)(MYUBRR>>8);
        UBRRL = (unsigned char)MYUBRR;
        #undef MYUBRR
        // Enable RX, TX and Receive Interrupt
        UCSRB = (1<<RXEN)|(1<<TXEN)|(1<<RXCIE);
        // Set frame format: 8data, 1stop bit
        UCSRC = (1<<URSEL)|(3<<UCSZ0);
        // Enable Interrupt
        // sei(); // not here (in main)
}


// ISR - USART - Receive Interrupt
ISR(USART_RXC_vect)
{
    uint16_t tmphead;
 
    // Circular Buffer Index
    tmphead = ( UART_RxHead + 1) & UART_RX0_BUFFER_MASK;
    
    if ( tmphead != UART_RxTail ) {
                // Increment head pointer
        UART_RxHead = tmphead;
        // Store received data
        UART_RxBuf[tmphead] = UDR;
    }
}


// USRAT get char (receive form FIFO)
// If no char available wait
uint8_t USART_getc(void)
{
        uint16_t tmptail;

        // wait for data
        while(UART_RxHead==UART_RxTail)
                ;//wait
/*
        if ( UART_RxHead==UART_RxTail )
        {
                return 0;   // no data
        }
*/

        // Increment Pointer
        tmptail = (UART_RxTail + 1) & UART_RX0_BUFFER_MASK;
        UART_RxTail = tmptail;

        // Get data from buffer
        return UART_RxBuf[tmptail];
}


// ISR - UART - Transmit Interrupt (on empty)
ISR(USART_UDRE_vect)
{
    uint16_t tmptail;

        if(UART_TxHead != UART_TxTail)
        {
        // Increment Tail Pointer
        tmptail = (UART_TxTail + 1) & UART_TX0_BUFFER_MASK;
        UART_TxTail = tmptail;
        // Send Byte
        UDR = UART_TxBuf[tmptail];
    } else
        {
        // TX FIFO empty, disable UDRE interrupt
        UCSRB &= ~(1<<UDRIE);
    }
}


// USART put char (send to FIFO)
// If no free room in buffer wait
void USART_putc(char data)
{
        uint16_t tmphead;

        // Increment FIFO pointer
        tmphead  = (UART_TxHead + 1) & UART_TX0_BUFFER_MASK;

        while (tmphead == UART_TxTail)
        {
                ;// wait for free space in buffer
        }

        // Put data to FIFO
        UART_TxBuf[tmphead] = data;
        UART_TxHead = tmphead;

        // Enable UDRE interrupt
        UCSRB |= (1<<UDRIE);
}


// Send string from Program Memory
void USART_TxString_P(const char * data)
{
        while(pgm_read_byte(data)!=0 )
                USART_putc(pgm_read_byte(data++));
}


// Print current config from RAM
void PrintConfig()
{
        USART_TxString_P(PSTR(
                "COMMAND   CHANNEL    TIME_ON  TIME_OFF    RND_ON    RND_OFF\n\r"
                "-----------------------------------------------------------\n\r"
                ));
        for(unsigned char i=0; i<8; i++)
        {
                printf("CONFIG          %u      %5u     %5u     %5u      %5u\n\r",
                                i, TimesOn[i], TimesOff[i], TimesOnRnd[i], TimesOffRnd[i]);
        }
}


// Print Error
void PrintError()
{
        printf("\n\r??");
}


// USART RX/TX stream
static FILE uart_io = FDEV_SETUP_STREAM(USART_putc, USART_getc, _FDEV_SETUP_RW);


// Program modes
#define MODE_MANUAL             1
#define MODE_COMMAND    0

char Mode = MODE_COMMAND;


// Main Program
int main ()
{
        // Load Config from EEPROM
        EE_Load();

        // Timer Init
        TimerInit();

        // USART Init 
        USART_Init();
        stdout = &uart_io;
    stdin  = &uart_io;

        // Enable Interrupt (timer and USART)
        sei();

        // Print Info
        USART_TxString_P(PSTR(
                "\n\r\n\rTrain Timer\n\r----------------\n\r(c) miho "YEAR" WWW.MLAB.CZ\n\r"VERSION"\n\r\n\r"
                ));
        PrintConfig();

        // Main Loop (deals with user via serial console)
        char ReceivedByte;
        for(;;)
        {
                // MODE COMMAND (wait for line and proces it)
                // ----------------------------------------------------
                if(Mode==MODE_COMMAND)
                {
                        // Line
                        char Line[LINE_LENGTH];
                        unsigned char LinePtr=0;

                        // Print help for command mode
                        USART_TxString_P(PSTR(
                                "\n\rCommand Mode\n\r\n\r"
                                "  MANUAL                                      -- Start Interactive Mode\n\r"
                                "  CONFIG Channel On Off Random_On Random_Off  -- Set Time Config\n\r"
                                "  SAVE                                        -- Save to EEPROM\n\r"
                                "  LIST                                        -- Display Curent Settings\n\r"
                                ));

                        // Get Line
                        for(;;)
                        {
                                // Prompt
                                printf("\n\r> ");
                                LinePtr=0;
                                for(;;)
                                {
                                        // Get Char
                                        ReceivedByte = USART_getc();

                                        // CR
                                        if(ReceivedByte==CR)
                                        {
                                                // Enter
                                                Line[LinePtr]=0;
                                                break;
                                        }

                                        // BS/DEL
                                        if((ReceivedByte==BS) || (ReceivedByte==DEL))
                                        {
                                                // Backspace or delete
                                                if(LinePtr>0)
                                                {
                                                        USART_putc(BS);
                                                        USART_putc(' ');
                                                        USART_putc(BS);
                                                        LinePtr--;
                                                }
                                                continue;
                                        }

                                        // Not printable ASCII
                                        if((ReceivedByte<' ') || (ReceivedByte>0x7E))
                                        {
                                                // Ignore control chars
                                                continue;
                                        }

                                        // Space at the beginning
                                        if((ReceivedByte==' ') && (LinePtr==0))
                                        {
                                                // Ignore space at the beginning
                                                continue;
                                        }

                                        // Store Char to Line Buffer
                                        if(LinePtr<sizeof(Line)-2)
                                        {
                                                // UpCase
                                                if(ReceivedByte>='a' && ReceivedByte<='z')
                                                        ReceivedByte+='A'-'a';
                                                // Echo
                                                USART_putc(ReceivedByte);
                                                // Store to Line
                                                Line[LinePtr++] = ReceivedByte;
                                        }

                                }

                                // Process Line
                                //printf(" [%s]\n\r", Line);

                                char * LineP = Line;
                                char s[sizeof(Line)];

                                // Get Command (first word)
                                if(sscanf(LineP, "%s", s)!=1)
                                        continue;

                                // Empty line or comment
                                if((s[0]=='#')||(s[0]==0))
                                {
                                        continue;
                                }

                                // Command MANUAL
                                if(strcmp(s, "MANUAL")==0)
                                {
                                        printf("\n\r");
                                        Mode=MODE_MANUAL;
                                        break;
                                }

                                // Command LIST
                                if(strcmp(s, "LIST")==0)
                                {
                                        printf("\n\r\n\r");
                                        PrintConfig();
                                        continue;
                                }

                                // Command CONFIG
                                if(strcmp(s, "CONFIG")==0)
                                {
                                        char * LineP = Line+sizeof("CONFIG");
                                        unsigned char ch;

                                        // Read channel number
                                        if(sscanf(LineP, "%hhu", &ch)!=1)
                                        {
                                                PrintError();
                                                continue;
                                        }

                                        // Test max number of channels
                                        if(ch>8)
                                        {
                                                PrintError();
                                                continue;
                                        }

                                        // Get data from RAM
                                        unsigned int a = TimesOn[ch];
                                        unsigned int b = TimesOff[ch];
                                        unsigned int c = TimesOnRnd[ch];
                                        unsigned int d = TimesOffRnd[ch];

                                        // Process input data
                                        if(sscanf(LineP, "%hhu%u%u%u%u", &ch, &a, &b, &c, &d)<2)
                                        {
                                                PrintError();
                                                continue;
                                        }

                                        //printf("New Config %u %u %u %u %u", ch, a,b,c,d);
                                        // Store new data to ram
                                        TimesOn[ch]     = a;
                                        TimesOff[ch]    = b;
                                        TimesOnRnd[ch]  = c;
                                        TimesOffRnd[ch] = d;

                                        // Clear Timer
                                        Timers[ch] = 0;
                                        continue;
                                }

                                // Command SAVE
                                if(strcmp(s, "SAVE")==0)
                                {
                                        EE_Store();
                                        continue;
                                }

                                // Unknown Command
                                PrintError();
                        }
                }

                // MODE MANUAL (Interactive Mode)
                // ----------------------------------------------------
                if(Mode==MODE_MANUAL)
                {
                        // Print help for command mode
                        USART_TxString_P(PSTR(
                                "\n\r"
                                "Interactive Mode\n\r\n\r"
                                "0..7     Reverse Channel 0..7 (and reset timer)\n\r"
                                "A..H     Set Channel 0..7 on\n\r"
                                "a..h     Set Channel 0..7 off\n\r"
                                "ESC      Return from Interactive Mode\n\r\n\r>  "
                                ));

                        // Interractive loop
                        for(;;)
                        {                                       
                                // Get CHar                                
                                ReceivedByte = USART_getc();
                        
                                // Commands A-H - Switch the relay on
                                if(ReceivedByte>='A' && ReceivedByte<='H')
                                {
                                        RelayState[ReceivedByte-'A'] = 1;
                                        SetOutput(ReceivedByte-'A', 1);
                                }

                                // Commands a-h - Switch the relay off
                                else if(ReceivedByte>='a' && ReceivedByte<='h')
                                {
                                        RelayState[ReceivedByte-'a'] = 0;
                                        SetOutput(ReceivedByte-'a', 0);
                                }

                                // Command 0-7 - Negate realay
                                else if(ReceivedByte>='0' && ReceivedByte<='7')
                                {
                                        // Just activated - set relay and exspire timer
                                        SetOutput(ReceivedByte-'0', !RelayState[ReceivedByte-'0']);
                                        Timers[ReceivedByte-'0'] = 0;
                                }

                                // ESC - return from interractive mode
                                else if(ReceivedByte==ESC)
                                {
                                        printf("\n\r");
                                        Mode=MODE_COMMAND;
                                        break;
                                }

                                // Unknown Command
                                else
                                {
                                        USART_putc(BS);
                                        USART_putc('?');
                                        continue;
                                }

                                // Display Char
                                USART_putc(BS);
                                USART_putc(ReceivedByte);
                        }
                }
        }

        return 0;
}