Author Topic: Arduino Powerjack companion.  (Read 576 times)

0 Members and 1 Guest are viewing this topic.

Offline welshman

  • Full Member
  • ***
  • Posts: 90
  • Karma: +0/-0
Arduino Powerjack companion.
« on: February 13, 2017, 07:44:18 AM »
 PowerJack Inverter Generator control.

How to tell when the powerjack has stopped charging the batteries and no longer passing any current to them. Thus giving a trigger of when to stop a generator.

The sketch monitors voltage and current of DC side to detect when inverter is providing no current to the battery bank. When the low voltage trigger level has been reached if the generator is not in off time set by start and end hours the relay will be triggered for the duration there is current travelling into the batteries and if the target volatage has been reached.

Uses an ADS1115 to get a differential reading of current across a shunt, allowing for current travelling in either direction. Uses analog input pin and a external voltage divider to get voltage values. 120k to 10k divider gives ratio of 13 and a range of 0-65v. Uses a DS3132 rtc for time keeping. Incorporates the DS3132 code due to changes, requires responsiveAnalogRead library for smooth voltage measurement.

PINS
A0 - Voltage divider
SDA - To ADS1115 ADC and DS3132 RTC
SCL - To ADS1115 ADC and DS3132 RTC
7 - Relay to operate generator.

Outputs DATE,TIME,ADC RAW VALUE,VOLTAGE,AMPS,WATTS,OFFTIME,RELAY STATE on serial line.

Code: [Select]
// PowerJack Inverter Generator control.
// monitors voltage and current of DC side to detect when inverter is providing no current to the battery bank.
// When the low voltage trigger level has been reached if the generator is not in off time set by start and end hours
// the relay will be triggered for the duration there is current travelling into the batteries. Once the target volatage
// has been reached.
// Uses a ADS1115 to get a differential reading of current across CT or shunt, allowing for current travelling in either
// direction. Uses analog input pin and a external voltage divider to get voltage values. 120k to 10k divider gives ratio
// of 13 and a range of 0-65v. Uses a DS3132 rtc for time keeping.
// PINS
// A0 - Voltage divider
// SDA - To ADS1115 ADC and DS3132 RTC
// SCL - To ADS1115 ADC and DS3132 RTC
// 7 - Relay to operate generator.
// Incorporates the DS3132 code due to changes.
// Outputs DATE,TIME,ADC RAW VALUE,VOLTAGE,AMPS,WATTS,OFFTIME,RELAY STATE on serial line.


#include <Wire.h> // wire library for comms to ds3132 and ads1115
#include <Adafruit_ADS1015.h>
#include <SoftwareSerial.h>
#include <ResponsiveAnalogRead.h>
#ifndef _RTCLIB_H_
#define _RTCLIB_H_
#include <Arduino.h>
class TimeSpan;
///DS3231 BEGIN
#ifdef __AVR__
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#elif defined(ARDUINO_ARCH_SAMD)
// nothing special needed
#elif defined(ARDUINO_SAM_DUE)
#define PROGMEM
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#define Wire Wire1
#endif
#if (ARDUINO >= 100)
#include <Arduino.h> // capital A so it is error prone on case-sensitive filesystems
// Macro to deal with the difference in I2C write functions from old and new Arduino versions.
#define _I2C_WRITE write
#define _I2C_READ  read
#else
#include <WProgram.h>
#define _I2C_WRITE send
#define _I2C_READ  receive
#endif
#define DS3231_ADDRESS  0x68
#define DS3231_CONTROL  0x0E
#define DS3231_STATUSREG 0x0F
#define SECONDS_PER_DAY 86400L
#define SECONDS_FROM_1970_TO_2000 946684800

// Simple general-purpose date/time class (no TZ / DST / leap second handling!)
class DateTime {
  public:
    DateTime (uint32_t t = 0);
    DateTime (uint16_t year, uint8_t month, uint8_t day,
              uint8_t hour = 0, uint8_t min = 0, uint8_t sec = 0);
    DateTime (const DateTime& copy);
    DateTime (const char* date, const char* time);
    DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time);
    uint16_t year() const       {
      return 2000 + yOff;
    }
    uint8_t month() const       {
      return m;
    }
    uint8_t day() const         {
      return d;
    }
    uint8_t hour() const        {
      return hh;
    }
    uint8_t minute() const      {
      return mm;
    }
    uint8_t second() const      {
      return ss;
    }
    uint8_t dayOfTheWeek() const;

    // 32-bit times as seconds since 1/1/2000
    long secondstime() const;
    // 32-bit times as seconds since 1/1/1970
    uint32_t unixtime(void) const;

    DateTime operator+(const TimeSpan& span);
    DateTime operator-(const TimeSpan& span);
    TimeSpan operator-(const DateTime& right);

  protected:
    uint8_t yOff, m, d, hh, mm, ss;
};

// Timespan which can represent changes in time with seconds accuracy.
class TimeSpan {
  public:
    TimeSpan (int32_t seconds = 0);
    TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds);
    TimeSpan (const TimeSpan& copy);
    int16_t days() const         {
      return _seconds / 86400L;
    }
    int8_t  hours() const        {
      return _seconds / 3600 % 24;
    }
    int8_t  minutes() const      {
      return _seconds / 60 % 60;
    }
    int8_t  seconds() const      {
      return _seconds % 60;
    }
    int32_t totalseconds() const {
      return _seconds;
    }

    TimeSpan operator+(const TimeSpan& right);
    TimeSpan operator-(const TimeSpan& right);

  protected:
    int32_t _seconds;
};

// RTC based on the DS3231 chip connected via I2C and the Wire library
enum Ds3231SqwPinMode { DS3231_OFF = 0x01, DS3231_SquareWave1Hz = 0x00, DS3231_SquareWave1kHz = 0x08, DS3231_SquareWave4kHz = 0x10, DS3231_SquareWave8kHz = 0x18 };

class RTC_DS3231 {
  public:
    boolean begin(void);
    static void adjust(const DateTime& dt);
    bool lostPower(void);
    static DateTime now();
    static Ds3231SqwPinMode readSqwPinMode();
    static void writeSqwPinMode(Ds3231SqwPinMode mode);
};

// RTC using the internal millis() clock, has to be initialized before use
// NOTE: this clock won't be correct once the millis() timer rolls over (>49d?)
class RTC_Millis {
  public:
    static void begin(const DateTime& dt) {
      adjust(dt);
    }
    static void adjust(const DateTime& dt);
    static DateTime now();

  protected:
    static long offset;
};

#endif // _RTCLIB_H_

RTC_DS3231 rtc;

#define VOLTIN           A0 // Voltage input pin
#define OUTPIN           7 // Output pin to control

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Adafruit_ADS1115 ads;  // ADS1115 Connected to SDA / SCL


/////////////////////////////////////////////////////////////////////////
//USER SETTINGS BEGIN

float targetvolt = 60; // When this target voltage is reached switch off relay only if current is 0
float lowvolt = 48; // Voltage to trigger on relay.
float multiplier = 2; // 2 - convert reading to 75mv / 300amp shunt. 1 is 100mv/100amp - 1.33 is 75mv/100amp
float offset = 1.041916167664671 ; // correction factor for analog voltage input.

int starthouroff = 21; // relay not on after 9 pm
int stophouroff = 8 ; // relay not on before 8 am


//USER SETTINGS END
//////////////////////////////////////////////////////////////////////////////


//prescaler bits
const unsigned char PS_16 = (1 << ADPS2);
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

ResponsiveAnalogRead analog(VOLTIN, true); // setup filtering

float amps;
int16_t adcresult;
float voltage;
int sensorValue;
bool offtime = false ;

void setup(void)
{
  ADCSRA &= ~PS_128;  // remove bits set by Arduino library
  ADCSRA |= PS_16;    // set our own prescaler to 16 -  1mhz sampling

  pinMode(OUTPIN, OUTPUT);
  digitalWrite(OUTPIN, LOW); //set to off on startup

  ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV
  ads.begin();
  rtc.begin();
  Serial.begin(115200);

}

void loop(void)
{

  DateTime now = rtc.now(); //get the current date and time

  adcresult = ads.readADC_Differential_2_3();
  amps = ((float)adcresult * 256) / 32768; // 100mv / 100amp shunt
  amps = amps * multiplier; // convert reading to 75mv / 300amp shunt.

  analog.update();
  voltage = analog.getValue() * (5.0 / 1023.0); // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
  voltage = voltage * 13 ; //multiply by 13 due to divider resistors input 0-65v 120k/10k-r1/r2
  voltage = voltage * offset ;

  if (now.hour() >= starthouroff ) {
    offtime = true;
  }
  else {
    if (now.hour() < stophouroff) {
      offtime = true;
    }
    else {
      offtime = false;
    }
  }

  Serial.print(now.day(), DEC);
  Serial.print('/');
  Serial.print(now.month(), DEC);
  Serial.print('/');
  Serial.print(now.year(), DEC);
  Serial.print(' ');
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
  Serial.print(' ');

  Serial.print(adcresult);
  Serial.print(",");
  Serial.print(voltage);
  Serial.print(",");
  Serial.print(amps);
  Serial.print(",");
  Serial.print(voltage * amps);
  Serial.print(",");
  Serial.print(offtime);
  Serial.print(",");
  Serial.println(digitalRead(OUTPIN));

  if (voltage > targetvolt) {
    if (adcresult  == 0) {
      digitalWrite(OUTPIN, LOW); ///switch off relay because charger has stopped charging.
    }
  }

  if (voltage < lowvolt) { // low voltage trigger switch on relay if offtime is not true.
    if (offtime == false) {
      digitalWrite(OUTPIN, HIGH);
    }
  }

  if ( offtime == true) { // if offtime is true then just switch off regardless.
    digitalWrite(OUTPIN, LOW);
  }



  //  if ( Serial.available() )
  //  {
  //    char c = toupper(Serial.read());
  //    if ( c == 'A' ) {
  //
  //      //what to do if A is input via serial
  //
  //    } else if ( c == 'B' ) {
  //
  //      //what to do if B is input via serial
  //
  //    }
  //
  //  }

  //delay(1000);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////
//LOOP END


static uint8_t read_i2c_register(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire.endTransmission();

  Wire.requestFrom(addr, (byte)1);
  return Wire._I2C_READ();
}

static void write_i2c_register(uint8_t addr, uint8_t reg, uint8_t val) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire._I2C_WRITE((byte)val);
  Wire.endTransmission();
}


////////////////////////////////////////////////////////////////////////////////
// utility code, some of this could be exposed in the DateTime API if needed

const uint8_t daysInMonth [] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// number of days since 2000/01/01, valid for 2001..2099
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
  if (y >= 2000)
    y -= 2000;
  uint16_t days = d;
  for (uint8_t i = 1; i < m; ++i)
    days += pgm_read_byte(daysInMonth + i - 1);
  if (m > 2 && y % 4 == 0)
    ++days;
  return days + 365 * y + (y + 3) / 4 - 1;
}

static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
  return ((days * 24L + h) * 60 + m) * 60 + s;
}

////////////////////////////////////////////////////////////////////////////////
// DateTime implementation - ignores time zones and DST changes
// NOTE: also ignores leap seconds

DateTime::DateTime (uint32_t t) {
  t -= SECONDS_FROM_1970_TO_2000;    // bring to 2000 timestamp from 1970

  ss = t % 60;
  t /= 60;
  mm = t % 60;
  t /= 60;
  hh = t % 24;
  uint16_t days = t / 24;
  uint8_t leap;
  for (yOff = 0; ; ++yOff) {
    leap = yOff % 4 == 0;
    if (days < 365 + leap)
      break;
    days -= 365 + leap;
  }
  for (m = 1; ; ++m) {
    uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
    if (leap && m == 2)
      ++daysPerMonth;
    if (days < daysPerMonth)
      break;
    days -= daysPerMonth;
  }
  d = days + 1;
}

DateTime::DateTime (uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) {
  if (year >= 2000)
    year -= 2000;
  yOff = year;
  m = month;
  d = day;
  hh = hour;
  mm = min;
  ss = sec;
}

DateTime::DateTime (const DateTime& copy):
  yOff(copy.yOff),
  m(copy.m),
  d(copy.d),
  hh(copy.hh),
  mm(copy.mm),
  ss(copy.ss)
{}

static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}

// A convenient constructor for using "the compiler's time":
//   DateTime now (__DATE__, __TIME__);
// NOTE: using F() would further reduce the RAM footprint, see below.
DateTime::DateTime (const char* date, const char* time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  yOff = conv2d(date + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (date[0]) {
    case 'J': m = date[1] == 'a' ? 1 : m = date[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = date[2] == 'r' ? 4 : 8; break;
    case 'M': m = date[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(date + 4);
  hh = conv2d(time);
  mm = conv2d(time + 3);
  ss = conv2d(time + 6);
}

// A convenient constructor for using "the compiler's time":
// This version will save RAM by using PROGMEM to store it by using the F macro.
//   DateTime now (F(__DATE__), F(__TIME__));
DateTime::DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  char buff[11];
  memcpy_P(buff, date, 11);
  yOff = conv2d(buff + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (buff[0]) {
    case 'J': m = buff[1] == 'a' ? 1 : m = buff[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = buff[2] == 'r' ? 4 : 8; break;
    case 'M': m = buff[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(buff + 4);
  memcpy_P(buff, time, 8);
  hh = conv2d(buff);
  mm = conv2d(buff + 3);
  ss = conv2d(buff + 6);
}

uint8_t DateTime::dayOfTheWeek() const {
  uint16_t day = date2days(yOff, m, d);
  return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6
}

uint32_t DateTime::unixtime(void) const {
  uint32_t t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  t += SECONDS_FROM_1970_TO_2000;  // seconds from 1970 to 2000

  return t;
}

long DateTime::secondstime(void) const {
  long t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  return t;
}

DateTime DateTime::operator+(const TimeSpan& span) {
  return DateTime(unixtime() + span.totalseconds());
}

DateTime DateTime::operator-(const TimeSpan& span) {
  return DateTime(unixtime() - span.totalseconds());
}

TimeSpan DateTime::operator-(const DateTime& right) {
  return TimeSpan(unixtime() - right.unixtime());
}

////////////////////////////////////////////////////////////////////////////////
// TimeSpan implementation

TimeSpan::TimeSpan (int32_t seconds):
  _seconds(seconds)
{}

TimeSpan::TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds):
  _seconds((int32_t)days * 86400L + (int32_t)hours * 3600 + (int32_t)minutes * 60 + seconds)
{}

TimeSpan::TimeSpan (const TimeSpan& copy):
  _seconds(copy._seconds)
{}

TimeSpan TimeSpan::operator+(const TimeSpan& right) {
  return TimeSpan(_seconds + right._seconds);
}

TimeSpan TimeSpan::operator-(const TimeSpan& right) {
  return TimeSpan(_seconds - right._seconds);
}

static uint8_t bcd2bin (uint8_t val) {
  return val - 6 * (val >> 4);
}
static uint8_t bin2bcd (uint8_t val) {
  return val + 6 * (val / 10);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_Millis implementation

long RTC_Millis::offset = 0;

void RTC_Millis::adjust(const DateTime& dt) {
  offset = dt.unixtime() - millis() / 1000;
}

DateTime RTC_Millis::now() {
  return (uint32_t)(offset + millis() / 1000);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_DS3231 implementation

boolean RTC_DS3231::begin(void) {
  Wire.begin();
  return true;
}

bool RTC_DS3231::lostPower(void) {
  return (read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG) >> 7);
}

void RTC_DS3231::adjust(const DateTime& dt) {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0); // start at location 0
  Wire._I2C_WRITE(bin2bcd(dt.second()));
  Wire._I2C_WRITE(bin2bcd(dt.minute()));
  Wire._I2C_WRITE(bin2bcd(dt.hour()));
  Wire._I2C_WRITE(bin2bcd(0));
  Wire._I2C_WRITE(bin2bcd(dt.day()));
  Wire._I2C_WRITE(bin2bcd(dt.month()));
  Wire._I2C_WRITE(bin2bcd(dt.year() - 2000));
  Wire.endTransmission();

  uint8_t statreg = read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG);
  statreg &= ~0x80; // flip OSF bit
  write_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG, statreg);
}

DateTime RTC_DS3231::now() {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0);
  Wire.endTransmission();

  Wire.requestFrom(DS3231_ADDRESS, 7);
  uint8_t ss = bcd2bin(Wire._I2C_READ() & 0x7F);
  uint8_t mm = bcd2bin(Wire._I2C_READ());
  uint8_t hh = bcd2bin(Wire._I2C_READ() & 0b111111); //  & 0b111111 Makes clock hours 24 hour format.
  Wire._I2C_READ();
  uint8_t d = bcd2bin(Wire._I2C_READ());
  uint8_t m = bcd2bin(Wire._I2C_READ());
  uint16_t y = bcd2bin(Wire._I2C_READ()) + 2000;

  return DateTime (y, m, d, hh, mm, ss);
}

Ds3231SqwPinMode RTC_DS3231::readSqwPinMode() {
  int mode;

  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE(DS3231_CONTROL);
  Wire.endTransmission();

  Wire.requestFrom((uint8_t)DS3231_ADDRESS, (uint8_t)1);
  mode = Wire._I2C_READ();

  mode &= 0x93;
  return static_cast<Ds3231SqwPinMode>(mode);
}

void RTC_DS3231::writeSqwPinMode(Ds3231SqwPinMode mode) {
  uint8_t ctrl;
  ctrl = read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL);

  ctrl &= ~0x04; // turn off INTCON
  ctrl &= ~0x18; // set freq bits to 0

  if (mode == DS3231_OFF) {
    ctrl |= 0x04; // turn on INTCN
  } else {
    ctrl |= mode;
  }
  write_i2c_register(DS3231_ADDRESS, DS3231_CONTROL, ctrl);

  //Serial.println( read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL), HEX);debug
}


Offline welshman

  • Full Member
  • ***
  • Posts: 90
  • Karma: +0/-0
Re: Arduino Powerjack companion.
« Reply #1 on: February 20, 2017, 06:26:35 AM »
Updated code, improvements. Fully tested in situ.


Code: [Select]

// PowerJack Inverter Generator control.
// monitors voltage and current of DC side to detect when inverter is providing no current to the battery bank.
// When the low voltage trigger level has been reached if the generator is not in off time set by start and end hours
// the relay will be triggered for the duration there is current travelling into the batteries. Once the target volatage
// has been reached.
// Uses a ADS1115 to get a differential reading of current across CT or shunt, allowing for current travelling in either
// direction. Uses analog input pin and a external voltage divider to get voltage values. 120k to 10k divider gives ratio
// of 13 and a range of 0-65v. Uses a DS3132 rtc for time keeping.
// PINS
// A0 - Voltage divider
// SDA - To ADS1115 ADC and DS3132 RTC
// SCL - To ADS1115 ADC and DS3132 RTC
// 7 - Relay to operate generator.
// Incorporates the DS3132 code due to changes.
// Outputs DATE,TIME,ADC RAW VALUE,VOLTAGE,AMPS,WATTS,OFFTIME,RELAY STATE on serial line.


#include <Wire.h> // wire library for comms to ds3132 and ads1115
#include <Adafruit_ADS1015.h>
#include <SoftwareSerial.h>
#include <ResponsiveAnalogRead.h>
#ifndef _RTCLIB_H_
#define _RTCLIB_H_
#include <Arduino.h>
class TimeSpan;
///DS3231 BEGIN
#ifdef __AVR__
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#elif defined(ARDUINO_ARCH_SAMD)
// nothing special needed
#elif defined(ARDUINO_SAM_DUE)
#define PROGMEM
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#define Wire Wire1
#endif
#if (ARDUINO >= 100)
#include <Arduino.h> // capital A so it is error prone on case-sensitive filesystems
// Macro to deal with the difference in I2C write functions from old and new Arduino versions.
#define _I2C_WRITE write
#define _I2C_READ  read
#else
#include <WProgram.h>
#define _I2C_WRITE send
#define _I2C_READ  receive
#endif
#define DS3231_ADDRESS  0x68
#define DS3231_CONTROL  0x0E
#define DS3231_STATUSREG 0x0F
#define SECONDS_PER_DAY 86400L
#define SECONDS_FROM_1970_TO_2000 946684800

// Simple general-purpose date/time class (no TZ / DST / leap second handling!)
class DateTime {
  public:
    DateTime (uint32_t t = 0);
    DateTime (uint16_t year, uint8_t month, uint8_t day,
              uint8_t hour = 0, uint8_t min = 0, uint8_t sec = 0);
    DateTime (const DateTime& copy);
    DateTime (const char* date, const char* time);
    DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time);
    uint16_t year() const       {
      return 2000 + yOff;
    }
    uint8_t month() const       {
      return m;
    }
    uint8_t day() const         {
      return d;
    }
    uint8_t hour() const        {
      return hh;
    }
    uint8_t minute() const      {
      return mm;
    }
    uint8_t second() const      {
      return ss;
    }
    uint8_t dayOfTheWeek() const;

    // 32-bit times as seconds since 1/1/2000
    long secondstime() const;
    // 32-bit times as seconds since 1/1/1970
    uint32_t unixtime(void) const;

    DateTime operator+(const TimeSpan& span);
    DateTime operator-(const TimeSpan& span);
    TimeSpan operator-(const DateTime& right);

  protected:
    uint8_t yOff, m, d, hh, mm, ss;
};

// Timespan which can represent changes in time with seconds accuracy.
class TimeSpan {
  public:
    TimeSpan (int32_t seconds = 0);
    TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds);
    TimeSpan (const TimeSpan& copy);
    int16_t days() const         {
      return _seconds / 86400L;
    }
    int8_t  hours() const        {
      return _seconds / 3600 % 24;
    }
    int8_t  minutes() const      {
      return _seconds / 60 % 60;
    }
    int8_t  seconds() const      {
      return _seconds % 60;
    }
    int32_t totalseconds() const {
      return _seconds;
    }

    TimeSpan operator+(const TimeSpan& right);
    TimeSpan operator-(const TimeSpan& right);

  protected:
    int32_t _seconds;
};

// RTC based on the DS3231 chip connected via I2C and the Wire library
enum Ds3231SqwPinMode { DS3231_OFF = 0x01, DS3231_SquareWave1Hz = 0x00, DS3231_SquareWave1kHz = 0x08, DS3231_SquareWave4kHz = 0x10, DS3231_SquareWave8kHz = 0x18 };

class RTC_DS3231 {
  public:
    boolean begin(void);
    static void adjust(const DateTime& dt);
    bool lostPower(void);
    static DateTime now();
    static Ds3231SqwPinMode readSqwPinMode();
    static void writeSqwPinMode(Ds3231SqwPinMode mode);
};

// RTC using the internal millis() clock, has to be initialized before use
// NOTE: this clock won't be correct once the millis() timer rolls over (>49d?)
class RTC_Millis {
  public:
    static void begin(const DateTime& dt) {
      adjust(dt);
    }
    static void adjust(const DateTime& dt);
    static DateTime now();

  protected:
    static long offset;
};

#endif // _RTCLIB_H_

RTC_DS3231 rtc;

#define VOLTIN           A0 // Voltage input pin
#define OUTPIN           7 // Output pin to control

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Adafruit_ADS1115 ads;  // ADS1115 Connected to SDA / SCL


/////////////////////////////////////////////////////////////////////////
//USER SETTINGS BEGIN

float targetvolt = 54; // When this target voltage is reached switch off relay only if current is 0
float lowvolt = 48; // Voltage to trigger on relay.
float multiplier = 7; // 2 - convert reading to 75mv / 300amp shunt. 1 is 100mv/100amp - 1.33 is 75mv/100amp
float offset = 1.025341463414634 ;// 1.041916167664671 ; // correction factor for analog voltage input.

int starthouroff = 22; // relay not on after 9 pm
int stophouroff = 8 ; // relay not on before 8 am


//USER SETTINGS END
//////////////////////////////////////////////////////////////////////////////


//prescaler bits
const unsigned char PS_16 = (1 << ADPS2);
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);


float amps;

int16_t adcresult;
float voltage;
int sensorValue;
bool offtime = false ;



ResponsiveAnalogRead analog(VOLTIN, true); // setup filtering




void setup(void)
{
  ADCSRA &= ~PS_128;  // remove bits set by Arduino library
  ADCSRA |= PS_16;    // set our own prescaler to 16 -  1mhz sampling

  pinMode(OUTPIN, OUTPUT);
  digitalWrite(OUTPIN, HIGH); //set to off on startup

  ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV
  ads.begin();
  rtc.begin();
  Serial.begin(115200);
  Serial.println("Press P for details, O for relay on, R for relay off, E for settings");

}

void loop(void)
{

  DateTime now = rtc.now(); //get the current date and time

  adcresult = ads.readADC_Differential_2_3();
  amps = ((float)adcresult * 256) / 32768; // 100mv / 100amp shunt
 
  amps = amps * multiplier; // convert reading to 75mv / 300amp shunt.

  analog.update();


  voltage = analog.getValue() * (5.0 / 1023.0); // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
  voltage = voltage * 13 ; //multiply by 13 due to divider resistors input 0-65v 120k/10k-r1/r2
  voltage = voltage * offset ;


  if (now.hour() >= starthouroff || now.hour() <= stophouroff )
  {
    offtime = true;
  }
  else
  {
    offtime = false;
  }


  if (voltage >= targetvolt && adcresult == 0) digitalWrite(OUTPIN, HIGH); ///switch off relay because charger has stopped charging.


  if (voltage <= lowvolt && offtime == false) digitalWrite(OUTPIN, LOW);  // low voltage trigger switch on relay if offtime is not true.


  if (offtime == true)  digitalWrite(OUTPIN, HIGH);  // if offtime is true then just switch off regardless.


  if ( Serial.available() > 0)
  {

    char c = toupper(Serial.read());
    if ( c == 'O' ) { // override relay

      digitalWrite(OUTPIN, LOW);

    } else if ( c == 'P' ) { // print details.


      Serial.print(now.day(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.year(), DEC);
      Serial.print(' ');
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.print(now.second(), DEC);
      Serial.print(' ');
      Serial.print(" raw adc: ");
      Serial.print(adcresult);

      Serial.print(" / volt: ");
      Serial.print(voltage);
      Serial.print(" / amps: ");
      Serial.print(amps);
      Serial.print(" / watts: ");
      Serial.print(voltage * amps);

      if (offtime == true) {
        Serial.print(" / OFFTIME");
      }
      else
      {
        Serial.print(" / ONTIME");
      }
      if (digitalRead(OUTPIN) == LOW) {
        Serial.print(" / Relay On");
      }
      else
      {
        Serial.print(" / Relay Off");
      }
      Serial.println();

    } else if ( c == 'E' ) { // print details.

      Serial.print("low volt level: ");
      Serial.print(lowvolt);
      Serial.print(" target voltage: ");
      Serial.print(targetvolt);
      Serial.print(" start off hour: ");
      Serial.print(starthouroff);
      Serial.print(" stop  off hour: ");
      Serial.print(stophouroff);
      Serial.print(" volt offset: ");
      Serial.print(offset, 15);
      Serial.println();


    } else if ( c == 'R' ) { // print details.
      digitalWrite(OUTPIN, HIGH);
    }
    else
    {
      Serial.println("Syntax Error");
      Serial.flush();
    }
  }



}


///////////////////////////////////////////////////////////////////////////////////////////////////////
//LOOP END


static uint8_t read_i2c_register(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire.endTransmission();

  Wire.requestFrom(addr, (byte)1);
  return Wire._I2C_READ();
}

static void write_i2c_register(uint8_t addr, uint8_t reg, uint8_t val) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire._I2C_WRITE((byte)val);
  Wire.endTransmission();
}


////////////////////////////////////////////////////////////////////////////////
// utility code, some of this could be exposed in the DateTime API if needed

const uint8_t daysInMonth [] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// number of days since 2000/01/01, valid for 2001..2099
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
  if (y >= 2000)
    y -= 2000;
  uint16_t days = d;
  for (uint8_t i = 1; i < m; ++i)
    days += pgm_read_byte(daysInMonth + i - 1);
  if (m > 2 && y % 4 == 0)
    ++days;
  return days + 365 * y + (y + 3) / 4 - 1;
}

static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
  return ((days * 24L + h) * 60 + m) * 60 + s;
}

////////////////////////////////////////////////////////////////////////////////
// DateTime implementation - ignores time zones and DST changes
// NOTE: also ignores leap seconds

DateTime::DateTime (uint32_t t) {
  t -= SECONDS_FROM_1970_TO_2000;    // bring to 2000 timestamp from 1970

  ss = t % 60;
  t /= 60;
  mm = t % 60;
  t /= 60;
  hh = t % 24;
  uint16_t days = t / 24;
  uint8_t leap;
  for (yOff = 0; ; ++yOff) {
    leap = yOff % 4 == 0;
    if (days < 365 + leap)
      break;
    days -= 365 + leap;
  }
  for (m = 1; ; ++m) {
    uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
    if (leap && m == 2)
      ++daysPerMonth;
    if (days < daysPerMonth)
      break;
    days -= daysPerMonth;
  }
  d = days + 1;
}

DateTime::DateTime (uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) {
  if (year >= 2000)
    year -= 2000;
  yOff = year;
  m = month;
  d = day;
  hh = hour;
  mm = min;
  ss = sec;
}

DateTime::DateTime (const DateTime& copy):
  yOff(copy.yOff),
  m(copy.m),
  d(copy.d),
  hh(copy.hh),
  mm(copy.mm),
  ss(copy.ss)
{}

static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}

// A convenient constructor for using "the compiler's time":
//   DateTime now (__DATE__, __TIME__);
// NOTE: using F() would further reduce the RAM footprint, see below.
DateTime::DateTime (const char* date, const char* time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  yOff = conv2d(date + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (date[0]) {
    case 'J': m = date[1] == 'a' ? 1 : m = date[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = date[2] == 'r' ? 4 : 8; break;
    case 'M': m = date[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(date + 4);
  hh = conv2d(time);
  mm = conv2d(time + 3);
  ss = conv2d(time + 6);
}

// A convenient constructor for using "the compiler's time":
// This version will save RAM by using PROGMEM to store it by using the F macro.
//   DateTime now (F(__DATE__), F(__TIME__));
DateTime::DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  char buff[11];
  memcpy_P(buff, date, 11);
  yOff = conv2d(buff + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (buff[0]) {
    case 'J': m = buff[1] == 'a' ? 1 : m = buff[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = buff[2] == 'r' ? 4 : 8; break;
    case 'M': m = buff[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(buff + 4);
  memcpy_P(buff, time, 8);
  hh = conv2d(buff);
  mm = conv2d(buff + 3);
  ss = conv2d(buff + 6);
}

uint8_t DateTime::dayOfTheWeek() const {
  uint16_t day = date2days(yOff, m, d);
  return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6
}

uint32_t DateTime::unixtime(void) const {
  uint32_t t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  t += SECONDS_FROM_1970_TO_2000;  // seconds from 1970 to 2000

  return t;
}

long DateTime::secondstime(void) const {
  long t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  return t;
}

DateTime DateTime::operator+(const TimeSpan& span) {
  return DateTime(unixtime() + span.totalseconds());
}

DateTime DateTime::operator-(const TimeSpan& span) {
  return DateTime(unixtime() - span.totalseconds());
}

TimeSpan DateTime::operator-(const DateTime& right) {
  return TimeSpan(unixtime() - right.unixtime());
}

////////////////////////////////////////////////////////////////////////////////
// TimeSpan implementation

TimeSpan::TimeSpan (int32_t seconds):
  _seconds(seconds)
{}

TimeSpan::TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds):
  _seconds((int32_t)days * 86400L + (int32_t)hours * 3600 + (int32_t)minutes * 60 + seconds)
{}

TimeSpan::TimeSpan (const TimeSpan& copy):
  _seconds(copy._seconds)
{}

TimeSpan TimeSpan::operator+(const TimeSpan& right) {
  return TimeSpan(_seconds + right._seconds);
}

TimeSpan TimeSpan::operator-(const TimeSpan& right) {
  return TimeSpan(_seconds - right._seconds);
}

static uint8_t bcd2bin (uint8_t val) {
  return val - 6 * (val >> 4);
}
static uint8_t bin2bcd (uint8_t val) {
  return val + 6 * (val / 10);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_Millis implementation

long RTC_Millis::offset = 0;

void RTC_Millis::adjust(const DateTime& dt) {
  offset = dt.unixtime() - millis() / 1000;
}

DateTime RTC_Millis::now() {
  return (uint32_t)(offset + millis() / 1000);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_DS3231 implementation

boolean RTC_DS3231::begin(void) {
  Wire.begin();
  return true;
}

bool RTC_DS3231::lostPower(void) {
  return (read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG) >> 7);
}

void RTC_DS3231::adjust(const DateTime& dt) {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0); // start at location 0
  Wire._I2C_WRITE(bin2bcd(dt.second()));
  Wire._I2C_WRITE(bin2bcd(dt.minute()));
  Wire._I2C_WRITE(bin2bcd(dt.hour()));
  Wire._I2C_WRITE(bin2bcd(0));
  Wire._I2C_WRITE(bin2bcd(dt.day()));
  Wire._I2C_WRITE(bin2bcd(dt.month()));
  Wire._I2C_WRITE(bin2bcd(dt.year() - 2000));
  Wire.endTransmission();

  uint8_t statreg = read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG);
  statreg &= ~0x80; // flip OSF bit
  write_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG, statreg);
}

DateTime RTC_DS3231::now() {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0);
  Wire.endTransmission();

  Wire.requestFrom(DS3231_ADDRESS, 7);
  uint8_t ss = bcd2bin(Wire._I2C_READ() & 0x7F);
  uint8_t mm = bcd2bin(Wire._I2C_READ());
  uint8_t hh = bcd2bin(Wire._I2C_READ() & 0b111111); //  & 0b111111 Makes clock hours 24 hour format.
  Wire._I2C_READ();
  uint8_t d = bcd2bin(Wire._I2C_READ());
  uint8_t m = bcd2bin(Wire._I2C_READ());
  uint16_t y = bcd2bin(Wire._I2C_READ()) + 2000;

  return DateTime (y, m, d, hh, mm, ss);
}

Ds3231SqwPinMode RTC_DS3231::readSqwPinMode() {
  int mode;

  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE(DS3231_CONTROL);
  Wire.endTransmission();

  Wire.requestFrom((uint8_t)DS3231_ADDRESS, (uint8_t)1);
  mode = Wire._I2C_READ();

  mode &= 0x93;
  return static_cast<Ds3231SqwPinMode>(mode);
}

void RTC_DS3231::writeSqwPinMode(Ds3231SqwPinMode mode) {
  uint8_t ctrl;
  ctrl = read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL);

  ctrl &= ~0x04; // turn off INTCON
  ctrl &= ~0x18; // set freq bits to 0

  if (mode == DS3231_OFF) {
    ctrl |= 0x04; // turn on INTCN
  } else {
    ctrl |= mode;
  }
  write_i2c_register(DS3231_ADDRESS, DS3231_CONTROL, ctrl);

  //Serial.println( read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL), HEX);debug
}




Offline welshman

  • Full Member
  • ***
  • Posts: 90
  • Karma: +0/-0
Re: Arduino Powerjack companion.
« Reply #2 on: February 21, 2017, 07:18:32 AM »
updated to deal with resuming charge after power fail, added some smoothing on the amps output.

Code: [Select]
// PowerJack Inverter Generator control.
// monitors voltage and current of DC side to detect when inverter is providing no current to the battery bank.
// When the low voltage trigger level has been reached if the generator is not in off time set by start and end hours
// the relay will be triggered for the duration there is current travelling into the batteries. Once the target volatage
// has been reached.
// Uses a ADS1115 to get a differential reading of current across CT or shunt, allowing for current travelling in either
// direction. Uses analog input pin and a external voltage divider to get voltage values. 120k to 10k divider gives ratio
// of 13 and a range of 0-65v. Uses a DS3132 rtc for time keeping.
// PINS
// A0 - Voltage divider
// SDA - To ADS1115 ADC and DS3132 RTC
// SCL - To ADS1115 ADC and DS3132 RTC
// 7 - Relay to operate generator.
// Incorporates the DS3132 code due to changes.
// Outputs DATE,TIME,ADC RAW VALUE,ADC AVERAGE VALUE,VOLTAGE,AMPS,WATTS,OFFTIME,RELAY STATE on serial line.


#include <Wire.h> // wire library for comms to ds3132 and ads1115
#include <Adafruit_ADS1015.h>
#include <SoftwareSerial.h>
#include <ResponsiveAnalogRead.h>
#ifndef _RTCLIB_H_
#define _RTCLIB_H_
#include <Arduino.h>
class TimeSpan;
///DS3231 BEGIN
#ifdef __AVR__
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#elif defined(ARDUINO_ARCH_SAMD)
// nothing special needed
#elif defined(ARDUINO_SAM_DUE)
#define PROGMEM
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#define Wire Wire1
#endif
#if (ARDUINO >= 100)
#include <Arduino.h> // capital A so it is error prone on case-sensitive filesystems
// Macro to deal with the difference in I2C write functions from old and new Arduino versions.
#define _I2C_WRITE write
#define _I2C_READ  read
#else
#include <WProgram.h>
#define _I2C_WRITE send
#define _I2C_READ  receive
#endif
#define DS3231_ADDRESS  0x68
#define DS3231_CONTROL  0x0E
#define DS3231_STATUSREG 0x0F
#define SECONDS_PER_DAY 86400L
#define SECONDS_FROM_1970_TO_2000 946684800

// Simple general-purpose date/time class (no TZ / DST / leap second handling!)
class DateTime {
  public:
    DateTime (uint32_t t = 0);
    DateTime (uint16_t year, uint8_t month, uint8_t day,
              uint8_t hour = 0, uint8_t min = 0, uint8_t sec = 0);
    DateTime (const DateTime& copy);
    DateTime (const char* date, const char* time);
    DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time);
    uint16_t year() const       {
      return 2000 + yOff;
    }
    uint8_t month() const       {
      return m;
    }
    uint8_t day() const         {
      return d;
    }
    uint8_t hour() const        {
      return hh;
    }
    uint8_t minute() const      {
      return mm;
    }
    uint8_t second() const      {
      return ss;
    }
    uint8_t dayOfTheWeek() const;

    // 32-bit times as seconds since 1/1/2000
    long secondstime() const;
    // 32-bit times as seconds since 1/1/1970
    uint32_t unixtime(void) const;

    DateTime operator+(const TimeSpan& span);
    DateTime operator-(const TimeSpan& span);
    TimeSpan operator-(const DateTime& right);

  protected:
    uint8_t yOff, m, d, hh, mm, ss;
};

// Timespan which can represent changes in time with seconds accuracy.
class TimeSpan {
  public:
    TimeSpan (int32_t seconds = 0);
    TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds);
    TimeSpan (const TimeSpan& copy);
    int16_t days() const         {
      return _seconds / 86400L;
    }
    int8_t  hours() const        {
      return _seconds / 3600 % 24;
    }
    int8_t  minutes() const      {
      return _seconds / 60 % 60;
    }
    int8_t  seconds() const      {
      return _seconds % 60;
    }
    int32_t totalseconds() const {
      return _seconds;
    }

    TimeSpan operator+(const TimeSpan& right);
    TimeSpan operator-(const TimeSpan& right);

  protected:
    int32_t _seconds;
};

// RTC based on the DS3231 chip connected via I2C and the Wire library
enum Ds3231SqwPinMode { DS3231_OFF = 0x01, DS3231_SquareWave1Hz = 0x00, DS3231_SquareWave1kHz = 0x08, DS3231_SquareWave4kHz = 0x10, DS3231_SquareWave8kHz = 0x18 };

class RTC_DS3231 {
  public:
    boolean begin(void);
    static void adjust(const DateTime& dt);
    bool lostPower(void);
    static DateTime now();
    static Ds3231SqwPinMode readSqwPinMode();
    static void writeSqwPinMode(Ds3231SqwPinMode mode);
};

// RTC using the internal millis() clock, has to be initialized before use
// NOTE: this clock won't be correct once the millis() timer rolls over (>49d?)
class RTC_Millis {
  public:
    static void begin(const DateTime& dt) {
      adjust(dt);
    }
    static void adjust(const DateTime& dt);
    static DateTime now();

  protected:
    static long offset;
};

#endif // _RTCLIB_H_

RTC_DS3231 rtc;

#define VOLTIN           A0 // Voltage input pin
#define OUTPIN           7 // Output pin to control

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Adafruit_ADS1115 ads;  // ADS1115 Connected to SDA / SCL


/////////////////////////////////////////////////////////////////////////
//USER SETTINGS BEGIN

float targetvolt = 54; // When this target voltage is reached switch off relay only if current is 0
float lowvolt = 48; // Voltage to trigger on relay.
float multiplier = 6.25; // 2 - convert reading to 75mv / 300amp shunt. 1 is 100mv/100amp - 1.33 is 75mv/100amp
float offset = 1.02 ; // correction factor for analog voltage input.

int starthouroff = 23; // relay not on after 11 pm
int stophouroff = 6 ; // relay not on before 6 am


//USER SETTINGS END
//////////////////////////////////////////////////////////////////////////////


//prescaler bits
const unsigned char PS_16 = (1 << ADPS2);
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);


float amps;

int16_t adcresult;
float voltage;
float ampsaverage;
int sensorValue;
bool offtime = false ;

const int numReadings = 100;

int16_t readings[numReadings];     
int readIndex = 0;             
int16_t total = 0;               
int16_t average = 0;

ResponsiveAnalogRead analog(VOLTIN, true); // setup filtering




void setup(void)
{
  ADCSRA &= ~PS_128;  // remove bits set by Arduino library
  ADCSRA |= PS_16;    // set our own prescaler to 16 -  1mhz sampling

  pinMode(OUTPIN, OUTPUT);
  digitalWrite(OUTPIN, HIGH); //set to off on startup

  ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV
  ads.begin();
  rtc.begin();
  Serial.begin(115200);
  Serial.println("Press P for details, O for relay on, R for relay off, E for settings");

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }

}

void loop(void)
{

  DateTime now = rtc.now(); //get the current date and time

  adcresult = ads.readADC_Differential_2_3();

  total = total - readings[readIndex];
  readings[readIndex] = adcresult;
  total = total + readings[readIndex];
  readIndex = readIndex + 1;
  if (readIndex >= numReadings)
   {
    readIndex = 0;
   }

  average = total / numReadings;
  amps = ((float)average * 256) / 32768; // 100mv / 100amp shunt
  amps = amps * multiplier; // convert reading to other shunt.

  analog.update();

  voltage = analog.getValue() * (5.0 / 1023.0); // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
  voltage = voltage * 13 ; //multiply by 13 due to divider resistors input 0-65v 120k/10k-r1/r2
  voltage = voltage * offset ;

  if (now.hour() >= starthouroff || now.hour() < stophouroff )
  {
    offtime = true;
  }
  else
  {
    offtime = false;
  }

  if (offtime == true) {
    digitalWrite(OUTPIN, HIGH);  // if offtime is true then just switch off regardless.
  }
  else
  {

    if (voltage >= targetvolt && adcresult == 0) {
      digitalWrite(OUTPIN, HIGH); ///switch off relay because charger has stopped charging.
    }
    else
    {
      if (voltage >= 60 && adcresult < -100) digitalWrite(OUTPIN, LOW); //switch on relay because it's in the middle of charging, resume.
    }

    if (voltage <= lowvolt) digitalWrite(OUTPIN, LOW);  // low voltage trigger switch on relay if offtime is not true.

  }

  if ( Serial.available() > 0)
  {

    char c = toupper(Serial.read());
    if ( c == 'O' ) { // override relay

      digitalWrite(OUTPIN, LOW);

    } else if ( c == 'P' ) { // print details.

      Serial.print(now.day(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.year(), DEC);
      Serial.print(' ');
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.print(now.second(), DEC);
      Serial.print(' ');
      Serial.print(" raw adc: ");
      Serial.print(adcresult);
      Serial.print(" average adc: ");
      Serial.print(average);
      Serial.print(" / volt: ");
      Serial.print(voltage);
      Serial.print(" / amps: ");
      Serial.print(amps);
      Serial.print(" / watts: ");
      Serial.print(voltage * amps);

      if (offtime == true) {
        Serial.print(" / OFFTIME");
      }
      else
      {
        Serial.print(" / ONTIME");
      }
      if (digitalRead(OUTPIN) == LOW) {
        Serial.print(" / Relay On");
      }
      else
      {
        Serial.print(" / Relay Off");
      }
      Serial.println();

    } else if ( c == 'E' ) { // print details.

      Serial.print("low volt level: ");
      Serial.print(lowvolt);
      Serial.print(" target voltage: ");
      Serial.print(targetvolt);
      Serial.print(" start off hour: ");
      Serial.print(starthouroff);
      Serial.print(" stop  off hour: ");
      Serial.print(stophouroff);
      Serial.print(" volt offset: ");
      Serial.print(offset, 15);
      Serial.println();


    } else if ( c == 'R' ) { // print details.
      digitalWrite(OUTPIN, HIGH);
    }
    else
    {
      Serial.println("Syntax Error");
      Serial.flush();
    }
  }



}


///////////////////////////////////////////////////////////////////////////////////////////////////////
//LOOP END


static uint8_t read_i2c_register(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire.endTransmission();

  Wire.requestFrom(addr, (byte)1);
  return Wire._I2C_READ();
}

static void write_i2c_register(uint8_t addr, uint8_t reg, uint8_t val) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire._I2C_WRITE((byte)val);
  Wire.endTransmission();
}


////////////////////////////////////////////////////////////////////////////////
// utility code, some of this could be exposed in the DateTime API if needed

const uint8_t daysInMonth [] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// number of days since 2000/01/01, valid for 2001..2099
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
  if (y >= 2000)
    y -= 2000;
  uint16_t days = d;
  for (uint8_t i = 1; i < m; ++i)
    days += pgm_read_byte(daysInMonth + i - 1);
  if (m > 2 && y % 4 == 0)
    ++days;
  return days + 365 * y + (y + 3) / 4 - 1;
}

static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
  return ((days * 24L + h) * 60 + m) * 60 + s;
}

////////////////////////////////////////////////////////////////////////////////
// DateTime implementation - ignores time zones and DST changes
// NOTE: also ignores leap seconds

DateTime::DateTime (uint32_t t) {
  t -= SECONDS_FROM_1970_TO_2000;    // bring to 2000 timestamp from 1970

  ss = t % 60;
  t /= 60;
  mm = t % 60;
  t /= 60;
  hh = t % 24;
  uint16_t days = t / 24;
  uint8_t leap;
  for (yOff = 0; ; ++yOff) {
    leap = yOff % 4 == 0;
    if (days < 365 + leap)
      break;
    days -= 365 + leap;
  }
  for (m = 1; ; ++m) {
    uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
    if (leap && m == 2)
      ++daysPerMonth;
    if (days < daysPerMonth)
      break;
    days -= daysPerMonth;
  }
  d = days + 1;
}

DateTime::DateTime (uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) {
  if (year >= 2000)
    year -= 2000;
  yOff = year;
  m = month;
  d = day;
  hh = hour;
  mm = min;
  ss = sec;
}

DateTime::DateTime (const DateTime& copy):
  yOff(copy.yOff),
  m(copy.m),
  d(copy.d),
  hh(copy.hh),
  mm(copy.mm),
  ss(copy.ss)
{}

static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}

// A convenient constructor for using "the compiler's time":
//   DateTime now (__DATE__, __TIME__);
// NOTE: using F() would further reduce the RAM footprint, see below.
DateTime::DateTime (const char* date, const char* time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  yOff = conv2d(date + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (date[0]) {
    case 'J': m = date[1] == 'a' ? 1 : m = date[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = date[2] == 'r' ? 4 : 8; break;
    case 'M': m = date[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(date + 4);
  hh = conv2d(time);
  mm = conv2d(time + 3);
  ss = conv2d(time + 6);
}

// A convenient constructor for using "the compiler's time":
// This version will save RAM by using PROGMEM to store it by using the F macro.
//   DateTime now (F(__DATE__), F(__TIME__));
DateTime::DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  char buff[11];
  memcpy_P(buff, date, 11);
  yOff = conv2d(buff + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (buff[0]) {
    case 'J': m = buff[1] == 'a' ? 1 : m = buff[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = buff[2] == 'r' ? 4 : 8; break;
    case 'M': m = buff[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(buff + 4);
  memcpy_P(buff, time, 8);
  hh = conv2d(buff);
  mm = conv2d(buff + 3);
  ss = conv2d(buff + 6);
}

uint8_t DateTime::dayOfTheWeek() const {
  uint16_t day = date2days(yOff, m, d);
  return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6
}

uint32_t DateTime::unixtime(void) const {
  uint32_t t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  t += SECONDS_FROM_1970_TO_2000;  // seconds from 1970 to 2000

  return t;
}

long DateTime::secondstime(void) const {
  long t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  return t;
}

DateTime DateTime::operator+(const TimeSpan& span) {
  return DateTime(unixtime() + span.totalseconds());
}

DateTime DateTime::operator-(const TimeSpan& span) {
  return DateTime(unixtime() - span.totalseconds());
}

TimeSpan DateTime::operator-(const DateTime& right) {
  return TimeSpan(unixtime() - right.unixtime());
}

////////////////////////////////////////////////////////////////////////////////
// TimeSpan implementation

TimeSpan::TimeSpan (int32_t seconds):
  _seconds(seconds)
{}

TimeSpan::TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds):
  _seconds((int32_t)days * 86400L + (int32_t)hours * 3600 + (int32_t)minutes * 60 + seconds)
{}

TimeSpan::TimeSpan (const TimeSpan& copy):
  _seconds(copy._seconds)
{}

TimeSpan TimeSpan::operator+(const TimeSpan& right) {
  return TimeSpan(_seconds + right._seconds);
}

TimeSpan TimeSpan::operator-(const TimeSpan& right) {
  return TimeSpan(_seconds - right._seconds);
}

static uint8_t bcd2bin (uint8_t val) {
  return val - 6 * (val >> 4);
}
static uint8_t bin2bcd (uint8_t val) {
  return val + 6 * (val / 10);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_Millis implementation

long RTC_Millis::offset = 0;

void RTC_Millis::adjust(const DateTime& dt) {
  offset = dt.unixtime() - millis() / 1000;
}

DateTime RTC_Millis::now() {
  return (uint32_t)(offset + millis() / 1000);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_DS3231 implementation

boolean RTC_DS3231::begin(void) {
  Wire.begin();
  return true;
}

bool RTC_DS3231::lostPower(void) {
  return (read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG) >> 7);
}

void RTC_DS3231::adjust(const DateTime& dt) {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0); // start at location 0
  Wire._I2C_WRITE(bin2bcd(dt.second()));
  Wire._I2C_WRITE(bin2bcd(dt.minute()));
  Wire._I2C_WRITE(bin2bcd(dt.hour()));
  Wire._I2C_WRITE(bin2bcd(0));
  Wire._I2C_WRITE(bin2bcd(dt.day()));
  Wire._I2C_WRITE(bin2bcd(dt.month()));
  Wire._I2C_WRITE(bin2bcd(dt.year() - 2000));
  Wire.endTransmission();

  uint8_t statreg = read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG);
  statreg &= ~0x80; // flip OSF bit
  write_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG, statreg);
}

DateTime RTC_DS3231::now() {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0);
  Wire.endTransmission();

  Wire.requestFrom(DS3231_ADDRESS, 7);
  uint8_t ss = bcd2bin(Wire._I2C_READ() & 0x7F);
  uint8_t mm = bcd2bin(Wire._I2C_READ());
  uint8_t hh = bcd2bin(Wire._I2C_READ() & 0b111111); //  & 0b111111 Makes clock hours 24 hour format.
  Wire._I2C_READ();
  uint8_t d = bcd2bin(Wire._I2C_READ());
  uint8_t m = bcd2bin(Wire._I2C_READ());
  uint16_t y = bcd2bin(Wire._I2C_READ()) + 2000;

  return DateTime (y, m, d, hh, mm, ss);
}

Ds3231SqwPinMode RTC_DS3231::readSqwPinMode() {
  int mode;

  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE(DS3231_CONTROL);
  Wire.endTransmission();

  Wire.requestFrom((uint8_t)DS3231_ADDRESS, (uint8_t)1);
  mode = Wire._I2C_READ();

  mode &= 0x93;
  return static_cast<Ds3231SqwPinMode>(mode);
}

void RTC_DS3231::writeSqwPinMode(Ds3231SqwPinMode mode) {
  uint8_t ctrl;
  ctrl = read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL);

  ctrl &= ~0x04; // turn off INTCON
  ctrl &= ~0x18; // set freq bits to 0

  if (mode == DS3231_OFF) {
    ctrl |= 0x04; // turn on INTCN
  } else {
    ctrl |= mode;
  }
  write_i2c_register(DS3231_ADDRESS, DS3231_CONTROL, ctrl);

  //Serial.println( read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL), HEX);debug
}



Offline welshman

  • Full Member
  • ***
  • Posts: 90
  • Karma: +0/-0
Re: Arduino Powerjack companion.
« Reply #3 on: February 21, 2017, 12:52:49 PM »
and a picture.


Online oztules

  • Forum Advisors
  • Hero Member
  • ****
  • Posts: 1078
  • Karma: +90/-5
  • Village idiot
Re: Arduino Powerjack companion.
« Reply #4 on: February 21, 2017, 02:33:34 PM »
Nice project.

..........oztules
Flinders Island...... Australia

Offline welshman

  • Full Member
  • ***
  • Posts: 90
  • Karma: +0/-0
Re: Arduino Powerjack companion.
« Reply #5 on: February 22, 2017, 11:10:10 AM »
Nice project.

..........oztules

Thanks, it's been fun.

Offline welshman

  • Full Member
  • ***
  • Posts: 90
  • Karma: +0/-0
Re: Arduino Powerjack companion.
« Reply #6 on: February 22, 2017, 11:11:19 AM »
Improvements made, Adjust settings from serial and commit to flash memory.

Code: [Select]

// PowerJack Inverter Generator control.
 // monitors voltage and current of DC side to detect when inverter is providing no current to the battery bank.
 // When the low voltage trigger level has been reached if the generator is not in off time set by start and end hours
 // the relay will be triggered for the duration there is current travelling into the batteries. Once the target volatage
 // has been reached.
 // Uses a ADS1115 to get a differential reading of current across CT or shunt, allowing for current travelling in either
 // direction. Uses analog input pin and a external voltage divider to get voltage values. 120k to 10k divider gives ratio
 // of 13 and a range of 0-65v. Uses a DS3132 rtc for time keeping.
 // PINS
 // A0 - Voltage divider
 // SDA - To ADS1115 ADC and DS3231 RTC
 // SCL - To ADS1115 ADC and DS3231 RTC
 // 7 - Relay to operate generator.
 // 5 - Relay On indicator.
 // Incorporates the DS3231 code due to changes.
 // Outputs DATE,TIME,ADC RAW VALUE,ADC AVERAGE VALUE,VOLTAGE,AMPS,WATTS,OFFTIME,RELAY STATE on serial line.

#include <EEPROM.h>
 #include <Wire.h> // wire library for comms to ds3132 and ads1115
 #include <Adafruit_ADS1015.h>
 #include <SoftwareSerial.h>
 #include <ResponsiveAnalogRead.h>
 #ifndef _RTCLIB_H_
 #define _RTCLIB_H_
 #include <Arduino.h>
 class TimeSpan;
 ///DS3231 BEGIN
 #ifdef __AVR__
 #include <avr/pgmspace.h>
 #elif defined(ESP8266)
 #include <pgmspace.h>
 #elif defined(ARDUINO_ARCH_SAMD)
 // nothing special needed
 #elif defined(ARDUINO_SAM_DUE)
 #define PROGMEM
 #define pgm_read_byte(addr) (*(const unsigned char *)(addr))
 #define Wire Wire1
 #endif
 #if (ARDUINO >= 100)
 #include <Arduino.h> // capital A so it is error prone on case-sensitive filesystems
 // Macro to deal with the difference in I2C write functions from old and new Arduino versions.
 #define _I2C_WRITE write
 #define _I2C_READ  read
 #else
 #include <WProgram.h>
 #define _I2C_WRITE send
 #define _I2C_READ  receive
 #endif
 #define DS3231_ADDRESS  0x68
 #define DS3231_CONTROL  0x0E
 #define DS3231_STATUSREG 0x0F
 #define SECONDS_PER_DAY 86400L
 #define SECONDS_FROM_1970_TO_2000 946684800

// Simple general-purpose date/time class (no TZ / DST / leap second handling!)
 class DateTime {
   public:
     DateTime (uint32_t t = 0);
     DateTime (uint16_t year, uint8_t month, uint8_t day,
               uint8_t hour = 0, uint8_t min = 0, uint8_t sec = 0);
     DateTime (const DateTime& copy);
     DateTime (const char* date, const char* time);
     DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time);
     uint16_t year() const       {
       return 2000 + yOff;
     }
     uint8_t month() const       {
       return m;
     }
     uint8_t day() const         {
       return d;
     }
     uint8_t hour() const        {
       return hh;
     }
     uint8_t minute() const      {
       return mm;
     }
     uint8_t second() const      {
       return ss;
     }
     uint8_t dayOfTheWeek() const;

    // 32-bit times as seconds since 1/1/2000
     long secondstime() const;
     // 32-bit times as seconds since 1/1/1970
     uint32_t unixtime(void) const;

    DateTime operator+(const TimeSpan& span);
     DateTime operator-(const TimeSpan& span);
     TimeSpan operator-(const DateTime& right);

  protected:
     uint8_t yOff, m, d, hh, mm, ss;
 };

// Timespan which can represent changes in time with seconds accuracy.
 class TimeSpan {
   public:
     TimeSpan (int32_t seconds = 0);
     TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds);
     TimeSpan (const TimeSpan& copy);
     int16_t days() const         {
       return _seconds / 86400L;
     }
     int8_t  hours() const        {
       return _seconds / 3600 % 24;
     }
     int8_t  minutes() const      {
       return _seconds / 60 % 60;
     }
     int8_t  seconds() const      {
       return _seconds % 60;
     }
     int32_t totalseconds() const {
       return _seconds;
     }

    TimeSpan operator+(const TimeSpan& right);
     TimeSpan operator-(const TimeSpan& right);

  protected:
     int32_t _seconds;
 };

// RTC based on the DS3231 chip connected via I2C and the Wire library
 enum Ds3231SqwPinMode { DS3231_OFF = 0x01, DS3231_SquareWave1Hz = 0x00, DS3231_SquareWave1kHz = 0x08, DS3231_SquareWave4kHz = 0x10, DS3231_SquareWave8kHz = 0x18 };

class RTC_DS3231 {
   public:
     boolean begin(void);
     static void adjust(const DateTime& dt);
     bool lostPower(void);
     static DateTime now();
     static Ds3231SqwPinMode readSqwPinMode();
     static void writeSqwPinMode(Ds3231SqwPinMode mode);
 };

// RTC using the internal millis() clock, has to be initialized before use
 // NOTE: this clock won't be correct once the millis() timer rolls over (>49d?)
 class RTC_Millis {
   public:
     static void begin(const DateTime& dt) {
       adjust(dt);
     }
     static void adjust(const DateTime& dt);
     static DateTime now();

  protected:
     static long offset;
 };

#endif // _RTCLIB_H_

RTC_DS3231 rtc;

#define VOLTIN           A0 // Voltage input pin
 #define OUTPIN           7 // Output pin to control
 #define INDPIN           5 // Output Indicator pin
 #define INVINDPIN        4 // Inverse Output indicator pin

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Adafruit_ADS1115 ads;  // ADS1115 Connected to SDA / SCL

//////////////////////////////////////////////////////////
 //EEPROM MEMORY MAP

int Eaddress1 = 0; //first run
 int Eaddress2 = 1; //low voltage trigger
 int Eaddress3 = 2; //target voltage trigger
 int Eaddress4 = 3; // multiplier
 int Eaddress5 = 4; // relay off time
 int Eaddress6 = 5; // relay on time
 int Eaddress7 = 6;

byte value;

/////////////////////////////////////////////////////////////////////////
 //USER SETTINGS BEGIN

float targetvolt = 54; // When this target voltage is reached switch off relay only if current is 0
 float lowvolt = 48; // Voltage to trigger on relay.
 float multiplier = 5.75; // 2 - convert reading to 75mv / 300amp shunt. 1 is 100mv/100amp - 1.33 is 75mv/100amp
 float offset = 1.025 ; // correction factor for analog voltage input.

int starthouroff = 23; // relay not on after 11 pm
 int stophouroff = 6 ; // relay not on before 6 am


 //USER SETTINGS END
 //////////////////////////////////////////////////////////////////////////////

int lowvoltset ;
 int targetvoltset;
 int starthouroffset;
 int stophouroffset;

//prescaler bits
 const unsigned char PS_16 = (1 << ADPS2);
 const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
 const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
 const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);


 float amps;

int16_t adcresult;
 float voltage;
 float ampsaverage;
 int sensorValue;
 bool offtime = false ;
 bool overrideoff = false;

const int numReadings = 100;

int16_t readings[numReadings];
 int readIndex = 0;
 int16_t total = 0;
 int16_t average = 0;

ResponsiveAnalogRead analog(VOLTIN, true); // setup filtering







void setup(void)
 {
   ADCSRA &= ~PS_128;  // remove bits set by Arduino library
   ADCSRA |= PS_16;    // set our own prescaler to 16 -  1mhz sampling

  pinMode(OUTPIN, OUTPUT);
   digitalWrite(OUTPIN, HIGH); //set to off on startup

  ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV
   ads.begin();
   rtc.begin();
   Serial.begin(115200);
   Serial.println("Press P for details, O for relay on, R for relay off, E for settings");

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
     readings[thisReading] = 0;
   }

  //get saved settings

  if (EEPROM.read(Eaddress1) == 1) {
     lowvolt = EEPROM.read(Eaddress2);
     targetvolt = EEPROM.read(Eaddress3);
     starthouroff = EEPROM.read(Eaddress5);
     stophouroff = EEPROM.read(Eaddress6);
   }
   else
   {
     EEPROM.write(Eaddress1, 1) ;
     EEPROM.write(Eaddress2, lowvolt) ;
     EEPROM.write(Eaddress3, targetvolt);
     EEPROM.write(Eaddress5, starthouroff);
     EEPROM.write(Eaddress6, stophouroff);
   }

}

void loop(void)
 {

  digitalWrite(INDPIN, digitalRead(OUTPIN));
   digitalWrite(INVINDPIN, !digitalRead(OUTPIN));

  DateTime now = rtc.now(); //get the current date and time

  adcresult = ads.readADC_Differential_2_3();

  total = total - readings[readIndex];
   readings[readIndex] = adcresult;
   total = total + readings[readIndex];
   readIndex = readIndex + 1;
   if (readIndex >= numReadings)
   {
     readIndex = 0;
   }

  average = total / numReadings;
   amps = ((float)average * 256) / 32768; // 100mv / 100amp shunt
   amps = amps * multiplier; // convert reading to other shunt.

  analog.update();

  voltage = analog.getValue() * (5.0 / 1023.0); // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
   voltage = voltage * 13 ; //multiply by 13 due to divider resistors input 0-65v 120k/10k-r1/r2
   voltage = voltage * offset ;

  if (now.hour() >= starthouroff || now.hour() < stophouroff )
   {
     offtime = true;
   }
   else
   {
     offtime = false;
   }


   if (offtime == true || overrideoff == true)
   {
     digitalWrite(OUTPIN, HIGH);  // if offtime is true then just switch off regardless.
   }
   else
   {

    if (voltage >= targetvolt && adcresult == 0)
     {
       digitalWrite(OUTPIN, HIGH); ///switch off relay because charger has stopped charging.
     }
     else
     {
       if (voltage >= 60 && adcresult < -100) digitalWrite(OUTPIN, LOW); //switch on relay because it's in the middle of charging, resume.
     }

    if (voltage <= lowvolt) digitalWrite(OUTPIN, LOW);  // low voltage trigger switch on relay if offtime is not true.

  }

  if (voltage <= lowvolt && digitalRead(OUTPIN) == LOW)
   {
     //generators yet to start, failed to start or breaker has tripped.
     //maybe do something here.
   }


   if ( Serial.available() > 0)
   {

    char c = toupper(Serial.read());
     if ( c == 'O' )
     { // override relay

      digitalWrite(OUTPIN, LOW);
       overrideoff = false;

    }
     else if ( c == 'P' )
     { // print details.

      Serial.print(now.day(), DEC);
       Serial.print('/');
       Serial.print(now.month(), DEC);
       Serial.print('/');
       Serial.print(now.year(), DEC);
       Serial.print(' ');
       Serial.print(now.hour(), DEC);
       Serial.print(':');
       Serial.print(now.minute(), DEC);
       Serial.print(':');
       Serial.print(now.second(), DEC);
       Serial.print(' ');
       Serial.print(" raw adc: ");
       Serial.print(adcresult);
       Serial.print(" average adc: ");
       Serial.print(average);
       Serial.print(" / volt: ");
       Serial.print(voltage);
       Serial.print(" / amps: ");
       Serial.print(amps);
       Serial.print(" / watts: ");
       Serial.print(voltage * amps);

      if (offtime == true)
       {
         Serial.print(" / OFFTIME");
       }
       else
       {
         Serial.print(" / ONTIME");
       }
       if (digitalRead(OUTPIN) == LOW)
       {
         Serial.print(" / Relay On");
       }
       else
       {
         Serial.print(" / Relay Off");
       }
       if (overrideoff == true)
       {
         Serial.print(" / MAN OFF");
       }
       Serial.println();

    }
     else if ( c == 'E' )
     { // print settings.

      Serial.print("low volt level: ");
       Serial.print(lowvolt);
       Serial.print(" target voltage: ");
       Serial.print(targetvolt);
       Serial.print(" start off hour: ");
       Serial.print(starthouroff);
       Serial.print(" stop  off hour: ");
       Serial.print(stophouroff);
       Serial.print(" volt offset: ");
       Serial.println(offset, 4);
       Serial.println("L(INT) Sets low voltage trigger. T(INT) Sets target voltage trigger. X(INT) Sets start of off time HR. Y(INT) Sets end of off time HR.");
       Serial.println("Examples. L48 sets low voltage trigger to 48v. Y5 sets generator can start after 05:00 AM. X19 sets generator can not start after 19:00 PM");


     }
     else if ( c == 'R' )
     {
       //digitalWrite(OUTPIN, HIGH);
       overrideoff = true;
     }
     else if ( c == 'L' )
     {

      lowvoltset = Serial.parseInt();
       if (lowvoltset <= 65 && lowvoltset > 0)
       {
         lowvolt = lowvoltset;
         EEPROM.update(Eaddress2, lowvoltset) ;
         Serial.println("Set lowvolt");
       }
       else
       {
         Serial.println("Incorrect input");
       }
     }
     else if ( c == 'T' )
     {

      targetvoltset = Serial.parseInt();
       if (targetvoltset <= 65 && targetvoltset > 0)
       {
         targetvolt = targetvoltset ;
         EEPROM.update(Eaddress3, targetvoltset) ;
         Serial.println("Set targetvolt");
       }
       else
       {
         Serial.println("Incorrect input");
       }
     }
     else if ( c == 'X' )
     {

      starthouroffset = Serial.parseInt();
       if (starthouroffset <= 24 && starthouroffset > 0)
       {
         starthouroff = starthouroffset;
         EEPROM.update(Eaddress5, starthouroffset) ;
         Serial.println("Set starthouroff");
       }
       else
       {
         Serial.println("Incorrect input");
       }
     }
     else if ( c == 'Y' )
     {

      stophouroffset = Serial.parseInt();
       if (stophouroffset <= 24 && stophouroffset > 0)
       {
         stophouroff = stophouroffset;
         EEPROM.update(Eaddress6, stophouroffset) ;
         Serial.println("Set stophouroff");
       }
       else
       {
         Serial.println("Incorrect input");
       }
     }
     else
     {
       Serial.println("Syntax Error");
       Serial.flush();
     }
   }
 }


///////////////////////////////////////////////////////////////////////////////////////////////////////
 //LOOP END


 static uint8_t read_i2c_register(uint8_t addr, uint8_t reg) {
   Wire.beginTransmission(addr);
   Wire._I2C_WRITE((byte)reg);
   Wire.endTransmission();

  Wire.requestFrom(addr, (byte)1);
   return Wire._I2C_READ();
 }

static void write_i2c_register(uint8_t addr, uint8_t reg, uint8_t val) {
   Wire.beginTransmission(addr);
   Wire._I2C_WRITE((byte)reg);
   Wire._I2C_WRITE((byte)val);
   Wire.endTransmission();
 }


 ////////////////////////////////////////////////////////////////////////////////
 // utility code, some of this could be exposed in the DateTime API if needed

const uint8_t daysInMonth [] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// number of days since 2000/01/01, valid for 2001..2099
 static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
   if (y >= 2000)
     y -= 2000;
   uint16_t days = d;
   for (uint8_t i = 1; i < m; ++i)
     days += pgm_read_byte(daysInMonth + i - 1);
   if (m > 2 && y % 4 == 0)
     ++days;
   return days + 365 * y + (y + 3) / 4 - 1;
 }

static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
   return ((days * 24L + h) * 60 + m) * 60 + s;
 }

////////////////////////////////////////////////////////////////////////////////
 // DateTime implementation - ignores time zones and DST changes
 // NOTE: also ignores leap seconds

DateTime::DateTime (uint32_t t) {
   t -= SECONDS_FROM_1970_TO_2000;    // bring to 2000 timestamp from 1970

  ss = t % 60;
   t /= 60;
   mm = t % 60;
   t /= 60;
   hh = t % 24;
   uint16_t days = t / 24;
   uint8_t leap;
   for (yOff = 0; ; ++yOff) {
     leap = yOff % 4 == 0;
     if (days < 365 + leap)
       break;
     days -= 365 + leap;
   }
   for (m = 1; ; ++m) {
     uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
     if (leap && m == 2)
       ++daysPerMonth;
     if (days < daysPerMonth)
       break;
     days -= daysPerMonth;
   }
   d = days + 1;
 }

DateTime::DateTime (uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) {
   if (year >= 2000)
     year -= 2000;
   yOff = year;
   m = month;
   d = day;
   hh = hour;
   mm = min;
   ss = sec;
 }

DateTime::DateTime (const DateTime& copy):
   yOff(copy.yOff),
   m(copy.m),
   d(copy.d),
   hh(copy.hh),
   mm(copy.mm),
   ss(copy.ss)
 {}

static uint8_t conv2d(const char* p) {
   uint8_t v = 0;
   if ('0' <= *p && *p <= '9')
     v = *p - '0';
   return 10 * v + *++p - '0';
 }

// A convenient constructor for using "the compiler's time":
 //   DateTime now (__DATE__, __TIME__);
 // NOTE: using F() would further reduce the RAM footprint, see below.
 DateTime::DateTime (const char* date, const char* time) {
   // sample input: date = "Dec 26 2009", time = "12:34:56"
   yOff = conv2d(date + 9);
   // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
   switch (date[0]) {
     case 'J': m = date[1] == 'a' ? 1 : m = date[2] == 'n' ? 6 : 7; break;
     case 'F': m = 2; break;
     case 'A': m = date[2] == 'r' ? 4 : 8; break;
     case 'M': m = date[2] == 'r' ? 3 : 5; break;
     case 'S': m = 9; break;
     case 'O': m = 10; break;
     case 'N': m = 11; break;
     case 'D': m = 12; break;
   }
   d = conv2d(date + 4);
   hh = conv2d(time);
   mm = conv2d(time + 3);
   ss = conv2d(time + 6);
 }

// A convenient constructor for using "the compiler's time":
 // This version will save RAM by using PROGMEM to store it by using the F macro.
 //   DateTime now (F(__DATE__), F(__TIME__));
 DateTime::DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time) {
   // sample input: date = "Dec 26 2009", time = "12:34:56"
   char buff[11];
   memcpy_P(buff, date, 11);
   yOff = conv2d(buff + 9);
   // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
   switch (buff[0]) {
     case 'J': m = buff[1] == 'a' ? 1 : m = buff[2] == 'n' ? 6 : 7; break;
     case 'F': m = 2; break;
     case 'A': m = buff[2] == 'r' ? 4 : 8; break;
     case 'M': m = buff[2] == 'r' ? 3 : 5; break;
     case 'S': m = 9; break;
     case 'O': m = 10; break;
     case 'N': m = 11; break;
     case 'D': m = 12; break;
   }
   d = conv2d(buff + 4);
   memcpy_P(buff, time, 8);
   hh = conv2d(buff);
   mm = conv2d(buff + 3);
   ss = conv2d(buff + 6);
 }

uint8_t DateTime::dayOfTheWeek() const {
   uint16_t day = date2days(yOff, m, d);
   return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6
 }

uint32_t DateTime::unixtime(void) const {
   uint32_t t;
   uint16_t days = date2days(yOff, m, d);
   t = time2long(days, hh, mm, ss);
   t += SECONDS_FROM_1970_TO_2000;  // seconds from 1970 to 2000

  return t;
 }

long DateTime::secondstime(void) const {
   long t;
   uint16_t days = date2days(yOff, m, d);
   t = time2long(days, hh, mm, ss);
   return t;
 }

DateTime DateTime::operator+(const TimeSpan& span) {
   return DateTime(unixtime() + span.totalseconds());
 }

DateTime DateTime::operator-(const TimeSpan& span) {
   return DateTime(unixtime() - span.totalseconds());
 }

TimeSpan DateTime::operator-(const DateTime& right) {
   return TimeSpan(unixtime() - right.unixtime());
 }

////////////////////////////////////////////////////////////////////////////////
 // TimeSpan implementation

TimeSpan::TimeSpan (int32_t seconds):
   _seconds(seconds)
 {}

TimeSpan::TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds):
   _seconds((int32_t)days * 86400L + (int32_t)hours * 3600 + (int32_t)minutes * 60 + seconds)
 {}

TimeSpan::TimeSpan (const TimeSpan& copy):
   _seconds(copy._seconds)
 {}

TimeSpan TimeSpan::operator+(const TimeSpan& right) {
   return TimeSpan(_seconds + right._seconds);
 }

TimeSpan TimeSpan::operator-(const TimeSpan& right) {
   return TimeSpan(_seconds - right._seconds);
 }

static uint8_t bcd2bin (uint8_t val) {
   return val - 6 * (val >> 4);
 }
 static uint8_t bin2bcd (uint8_t val) {
   return val + 6 * (val / 10);
 }

////////////////////////////////////////////////////////////////////////////////
 // RTC_Millis implementation

long RTC_Millis::offset = 0;

void RTC_Millis::adjust(const DateTime& dt) {
   offset = dt.unixtime() - millis() / 1000;
 }

DateTime RTC_Millis::now() {
   return (uint32_t)(offset + millis() / 1000);
 }

////////////////////////////////////////////////////////////////////////////////
 // RTC_DS3231 implementation

boolean RTC_DS3231::begin(void) {
   Wire.begin();
   return true;
 }

bool RTC_DS3231::lostPower(void) {
   return (read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG) >> 7);
 }

void RTC_DS3231::adjust(const DateTime& dt) {
   Wire.beginTransmission(DS3231_ADDRESS);
   Wire._I2C_WRITE((byte)0); // start at location 0
   Wire._I2C_WRITE(bin2bcd(dt.second()));
   Wire._I2C_WRITE(bin2bcd(dt.minute()));
   Wire._I2C_WRITE(bin2bcd(dt.hour()));
   Wire._I2C_WRITE(bin2bcd(0));
   Wire._I2C_WRITE(bin2bcd(dt.day()));
   Wire._I2C_WRITE(bin2bcd(dt.month()));
   Wire._I2C_WRITE(bin2bcd(dt.year() - 2000));
   Wire.endTransmission();

  uint8_t statreg = read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG);
   statreg &= ~0x80; // flip OSF bit
   write_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG, statreg);
 }

DateTime RTC_DS3231::now() {
   Wire.beginTransmission(DS3231_ADDRESS);
   Wire._I2C_WRITE((byte)0);
   Wire.endTransmission();

  Wire.requestFrom(DS3231_ADDRESS, 7);
   uint8_t ss = bcd2bin(Wire._I2C_READ() & 0x7F);
   uint8_t mm = bcd2bin(Wire._I2C_READ());
   uint8_t hh = bcd2bin(Wire._I2C_READ() & 0b111111); //  & 0b111111 Makes clock hours 24 hour format.
   Wire._I2C_READ();
   uint8_t d = bcd2bin(Wire._I2C_READ());
   uint8_t m = bcd2bin(Wire._I2C_READ());
   uint16_t y = bcd2bin(Wire._I2C_READ()) + 2000;

  return DateTime (y, m, d, hh, mm, ss);
 }

Ds3231SqwPinMode RTC_DS3231::readSqwPinMode() {
   int mode;

  Wire.beginTransmission(DS3231_ADDRESS);
   Wire._I2C_WRITE(DS3231_CONTROL);
   Wire.endTransmission();

  Wire.requestFrom((uint8_t)DS3231_ADDRESS, (uint8_t)1);
   mode = Wire._I2C_READ();

  mode &= 0x93;
   return static_cast<Ds3231SqwPinMode>(mode);
 }

void RTC_DS3231::writeSqwPinMode(Ds3231SqwPinMode mode) {
   uint8_t ctrl;
   ctrl = read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL);

  ctrl &= ~0x04; // turn off INTCON
   ctrl &= ~0x18; // set freq bits to 0

  if (mode == DS3231_OFF) {
     ctrl |= 0x04; // turn on INTCN
   } else {
     ctrl |= mode;
   }
   write_i2c_register(DS3231_ADDRESS, DS3231_CONTROL, ctrl);

  //Serial.println( read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL), HEX);debug
 }


Offline welshman

  • Full Member
  • ***
  • Posts: 90
  • Karma: +0/-0
Re: Arduino Powerjack companion.
« Reply #7 on: March 01, 2017, 08:14:56 AM »
Many improvements made. Next update will have data logging to memory card.


Code: [Select]
// PowerJack Inverter Generator control.
// monitors voltage and current of DC side to detect when inverter is providing no current to the battery bank.
// When the low voltage trigger level has been reached if the generator is not in off time set by start and end hours
// the relay will be triggered for the duration there is current travelling into the batteries. Once the target volatage
// has been reached.
// Uses a ADS1115 to get a differential reading of current across CT or shunt, allowing for current travelling in either
// direction. Uses analog input pin and a external voltage divider to get voltage values. 120k to 10k divider gives ratio
// of 13 and a range of 0-65v. Uses a DS3231 rtc for time keeping.
// PINS
// A0 - Voltage divider
// SDA - To ADS1115 ADC and DS3231 RTC
// SCL - To ADS1115 ADC and DS3231 RTC
// 7 - Relay to operate generator.
// 5 - Relay On indicator.
// Incorporates the DS3231 code due to changes.
// Outputs DATE,TIME,ADC RAW VALUE,ADC AVERAGE VALUE,VOLTAGE,AMPS,WATTS,OFFTIME,RELAY STATE on serial line.

#include <EEPROM.h>
#include <Wire.h> // wire library for comms to ds3231 and ads1115
#include <Adafruit_ADS1015.h>
#include <SoftwareSerial.h>
#include <ResponsiveAnalogRead.h>
#ifndef _RTCLIB_H_
#define _RTCLIB_H_
#include <Arduino.h>
class TimeSpan;
///DS3231 BEGIN
#ifdef __AVR__
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#elif defined(ARDUINO_ARCH_SAMD)
// nothing special needed
#elif defined(ARDUINO_SAM_DUE)
#define PROGMEM
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#define Wire Wire1
#endif
#if (ARDUINO >= 100)
#include <Arduino.h> // capital A so it is error prone on case-sensitive filesystems
// Macro to deal with the difference in I2C write functions from old and new Arduino versions.
#define _I2C_WRITE write
#define _I2C_READ  read
#else
#include <WProgram.h>
#define _I2C_WRITE send
#define _I2C_READ  receive
#endif
#define DS3231_ADDRESS  0x68
#define DS3231_CONTROL  0x0E
#define DS3231_STATUSREG 0x0F
#define SECONDS_PER_DAY 86400L
#define SECONDS_FROM_1970_TO_2000 946684800

// Simple general-purpose date/time class (no TZ / DST / leap second handling!)
class DateTime {
  public:
    DateTime (uint32_t t = 0);
    DateTime (uint16_t year, uint8_t month, uint8_t day,
              uint8_t hour = 0, uint8_t min = 0, uint8_t sec = 0);
    DateTime (const DateTime& copy);
    DateTime (const char* date, const char* time);
    DateTime (const __FlashStringHelper* date, const __FlashStringHelper* time);
    uint16_t year() const       {
      return 2000 + yOff;
    }
    uint8_t month() const       {
      return m;
    }
    uint8_t day() const         {
      return d;
    }
    uint8_t hour() const        {
      return hh;
    }
    uint8_t minute() const      {
      return mm;
    }
    uint8_t second() const      {
      return ss;
    }
    uint8_t dayOfTheWeek() const;

    // 32-bit times as seconds since 1/1/2000
    long secondstime() const;
    // 32-bit times as seconds since 1/1/1970
    uint32_t unixtime(void) const;

    DateTime operator+(const TimeSpan& span);
    DateTime operator-(const TimeSpan& span);
    TimeSpan operator-(const DateTime& right);

  protected:
    uint8_t yOff, m, d, hh, mm, ss;
};

// Timespan which can represent changes in time with seconds accuracy.
class TimeSpan {
  public:
    TimeSpan (int32_t seconds = 0);
    TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds);
    TimeSpan (const TimeSpan& copy);
    int16_t days() const         {
      return _seconds / 86400L;
    }
    int8_t  hours() const        {
      return _seconds / 3600 % 24;
    }
    int8_t  minutes() const      {
      return _seconds / 60 % 60;
    }
    int8_t  seconds() const      {
      return _seconds % 60;
    }
    int32_t totalseconds() const {
      return _seconds;
    }

    TimeSpan operator+(const TimeSpan& right);
    TimeSpan operator-(const TimeSpan& right);

  protected:
    int32_t _seconds;
};

// RTC based on the DS3231 chip connected via I2C and the Wire library
enum Ds3231SqwPinMode { DS3231_OFF = 0x01, DS3231_SquareWave1Hz = 0x00, DS3231_SquareWave1kHz = 0x08, DS3231_SquareWave4kHz = 0x10, DS3231_SquareWave8kHz = 0x18 };

class RTC_DS3231 {
  public:
    boolean begin(void);
    static void adjust(const DateTime& dt);
    bool lostPower(void);
    static DateTime now();
    static Ds3231SqwPinMode readSqwPinMode();
    static void writeSqwPinMode(Ds3231SqwPinMode mode);
};

// RTC using the internal millis() clock, has to be initialized before use
// NOTE: this clock won't be correct once the millis() timer rolls over (>49d?)
class RTC_Millis {
  public:
    static void begin(const DateTime& dt) {
      adjust(dt);
    }
    static void adjust(const DateTime& dt);
    static DateTime now();

  protected:
    static long offset;
};

#endif // _RTCLIB_H_

RTC_DS3231 rtc;

#define VOLTIN           A0 // Voltage input pin
#define OUTPIN           7 // Output pin to control
#define INDPIN           5 // Output Indicator pin
#define INVINDPIN        4 // Inverse Output indicator pin

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Adafruit_ADS1115 ads;  // ADS1115 Connected to SDA / SCL

//////////////////////////////////////////////////////////
//EEPROM MEMORY MAP

int Eaddress1 = 0; //first run
int Eaddress2 = 1; //low voltage trigger
int Eaddress3 = 2; //target voltage trigger
int Eaddress4 = 3; // multiplier
int Eaddress5 = 4; // relay off time
int Eaddress6 = 5; // relay on time
int Eaddress7 = 6;

byte value;

/////////////////////////////////////////////////////////////////////////
//USER SETTINGS BEGIN

float targetvolt = 54; // When this target voltage is reached switch off relay only if current is 0
float lowvolt = 48; // Voltage to trigger on relay.
float multiplier = 5.75; // 2 - convert reading to 75mv / 300amp shunt. 1 is 100mv/100amp - 1.33 is 75mv/100amp
float offset = 1.025 ; // correction factor for analog voltage input.

int starthouroff = 23; // relay not on after 11 pm
int stophouroff = 6 ; // relay not on before 6 am


//USER SETTINGS END
//////////////////////////////////////////////////////////////////////////////

int lowvoltset ;
int targetvoltset;
int starthouroffset;
int stophouroffset;

//prescaler bits
const unsigned char PS_16 = (1 << ADPS2);
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);


float amps;

int16_t adcresult;
float voltage;
float ampsaverage;
int sensorValue;
bool offtime = false ;
bool overrideoff = false;

const int numReadings = 100;

int16_t readings[numReadings];
int readIndex = 0;
int16_t total = 0;
int16_t average = 0;

ResponsiveAnalogRead analog(VOLTIN, true); // setup filtering





void setup(void)
{
  ADCSRA &= ~PS_128;  // remove bits set by Arduino library
  ADCSRA |= PS_16;    // set our own prescaler to 16 -  1mhz sampling

  pinMode(OUTPIN, OUTPUT);
  digitalWrite(OUTPIN, HIGH); //set to off on startup

  ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV
  ads.begin();
  rtc.begin();
  Serial.begin(115200);
  Serial.println("Press P for details, O for relay on, R for relay off, E for settings");

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }

  //get saved settings

  if (EEPROM.read(Eaddress1) == 1) {
    lowvolt = EEPROM.read(Eaddress2);
    targetvolt = EEPROM.read(Eaddress3);
    starthouroff = EEPROM.read(Eaddress5);
    stophouroff = EEPROM.read(Eaddress6);
  }
  else
  {
    EEPROM.write(Eaddress1, 1) ;
    EEPROM.write(Eaddress2, lowvolt) ;
    EEPROM.write(Eaddress3, targetvolt);
    EEPROM.write(Eaddress5, starthouroff);
    EEPROM.write(Eaddress6, stophouroff);
  }

}

void loop(void)
{

  digitalWrite(INDPIN, digitalRead(OUTPIN));
  digitalWrite(INVINDPIN, !digitalRead(OUTPIN));

  DateTime now = rtc.now(); //get the current date and time

  adcresult = ads.readADC_Differential_2_3();

  total = total - readings[readIndex];
  readings[readIndex] = adcresult;
  total = total + readings[readIndex];
  readIndex = readIndex + 1;
  if (readIndex >= numReadings)
  {
    readIndex = 0;
  }

  average = total / numReadings;
  amps = ((float)average * 256) / 32768; // 100mv / 100amp shunt
  amps = amps * multiplier; // convert reading to other shunt.

  analog.update();

  voltage = analog.getValue() * (5.0 / 1023.0); // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
  voltage = voltage * 13 ; //multiply by 13 due to divider resistors input 0-65v 120k/10k-r1/r2
  voltage = voltage * offset ;

  if (now.hour() >= starthouroff || now.hour() < stophouroff )
  {
    offtime = true;
  }
  else
  {
    offtime = false;
  }


  if (offtime == true || overrideoff == true)
  {

    if (digitalRead(OUTPIN) != HIGH) {



      Serial.print(now.day(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.year(), DEC);
      Serial.print(' ');
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.print(now.second(), DEC);
      Serial.print(' ');
      if (offtime == true) {
        Serial.println("Offtime - Relay OFF");
      }
      else
      {
        Serial.println("Override - Relay OFF");
      }
    }
    digitalWrite(OUTPIN, HIGH);  // if offtime or override is true then just switch off regardless.

  }
  else
  {

    if (voltage >= targetvolt && adcresult >= -40)
    {

      if (digitalRead(OUTPIN) != HIGH) {
        Serial.print(now.day(), DEC);
        Serial.print('/');
        Serial.print(now.month(), DEC);
        Serial.print('/');
        Serial.print(now.year(), DEC);
        Serial.print(' ');
        Serial.print(now.hour(), DEC);
        Serial.print(':');
        Serial.print(now.minute(), DEC);
        Serial.print(':');
        Serial.print(now.second(), DEC);
        Serial.print(' ');
        Serial.println("Finished charging - Relay OFF");
      }
      digitalWrite(OUTPIN, HIGH); ///switch off relay because charger has stopped charging.
    }
    else
    {
      if (adcresult < -100) {
        if (digitalRead(OUTPIN) != LOW)
        {

          Serial.print(now.day(), DEC);
          Serial.print('/');
          Serial.print(now.month(), DEC);
          Serial.print('/');
          Serial.print(now.year(), DEC);
          Serial.print(' ');
          Serial.print(now.hour(), DEC);
          Serial.print(':');
          Serial.print(now.minute(), DEC);
          Serial.print(':');
          Serial.print(now.second(), DEC);
          Serial.print(' ');
          Serial.println("Relay ON - Resumed");
        }
        digitalWrite(OUTPIN, LOW); //switch on relay because it's in the middle of charging, resume.
      }
    }

    if (voltage <= lowvolt) {

      if (digitalRead(OUTPIN) != LOW)
      {
        Serial.print(now.day(), DEC);
        Serial.print('/');
        Serial.print(now.month(), DEC);
        Serial.print('/');
        Serial.print(now.year(), DEC);
        Serial.print(' ');
        Serial.print(now.hour(), DEC);
        Serial.print(':');
        Serial.print(now.minute(), DEC);
        Serial.print(':');
        Serial.print(now.second(), DEC);
        Serial.print(' ');
        Serial.println("Low voltage triggered - Relay ON");
      }
      digitalWrite(OUTPIN, LOW);  // low voltage trigger switch on relay if offtime is not true.
    }

  }

  if (voltage <= lowvolt && digitalRead(OUTPIN) == LOW)
  {
    //generators yet to start, failed to start or breaker has tripped.
    //maybe do something here.
  }


  if ( Serial.available() > 0)
  {

    char c = toupper(Serial.read());
    if ( c == 'O' )
    { // override relay

      digitalWrite(OUTPIN, LOW);
      overrideoff = false;
      Serial.print(now.day(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.year(), DEC);
      Serial.print(' ');
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.print(now.second(), DEC);
      Serial.print(' ');
      Serial.println("Override - Relay ON");


    }
    else if ( c == 'P' )
    { // print details.

      Serial.print(now.day(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.year(), DEC);
      Serial.print(' ');
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.print(now.second(), DEC);
      Serial.print(' ');
      Serial.print(" raw adc: ");
      Serial.print(adcresult);
      Serial.print(" average adc: ");
      Serial.print(average);
      Serial.print(" / volt: ");
      Serial.print(voltage);
      Serial.print(" / amps: ");
      Serial.print(amps);
      Serial.print(" / watts: ");
      Serial.print(voltage * amps);

      if (offtime == true)
      {
        Serial.print(" / OFFTIME");
      }
      else
      {
        Serial.print(" / ONTIME");
      }
      if (digitalRead(OUTPIN) == LOW)
      {
        Serial.print(" / Relay On");
      }
      else
      {
        Serial.print(" / Relay Off");
      }
      if (overrideoff == true)
      {
        Serial.print(" / MAN OFF");
      }
      Serial.println();

    }
    else if ( c == 'E' )
    { // print settings.

      Serial.print("low volt level: ");
      Serial.print(lowvolt);
      Serial.print(" target voltage: ");
      Serial.print(targetvolt);
      Serial.print(" start off hour: ");
      Serial.print(starthouroff);
      Serial.print(" stop  off hour: ");
      Serial.print(stophouroff);
      Serial.print(" volt offset: ");
      Serial.println(offset, 4);
      Serial.println("L(INT) Sets low voltage trigger. T(INT) Sets target voltage trigger. X(INT) Sets start of off time HR. Y(INT) Sets end of off time HR.");
      Serial.println("Examples. L48 sets low voltage trigger to 48v. Y5 sets generator can start after 05:00 AM. X19 sets generator can not start after 19:00 PM");


    }
    else if ( c == 'R' )
    {
      //digitalWrite(OUTPIN, HIGH);
      overrideoff = true;
    }
    else if ( c == 'L' )
    {

      lowvoltset = Serial.parseInt();
      if (lowvoltset <= 65 && lowvoltset > 0)
      {
        lowvolt = lowvoltset;
        EEPROM.update(Eaddress2, lowvoltset) ;
        Serial.println("Set lowvolt");
      }
      else
      {
        Serial.println("Incorrect input");
      }
    }
    else if ( c == 'T' )
    {

      targetvoltset = Serial.parseInt();
      if (targetvoltset <= 65 && targetvoltset > 0)
      {
        targetvolt = targetvoltset ;
        EEPROM.update(Eaddress3, targetvoltset) ;
        Serial.println("Set targetvolt");
      }
      else
      {
        Serial.println("Incorrect input");
      }
    }
    else if ( c == 'X' )
    {

      starthouroffset = Serial.parseInt();
      if (starthouroffset <= 24 && starthouroffset > 0)
      {
        starthouroff = starthouroffset;
        EEPROM.update(Eaddress5, starthouroffset) ;
        Serial.println("Set starthouroff");
      }
      else
      {
        Serial.println("Incorrect input");
      }
    }
    else if ( c == 'Y' )
    {

      stophouroffset = Serial.parseInt();
      if (stophouroffset <= 24 && stophouroffset > 0)
      {
        stophouroff = stophouroffset;
        EEPROM.update(Eaddress6, stophouroffset) ;
        Serial.println("Set stophouroff");
      }
      else
      {
        Serial.println("Incorrect input");
      }
    }
    else
    {
      Serial.println("Syntax Error");
      Serial.flush();
    }
  }




}

//float targetvolt = 54; // When this target voltage is reached switch off relay only if current is 0
//float lowvolt = 48; /
//int starthouroff = 23; // relay not on after 11 pm
//int stophouroff = 6 ; // relay not on before 6 am
//EEPROM.write(Eaddress1, 1) ;
////
//int Eaddress1 = 0; //first run
//int Eaddress2 = 1; //low voltage trigger
//int Eaddress3 = 2; //target voltage trigger
//int Eaddress4 = 3; // multiplier
//int Eaddress5 = 4; // relay off time
//int Eaddress6 = 5; // relay on time
//int Eaddress7 = 6;

//int lowvoltset ;
//int targetvoltset;
//int starthouroffset;
//int stophouroffset;

///////////////////////////////////////////////////////////////////////////////////////////////////////
//LOOP END


static uint8_t read_i2c_register(uint8_t addr, uint8_t reg) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire.endTransmission();

  Wire.requestFrom(addr, (byte)1);
  return Wire._I2C_READ();
}

static void write_i2c_register(uint8_t addr, uint8_t reg, uint8_t val) {
  Wire.beginTransmission(addr);
  Wire._I2C_WRITE((byte)reg);
  Wire._I2C_WRITE((byte)val);
  Wire.endTransmission();
}


////////////////////////////////////////////////////////////////////////////////
// utility code, some of this could be exposed in the DateTime API if needed

const uint8_t daysInMonth [] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

// number of days since 2000/01/01, valid for 2001..2099
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
  if (y >= 2000)
    y -= 2000;
  uint16_t days = d;
  for (uint8_t i = 1; i < m; ++i)
    days += pgm_read_byte(daysInMonth + i - 1);
  if (m > 2 && y % 4 == 0)
    ++days;
  return days + 365 * y + (y + 3) / 4 - 1;
}

static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
  return ((days * 24L + h) * 60 + m) * 60 + s;
}

////////////////////////////////////////////////////////////////////////////////
// DateTime implementation - ignores time zones and DST changes
// NOTE: also ignores leap seconds

DateTime::DateTime (uint32_t t) {
  t -= SECONDS_FROM_1970_TO_2000;    // bring to 2000 timestamp from 1970

  ss = t % 60;
  t /= 60;
  mm = t % 60;
  t /= 60;
  hh = t % 24;
  uint16_t days = t / 24;
  uint8_t leap;
  for (yOff = 0; ; ++yOff) {
    leap = yOff % 4 == 0;
    if (days < 365 + leap)
      break;
    days -= 365 + leap;
  }
  for (m = 1; ; ++m) {
    uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
    if (leap && m == 2)
      ++daysPerMonth;
    if (days < daysPerMonth)
      break;
    days -= daysPerMonth;
  }
  d = days + 1;
}

DateTime::DateTime (uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) {
  if (year >= 2000)
    year -= 2000;
  yOff = year;
  m = month;
  d = day;
  hh = hour;
  mm = min;
  ss = sec;
}

DateTime::DateTime (const DateTime & copy):
  yOff(copy.yOff),
  m(copy.m),
  d(copy.d),
  hh(copy.hh),
  mm(copy.mm),
  ss(copy.ss)
{}

static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}

// A convenient constructor for using "the compiler's time":
//   DateTime now (__DATE__, __TIME__);
// NOTE: using F() would further reduce the RAM footprint, see below.
DateTime::DateTime (const char* date, const char* time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  yOff = conv2d(date + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (date[0]) {
    case 'J': m = date[1] == 'a' ? 1 : m = date[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = date[2] == 'r' ? 4 : 8; break;
    case 'M': m = date[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(date + 4);
  hh = conv2d(time);
  mm = conv2d(time + 3);
  ss = conv2d(time + 6);
}

// A convenient constructor for using "the compiler's time":
// This version will save RAM by using PROGMEM to store it by using the F macro.
//   DateTime now (F(__DATE__), F(__TIME__));
DateTime::DateTime (const __FlashStringHelper * date, const __FlashStringHelper * time) {
  // sample input: date = "Dec 26 2009", time = "12:34:56"
  char buff[11];
  memcpy_P(buff, date, 11);
  yOff = conv2d(buff + 9);
  // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  switch (buff[0]) {
    case 'J': m = buff[1] == 'a' ? 1 : m = buff[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = buff[2] == 'r' ? 4 : 8; break;
    case 'M': m = buff[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
  }
  d = conv2d(buff + 4);
  memcpy_P(buff, time, 8);
  hh = conv2d(buff);
  mm = conv2d(buff + 3);
  ss = conv2d(buff + 6);
}

uint8_t DateTime::dayOfTheWeek() const {
  uint16_t day = date2days(yOff, m, d);
  return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6
}

uint32_t DateTime::unixtime(void) const {
  uint32_t t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  t += SECONDS_FROM_1970_TO_2000;  // seconds from 1970 to 2000

  return t;
}

long DateTime::secondstime(void) const {
  long t;
  uint16_t days = date2days(yOff, m, d);
  t = time2long(days, hh, mm, ss);
  return t;
}

DateTime DateTime::operator+(const TimeSpan & span) {
  return DateTime(unixtime() + span.totalseconds());
}

DateTime DateTime::operator-(const TimeSpan & span) {
  return DateTime(unixtime() - span.totalseconds());
}

TimeSpan DateTime::operator-(const DateTime & right) {
  return TimeSpan(unixtime() - right.unixtime());
}

////////////////////////////////////////////////////////////////////////////////
// TimeSpan implementation

TimeSpan::TimeSpan (int32_t seconds):
  _seconds(seconds)
{}

TimeSpan::TimeSpan (int16_t days, int8_t hours, int8_t minutes, int8_t seconds):
  _seconds((int32_t)days * 86400L + (int32_t)hours * 3600 + (int32_t)minutes * 60 + seconds)
{}

TimeSpan::TimeSpan (const TimeSpan & copy):
  _seconds(copy._seconds)
{}

TimeSpan TimeSpan::operator+(const TimeSpan & right) {
  return TimeSpan(_seconds + right._seconds);
}

TimeSpan TimeSpan::operator-(const TimeSpan & right) {
  return TimeSpan(_seconds - right._seconds);
}

static uint8_t bcd2bin (uint8_t val) {
  return val - 6 * (val >> 4);
}
static uint8_t bin2bcd (uint8_t val) {
  return val + 6 * (val / 10);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_Millis implementation

long RTC_Millis::offset = 0;

void RTC_Millis::adjust(const DateTime & dt) {
  offset = dt.unixtime() - millis() / 1000;
}

DateTime RTC_Millis::now() {
  return (uint32_t)(offset + millis() / 1000);
}

////////////////////////////////////////////////////////////////////////////////
// RTC_DS3231 implementation

boolean RTC_DS3231::begin(void) {
  Wire.begin();
  return true;
}

bool RTC_DS3231::lostPower(void) {
  return (read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG) >> 7);
}

void RTC_DS3231::adjust(const DateTime & dt) {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0); // start at location 0
  Wire._I2C_WRITE(bin2bcd(dt.second()));
  Wire._I2C_WRITE(bin2bcd(dt.minute()));
  Wire._I2C_WRITE(bin2bcd(dt.hour()));
  Wire._I2C_WRITE(bin2bcd(0));
  Wire._I2C_WRITE(bin2bcd(dt.day()));
  Wire._I2C_WRITE(bin2bcd(dt.month()));
  Wire._I2C_WRITE(bin2bcd(dt.year() - 2000));
  Wire.endTransmission();

  uint8_t statreg = read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG);
  statreg &= ~0x80; // flip OSF bit
  write_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG, statreg);
}

DateTime RTC_DS3231::now() {
  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE((byte)0);
  Wire.endTransmission();

  Wire.requestFrom(DS3231_ADDRESS, 7);
  uint8_t ss = bcd2bin(Wire._I2C_READ() & 0x7F);
  uint8_t mm = bcd2bin(Wire._I2C_READ());
  uint8_t hh = bcd2bin(Wire._I2C_READ() & 0b111111); //  & 0b111111 Makes clock hours 24 hour format.
  Wire._I2C_READ();
  uint8_t d = bcd2bin(Wire._I2C_READ());
  uint8_t m = bcd2bin(Wire._I2C_READ());
  uint16_t y = bcd2bin(Wire._I2C_READ()) + 2000;

  return DateTime (y, m, d, hh, mm, ss);
}

Ds3231SqwPinMode RTC_DS3231::readSqwPinMode() {
  int mode;

  Wire.beginTransmission(DS3231_ADDRESS);
  Wire._I2C_WRITE(DS3231_CONTROL);
  Wire.endTransmission();

  Wire.requestFrom((uint8_t)DS3231_ADDRESS, (uint8_t)1);
  mode = Wire._I2C_READ();

  mode &= 0x93;
  return static_cast<Ds3231SqwPinMode>(mode);
}

void RTC_DS3231::writeSqwPinMode(Ds3231SqwPinMode mode) {
  uint8_t ctrl;
  ctrl = read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL);

  ctrl &= ~0x04; // turn off INTCON
  ctrl &= ~0x18; // set freq bits to 0

  if (mode == DS3231_OFF) {
    ctrl |= 0x04; // turn on INTCN
  } else {
    ctrl |= mode;
  }
  write_i2c_register(DS3231_ADDRESS, DS3231_CONTROL, ctrl);

  //Serial.println( read_i2c_register(DS3231_ADDRESS, DS3231_CONTROL), HEX);debug
}