DailyTimerSpark library added

I spend a lot of time on presence simulation projects that do things like turning on/off various things in our home at seemingly random times. The classic example of this use case is an indoor or outdoor light that comes on and off randomly as to fool would be villains into believing someone is home.

I developed a library for Arduino some time back and finally ported it to Particle. It uses the Time class in particle and you can set timers for WEEKENDS, WEEKDAYS, single days (e.g. WEDNESDAY) or even random days or custom templates (e.g. Monday Tuesday and Friday). I used a technique to allow the time to run over midnight, allowing timers to start in the evening and stop in the early am, for example.

I uploaded it to the Particle Library of Libraries and I hope maybe someone finds it useful, besides me. Of course, I would appreciate any contribution/feedback or suggestions for improvement. I’ve been working with it across the Arduino and Particle universe and it seems stable.

Find DailyTimerSpark here on GitHub and in the Particle Library of Libraries in the Build IDE.

Code snippet to see how easy it can be to set up a daily timer:

#include "DailyTimerSpark.h"


DailyTimer timer1(18, 30,  1, 30, EVERY_DAY, RANDOM_END, customSeedGenerator);      // optional callback function for random number generation, see below example
DailyTimer timer2(12,  0, 13,  0, SATURDAY, FIXED);                                 // default is FIXED, this will randomize the start time only
DailyTimer timer3( 8, 30, 23, 30, WEEKENDS);                                        // creates with a default fixed start time and end time
bool timer1_LastState = false;

uint32_t lastUpdateTime = 0;

void setup()
{
  timer3.begin(); // use this for syncing the state change tools startTrigger() and endTrigger() on startup
  timer2.begin();
  timer1.begin();
  Serial.begin(9600);
  pinMode(D7, OUTPUT);
  timer1.setRandomOffset(15, RANDOM_END);
  //timer1.setDaysActive(WEEKDAYS);             // Set the timer to be active on weekdays
  //timer1.setDaysActive(B10101010);            // or define a custom day mask... SMTWTFS0  example: Sunday, Tuesday, Yhursday, Saturday
  //timer1.setRandomDays(4);                    // or four random days per week
  //timer3.setRandomOffset(5);                  // Change random start time, +/- 5 minutes... max 59 mins, default is 15mins
  //Serial.println(timer1.getDays(), BIN);      // getDays() returns active days as a byte in the format above
}

void loop()
{
  bool timerState = timer1.isActive();  //State Change method this block
  if(timerState != timer1_LastState)
  {
    if(timerState)
    {
      digitalWrite(D7, HIGH);
      Serial.println("LED is ON");
      Particle.publish("DailyTimer", "LED is ON", 60, PRIVATE);
    }
    else
    {
      digitalWrite(D7, LOW);
      Serial.println("LED is OFF");
      Particle.publish("DailyTimer", "LED is OFF", 60, PRIVATE);
    }
    timer1_LastState = timerState;
  }

  if(timer2.startTrigger())  // Boundary method following blocks
  {
    Serial.println("Timer 2 FIRED!");
    Particle.publish("DailyTimer", "Timer2 Fired", 60, PRIVATE);
  }
  if(timer3.startTrigger())
  {
    Serial.println("Timer 3 FIRED!");
    Particle.publish("DailyTimer", "Timer3 Fired", 60, PRIVATE);
  }
  if(timer3.endTrigger())
  {
    Serial.println("Timer 3 Expired!");
    Particle.publish("DailyTimer", "Timer3 is Inactive", 60, PRIVATE);
  }
  if(millis() - lastUpdateTime > 1000UL)
  {
    char timeBuffer[32] = "";
    Serial.println(Time.timeStr());
    lastUpdateTime += 1000UL;
  }
}

uint32_t customSeedGenerator()
{
  Serial.println("New Seed Created");
  return  random(0xFFFFFFFF);
}
5 Likes

Your timing couldn’t be more perfect @BulldogLowell Thanks much!

1 Like

thanks… let me know if you see any issues!

I’m getting this error…

DailyTimerSpark/DailyTimerSpark.cpp:170:26: error: 'class TimeClass' has no member named 'local'
   time_t now_time = Time.local();

hmmm… which firmware are you building with?

my example compiles fine with 0.5.1

At this point I haven’t gotten passed the IDE

Well it just compiled fine using ParticleDev (in the cloud), but I still get the same error in the IDE

I tried compiling for a Core and got that error… so you must be compiling for a Core :frowning:

try replacing this line in the implementation (.cpp) file:

time_t now_time = Time.local();

with this:

time_t now_time = tmConvert_t(Time.year(), Time.month(), Time.day(), Time.hour(), Time.minute(), Time.second());

The Core also supports Time.local() since firmware 0.4.9.
So @techbutler, just go with @BulldogLowell’s suggestion to move on to 0.5.1

Sorry guys this was my bad. The intended device is in fact a Photon, but I had just previously flashed a Core in the field and forgot to switch it in the Web IDE, thus I was verifying against an outdated Core. Sorry for the run around @BulldogLowell

2 Likes

Old topic but I try. Sorry for my lack of knowledge. I am trying to switch on 8:00 and the off ten minutes later 8:10 for hour 8,9,…21,22. The minutes between every “on” minute 11 to 59 a need to switch off wifi to conserve power. For now it only switch timer1. Can anybody give me a hint?

    #include "DailyTimerSpark.h"


DailyTimer timer1(8, 0,  8, 10, EVERY_DAY);      // optional callback function for random number generation, see below example
DailyTimer timer2(9, 0,  9, 10, EVERY_DAY);                                 // default is FIXED, this will randomize the start time only
DailyTimer timer3(10, 0,  10, 10, EVERY_DAY);                                        // creates with a default fixed start time and end time
bool timer1_LastState = false;
bool timer2_LastState = false;
bool timer3_LastState = false;

uint32_t lastUpdateTime = 0;

void setup()
{
  timer3.begin(); // use this for syncing the state change tools startTrigger() and endTrigger() on startup
  timer2.begin();
  timer1.begin();
  Serial.begin(9600);
  pinMode(D7, OUTPUT);
  //timer1.setRandomOffset(15, RANDOM_END);
  timer1.setDaysActive(EVERY_DAY);             // Set the timer to be active on every dayes
  timer2.setDaysActive(EVERY_DAY);
  timer3.setDaysActive(EVERY_DAY);
  //timer1.setDaysActive(B10101010);            // or define a custom day mask... SMTWTFS0  example: Sunday, Tuesday, Yhursday, Saturday
  //timer1.setRandomDays(4);                    // or four random days per week
  //timer3.setRandomOffset(5);                  // Change random start time, +/- 5 minutes... max 59 mins, default is 15mins
  //Serial.println(timer1.getDays(), BIN);      // getDays() returns active days as a byte in the format above
  Time.zone(+1); //utc time - 1 hour behind GMT - I use GMT
}

void loop()
{
  bool timerState = timer1.isActive();  //State Change method this block
  if(timerState != timer1_LastState)
  {
    if(timerState)
    {
      digitalWrite(D7, HIGH);
      Serial.println("LED is ON");
      Particle.publish("DailyTimer", "LED is ON", 60, PRIVATE);
    }
    else
    {
      digitalWrite(D7, LOW);
      Serial.println("LED is OFF");
      Particle.publish("DailyTimer", "LED is OFF", 60, PRIVATE);
    }
    timer1_LastState = timerState;
  }

  bool timerState2 = timer2.isActive();  //State Change method this block
  if(timerState != timer2_LastState)
  {
    if(timerState)
    {
      digitalWrite(D7, HIGH);
      Serial.println("LED is ON");
      Particle.publish("DailyTimer", "LED is ON", 60, PRIVATE);
    }
    else
    {
      digitalWrite(D7, LOW);
      Serial.println("LED is OFF");
      Particle.publish("DailyTimer", "LED is OFF", 60, PRIVATE);
    }
    timer2_LastState = timerState;
  }
  
  bool timerState3 = timer3.isActive();  //State Change method this block
  if(timerState != timer3_LastState)
  {
    if(timerState)
    {
      digitalWrite(D7, HIGH);
      Serial.println("LED is ON");
      Particle.publish("DailyTimer", "LED is ON", 60, PRIVATE);
    }
    else
    {
      digitalWrite(D7, LOW);
      Serial.println("LED is OFF");
      Particle.publish("DailyTimer", "LED is OFF", 60, PRIVATE);
    }
    timer3_LastState = timerState;
  }
2 Likes

Interesting use of the library that I hadn’t considered… When I wrote it, I was thinking about only one on/off event in a day!

It looks like the timers are stepping on each other… but you can do what you want to do (the good news).

I’d try like this, but it is not tested. I can try to do that later…

#include "DailyTimerSpark.h"

DailyTimer myTimers[] = { .     // add as many intervals as you want during a day
  { 8, 0,   8, 10, EVERY_DAY},
  { 9, 0,   9, 10, EVERY_DAY},
  {10, 0,  10, 10, EVERY_DAY},
  {11, 0,  11, 10, EVERY_DAY}
};

//bool myTimerLastState[sizeof(myTimers)/sizeof(myTimers[0])] = {false};

bool lastTimerState = false;

void setup()
{
  Time.zone(+1); //utc time - 1 hour behind GMT - I use GMT . // moved to top!
  pinMode(D7, OUTPUT);
  bool anyTimer = false;
  for (int i = 0; i < sizeof(myTimers)/sizeof(myTimers[0]); i++)
  {
    if(myTimers[i].begin()) anyTimer = true;
  }
  if(anyTimer)
  {
    digitalWrite(D7, HIGH);
  }
  
}

void loop()
{
  bool anyTimer = false;
  for (int i = 0; i < sizeof(myTimers)/sizeof(myTimers[0]); i++)
  {
    if(myTimers[i].isActive())
      anyTimer = true;
  }
  if(anyTimer != lastTimerState)
  {
    if(anyTimer)
    {
      digitalWrite(D7, HIGH);
    }
    else
    {
      digitalWrite(D7, LOW);
    }
    lastTimerState = anyTimer;
  }
}

Try it and let me know.

Perfect! Thank you!
Is it possible to implement any kind of power savings to the library, in the waiting periods? Or is it to user code depending? I’ve tried to implement SLEEP_MODE_DEEP in the waiting periodes but struggling to get it to work …

In that case you may be better off not using my library and simply polling the time:

if(Time.hour() > 7 && Time.minute() > 9)
{
  // gotoSleep for 10mins
}
1 Like

In your last post you mention SLEEP_MODE_DEEP, but in your first post you talk about turning off the Wi-Fi. Those are two very different things; you can turn off the Wi-Fi with System.sleep(seconds), and in this mode the program execution continues on. On the other hand, if you use System.sleep(SLEEP_MODE_DEEP, seconds), the program execution stops, and when the system awakes, it doesn't start where it left off, it resets, and runs setup again, with none of your values being maintained in memory. This mode uses the least amount of power.

It sounds like you want the device awake for ten minutes at the top of every hour between 8AM and 10PM, and asleep all the rest of the time. Is that correct? If so, using timers in probably not the way to go. If you want to use deep sleep, it could be done very simply with something like this,

if (Time.hour() == 22 && Time.minute() ==10 ) { // sleep all night after 10:10 PM
    System.sleep(SLEEP_MODE_DEEP, 35400); // 35400 seconds = 9 hours, 50 minutes
}else if (Time.hour() > 7 && Time.hour() < 22 && Time.minute() == 10) {
    System.sleep(SLEEP_MODE_DEEP, 3000); // sleep for 50 minutes
}

If you only want to switch off the Wi-Fi, it's a little bit more complicated, since the code keeps running. Let us know which type of sleep you want.

Yes first i thought turn off WiFi was enough, but it still consumes 1At/day + the awake periods and that is still too much, I think. So therefor I think I must implement next level of power saving.
I have a mountain cabin with limited solar power (south west coast of Norway and only a few hours daylight at wintertime). My goal is to periodically turn on a cellular internet connection, power a RPi that collect weather data from a local weather station, fire some pictures, upload it all and then switch it all off for next cycle. So @Ric yes that is exactly what I try to archive.
I try with DEEP mode, but only manage to lock myself out :\ . I really appreciate your replays and you should know that I analyze your answers and make myself learning.

I don’t have an Electron, so I’m not too familiar with the specific problems with it. I have seen other posts on the forum about the Electron and deep sleep, so you might want to search the forum for those. Lykke til! Nå studerer jeg norsk, fordi datteren min bor i Asker. Vi kommer til å reise til Norge i mai. Jeg håper at jeg kan hjelpe deg mer i morgen. Klokka er tjuetre her, så jeg må sove nå.

This is how I do a similar thing to what @Ric suggested, with the extra effect, that the sleep duration will be adjusted to the current time, in order to wake up at the “same” second over and over without a consistent drift (which constant sleep periodes would cause)

const uint32_t SLEEP_MINUTES = 10;      // wake every xx minutes
const uint32_t WAKE_HOUR     =  6;      // starting time per day
const uint32_t SLEEP_HOUR    = 22;      // stop
...
  uint32_t dt;
  if (WAKE_HOUR <= (uint32_t)Time.hour() && (uint32_t)Time.hour() < SLEEP_HOUR)
  {
    dt = (SLEEP_MINUTES - Time.minute() % SLEEP_MINUTES) * 60 - Time.second();  // wake at next time boundary
  }
  else
  {
    uint32_t now = 86400 - Time.local() % 86400;
    dt = (now + WAKE_HOUR * 3600) % 86400;
  }
  expectedWake = Time.now() + dt;

  System.sleep(SLEEP_MODE_DEEP , dt);  // , SLEEP_NETWORK_STANDBY);
  //System.sleep(BTN, FALLING  , dt);  // , SLEEP_NETWORK_STANDBY);  // use SETUP BUTTON (20) to wake
  //System.sleep(BUTTON, RISING, dt);  // , SLEEP_NETWORK_STANDBY);  // use own hardware BUTTON to wake

Sorry for late response. I have struggling to get the Photon out of SLEEP_MODE_DEEP for two days :slight_smile: OMG. Finally I manage with safe mode. puh.

Thank you all for help. @ScruffR
If I uncomment the line
//System.sleep(BTN, FALLING , dt); // , SLEEP_NETWORK_STANDBY); // use SETUP BUTTON (20) to wake
is it then possible to yield the machine awake by pushing setup button briefly? (so I dont need two more days to get i back :wink: )

I thought the syntax for your tip would be something like this:

// This #include statement was automatically added by the Particle IDE.
#include <DailyTimerSpark.h>

const uint32_t SLEEP_MINUTES = 10;      // wake every xx minutes
const uint32_t WAKE_HOUR     =  6;      // starting time per day
const uint32_t SLEEP_HOUR    = 22;      // stop


//bool myTimerLastState[sizeof(myTimers)/sizeof(myTimers[0])] = {false};

// bool lastTimerState = false;

void setup()
{
  Time.zone(+1); //utc time - 1 hour behind GMT - I use GMT . // moved to top!
  pinMode(D7, OUTPUT);
}


void loop()
{
  uint32_t dt;
  if (WAKE_HOUR <= (uint32_t)Time.hour() && (uint32_t)Time.hour() < SLEEP_HOUR)
  {
    dt = (SLEEP_MINUTES - Time.minute() % SLEEP_MINUTES) * 60 - Time.second();  // wake at next time boundary
  }
  else
  {
    uint32_t now = 86400 - Time.local() % 86400;
    dt = (now + WAKE_HOUR * 3600) % 86400;
  }
  
  expectedWake = Time.now() + dt;

  System.sleep(SLEEP_MODE_DEEP , dt);  // , SLEEP_NETWORK_STANDBY);
  //System.sleep(BTN, FALLING  , dt);  // , SLEEP_NETWORK_STANDBY);  // use SETUP BUTTON (20) to wake
  //System.sleep(BUTTON, RISING, dt);  // , SLEEP_NETWORK_STANDBY);  // use own hardware BUTTON to wake
}

But i got a declar error for expectedWake. Maybe some of you can take a look?

Exactly, but your actual problem is not the waking, but that your code is not doing anything apart from going back to sleep immediately :wink:

You may want to add some functionality - or just a delay(60000) - before the sleep stuff :sunglasses:

You also need to add a global

retained uint32_t expectedWake = 0;

and enable the retained feature via

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY))