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

0 Members and 1 Guest are viewing this topic.

Offline welshman

  • Full Member
  • ***
  • Posts: 136
  • Karma: +6/-1
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: 136
  • Karma: +6/-1
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: 136
  • Karma: +6/-1
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: 136
  • Karma: +6/-1
Re: Arduino Powerjack companion.
« Reply #3 on: February 21, 2017, 12:52:49 pm »
and a picture.


Offline oztules

  • Forum Advisors
  • Hero Member
  • ****
  • Posts: 1177
  • Karma: +105/-8
  • 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: 136
  • Karma: +6/-1
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: 136
  • Karma: +6/-1
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: 136
  • Karma: +6/-1
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
}

Offline welshman

  • Full Member
  • ***
  • Posts: 136
  • Karma: +6/-1
Re: Arduino Powerjack companion.
« Reply #8 on: September 26, 2017, 05:24:16 pm »
been a while since i uploaded the code update. fault detection, trigger delay. some refinements and reformatting.

the system is in use and runs perfectly daily.

hopefully some or all of this code will be useful.

Code: [Select]
// PowerJack Inverter Generator control. v2.0 28/08/17
// i2c oled disp 128x32
// 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 <Adafruit_GFX.h>

#include <Adafruit_SSD1306.h>
#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>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
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;


char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
Adafruit_ADS1115 ads;  // ADS1115 Connected to SDA / SCL

//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);




#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


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

float targetvolt = 57; // When this target voltage is reached switch off relay
float bulkcharge = -120;
float fullcharge = -30;

float lowvolt = 48; // Voltage to trigger on relay.
float multiplier = 6.7; // 2 - convert reading to 75mv / 300amp shunt. 1 is 100mv/100amp - 1.33 is 75mv/100amp
float offset = 1.0080 ; // 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
//////////////////////////////////////////////////////////////////////////////


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

int Eaddress1 = 0; //first run
int Eaddress2 = 10; //low voltage trigger
int Eaddress3 = 30; //target voltage trigger
int Eaddress4 = 50; // multiplier
int Eaddress5 = 4; // relay off time
int Eaddress6 = 5; // relay on time
int Eaddress7 = 6; // current status output on or off 1/0
int Eaddress8 = 70; // voltage offset store as float
int Eaddress9 = 90; // amp multiplier store as float

//////////////////////////////////////////////////////////
//VARIABLES

byte value;
int currentstatus;
float lowvoltset ;
float targetvoltset;
int starthouroffset;
int stophouroffset;
bool bulkonly = true;
int16_t triggercount = 0;
bool charging = false;
int16_t targetcount = 5000;
int16_t resetcount = 0;
int16_t resetcounttarget = 8000;
int16_t faultcount = 0 ;
int16_t faultmax = 100 ;
int16_t triggerdelay = 20000;
int16_t errordelay = 120000;
unsigned long triggertime;
unsigned long errortime;
float amps;
int16_t adcresult;
float voltage;
float ampsaverage;
int sensorValue;
bool fault;

bool offtime = false ;
bool overrideoff = false;
bool overrideon = false;
float targetcharge ;
const int numReadings = 20;
bool customcharge = false;
float customchargevalue ;
int16_t readings[numReadings];
int readIndex = 0;
int16_t total = 0;
int16_t average = 0;

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

///////////////////////////////////////////////////////////
//SETUP
/////////////////////////////////////////////////////////////////////////////
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("PJC v2.01 - P=Details, I=on, O=off, N=Auto, E=Setting, J=Toggle Bulk");
  //////////////////////////////////////////////////////////

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

  //////////////////////////////////////////////////////////
  //get saved settings

  if (EEPROM.read(Eaddress1) == 1) { //check if eeprom address 1 indicates first run

    EEPROM.get(Eaddress2, lowvolt) ;
    EEPROM.get(Eaddress3, targetvolt);
    starthouroff = EEPROM.read(Eaddress5);
    stophouroff = EEPROM.read(Eaddress6);
    currentstatus = EEPROM.read(Eaddress7);
    EEPROM.get(Eaddress8, offset) ;
    EEPROM.get(Eaddress9, multiplier) ;
  }
  else // if first run is true
  {
    EEPROM.write(Eaddress1, 1) ;
    EEPROM.put(Eaddress2, lowvolt) ;
    EEPROM.put(Eaddress3, targetvolt);
    EEPROM.write(Eaddress5, starthouroff);
    EEPROM.write(Eaddress6, stophouroff);
    EEPROM.put(Eaddress8, offset);
    EEPROM.put(Eaddress9, multiplier) ;
    rtc.adjust (DateTime(2017, 9, 17, 13, 29, 32)); //set time to compiler time.
  }

  ////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.clearDisplay();
  //////////////////////////////////////////////////////////
}

//////////////////////////////////////////////////////////
//MAIN LOOP

void loop(void)
{

  chargetype();
  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++;
  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 ;

  detectchargestatus();
  whattime(now);

  if (charging == false)
  {
    if (fault == false)
    {
      lowvoltdetect(now);
    }
    recovercountreset();
  }

  detectstopcharge(now);
  detectfault(now);
  handleserial(now);
  oledout(now);

}

///////////////////////////////////////////////////////////////////////////////////////////////////////
// MAIN LOOP END

/////////////////////////////////////////////////////////////////////////////
void serialprintdatetime(DateTime now)
{
  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(' ');
}
/////////////////////////////////////////////////////////////////////////////


void oledout(DateTime now) {

  //////////////////////////////////////////////////////////
  //OUTPUT TO OLED DISPLAY

  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print(now.day(), DEC);
  display.print('.');
  display.print(now.month(), DEC);
  display.print(' ');
  display.print(now.hour(), DEC);
  display.print(':');
  display.print(now.minute(), DEC);
  display.print(':');
  display.print(now.second(), DEC);
  display.println();
  display.print("ADC=");
  display.print(adcresult);
  display.print(" MADC=");
  display.println(average);
  display.print("V");
  display.print(voltage);
  display.print(" A");
  display.print(amps);
  display.print(" W");
  display.print(voltage * amps);
  display.setCursor(0, 24);
  if (offtime == true)
  {
    display.print("DISABLE ");
  }
  else
  {
    display.print("ACTIVE ");
  }
  if (digitalRead(OUTPIN) == LOW)
  {
    display.print("ON ");
  }
  else
  {
    display.print("OFF ");
  }
  if (overrideoff == true || overrideon == true)
  {
    display.print("OVERIDE");
  }
  display.display();
 
if ((millis() / 100) % 10 == 1)  {
   display.clearDisplay();
  }
  //////////////////////////////////////////////////////////
  //OLED DISPLAY OUTPUT END

}

void detectfault(DateTime now) {

  if (charging == false && currentstatus == 1 && millis() > triggertime + triggerdelay) // if no current and should be on and is over x time since switched on then trigger fault
  {
    errortime = millis();

    serialprintdatetime(now);
    Serial.println("FAULT - OFF");

    digitalWrite(OUTPIN, HIGH); // switch off output.
    customcharge = false ;
    customchargevalue = 0 ;
    bulkonly = true;
    if (EEPROM.read(Eaddress7) != 0) {
      EEPROM.write(Eaddress7, 0); // store off in eeprom currentstatus
    }
    currentstatus = 0;

    fault = true ;
  }

  if (millis() > errortime + errordelay) // if fault true then wait x amount of time to reset fault to false
  {
    fault = false;

  }

}
void detectchargestatus(void) {

  if (average < 0) {
    charging = true;
  }
  else
  {
    charging = false;
  }

  if (charging == true)
  {
    triggercount = 0 ;
    resetcount = 0;
  }

}
/////////////////////////////////////////////////////////////////////////////
void whattime (DateTime now)
{

  if (now.hour() >= starthouroff || now.hour() < stophouroff )
  {
    offtime = true; // output is not allowed to be on
  }
  else
  {
    offtime = false; // output is allowed to be on
  }

  if (overrideon == true ) offtime = false; // if manual over ride is on then ignore offtime.

}
/////////////////////////////////////////////////////////////////////////////
void chargetype (void)
{
  if (bulkonly == true)
  {
    targetcharge = bulkcharge;
  }
  else
  {
    targetcharge = fullcharge;
  }

  if (customcharge == true)
  {
    bulkonly = false;
    targetcharge = customchargevalue;
  }
}
/////////////////////////////////////////////////////////////////////////////
void lowvoltdetect (DateTime now)

{
  if (voltage <= lowvolt) { // low voltage trigger code

    if (triggercount >= targetcount) { // if trigger delay count has reached target

      if (digitalRead(OUTPIN) != LOW) // output to serial if change
      {
        serialprintdatetime(now);
        Serial.println("Low volt trigger - Gen ON");
      }
      digitalWrite(OUTPIN, LOW);  // low voltage trigger switch on relay
      triggertime = millis(); //store in memory trigger time
      if (EEPROM.read(Eaddress7) != 1) {
        EEPROM.write(Eaddress7, 1); // store on in eeprom currentstatus
      }

      currentstatus = 1; //set in memory current running status
      triggercount = 0; // reset trigger delay count to 0

    }
    else // if trigger delay count is less than target to swtch on
    {
      if (voltage < (lowvolt - 2))
      {
        triggercount = triggercount + 2; // increment trigger delay count every loop
      }
      else
      {
        triggercount++; // increment trigger delay count every loop

      }
    }
  }
}
/////////////////////////////////////////////////////////////////////////////
void detectstopcharge (DateTime now)
{
  if (offtime == true || overrideoff == true) // if either the current time is in offtime or the manual off over ride has been triggered then print to serial and switch off output
  {
    if (digitalRead(OUTPIN) != HIGH) {
      serialprintdatetime(now);
      if (offtime == true) {
        Serial.println("Offtime - OFF");
      }
      else
      {
        Serial.println("Override - OFF");
      }
    }
    digitalWrite(OUTPIN, HIGH);  // switch off output.

    if (EEPROM.read(Eaddress7) != 0) {
      EEPROM.write(Eaddress7, 0); // store off in eeprom currentstatus
    }
    currentstatus = 0;

    //////////////////////////////////////////////////////////
  }
  else // the result of offtime being false or and overrideoff being false, keep charging.
  {
    //////////////////////////////////////////////////////////

    if (voltage >= targetvolt && average >= targetcharge) // if the battery voltage is at target and the current is less than x amount then switch off output. end of charge. average/adcresult
    {
      if (digitalRead(OUTPIN) != HIGH) {
        serialprintdatetime(now);
        Serial.println("End charge - OFF");
      }
      digitalWrite(OUTPIN, HIGH); // switch off output.
      customcharge = false ;
      customchargevalue = 0 ;
      bulkonly = true;
      if (EEPROM.read(Eaddress7) != 0) {
        EEPROM.write(Eaddress7, 0); // store off in eeprom currentstatus
      }
      currentstatus = 0;
    }
    //////////////////////////////////////////////////////////

    else
    {
      //////////////////////////////////////////////////////////
      if (currentstatus == 1) { // if last status read from eeprom is 1 for on then resume charging.
        if (digitalRead(OUTPIN) != LOW)
        {
          serialprintdatetime(now);
          Serial.println("ON - Resumed");
        }
        digitalWrite(OUTPIN, LOW); //switch on relay because it's in the middle of charging, resume.
      }
      //////////////////////////////////////////////////////////
    }

  }

}

/////////////////////////////////////////////////////////////////////////////
void recovercountreset (void)
{
  if (triggercount > 0) {
    if (voltage > lowvolt) {
      if (resetcount >= resetcounttarget) {
        triggercount = 0 ;
        resetcount = 0;
        Serial.println("Trigger Count Reset");
      }
      else
      {
        resetcount++;
      }
    }
  }
}

/////////////////////////////////////////////////////////////////////////////
void handleserial (DateTime now)
{
  if ( Serial.available() > 0)
  {
    char c = toupper(Serial.read());
    if ( c == 'I' )
    { // override relay ON
      digitalWrite(OUTPIN, LOW);
      overrideoff = false;
      overrideon = true;
      serialprintdatetime(now);
      Serial.println("Override - ON");
    }
    else if ( c == 'P' )
    { // print details.
      serialprintdatetime(now);
      Serial.println(' ');
      Serial.print("adc:");
      Serial.print(adcresult);
      Serial.print("|av adc:");
      Serial.print(average);
      Serial.print("|target adc:");
      Serial.println(targetcharge);
      Serial.print("V:");
      Serial.print(voltage);
      Serial.print("|A:");
      Serial.print(amps);
      Serial.print("|W:");
      Serial.println(voltage * amps);
      Serial.print("Low Volt trigger:");
      Serial.print(triggercount);
      Serial.print(":");
      Serial.println(resetcount);
      if (bulkonly == true)
      {
        Serial.print("BULK");
      }
      else
      {
        if (customcharge == true)
        {
          Serial.print("CUSTOM");
        }
        else
        {


          Serial.print("FULL");

        }
      }

      if (offtime == true)
      {
        Serial.print(" / OFFTIME");
      }
      else
      {
        Serial.print(" / ONTIME");
      }
      if (digitalRead(OUTPIN) == LOW)
      {
        Serial.print(" / Gen On");
      }
      else
      {
        Serial.print(" / Gen Off");
      }
      if (overrideoff == true  || overrideon == true)
      {
        Serial.print(" / OVERIDE");
      }
      if (charging == true)
      {
        Serial.print(" / CHARGING");
      }
      else
      {
        Serial.print(" / DISCHARGING");
      }
      Serial.println();
    }
    else if ( c == 'E' )
    { // print settings.
      Serial.print("low volt=");
      Serial.println(lowvolt, 2);
      Serial.print("target volt=");
      Serial.println(targetvolt, 2);
      Serial.print("start off hr=");
      Serial.println(starthouroff);
      Serial.print("stop off hr=");
      Serial.println(stophouroff);
      Serial.print("amp mult=");
      Serial.println(multiplier);
      Serial.print("target charge=");
      Serial.println(targetcharge);
      Serial.print("custom val=");
      Serial.println(customchargevalue);
      Serial.print("volt offset=");
      Serial.println(offset, 4);

      //  Serial.println("L:Low volt|T:Target volt|X:Off time start HR|Y:Off time end HR|H:Charge target");
      //  Serial.println("L48-low volt=48v. Y5- gen start after 05:00 X19-gen off after 19:00");
      //  Serial.println("AMP MULT .01 INC = A, DEC = B. VOLT OFFSET .001 INC = C, DEC = D");
    }
    else if ( c == 'N' ) // set all over rides to off
    {
      overrideoff = false;
      overrideon = false;
      Serial.print("Auto Mode");
    }
    else if ( c == 'U' ) // set time
    {
      //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); //set time to compiler time.
    }
    else if ( c == 'H' ) // set custom charge target value
    {
      int customchargeinput = Serial.parseInt();
      if (customchargeinput <= 1024 && customchargeinput > 0)
      {
        customcharge = true ;
        customchargevalue = customchargeinput - (customchargeinput * 2);
        Serial.println("Set");
      }
      else if (customchargeinput == 0)
      {
        customcharge = true ;
        customchargevalue = 0 ;
        Serial.println("Set");
      }
      else
      {
        Serial.println("err");
      }
    }
    else if ( c == 'A' ) // AMP MULTIPLIER INCREASE .01
    {
      multiplier = multiplier + .01;
      Serial.println(multiplier);
      EEPROM.put(Eaddress9, multiplier) ;
    }
    else if ( c == 'B' ) // AMP MULTIPLIER DECREASE .01
    {
      multiplier = multiplier - .01;
      Serial.println(multiplier);
      EEPROM.put(Eaddress9, multiplier) ;
    }
    else if ( c == 'C' ) // VOLTAGE OFFSET INCREASE .001
    {
      offset = offset + .001;
      Serial.println(offset, 4);
      EEPROM.put(Eaddress8, offset) ;
    }
    else if ( c == 'D' ) // VOLTAGE OFFSET DECREASE .001
    {
      offset = offset - .001;
      Serial.println(offset, 4);
      EEPROM.put(Eaddress8, offset) ;
    }

    else if ( c == 'J' ) // toggle bulk only
    {
      bulkonly = !bulkonly;
      customcharge = false ;
      customchargevalue = 0 ;
    }

    else if ( c == 'O' ) // over ride swtich off
    {
      digitalWrite(OUTPIN, HIGH);
      overrideoff = true;
      overrideon = false;
    }
    else if ( c == 'L' ) // set low voltage level
    {
      lowvoltset = Serial.parseFloat();
      if (lowvoltset <= 65 && lowvoltset > 0)
      {
        lowvolt = lowvoltset;
        EEPROM.put(Eaddress2, lowvoltset) ;
        Serial.println("Set");
      }
      else
      {
        Serial.println("err");
      }
    }
    else if ( c == 'T' ) // set target charge voltage
    {
      targetvoltset = Serial.parseFloat();
      if (targetvoltset <= 65 && targetvoltset > 0)
      {
        targetvolt = targetvoltset ;
        EEPROM.put(Eaddress3, targetvoltset) ;
        Serial.println("Set");
      }
      else
      {
        Serial.println("err");
      }
    }
    else if ( c == 'X' ) // set start of offtime
    {
      starthouroffset = Serial.parseInt();
      if (starthouroffset <= 24 && starthouroffset > 0)
      {
        starthouroff = starthouroffset;
        EEPROM.update(Eaddress5, starthouroffset) ;
        Serial.println("Set");
      }
      else
      {
        Serial.println("err");
      }
    }
    else if ( c == 'Y' ) // set end of offtime
    {
      stophouroffset = Serial.parseInt();
      if (stophouroffset <= 24 && stophouroffset > 0)
      {
        stophouroff = stophouroffset;
        EEPROM.update(Eaddress6, stophouroffset) ;
        Serial.println("Set");
      }
      else
      {
        Serial.println("err");
      }
    }
    else
    {
      Serial.println("Err");
      Serial.flush();
    }
  }
}




//////////////////////////////////////////////////////////
// DS3231 CODE

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';
}



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
}