//Modbus relay card firmware Revision 0.1
//This firmware is designed for an AtMega328 running at 8MHz.
//The fuse bits should be set to 0xFF 0xD5 0xE2.

#include "Modbus_Relay_Board_V_2.h"

void do_read_coils(Mb_rx_struct *rx_struct, Mb_tx_struct *tx_struct)
{
    uint16_t start;
    uint16_t end;
    uint8_t bit_position = 0;
    uint8_t relay_byte = 0;
    uint8_t relay_status;
    uint16_t crc;
    
    //Get first coil.
    start = rx_struct->mb_address + 1;
    
    //Get last coil.
    end = rx_struct->mb_address + rx_struct->mb_values;
    
    //Address range must be between 1 and 4.
    if(start < 1 || end > 4)
    {
        tx_struct->mb_tx_exception = ILLEGAL_DATA_ADDRESS;
        return;
    }
    
    //Read relay status from port.
    relay_status = PINC & 0x0F;
    
    //Get coil values.
    for(int i = start; i <= end; i++)
    {
        switch(i)
        {
            case 1:
                if(relay_status & 0x01)
                    relay_byte |= (1 << bit_position);
            break;
            
            case 2:
                if(relay_status & 0x02)
                    relay_byte |= (1 << bit_position);
            break;
            
            case 3:
                if(relay_status & 0x04)
                    relay_byte |= (1 << bit_position);
            break;
            
            case 4:
                if(relay_status & 0x08)
                    relay_byte |= (1 << bit_position);
            break;
            
            default:
            break;  
        }
        bit_position++;
    }
    
    tx_struct->mb_tx_frame[tx_struct->mb_tx_index++] = mb_this_address; //Set slave address in response.
    tx_struct->mb_tx_frame[tx_struct->mb_tx_index++] = MB_READ_COILS;   //Set request type in response.
    tx_struct->mb_tx_frame[tx_struct->mb_tx_index++] = 0x01;            //1 data byte in response.
    tx_struct->mb_tx_frame[tx_struct->mb_tx_index++] = relay_byte;      //Coil status byte.
    
    //Get CRC.
    crc = mb_crc_16(tx_struct->mb_tx_frame, tx_struct->mb_tx_index);
    
    tx_struct->mb_tx_frame[tx_struct->mb_tx_index++] = crc;
    tx_struct->mb_tx_frame[tx_struct->mb_tx_index++] = crc >> 8;
}

void do_write_single_coil(Mb_rx_struct *rx_struct, Mb_tx_struct *tx_struct)
{
    if(rx_struct->mb_address < 1 || rx_struct->mb_address > 4)                      //Address must be between 1 and 4 inclusive.
    {
        tx_struct->mb_tx_exception = ILLEGAL_DATA_ADDRESS;
        return;
    }
    
    if(this_hand_status)                                                            //Mode must be in auto mode.
    {
        tx_struct->mb_tx_exception = SERVER_DEVICE_FAILURE;
        return;
    }
    
    //Set relay value.
    switch(rx_struct->mb_address)
    {
        case 1:
            if(rx_struct->mb_values)
            {
                prtRLY1 |= (1 << RLY1);                                             //Turn on relay 1.
            }
            else
            {
                prtRLY1 &= ~(1 << RLY1);                                            //Turn off relay 1.
            }
        break;
        
        case 2:
            if(rx_struct->mb_values)
            {
                prtRLY2 |= (1 << RLY2);                                             //Turn on relay 2.
            }
            else
            {
                prtRLY2 &= ~(1 << RLY2);                                            //Turn off relay 2.
            }
        break;
        
        case 3:
            if(rx_struct->mb_values)
            {
                prtRLY3 |= (1 << RLY3);                                             //Turn on relay 3.
            }
            else
            {
                prtRLY3 &= ~(1 << RLY3);                                            //Turn off relay 3.
            }
        break;
        
        case 4:
            if(rx_struct->mb_values)
            {
                prtRLY4 |= (1 << RLY4);                                             //Turn on relay 4.
            }
            else
            {
                prtRLY4 &= ~(1 << RLY4);                                            //Turn off relay 4.
            }
        break;
        
        default:
        break;
    }
        
    for(int i = 0; i < mb_rx_struct->mb_rx_index; i++)                              //Echo frame back to sender.
    {
        mb_tx_struct->mb_tx_frame[mb_tx_struct->mb_tx_index++] =
        mb_rx_struct->mb_rx_frame[i];
    }
}

int main(void)
{   
    //The following lines are used to initialize
    //the EEPROM On the very first power up.
    //EEPROM_write_word((uint16_t *)MODBUS_ADDRESS, 1);
    //EEPROM_write_word((uint16_t *)MODBUS_BAUD_RATE, 1);
    //EEPROM_write_word((uint16_t *)MODBUS_PARITY, 1);
    //EEPROM_write_word((uint16_t *)MODBUS_MODE, 1);
    
    init_micro();
    EEPROM_read_all();
    enable_rx();
    check_and_set_mode();
    
    //Initialize modbus library.
    mb_init(modbus_callback_functions, &TX_byte, modbus_mode - 1, modbus_address);
    
    while(1){}
        
    //Should never get here, but will clean up modbus memory if it does.
    mb_close();
}

//Interrupt based USART RX function.
ISR(USART_RX_vect)
{
    uint8_t rx_byte;
    
    rx_timer = 20;  //Turn on RX LED for 20 ms.
    
    rx_byte = UDR0; //Get RX byte.
    
    if(!last_mode_status)       //Check if in Modbus mode.
    {
        TCNT2 = 0x00;           //Reset timer value.
        OCR2A  = t1_5char;      //Set timer value to look for next character.
        TCCR2B = 0x06;          //Start character timer (clock / 256).
        mb_rx_byte(rx_byte, UCSR0A & (1 << UPE0));  //Send byte to modbus rx function.
    }
    
    else if(last_mode_status)   //Check if in configuration mode.
    {
        for(volatile int i = 0; i < 20; i++)
        {
            //Wait for transmitter to finish.
        }
        TX_byte(rx_byte);       //Echo byte.
        terminal_tok(rx_byte);  //Send byte to terminal tokenizer.
    }
}

//16-bit timer used for 1 ms system timing.
ISR(TIMER1_COMPA_vect)
{
    static uint16_t ms_timer;
    
    //Get current status of hand switch and config header.
    update_modes();
    
    //Update RX/TX LEDs.
    update_RXTX_LEDs();
    
    //Update the hand and config LEDs.
    update_Mode_LEDs(ms_timer);
    
    //Update relay states if hand mode active.
    update_hand_mode_relays();
    
    //Decrement debug counter if active.
    if(debug_counter)
        debug_counter--;
    
    //Increment timer.
    ms_timer++;
    
    //Reset timer value every second.
    if(ms_timer >= 1000)
        ms_timer = 0;
}

//8-bit timer used for character timing.
ISR(TIMER2_COMPA_vect)
{
    static uint8_t rx_timer_state = MB_RX_IDLE;
    uint8_t error;
    
    //First state indicates the frame has ended.
    if(rx_timer_state == MB_RX_IDLE)
    {
        rx_timer_state = MB_RX_READY_TO_TX;
        OCR2A  = t3_5char;  
    }
    
    //Second state indicates the device is ready to transmit a response.
    else if(rx_timer_state == MB_RX_READY_TO_TX)
    {       
        TCCR2B = 0x00;              //Stop timer.
        
        error = mb_rtu_rx_parse();  //Parse frame and get error code.
        
        if(error)
        {
            //Do error stuff.
        }
        
        rx_timer_state = MB_RX_IDLE;    //Reset control variables for next rx frame.
    }   
}