

/*
Sump pump and Cistern level monitor

 A0  Sump Level
 A1  Cistern Level
 A2  Thermistor

 2  Sumppump sense

 6    Servo for water pump switch
 7    Enable for water


 */
//#define DEBUG
#define VERSION "2.22"      // Version code for serial string
#define MYID 0             // ID for eventual multidrop capability


//#include <WProgram.h>
#define  DS2438
//#include <Wire.h>
#include <math.h>
#include <Servo.h>
#include <OneWire.h>
#include <x10.h>
#include <x10constants.h>
#include <avr/wdt.h>

#ifdef DS2438
#include <DS2438.h>
#endif

int SumpPin = A0;      // Input pin for Sump pressure sensor
int CisternPin = A1;   // Input pin for Cistern pressure sensor
// Include a thermister to have more analog data to look at
int ThermoPin1 = A2;    // Input pin for Thermister1

int Led0 = 13;       // select the pin for the LED
int LedRed = 8;       // select the pin for the LED
int LedYel = 9;       // select the pin for the LED


int PumpPin = 2;        // Input pin for pump cyle sensing
int PumpPin2 = 3;        // Input pin for pump cyle sensing


int timercount = 0;
int SumpValue = 0;
int CisternValue = 0;
float ThermoValue = 0;


volatile int Pumpcycle = 0;
volatile boolean PumpStatus = false;
volatile unsigned long PumpDur = 0;

String inputString = "";         // a string to hold incoming data
String parseString;
boolean stringComplete = false;  // whether the string is complete
int idx = 0;
String field[15];


// 1-wire
byte OneWire_Interval = 0;
byte analog_Interval = 0;
OneWire  ds(10);  // 1-Wire network on pin 10

// Servo motor for turning water pump on and off
int WaterServoPin = 6; // Pin for controlling servo
int WaterServoPower = 7; // Output to control power to servo
Servo waterservo;

// X-10
const int zcPin = 2;    // zero crossing pin
const int rxPin = 3;    // data receive pin
const int txPin = 4;    // data transmit pin
#define repeatTimes 3   // how many times each X10 message should repeat

void serialEvent(void);
double GetTemp(void);
int WireTemp(void);
void mytimerIsr(void);
long readVcc(void);
void WaterPump(boolean cmd);
void PumpTrigger(void);
float Thermistor(int RawADC);
void setup(void);
void loop(void);

x10 myHouse =  x10(zcPin, txPin);


const byte HOUSE[16] = {              // Lookup table for House Code
  B0110,  // A
  B1110,  // B
  B0010,  // C
  B1010,  // D
  B0001,  // E
  B1001,  // F
  B0101,  // G
  B1101,  // H
  B0111,  // I
  B1111,  // J
  B0011,  // K
  B1011,  // L
  B0000,  // M
  B1000,  // N
  B0100,  // O
  B1100,  // P
};

const byte UNIT[17] = {               // Lookup table for Unit Code
  B11111,  // Start at unit 1 by convention
  B01100,  // 1
  B11100,  // 2
  B00100,  // 3
  B10100,  // 4
  B00010,  // 5
  B10010,  // 6
  B01010,  // 7
  B11010,  // 8
  B01110,  // 9
  B11110,  // 10
  B00110,  // 11
  B10110,  // 12
  B00000,  // 13
  B10000,  // 14
  B01000,  // 15
  B11000,  // 16
};

void setup() {
  watchdogSetup();
  pinMode(Led0, OUTPUT);
  pinMode(LedRed, OUTPUT);
  pinMode(LedYel, OUTPUT);
  digitalWrite(Led0, HIGH);     // Led on
  digitalWrite(LedRed, LOW);    // Led on
  digitalWrite(LedYel, LOW);    // Led on
  pinMode(PumpPin, INPUT);
  pinMode(PumpPin2, INPUT);
  pinMode(WaterServoPower, OUTPUT);


  inputString.reserve(100);
  parseString.reserve(100);

  // initialize serial:
  Serial.begin(115200);


  waterservo.attach(WaterServoPin);
  delay(500);
  Serial.print("$uno,dbg,Hello,#");
  digitalWrite(Led0, LOW);     // Led off
  digitalWrite(LedRed, HIGH);    // Led off
  digitalWrite(LedYel, HIGH);    // Led off

  analogReference(DEFAULT);    // Probably already default...

  // Timer1.initialize(100000); // set a timer of length 100000 microseconds (or 0.1 sec - or 10Hz)
  // Timer1.attachInterrupt( timerIsr ); // attach the service routine here
  digitalWrite(LedRed, HIGH);
  digitalWrite(LedRed, LOW);


  Pumpcycle = 0;

  wdt_reset();
  attachInterrupt(0, PumpTrigger, CHANGE);//RISING);
}

void loop() {
  if (stringComplete) {
    parseString = inputString;
    inputString = "";
    stringComplete = false;
    idx = 0;
    int index0 = 0;
    while (-1 != index0) {
      index0 = parseString.indexOf(',');
      field[idx++] =  parseString.substring(0, index0);
      parseString = parseString.substring(index0 + 1, parseString.length());
    }

    field[0].toUpperCase();             // Convert destination char to uppercase
    if (field[0] != "C") {              // Compare destination to see if it is for me
      return;
    }
    if (field[1].toInt() != MYID) {      // Compare ID to see if it is for me
      return;
    }

    field[2].toUpperCase();             // Convert command to uppercase
    if (field[2] == "LED") {
      digitalWrite(LedYel, (field[3] == "0"));
      digitalWrite(LedRed, (field[4] == "0"));
    } else if (field[2] == "PUMP") {
      WaterPump(!(field[3] == "0"));
    } else if (field[2] == "X10") {
      //      parse_x10(field[3].toInt(), field[4].toInt(), field[5].toInt());
      sendX10(field[3].toInt(), field[4].toInt(), field[5].toInt(), field[6].toInt());
    } else if (field[2] == "VERSION") {
      //      Serial.print("#VERSION: ");
      Serial.print("$uno,version,");
      Serial.print(VERSION);
      Serial.println(",#");

    }
  }


  SumpValue = analogRead(SumpPin);
  CisternValue = analogRead(CisternPin);
  ThermoValue = Thermistor(analogRead(ThermoPin1));

  Serial.print("$uno,lvls,");
  Serial.print(CisternValue);
  Serial.print(",");
  Serial.print(SumpValue);
  Serial.print(",");
  if (PumpStatus) {
    Serial.print("1");
  }
  else {
    Serial.print("0");

  }
  Serial.print(",");
  Serial.print(!digitalRead(PumpPin2));
  Serial.print(",");
  Serial.print(Pumpcycle);
  Serial.print(",");
  Serial.print(PumpDur);
  Serial.print(",");
  Serial.print(ThermoValue);
  Serial.print(",#\n");


  if (3 == OneWire_Interval++) { // Once every 3 cycles
    ds.reset_search();
    delay(500);
    while (WireTemp()) {
    }
    OneWire_Interval = 0;
    if (10 == analog_Interval++) { // Once every 10 cycles
      analog_Interval = 0;
      Serial.print("$uno,alg,");
      Serial.print(readVcc());
      Serial.print(",");
      Serial.print(GetTemp(), 1);
      Serial.print(",#\n");
      
    }

  }
  wdt_reset();
  //delay(500);
}



/*
 * Inputs ADC Value from Thermistor and outputs Temperature in Celsius
 *  requires: include <math.h>
 * Utilizes the Steinhart-Hart Thermistor Equation:
 *    Temperature in Kelvin = 1 / {A + B[ln(R)] + C[ln(R)]3}
 *    where A = 0.001129148, B = 0.000234125 and C = 8.76741E-08
 *
 * These coefficients seem to work fairly universally, which is a bit of a
 * surprise.
 *
 * Schematic:
 *   [Ground] -- [10k-pad-resistor] -- | -- [thermistor] --[Vcc (5 or 3.3v)]
 *                                     |
 *                                Analog Pin 0
 *
 * In case it isn't obvious (as it wasn't to me until I thought about it), the analog ports
 * measure the voltage between 0v -> Vcc which for an Arduino is a nominal 5v, but for (say)
 * a JeeNode, is a nominal 3.3v.
 *
 * The resistance calculation uses the ratio of the two resistors, so the voltage
 * specified above is really only required for the debugging that is commented out below
 *
 * Resistance = (1024 * PadResistance/ADC) - PadResistor
 *
 * I have used this successfully with some CH Pipe Sensors (http://www.atcsemitec.co.uk/pdfdocs/ch.pdf)
 * which be obtained from http://www.rapidonline.co.uk.
 *
 */

#define ThermistorPIN 0                 // Analog Pin 0

float vcc = 5.0;                       // only used for display purposes, if used
// set to the measured Vcc.
float pad = 10000;                       // balance/pad resistor value, set this to
// the measured resistance of your pad resistor
float thermr = 10000;                   // thermistor nominal resistance



float Thermistor(int RawADC) {
  long Resistance;
  float Temp;  // Dual-Purpose variable to save space.

  Resistance = ((1024 * pad / RawADC) - pad);
  Temp = log(Resistance); // Saving the Log(resistance) so not to calculate  it 4 times later
  Temp = 1 / (0.001129148 + (0.000234125 * Temp) + (0.0000000876741 * Temp * Temp * Temp));
  Temp = Temp - 273.15;  // Convert Kelvin to Celsius

  // BEGIN- Remove these lines for the function not to display anything
  //Serial.print("ADC: ");
  //Serial.print(RawADC);
  //Serial.print("/1024");                           // Print out RAW ADC Number
  //Serial.print(", vcc: ");
  //Serial.print(vcc,2);
  //Serial.print(", pad: ");
  //Serial.print(pad/1000,3);
  //Serial.print(" Kohms, Volts: ");
  //Serial.print(((RawADC*vcc)/1024.0),3);
  //Serial.print(", Resistance: ");
  //Serial.print(Resistance);
  //Serial.print(" ohms, ");
  // END- Remove these lines for the function not to display anything

  // Uncomment this line for the function to return Fahrenheit instead.
  //temp = (Temp * 9.0)/ 5.0 + 32.0;                  // Convert to Fahrenheit
  return Temp;                                      // Return the Temperature
}


void PumpTrigger() {
  wdt_reset();
  static unsigned long PumpOn;
  //  static unsigned long PumpOff;

  PumpStatus = !digitalRead(PumpPin);
  if (PumpStatus) {
    PumpOn = millis();
    Pumpcycle++;
  }
  else if (!PumpStatus) {
    PumpDur = millis() - PumpOn;
    PumpOn = 0;
  }
  wdt_reset();
}


void WaterPump(boolean cmd) {
  wdt_reset();
  int pos = 0;
  Serial.print("$uno,status,Turning water pump o");
  if (cmd == false) {
    Serial.print("ff.,#\n");
    pos = 0;  // Final position of servo motor
  }
  else if (cmd == true) {
    Serial.print("n.,#\n");
    pos = 150; // Final position of servo motor
  }

  waterservo.write(pos);  // Send PWM to servo
  digitalWrite(WaterServoPower, HIGH);// Enable Servo motor
  delay(1000);
  digitalWrite(WaterServoPower, LOW);// Disable Servo motor
  Serial.print("$uno,status,Water pump on/off.,#\n");
  wdt_reset();
}


long readVcc(void) {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both

  long result = (high << 8) | low;

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}


void mytimerIsr()
{
  timercount++;
  // Toggle LED
  if ((timercount % 10) == 1) {
    digitalWrite(Led0, digitalRead(Led0) ^ 1 );
    //    digitalWrite(LedRed, digitalRead(Led0));
  }
  /*  if((timercount%4)==1){
   digitalWrite(LedYel, digitalRead(LedYel) ^ 1 );
   }
   */
  timercount++;
}


int WireTemp(void) {
  byte i;
  static byte r = 0;
  byte present = 0;
  byte therm;
  byte humid;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;

  wdt_reset();
  if ( !ds.search(addr)) {
    //    Serial.println("1-Wire End.");
    ds.reset_search();
    r = 0;
    return 0;
  }
  wdt_reset();
  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      type_s = 1;
      therm = 1;
      humid = 0;
      break;
    case 0x28:
      type_s = 0;
      therm = 1;
      humid = 0;
      break;
    case 0x22:
      type_s = 0;
      therm = 1;
      humid = 0;
      break;
    case 0x26:
      therm = 0;
      humid = 1;
      break;
    default:
      return 0;
  }
  digitalWrite(LedYel, LOW);    // Led on

  if (humid) {
#ifdef DS2438
    ds2438 hum1(&ds, addr);
    if (hum1.readSetup() != 0x00) {
      hum1.writeSetup(0x00);
    }
#endif
    Serial.print("$uno,1wire,");
    Serial.print(r++);
    Serial.print(",");
    for ( i = 0; i < 8; i++) {
      Serial.print(addr[i], HEX);//  print ROM
    }
    Serial.print("0");    // Fictional ROM code for temperature
    if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.print("#Error:CRC is not valid\n");
      return (0);
    }
#ifdef DS2438
    Serial.print(",");
    Serial.print(hum1.readTempC());
    Serial.println(",#");
    Serial.print("$uno,1wire,");
    Serial.print(r++);
    Serial.print(",");
    for ( i = 0; i < 8; i++) {
      Serial.print(addr[i], HEX);//  print ROM
    }
    Serial.print("1");    // Fictional ROM code for humidity
    Serial.print(",");
    Serial.print(hum1.readHum());
#endif
    Serial.println(",#");

  }
  wdt_reset();
  if (therm) {
    ds.reset();
    ds.select(addr);
    ds.write(0x44, 1);        // start conversion, with parasite power on at the end

    delay(1000);     // maybe 750ms is enough, maybe not
    // we might do a ds.depower() here, but the reset will take care of it.

    present = ds.reset();
    ds.select(addr);
    ds.write(0xBE);         // Read Scratchpad

    for ( i = 0; i < 9; i++) {           // we need 9 bytes
      data[i] = ds.read();
    }

    // Convert the data to actual temperature
    // because the result is a 16 bit signed integer, it should
    // be stored to an "int16_t" type, which is always 16 bits
    // even when compiled on a 32 bit processor.
    int16_t raw = (data[1] << 8) | data[0];
    if (type_s) {
      raw = raw << 3; // 9 bit resolution default
      if (data[7] == 0x10) {
        // "count remain" gives full 12 bit resolution
        raw = (raw & 0xFFF0) + 12 - data[6];
      }
    }
    else {
      byte cfg = (data[4] & 0x60);
      // at lower res, the low bits are undefined, so let's zero them
      if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
      else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
      else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
      //// default is 12 bit resolution, 750 ms conversion time
    }
    celsius = (float)raw / 16.0;
    Serial.print("$uno,1wire,");
    Serial.print(r++);
    Serial.print(",");
    for ( i = 0; i < 8; i++) {
      Serial.print(addr[i], HEX);//  print ROM
    }

    if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.print("#Error:CRC is not valid\n");
      return (0);
    }
    Serial.print(",");
    Serial.print(celsius);
    Serial.print(",#\n");
    return 1;
  }
  digitalWrite(LedYel, HIGH);    // Led off
  wdt_reset();
}


double GetTemp(void)
{
  unsigned int wADC;
  double t;
  // Set the internal reference and mux.
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  // enable the ADC
  delay(20);            // wait for voltages to become stable.
  ADCSRA |= _BV(ADSC);  // Start the ADC
  // Detect end-of-conversion
  while (bit_is_set(ADCSRA, ADSC));
  // Reading register "ADCW" takes care of how to read ADCL and ADCH.
  wADC = ADCW;
  // The offset of 324.31 could be wrong. It is just an indication.
  t = (wADC - 324.31 ) / 1.22;
  // The returned temperature is in degrees Celcius.
  return (t);
}


void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '#') {
      // TODO: Send Xoff command
      stringComplete = true;
    }
    if (inChar == '$') {
      inputString = "";
    }
  }
}




void watchdogSetup(void)
{
  cli(); // disable all interrupts
  wdt_reset(); // reset the WDT timer
  /*
   WDTCSR conﬁguration:
   WDIE = 1: Interrupt Enable
   WDE = 1 :Reset Enable
   WDP3 = 0 :For 2000ms Time-out
   WDP2 = 1 :For 2000ms Time-out
   WDP1 = 1 :For 2000ms Time-out
   WDP0 = 1 :For 2000ms Time-out
  */
  // Enter Watchdog Conﬁguration mode:
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  // Set Watchdog settings:
  //  WDTCSR = (1 << WDIE) | (1 << WDE) | (0 << WDP3) | (1 << WDP2) | (1 << WDP1) | (1 << WDP0);    // 2000ms timeout
  WDTCSR = (1 << WDIE) | (1 << WDE) | (1 << WDP3) | (0 << WDP2) | (0 << WDP1) | (1 << WDP0);    // 8000ms timeout
  sei();
}

void sendX10(byte hse, byte unit, int msg, int repeat) {
  if (repeat < 1 || repeat > 30) {
    repeat = repeatTimes;
  }
  if (pulseIn(zcPin, HIGH, 20000) < 4000) {
    Serial.println("$uno,dbg,No ZC found.");
    wdt_reset();
    delay(2000);
    wdt_reset();
    return;
  }
  if ( msg == -1) {
    myHouse.write(HOUSE[hse], unit, repeat);
    Serial.print("@");

  } else {
    if (msg == 1) {
      msg = ON;
      myHouse.write(HOUSE[hse], UNIT[unit], repeat);
      myHouse.write(HOUSE[hse], msg, repeat);
    } else if (msg == 0) {
      msg = OFF;
      myHouse.write(HOUSE[hse], UNIT[unit], repeat);
      myHouse.write(HOUSE[hse], msg, repeat);
    } else if (msg == 2) {
      myHouse.write(HOUSE[hse], DIM, repeat);
    } else if (msg == 3) {
      myHouse.write(HOUSE[hse], BRIGHT, repeat);
    } else {
      // Unknown
    }
  }
  wdt_reset();
}

#ifdef DEBUG
ISR(WDT_vect) // Watchdog timer interrupt.
{
  Serial.print("$uno,dbg,WDT timeout,#");
  //  delay(1000);
  Serial.print("resetting...\n\n");

}
#endif


// $c,0,x10,1,1,1,#
