Storing time variables for relay on/off - Newbie question

Hi All

Well it is almost the end of week 4 of lock down in NZ, still 2.5 weeks to go at least. I spent my time trying to learn Arduino programming, or at least the basics.

I'm working on a spa controller (among other things including an automatic chicken door which the community here has helped me with and now installed and going) This has lead me to learn RTC and the TimeLib.h, menus and state machine concepts. None of which I have mastered at all but I have a very nice desktop digital clock at present.

The spa will have user defined "comfort" periods where the spa is at or near the correct temperature. These will be set via a simple menu and 16x2 I2C LCD. I have struggled to find good examples of setting on/off periods programmed in a way that can be edited via a menu, I know they are out there somewhere. I anticipate there will only be 3 sets used but was going to allow for up to 5 comfort periods to be programmed along with flags to say if they are used or not (haven't gone down the road of weekends vs weekdays yet).

My question is do I
A create a bunch of variables (this seems to be the most common approach for basic sketches to turn a relay on or off)
int Comfort_P1_ON_hrs
int Comfort_P1_ON_min
int Comfort_P1_OFF_hrs
int Comfort_P1_OFF_min

x5 sets (seems like a lot of variables)

or
B Use some form of typedef struct like (which I have never attempted before)
typedef struct {
int On_hrs;
int On_min;
int Off_hrs;
int off_min;
} Comfort;

Comfort Comfort_1 = {7, 30, 9, 30};
then somehow use this in a if statement to compare the time from the RTC and displayed on an LCD in setup to be edited.

Secondary question, I have been investigating a lot of menu libraries, I liked menwiz but I understand it is no longer supported, I have also looked at LCDMenuLib2, Liquidmenu and arduino-menusystem. Is there one anyone would recommend for a 16x2 I2C LCD display to do a simple menu for setup. I started writing my own switch case until I realised there were pre-written libraries.

The menu will be very rarely accessed and is there to set the RTC time, comfort times, temperatures, and exit delay for the spa area lighting plus a reset to default.

I'm just looking for a prod in the right direction so I can carry on my google research and find some good examples to work from.

Cheers
Al

I'd start with designing the user interface first and solve the problem of the internal representation later.

Ultimately, you will be comparing a time derived from the realtime clock (which may be running in UTC or your local time - your choice) with the times that were originally entered via the display/buttons and operating relays etc. This is a typical time switch application where you may also want to handle daylight saving time automatically. Epoch time comparisons are usually the best here.

Anyway, if you are using a 1602 LCD display you will probably want to select a function (say set time comfort start) then maybe a default time appears, then maybe a flashing cursor indicates the first digit to be active. Pressing an up or down button increments/decrements that digit. Once the time is OK for the user, a press of another button saves it. Etc. Etc. You don't want too many buttons so some use of LCD menu items can reduce this. Initially, an a time so entered will probably be stored in a byte array.

You may also want to use an IR remote control unit to make this a bit easier.

Thank you for the reply

My current approach to this project is picking part of the project then working it out and eventually piecing it all together.

I have numerous parts working independently including setting variables via the lcd (not within a menu structure yet), setting the temperature between preset limits for different states (up down button), I have an LCD to set the time and date working with a blinking cursor and 4 buttons currently. Its not to far a step to edit an on and off time from there and instead of writing to the RTC write to a variable.

My current research project is a menu structure but I first stalled at writing my own vs a library and then stalled at how to store the variable for the "comfort time" which took me down the rabbit whole of "on hrs, on min, off hrs, off min" etc and how to deal with those and call them back again. I have become conscious of memory as none of my previous projects have ever come close to using it all plus I want to learn efficient programming rather than a 1000 if statements :slight_smile:

So I should start looking into byte arrays to store that information? I haven't used arrays from scratch, only copy and pasted bits and pieces but I get the concept. Tomorrows learning day :slight_smile:

Background: We run a bed and breakfast here and guests use the spa but it is anticipated that only I will access the settings menu. I have mapped out the menu and it is a bit tedious but once set should only be altered maybe 1 or 2 times a year for things like the ski season or middle of summer when it is still light at 9pm. The idea of IR is something I would want to explore for other projects once this is working

Cheers
Al

I suggested the byte array because it is close to the representation of the display. However, once the user has adjusted the time and confirmed that what is in the display is correct, then it is best stored in a format where say the hours (0 to 23) are in a single variable and not two separate array elements. If you have many such times to store, comfort_time, weekend_schedule etc. etc. , then a struct is a nice way of doing it, something like you have illustrated.

struct Shedule {
int On_hrs;
int On_min;
int Off_hrs;
int off_min;
byte dayControl ; // 1 = weekday, 2 = weekend ; 0 = all 7 days
} ;

To declare variables of the type defined above:

Shedule comfortTime , luxuryTime , otherTime ;

Then you can set the variables:
comfortTime.On_hrs = 16 ; // 4pm
etc.

to compare them with the arduino's system time (which you would synchronise with the RTC) :

if ( hours() == comfortTime.On_hrs && minutes() == comfortTime.On_min ) { do something }.

if you have many of these times to compare, you could write a function which takes a struct as an argument and returns true if it matches the current time and false otherwise.

Something like:

bool timeMatch( Shedule * testTime ) {
// returns true if testTime matches the current time, false otherwise.
if ( hours() == testTime->On_hrs && minutes() == testTime->On_min ) return true ;
else return false ;
}

which could make the comparison easier:

if ( timeMatch( comfortTime ) ) { do something ;}

Thanks,

I'll carry on working through the code. I'm at the point I can follow (what i consider) relatively complex code but struggle to come up with the initial idea how to do something. Like speaking a language, I understand it but it doesn't roll off the tongue yet.

Just the quick, off the cuff, code you wrote has given me enough to realise how I should be thinking. I won't get it 100% correct but google is wonderful thing if you know where to start and have some key words.

Tomorrow is a new day, aim is to have a basic menu to set the RTC and a "comfort" time, display the actual time (done) and have a simple led turn on or off accordingly, then just expand it to have multiple "comfort" times.

Cheers
Al

If I see variables with numbers, it’s probably a good idea to start thinking in arrays. The below demonstrates a little bit of arrays of your Comfort struct.

// macro to calculate number of elements in any type of array
#define NUMELEMENTS(x) (sizeof(x)/sizeof(x[0]))

typedef struct {
  byte On_hrs;
  byte On_min;
  byte Off_hrs;
  byte Off_min;
} Comfort;

// array with comfort times; you probably want to move this to EEPROM so it will be remembered between power cycles.
Comfort comfortTimes[] =
{
  {7, 30, 9, 30},
  {20, 0, 21, 0},
  // more here
};

// you can not add more comfort times after this as the array has a fixed number of entries
// you can set up the array that allows for sufficient entries and set one of the elements (e.g. On_hrs) to 255 to indicate that it's not configured

void setup()
{
  Serial.begin(57600);
  while (!Serial);

  Serial.print("There are ");
  Serial.print(NUMELEMENTS(comfortTimes));
  Serial.println(" comfort time entries");

  // print initial info
  Serial.println("Original");
  for (byte cnt = 0; cnt < NUMELEMENTS(comfortTimes); cnt++)
  {
    Serial.print("Comfort time: "); Serial.println(cnt + 1);
    printComfortTime(comfortTimes[cnt]);
  }

  // user entry; how you fill the struct elements from the menu is outside the scope
  Comfort userEntry;
  userEntry.On_hrs = 16;
  userEntry.On_min = 15;
  userEntry.Off_hrs = 17;
  userEntry.Off_min = 15;

  Serial.println("User entry: ");
  printComfortTime(userEntry);

  // replace second entry in original by user entry
  memcpy(&comfortTimes[1], &userEntry, sizeof(userEntry));

  // and display
  Serial.println("Updated");
  for (byte cnt = 0; cnt < NUMELEMENTS(comfortTimes); cnt++)
  {
    Serial.print("Comfort time: "); Serial.println(cnt + 1);
    printComfortTime(comfortTimes[cnt]);
  }
}

void loop()
{

}

void printComfortTime(Comfort comfort)
{
  Serial.print("\tOn:  ");
  if (comfort.On_hrs < 10)
    Serial.print("0");
  Serial.print(comfort.On_hrs);
  Serial.print(":");
  if (comfort.On_min < 10)
    Serial.print("0");
  Serial.println(comfort.On_min);
  Serial.print("\tOff: ");
  if (comfort.Off_hrs < 10)
    Serial.print("0");
  Serial.print(comfort.Off_hrs);
  Serial.print(":");
  if (comfort.Off_min < 10)
    Serial.print("0");
  Serial.println(comfort.Off_min);

}

Note:
when using EEPROM, the approach will differ a little as you don’t have to allocate an array in RAM.

OK. When you get to the next stage, and have further questions, it would be a good idea to post the code you have developed.
But getting something basic together to start with , then both optimising it and adding features in an iterative process is a good approach.

@sterretje - PROGMEM (FLASH) is appropriate for truly static data but not for data which is derived from a user input. This is better stored in EEPROM if it is to survive a system restart. At least, this is true for AVRs (Uno etc.) where flash memory cannot be written to from a sketch. For ESPs, EEPROM is actually emulated in flash memory.

6v6gt:
@sterretje - PROGMEM (FLASH) is appropriate for truly static data but not for data which is derived from a user input. This is better stored in EEPROM if it is to survive a system restart. At least, this is true for AVRs (Uno etc.) where flash memory cannot be written to from a sketch. For ESPs, EEPROM is actually emulated in flash memory.

My mistake, I did mean EEPROM. time for a nap, I guess.

Will correct it.

Hi All

I thought I would post here, I’m not sure what the forum etiquette is about how long to continue with a post topic. But it does relate to the topic problem.

I have been working through my spa controller, ironically I got stuck on while loops :slight_smile: and how to remove them and exchanging String() for string. Both now completed and I now have a working state machine, non-blocking lcd clock that you can edit the RTC time. This will ultimately form part of the project.

Back to the issue of comfort times for the spa . I now have working code to cycle through the 6 different options and some code that will allow me to edit each one. however I’m stuck on testing the time at the state “CheckTimes”.

I thought I would use seconds past midnight from the TimeLib.h then it is only a matter of testing via an if() for example,

if ( elapsedSecsToday(curTime) >= comfort_on_sec[1]) mState = Comfort.

This also means I can test current seconds during a restart as it is a fixed point. Plus I can check for time overlaps in setup. (I’m not expecting midnight issues with comfort time)

I also think that will make storing my times in the EEPROM easier??. There was a comment the EEPROM would change how I did things but didn’t elaborate. A clue as to what approach I would take to use EEPROM in that situation would be great, happy to research myself, but I don’t have key words/concepts to start googling, but storing unsigned long numbers seemed like the easiest solution rather than arrays. :slight_smile:

However I’m a little stuck on the following code, I am trying to be more economical in my coding and I though this would work to set an array of “on times” in seconds (I was going to do the same for off times), I could just declare them all manually.

Concept being the admin user sets comfort on and off times with a max of 6. these are stored as On Hour, On Min and Off Hour, Off Minute plus a frequency of everyday, weekdays, weekends, or off (disabled)

then a for (loop) takes those and creates 6 variables on times based on seconds from midnight (and same for off)

but my Serial.print just prints 6 lines of the first comfortTimes array of 32400 (0900 hrs)

Probably a simple solution but can anyone see where I went wrong, or is what I am doing not going to work (I realise there maybe better ways so please let me know but I would also like to know where I went wrong in the for loop.)

code is below.

//****************************************************************
//Setup Libraries

#include "TimeLib.h" // https://github.com/PaulStoffregen/Time

tmElements_t tm;  // data structure for the time.

#define  Not_Set  0
#define  Everyday 1
#define  Weekends 2
#define  Weekdays 3

//*****************************************************************
// setup data structures

typedef struct {
  byte On_hrs;
  byte On_min;
  byte Off_hrs;
  byte Off_min;
  byte Frequency;  // Weekdays, Weekends, Everyday
} Comfort;

//*****************************************************************
//Setup arrays

// array with comfort times; you probably want to move this to EEPROM so it will be remembered between power cycles.
Comfort comfortTimes[] =
{
  {9, 00, 13, 30, 1},  // Comfort 0 On Everyday
  {14, 20, 15, 00, 1},  // Comfort 1 On Weekends only
  {17, 00, 18, 00, 3},  // Comfort 2 Weekdays Only
  {20, 30, 22, 30, 1},  // Comfort 1 everyday
  {5, 30, 17, 30, 0},  // Comfort 4 not set
  {6, 00, 9, 30, 0},  // Comfort 5  not set     
}; // array of 6 items

unsigned long comfort_on_sec[5];  // Array with 6 items



//                              s e t u p ( )
//********************************************************************************
void setup()
{
  Serial.begin(9600);

 for (int i = 0; i < 6; i++){

        unsigned long comfort_on_sec[i] = {(((comfortTimes[i].On_hrs * 60ul ) + comfortTimes[i].On_min) *60ul )};

        Serial.println(comfort_on_sec[i]);
         } // end for loop


} //END of setup()

void loop()
{
  
}


// End Program

intention then is to test the comfortTimes*.Frequency in an if with the various weekdays/weekends and offs.*

Solved it. I had re-decleared the unsigned long.

//****************************************************************
//Setup Libaries

#include "TimeLib.h" // https://github.com/PaulStoffregen/Time

tmElements_t tm;  // data structure for the time.

#define  Not_Set  0
#define  Everyday 1
#define  Weekends 2
#define  Weekdays 3

//*****************************************************************
// setup data structures

typedef struct {
  byte On_hrs;
  byte On_min;
  byte Off_hrs;
  byte Off_min;
  byte Frequency;  // Weekdays, Weekends, Everyday
} Comfort;

//*****************************************************************
//Setup arrays

// array with comfort times; you probably want to move this to EEPROM so it will be remembered between power cycles.
Comfort comfortTimes[] =
{
  {9, 00, 13, 30, 1},  // Comfort 0 On Everyday
  {14, 20, 15, 00, 1},  // Comfort 1 On Weekends only
  {17, 00, 18, 00, 3},  // Comfort 2 Weekdays Only
  {20, 30, 22, 30, 1},  // Comfort 1 everyday
  {5, 30, 17, 30, 0},  // Comfort 4 not set
  {5, 00, 9, 30, 0},  // Comfort 5  not set     
}; // array of 6 items

unsigned long comfort_on_sec[6];  // Array with 6 items



//                              s e t u p ( )
//********************************************************************************
void setup()
{
  Serial.begin(9600);

 for (byte i = 0; i < 6; i++){

       // problem was here.

        comfort_on_sec[i] = { (((comfortTimes[i].On_hrs * 60ul ) + comfortTimes[i].On_min) *60ul ) };

        Serial.println (comfort_on_sec[i]);
        Serial.println (comfortTimes[i].On_hrs);
        Serial.println(i);

        
         } // end foor loop


} //END of setup()

void loop()
{
  
}


// End Programe