/*
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
*/

#include <SandS.h>
#include <Wire.h>
#include <EEPROM.h>
#include <SoftwareSerial.h>

#define SerialPort Serial1
#define PIN_HDPLX  6
#define PIN_SHDN  12
#define PIN_RS485  9
#define PIN_FAST   8
#define PIN_CTS    7 // BAD on Rev 001
#define PIN_RTS    4

enum {
  RS232    = 0x00,
  RS485_FD = 0x02,
  RS485_HD = 0x03,
};

byte    buffer[1024];
byte   *pwr_buffer = buffer;
byte   *prd_buffer = buffer;
word    buffer_count = 0;
boolean enable_rts = false;

// Valid device commands are 0x01 ... 0x7F
typedef enum {
  CMD_BEGIN      = 0x01,
  CMD_TX         = 0x02,
  CMD_RTS        = 0x03,
} relay_command_e;

// Command handler
void command(byte cmd, int params) {
  word baudrate;
  byte fmt;
  switch(cmd) {
    case CMD_BEGIN:
      if(params != 5) break;
      baudrate = SandS.read() << 8;
      baudrate |= SandS.read();
      switch(SandS.read()) {
        case 0x00: fmt = SERIAL_5N1; break;
        case 0x02: fmt = SERIAL_6N1; break;
        case 0x04: fmt = SERIAL_7N1; break;
        case 0x08: fmt = SERIAL_5N2; break;
        case 0x0A: fmt = SERIAL_6N2; break;
        case 0x0C: fmt = SERIAL_7N2; break;
        case 0x0E: fmt = SERIAL_8N2; break;
        case 0x20: fmt = SERIAL_5E1; break;
        case 0x22: fmt = SERIAL_6E1; break;
        case 0x24: fmt = SERIAL_7E1; break;
        case 0x26: fmt = SERIAL_8E1; break;
        case 0x28: fmt = SERIAL_5E2; break;
        case 0x2A: fmt = SERIAL_6E2; break;
        case 0x2C: fmt = SERIAL_7E2; break;
        case 0x2E: fmt = SERIAL_8E2; break;
        case 0x30: fmt = SERIAL_5O1; break;
        case 0x32: fmt = SERIAL_6O1; break;
        case 0x34: fmt = SERIAL_7O1; break;
        case 0x36: fmt = SERIAL_8O1; break;
        case 0x38: fmt = SERIAL_5O2; break;
        case 0x3A: fmt = SERIAL_6O2; break;
        case 0x3C: fmt = SERIAL_7O2; break;
        case 0x3E: fmt = SERIAL_8O2; break;
        default:   fmt = SERIAL_8N1;
      }        
      SerialPort.begin(baudrate, fmt);
      pinMode(PIN_SHDN,  OUTPUT);
      pinMode(PIN_FAST,  OUTPUT);
      pinMode(PIN_RS485, OUTPUT);
      pinMode(PIN_HDPLX, OUTPUT);
      pinMode(PIN_RTS,   OUTPUT);
      pinMode(PIN_CTS,   INPUT );
      enable_rts = false;
      switch(SandS.read()) {
        case RS485_FD:
          digitalWrite(PIN_RS485, HIGH);
          digitalWrite(PIN_HDPLX, LOW );
          digitalWrite(PIN_RTS,   HIGH);
          break;
        case RS485_HD:
          digitalWrite(PIN_RS485, HIGH);
          digitalWrite(PIN_HDPLX, HIGH);
          digitalWrite(PIN_RTS,   LOW );
          break;
        default:
          digitalWrite(PIN_RS485, LOW );
          digitalWrite(PIN_HDPLX, LOW );
          enable_rts = true;
      }
      digitalWrite(PIN_SHDN, HIGH);
      digitalWrite(PIN_FAST, !SandS.read());
      break;
    case CMD_TX:
      WireSEI(); // Enable nested interrupts (because serial write may require interrupts)
      while(params--) SerialPort.write(SandS.read());
      WireCLI(); // Disable nested interrupts
      break;
    case CMD_RTS:
      if(params != 1 || !enable_rts) break;
      digitalWrite(PIN_RTS, SandS.read() != 0);
      break;
  }
}

void request(byte maxbytes) {
  maxbytes--;
  if(maxbytes > 0x7F) maxbytes = 0x7F;
  if(maxbytes > buffer_count) maxbytes = buffer_count;
  buffer_count -= maxbytes;
  if(digitalRead(PIN_CTS)) maxbytes |= 0x80;
  Wire.write(maxbytes);
  while(maxbytes--) {
    Wire.write(*prd_buffer++);
    if(prd_buffer >= &buffer[sizeof(buffer)]) prd_buffer = buffer;
  }
}

extern uint8_t address;

boolean save_address();

void setup(void) {
  // Identify us as a relay-board v1.0.0
  SandS.begin(DEVTYPE_SHIELD, '1', '0', '0');
  // Set "command" as our command handler
  SandS.onCommand(command);
  // Set custom request handler
  SandS.onRequest(request);
}

void loop() {
  while(SerialPort.available() && buffer_count < sizeof(buffer)) {
    *pwr_buffer++ = SerialPort.read();
    cli();
    buffer_count++;
    sei();
    if(pwr_buffer >= &buffer[sizeof(buffer)]) pwr_buffer = buffer;
  }
}
