Go Down

Topic: Time based relay control for aquarium lights (Read 5187 times) previous topic - next topic

0coool

Mar 22, 2014, 03:47 am Last Edit: Mar 22, 2014, 03:58 am by 0coool Reason: 1
I'm using this code: https://github.com/d0ughb0y/Chauvet16 to control my aquarium. The problem I'm having is that the time control for the relays is based on a cycle. I need to be able to run my lights for 6-12 hours and I need them to come on at the same time every day.  This seems stupidly simple but I could really use some help here.

From Config.h
Code: [Select]

#define OUTLET1 "Actinic"
#define OUTLET2 "Lights"
#define OUTLET3 "Moon"
#define OUTLET4 "Pump"
#define OUTLET5 "Heater"
#define OUTLET6 "Kalk"
#define OUTLET7 "Skimmer"
#define OUTLET8 "Return"
#define OUTLET9 "Unused"
#define OUTLET10 "Unused"
#define OUTLET11 "Unused"
#define OUTLET12 "Unused"
#define OUTLET13 "Unused"
#define OUTLET14 "Unused"
#define OUTLET15 "Unused"
#define OUTLET16 "Unused"

#define MACRO1 "Feed Now"
#define MACRO2 "Feed"
#define MACRO3 "All Pumps On"
#define MACRO4 "All Pumps Off"

#define OUTLETDEFS Actinic,Lights, Moon, Pump, Heater, Kalk, Skimmer, Return,\
                  Unused9, Unused10, Unused11, Unused12, Unused13, Unused14, Unused15, Unused16
//define the default outlet program here. outlets must appear in exact order defined in outlet names definition
//program outletname, initial off time, on time, off time, days active, mode
#define OUTLETSDEFAULT {{OUTLET1,0,0,0,0xff,_auto},\
                       {OUTLET2,0,0,0,0xff,_auto},\
                       {OUTLET3,0,0,0,0xff,_auto},\
                       {OUTLET4,0,0,0,0xff,_auto},\
                       {OUTLET5,0,0,0,0xff,_auto},\
                       {OUTLET6,0,0,0,0xff,_auto},\
                       {OUTLET7,0,0,0,0xff,_auto},\
                       {OUTLET8,0,0,0,0xff,_auto}}      
#define MACROSDEFAULT {{MACRO1,0,1,0,0xff,_disabled},\
                      {MACRO2,15*SECS_PER_HOUR,240,SECS_PER_DAY-15*SECS_PER_HOUR-240,0xff,_auto},\
                      {MACRO3, 0,10*SECS_PER_MIN,0,0xff,_disabled},\
                      {MACRO4,10*SECS_PER_MIN,0,0,0xff,_disabled}}                    
#define ACTIONSDEFAULT {{{Feeder,0,2},{End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}},\
                       {{Pump0,240,0},{Return,150,90},{Feeder,30,2}, {End,0,0}, {End,0,0}, {End,0,0}},\
                       {{Actinic,0,600}, {Pump0,600,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}},\
                       {{Actinic,600,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}}}


raschemmel

Could you please use the MODIFY button to edit your post and highlight the code and click the "#" CODE button to create a scroll window for the code ?
Arduino UNOs, Pro-Minis, ATMega328, ATtiny85, LCDs, MCP4162, keypads,<br />DS18B20s,74c922,nRF24L01, RS232, SD card, RC fixed wing, quadcopter

0coool

This is from the code from the outlets file. I only pasted part of the config file because it has personal information.
Code: [Select]
/*
* Copyright (c) 2013 by Jerry Sy aka d0ughb0y
* mail-to: j3rry5y@gmail.com
* Commercial use of this software without
* permission is absolutely prohibited.
*/
void initOutlets() {
  DDRA = DDRC = 0xFF;
  PORTA = 0xFF;
  PORTC = 0;

  p(F("Outlet Timers OK"));
  logMessage(F("Outlet Timers initialized"));
}

inline uint16_t outletHandlerA() {
  static uint8_t p = 0;
  static uint8_t q = 0;
  static uint8_t i = 0;
  static boolean checkmacros = true;
  static unsigned long starttime = 0;

  if (checkmacros) {
    if (q==0)
      starttime=micros();
    if (conf.macrosRec[q].mode == _auto && _ActiveMacro>=4) {
      time_t total = conf.macrosRec[q].initoff+conf.macrosRec[q].ontime+conf.macrosRec[q].offtime;   
      time_t currSecs = elapsedSecsToday(now2()) % (total);
      if (conf.macrosRec[q].days & _BV(weekday(now2())) &&
        currSecs == conf.macrosRec[q].initoff /*&&
        currSecs < (macrosRec[q].initoff+macrosRec[q].ontime)*/){
        _ActiveMacro=q;
        _MacroTime=now2();   
      }
    }
    time_t deviceTime;
    if (q==_ActiveMacro)  {
      MacroActions_t *actions = (MacroActions_t*)conf.actions[q];
      deviceTime = now2() - _MacroTime;
      if (i<MAXMACROACTIONS && actions[i].outlet != End) {
        uint8_t idx= actions[i].outlet;
        if ((idx < MAXOUTLETS && (conf.outletRec[idx].mode==_macro ||
          conf.outletRec[idx].mode==_auto)) || idx==Feeder || idx==Pump0) {       
          if (deviceTime>actions[i].initoff &&
            deviceTime<=(actions[i].initoff+actions[i].ontime)) {
              _outletOn(idx);
            } else {
              _outletOff(idx);
            }
          if (idx<MAXOUTLETS) {
            if (deviceTime >= conf.macrosRec[q].ontime) {
              conf.outletRec[idx].mode=_auto;
            } else {
              conf.outletRec[idx].mode=_macro;
            }
          }
        }
        i++; 
      } else {
         i=0;
         checkmacros=false;
      }
      if (deviceTime >= conf.macrosRec[q].ontime) {
        _ActiveMacro=4;
        _MacroTime=0;
        _MacroCountDown=0;
        FeedModeOFF();
      } else {
        _MacroCountDown = conf.macrosRec[q].ontime - deviceTime;
      }
    }else {
      i=0;
      q++;
      if (q>=MAXMACROS) q=0;
      checkmacros = (q!=0);
    }
  } else {
    time_t total = conf.outletRec[p].initoff+conf.outletRec[p].ontime+conf.outletRec[p].offtime;
    if (conf.outletRec[p].mode == _macro && _ActiveMacro>=4 ) {
      conf.outletRec[p].mode=_auto;
      _MacroCountDown=0;
      FeedModeOFF();
    }
    if (conf.outletRec[p].mode == _auto && total>0) {
      time_t currSecs = elapsedSecsToday(now2()) % (total);
      if (conf.outletRec[p].days & _BV(weekday(now2())) &&
        currSecs >= conf.outletRec[p].initoff &&
        currSecs < (conf.outletRec[p].initoff+conf.outletRec[p].ontime)) {
        _outletOn(p);
      } else {
        _outletOff(p);
      }
    }
    p++;
    if (p>=MAXOUTLETS) p=0;
    checkmacros = (p==0);
    if (p==0) {
      //microseconds elapsed since q=0
      unsigned long elapsed = micros()-starttime;
      if (elapsed<1000000) {
        unsigned long diff = 1000000 - elapsed;
        uint16_t modval = (diff/1020);
        if (modval<10 || modval>900)
          return 10;
        else
          return modval;
      } else
        return 10;
    }
  }
  return 50;
}

inline void outletHandlerB() { //once per second handler
  lightToggle();
  time_t timenow = now2();
  int secnow = second(timenow);
  int minnow = minute(timenow);
  int hrnow = hour(timenow);
  int monnow = month(timenow);
  if (secnow==0) {  // one minute
    #ifdef AUTODST
    if (hrnow==2 && (monnow==3 || monnow==11)) {//2am
      testDst = true;
    }
    #endif
    if (minnow%10==0 && !logSensorsFlag) {
      logSensorsFlag = true;
      updatePWMPumps();
    }
  }
  //check if network is still up at 1,21,41 mins past the hour (not a busy time)
  if ((minnow+1)%20==1 && secnow==15) {
    netCheckFlag=true;
  }
  if (secnow%LCD_MSG_CYCLE_SECS==0) {
    displaymode++;
    if (displaymode>=LCD_NUM_MSGS) displaymode=0;   
  }
  if (secnow%15==0) {
    checkTempISR();
    checkAlarm();
  }
  checkATO();
}

void _outlogentry(uint8_t p, boolean state) {
  uint8_t h = _head;
  if (++h>=OUTLOGSZ) h=0;
  if (h==_tail) {
    return;//don't overlap, we lose this log
  }
  _outlog[_head].timestamp = now2();
  _outlog[_head].changed = p;
  _outlog[_head].state = state;
  _head=h;
}

void _outletOn(uint8_t p){
  if (p<8) {
    if (!(~PORTA & _BV(p))) {//is off
      PORTA &= ~_BV(p); 
      _outlogentry(p,true);
    }
  } else if (p==Feeder) {
    feed();
  } else if (p==Pump0) {
    FeedModeOFF();
  }else {
    if (!(PORTC & _BV(p-8))) {
      PORTC |= _BV(p-8);
      _outlogentry(p,true);
    }
  }
}

void _outletOff(uint8_t p) {
  if (p<8) {
    if (~PORTA & _BV(p)) {//is on
      PORTA |= _BV(p);
      _outlogentry(p,false);
    }
  } else if (p==Feeder) {
  } else if (p==Pump0) {
     FeedModeON();
  }else {
    if (PORTC & _BV(p-8)) {
      PORTC &= ~_BV(p-8);
      _outlogentry(p,false);
    }
  }
}

void outletOn(uint8_t _p, boolean isAuto) {
  cli();
  if (isAuto && conf.outletRec[_p].mode==_auto) {
    _outletOn(_p);       
  } else if (!isAuto && conf.outletRec[_p].mode!=_macro) {
    conf.outletRec[_p].mode=_manual;
    _outletOn(_p);   
  }
  beepx(20,20,0,50,0);
  sei();
}

void outletOff(uint8_t _p, boolean isAuto) {
  cli();
  if (isAuto && conf.outletRec[_p].mode==_auto) {
    _outletOff(_p); 
  } else if (!isAuto && conf.outletRec[_p].mode!=_macro) {
    conf.outletRec[_p].mode=_manual;
    _outletOff(_p);   
  }
  beepx(20,0,0,0,0);
  sei();
}

void outletAuto(uint8_t _p) {
  cli();
  if (conf.outletRec[_p].mode!=_macro)
    conf.outletRec[_p].mode = _auto;
  beepx(20,20,20,50,0);
  sei();
}

boolean isOutletOn(uint8_t p) {
   if (p<8){
      return ~PORTA & _BV(p);
   } else {
      return PORTC & _BV(p-8);
   }
}

char* getOutletState(uint8_t p) {
  static char buffer[4];
  boolean s = isOutletOn(p);
  if (conf.outletRec[p].mode==_auto) {
    buffer[0]='A';
    buffer[1]='O';
    s?buffer[2]='N':buffer[2]='F';
  } else {
    buffer[0]='O';
    if (s) {
      buffer[1]='N';
      buffer[2]=0;
    } else {
      buffer[1]='F';
      buffer[2]='F';
    }
  }
  buffer[3]=0;
  return buffer;
}

uint8_t getOutletChannel(char* name) {
  for (int i=0;i<MAXOUTLETS;i++) {
     if (strcmp(name,(const char*)conf.outletRec[i].name)==0) {
       return i;
     }
  }
  return 0;
}

uint8_t getFeedName() {
  uint8_t name = _ActiveMacro;
  if (name<4)
    return name+1;
  else
    return 0; 
}

time_t getFeedActive() {
  cli();
  time_t val = _MacroCountDown;
  sei();
  return val;
}



0coool

Full configuration code
Code: [Select]
/*
* Copyright (c) 2013 by Jerry Sy aka d0ughb0y
* mail-to: j3rry5y@gmail.com
* Commercial use of this software without
* permission is absolutely prohibited.
*/
#define CONTROLLER_NAME "Jerry's Reef" //change this to your controller name
#define NTPSERVER 193,193,193,107  //pool.ntp.org
#define LOCAL_IP 192,168,1,15 //change this to a local fixed ip address
#define ROUTER_IP 192,168,1,1 //change this to your router ip address
#define ROUTER_PORT 80 //usually port 80 for the ip configuration web page
#define _TEMP  //comment out if no temp probe
#define _PH  //comment out if no ph probe or ph stamp
#define _HEATER
//#define _FAN
#define _SONAR
#define _FEEDER
#define RTC_ADDR 0x68
#define LCD_ADDR 0x20
#define LCD_ROWS 2
#define LCD_COLS 16
#define LCD_NUM_MSGS 4+MAXPWMPUMPS/2
#define LCD_MSG_CYCLE_SECS 2
#define LCD_EN 4
#define LCD_RW 5
#define LCD_RS 6
#define LCD_D4 0
#define LCD_D5 1
#define LCD_D6 2
#define LCD_D7 3
#define LCD_BACKLIGHT 7

#define STDTZOFFSET -8
//comment the next line if your location does not use daylight savings time
#define AUTODST

#define WEBSERVERPORT 8000
//go to http://base64-encoder-online.waraxe.us
//to create your encoded string
//BASICAUTH = username:password,  sample below is admin:password
//SMTPUSER = your email login, sample below is admin
//SMTPPASSWORD = your email password, sample below is password
#define BASICAUTH "YWRtaW46cGFzc3dvcmQ="
#define SMTPSERVER "my.smtp.net"
#define SMTPPORT 587
#define SMTPUSER "YWRtaW4="
#define SMTPPASSWORD "cGFzc3dvcmQ="
#define EMAILFROM "user@host.com"
#define EMAILTO "8005551212@txt.att.net"
#define SD_CS 4
#define ETHER_CS 10
#define OUTLOGSZ 6  //size of circular queue for outlet log

#define MAXOUTLETS  8 //either 8 or 16, currently no support for 16 yet
#define MAXMACROS  4 //fixed
#define MAXMACROACTIONS 6  //fixed for now, can be made longer if needed
#define EEPROMSIG 0xA0 //change this everytime you want the eeprom defaults to change
//define OUTLET names here, order is fixed
#define OUTLET1 "WP25"
#define OUTLET2 "Unused2"
#define OUTLET3 "WP25B"
#define OUTLET4 "Pump"
#define OUTLET5 "Heater"
#define OUTLET6 "Kalk"
#define OUTLET7 "Skimmer"
#define OUTLET8 "Return"
#define OUTLET9 "Unused"
#define OUTLET10 "Unused"
#define OUTLET11 "Unused"
#define OUTLET12 "Unused"
#define OUTLET13 "Unused"
#define OUTLET14 "Unused"
#define OUTLET15 "Unused"
#define OUTLET16 "Unused"

#define MACRO1 "Feed Now"
#define MACRO2 "Feed"
#define MACRO3 "All Pumps On"
#define MACRO4 "All Pumps Off"

#define OUTLETDEFS WP25, Unused2, WP25B, Pump, Heater, Kalk, Skimmer, Return,\
                   Unused9, Unused10, Unused11, Unused12, Unused13, Unused14, Unused15, Unused16
//define the default outlet program here. outlets must appear in exact order defined in outlet names definition
//program outletname, initial off time, on time, off time, days active, mode
#define OUTLETSDEFAULT {{OUTLET1,0,SECS_PER_DAY,0,0xff,_auto},\
                        {OUTLET2,0,0,0,0xff,_auto},\
                        {OUTLET3,0,SECS_PER_DAY,0,0xff,_auto},\
                        {OUTLET4,0,SECS_PER_DAY,0,0xff,_auto},\
                        {OUTLET5,0,0,0,0xff,_auto},\
                        {OUTLET6,0,0,0,0xff,_auto},\
                        {OUTLET7,0,SECS_PER_DAY,0,0xff,_auto},\
                        {OUTLET8,0,SECS_PER_DAY,0,0xff,_auto}}     
#define MACROSDEFAULT {{MACRO1,0,1,0,0xff,_disabled},\
                       {MACRO2,15*SECS_PER_HOUR,240,SECS_PER_DAY-15*SECS_PER_HOUR-240,0xff,_auto},\
                       {MACRO3, 0,10*SECS_PER_MIN,0,0xff,_disabled},\
                       {MACRO4,10*SECS_PER_MIN,0,0,0xff,_disabled}}                   
#define ACTIONSDEFAULT {{{Feeder,0,2},{End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}},\
                        {{Pump0,240,0},{Return,150,90},{Feeder,30,2}, {End,0,0}, {End,0,0}, {End,0,0}},\
                        {{WP25,0,600}, {Pump0,600,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}},\
                        {{WP25,600,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}, {End,0,0}}}
//controls 1-2 pwm pumps                       
#define _PWMA
//uncomment if you have 3 or 4 pumps
//#define _PWMB
#if defined(_PWMB)
#define MAXPWMPUMPS 4
#elif !defined(_PWMB)
#define MAXPWMPUMPS 2
#endif
//change wavemode 6x per day at the followiing time (hours)
#define MAXINTERVALS 6
#define INTERVALS  {0,6,9,12,18,22}

//below are the settings I use on my pair of wp-25
//I set the default program to run pumps on H1 at 50% speed.
//change the program by specifying the {syncmode,wavemode,level,pulsewidth}
//in that order, 6x per pump.
//valid level values are from 48-255, pulsewidth from 0-10
//if syncmode is not master, then the rest of the parameters don't need to be specified
#define PUMP0 {{_master,H1,128,0},{_master,W2,192,10},{_master,ELSE,192,10},{_master,W1,255,4},{_master,W3,255,10},{_master,H1,128,0}}
#define PUMP1 {{_sync},{_antisync},{_master,ELSE,192,10},{_antisync},{_antisync},{_sync}}
//#define PUMP0 {{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0}}
//#define PUMP1 {{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0}}
#define PUMP2 {{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0}}
#define PUMP3 {{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0},{_master,H1,128,0}}
#define CUSTOMPATTERN {128,180,220,228,220,180,188,196,200,196,188,160,\
                       168,176,180,176,168,140,148,150,148,144,136,128,\
                       128,177,218,246,255,246,218,177}     
#define CUSTOMPATTERNSTEPS 32

/////////////////////////////////
//   typedefs
/////////////////////////////////
enum {_auto, _manual, _disabled, _macro };
//pump 0 is always master
//pumps 1,2,3 can run in sync or antisync with respect to pump 0
enum {_master,_sync,_antisync};

//function that returns 8 bit value for the given step over the resolution of the wave pattern
typedef uint8_t(*PatternFP)(uint8_t);

typedef struct {
  char       name[5];
  uint8_t    resolution;
  PatternFP  _getLevel;
} WaveDef_t;

typedef struct {
  uint8_t   syncMode;
  uint8_t   waveMode;
  uint8_t   level;//48-255
  uint8_t   pulseWidth;//0-10
} PumpDef_t;

typedef struct {
  char     name[14];
  time_t   initoff;
  time_t   ontime;
  time_t   offtime;
  uint8_t  days;
  uint8_t  mode;
} ORec_t;

typedef struct {
  time_t timestamp;
  uint8_t changed;
  boolean state;
} OutLogRec_t;

typedef struct {
  uint8_t outlet;
  time_t initoff;
  time_t ontime;
} MacroActions_t;

typedef struct {
  ORec_t outletRec[MAXOUTLETS];
  ORec_t macrosRec[MAXMACROS]; 
  MacroActions_t actions[MAXMACROS][MAXMACROACTIONS];
  PumpDef_t pump[MAXPWMPUMPS][MAXINTERVALS];
  float htrlow;
  float htrhigh;
  float fanlow;
  float fanhigh;
  uint8_t sonarhigh;
  uint8_t sonarlow;
  uint8_t sonaralertval;
  boolean sonaralert;
  float alerttemplow;
  float alerttemphigh;
  float alertphlow;
  float alertphhigh;
  boolean soundalert;
  boolean emailalert;
  uint8_t initialized;
} conf_t;

raschemmel

Check out millis. I think it can be done using a Timer but you would have to ask Nick Gammon about that. I don't have as much experience as he does.
CHECK out this post:
http://answers.oreilly.com/topic/2704-how-to-create-an-arduino-alarm-that-calls-a-function/
Arduino UNOs, Pro-Minis, ATMega328, ATtiny85, LCDs, MCP4162, keypads,<br />DS18B20s,74c922,nRF24L01, RS232, SD card, RC fixed wing, quadcopter

Robin2

I think it would be easier to write a new program from scratch rather than figure out how to modify that program.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Go Up