/*
  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 <TimerOne.h> // From Arduino playground
#include <EEPROM.h>   // Needed by SandS
#include <Wire.h>     // Needed by SandS
#include <SandS.h>

// TODO: figure out how to do calibration

#define PIN_INPUT 0

// Device configuration
typedef struct {
  byte range; // current sensor range
  word alarm_comm;  // alarm if no comm for n milliseconds
  byte alarm_mode;  // 0: no alarm, 1: alarm if level below, 2: alarm if level equal or above
  word alarm_level; // alarm level
  word alarm_delay; // alarm only if condition met for n milliseconds
} config_t;

#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,
  RS485_FD,
  RS485_HD
};

// Valid device commands are 0x01 ... 0x7F
typedef enum {
  CMD_READ       = 0x01,
  CMD_CONFIG_SET = 0x20,
  CMD_CONFIG_GET = 0x21,
} consumption_command_e;

// Device default configuration
static config_t config = {
  10, // 10 amperes
  0, // No comm alarm
  0, // Alarm level
  0, // No level alarm
  0, // Immediate alarm
};

// Current readout
static word readout;

// Alarm
static word last_time;
static word comm_timeout = 0;
static word alarm_timeout = 0;

// Command handler
void command(byte cmd, int params) {
  config_t set;
  boolean accept = true;
  switch(cmd) {
    case CMD_READ:
      // Return current readout
      SandS.write(highByte(readout));
      SandS.write(lowByte(readout));      
      break;
    case CMD_CONFIG_SET:
      // Wrong parameter count? Abort
      if(params != 8) break;
      // Read configuration
      set.range = SandS.read();
      set.alarm_mode   = SandS.read();
      set.alarm_comm   = SandS.read() << 8;
      set.alarm_comm  |= SandS.read();
      set.alarm_level  = SandS.read() << 8;
      set.alarm_level |= SandS.read();
      set.alarm_delay  = SandS.read() << 8;
      set.alarm_delay |= SandS.read();
      // Verify validity of configuration
      if(set.range != 0 && set.range <= 50) {
        if(set.alarm_mode < 3) {
          // Store configuration
          config = set;
          SandS.saveData(&config, sizeof(config));
        }
      }
      break;
    case CMD_CONFIG_GET:
      // Wrong parameter count? Abort
      if(params != 0) break;
      // Return configuration
      SandS.write(config.range);
      SandS.write(config.alarm_mode);
      SandS.write(highByte(config.alarm_comm));
      SandS.write(lowByte(config.alarm_comm));
      SandS.write(highByte(config.alarm_level));
      SandS.write(lowByte(config.alarm_level));
      SandS.write(highByte(config.alarm_delay));
      SandS.write(lowByte(config.alarm_delay));
      break;
    default:
      accept = false;
  }
  if(accept) comm_timeout = 0;
}

void setup(void) {
  // Identify us as a consumption-board v1.0.0
  SandS.begin(DEVTYPE_CONSUMPTION, '1', '0', '0');
  // Set "command" as our command handler
  SandS.onCommand(command);
  // Load configuration (if it exists)
  SandS.loadData(&config, sizeof(config));
  // Set up the ADC
  pinMode(PIN_INPUT, INPUT);
  analogReference(INTERNAL);
  last_time = millis();
}

void loop() {
  word value;
  // Read and convert the value to milliamperes
  value = analogRead(PIN_INPUT);
  value = ((double)value * 1.07421875) * (double)config.range;
  // Copy the value
  cli();
  readout = value;
  sei();
  // Limit nof readouts per second slightly
  delay(10);

  // Alarm timing and handling
  word time = millis();
  boolean alarm = false;
  if(config.alarm_mode) {
    alarm = (readout >= config.alarm_level) != (config.alarm_mode == 1);
    if(alarm) {
      alarm_timeout += time - last_time;
      if(alarm_timeout <= config.alarm_delay) alarm = false;
    } else {
      alarm_timeout = 0;
    }
  } else {
    alarm_timeout = 0;
  }
  if(config.alarm_comm) {
    comm_timeout += time - last_time;
    if(comm_timeout > config.alarm_comm) alarm = true;
  } else {
    comm_timeout = 0;
  }
  last_time = time;
  SandS.setAlarm(alarm);
}

