How We Do It: Making Things Happen At Specific Times of the Day

Connected products need scheduled actions. Scheduled actions require time synchronized deceives. Here is we make that happen.

Marcus Linderoth, Thingsquare VP of EngineeringBy Marcus Linderoth, Thingsquare VP of Engineering – November 24, 2021

In our daily lives, we are governed by time and schedules. The same goes for connected products.

We may wake up at 6:30 AM, take the bus at 7:34 AM, start work at 8:10 AM, pick up from school at 4:00 PM. Based on synchronized and often fixed times of day.

Connected products are made to work with us and for us. So they need to follow schedules too.

A few examples:

  • Street lights should turn on and off at specific times of the day.
  • Horticultural lighting devices follow complex lighting patterns to ensure maximum growth of the plants.
  • Wireless sensors can be turned off at night to save battery power.

These are slightly different use cases.

But they share the same underlying mechanism: scheduling and time synchronization.

Many of the many connected products and IoT systems that we have built at Thingsquare rely on scheduling.

Today we look at how we implement scheduling in the Thingsquare IoT system.

The Basis: Time Synchronization and Distribution

The basis of scheduling is time.

Without knowing what time it is, scheduling is useless.

But how can devices know what time it is?

In general, time synchronization is a complex problem with many different solutions.

But for our purposes, the device only needs two things:

  • An internal clock that runs at a reasonably precise speed.
  • A way to synchronize the internal clock with the current world clock.

Once the device has synchronized with the global time, it can run by itself without having to be synchronized again. At least until its internal clock drifts too far away from the global clock. Or until the device drops power.

In the Thingsquare system, devices already are synchronized with each other as part of the built-in wireless channel hopping mechanism. On top of this, the backend distributes an absolute time offset.

With the network in synch, and with knowledge of what the global clock is, all devices know the absolute time.

Devices use their notion of time both to timestamp all their data and for scheduled actions.

Scheduling: The Details

All Thingsquare devices know what time it is.

How do we use this information to schedule actions?

The basis for schedules in the Thingsquare system is a week. This is because most real-world schedules are based on weekly events.

To figure out what day of the week it is, devices use the current global time and make a simple computation: compute the number of seconds from a known Monday.

Based on this information, events can be scheduled. When an event occurs, a callback function inside the firmware of the device is called.

Our callback function will be invoked at a combination of weekday and time of day. We also pair an action argument to the datetime. The argument is used when the callback is invoked, so we can tell what to do.

In the callback, we may do whatever action we want.

For example, it may be an event that is conditional, so we may check if those are met. We may start a calibration of a sensor, or turn lights on.

Setting it up

Here, we’ll use the example of a device that needs calibration twice a day. This may be an industrial tool for example.

For periodic sensor samples, we have another mechanism that is even better suited, namely the Thingsquare callback with THSQ_PERIOD argument. But that is for another blog post.

So, to begin with, we’ll need some basic setup. We will give the schedule a name, so we’ll be able to override the schedule in-situ.

static struct thsq_schedule calib_schedule;
thsq_schedule_init(&calib_schedule, "calsched", calib_cb);

Add events

Then, we should add events to the schedule. For this, we need a buffer and a couple of convenience function calls.

By default, the arguments we register with each event is three bytes long. Perfect for lighting applications with RGB channels. That can be changed though.

/* create the events */
extern void thsq_schedule_build_schedule_action(uint8_t *buf,
            int mon, int tue, int wed, int thu, int fri,
            int sat, int sun,
            uint8_t hours, uint8_t minutes,
            uint8_t red, uint8_t green, uint8_t blue);

#define INSTRUMENT_0 0
#define INSTRUMENT_1 100
static uint8_t events[2 * EVENT_LEN]; /* room for two events */
            /* mon-fri */
            1, 1, 1, 1, 1, 0, 0,
            /* 0600 in the morning */
            06, 00,
            /* arguments */
            INSTRUMENT_0, 1, 2);
            /* mon-fri */
            1, 1, 1, 1, 1, 0, 0,
            /* 1830 in the evening */
            18, 30,
            /* arguments */
            INSTRUMENT_1, 3, 4);

Add events to schedule

Now we defined the schedule and the events, we just need to associate them together.

  thsq_schedule_set_default(&calib_schedule, events, 2 * EVENT_LEN);


The final piece of the puzzle is to create the actual callback.

Since the same callback can be used with different schedules, we will get a pointer to the schedule that triggered. We also get a pointer to the arguments, and the length of the arguments.

In our example, the callback will be invoked with arglen equal 3, and arg equal to [0, 1, 2] or [100, 3, 4], and we here use that in the callback to indicate to our application how to run.

static void
calib_cb(struct thsq_schedule *s, uint8_t *arg, int arglen)
  uint8_t instrument = arg[0];
  uint8_t calib_arg1 = arg[1];
  uint8_t calib_arg2 = arg[2];
  instrument_calibrate(instrument, calib_arg1, calib_arg2);

Changes after deployment

Now, say that we’ve deployed devices with the above schedule. Let’s say that one customer prefers not to run a calibration event in the morning. How can we make that happen?

The simplest way would be to push d.calsched with the new schedule, without the morning event. That’s easy since the event list is just a buffer.

If they later change their mind, we could yet again push an updated d.calsched.

Adding randomness

Often, randomness is a good thing. Especially when it comes to pushing data over the network.

Having hundreds of devices pushing data at the same time can overload the network, surpassing the available bandwidth.

We can add some randomness to the above quite easily. We just need to ensure the calibration doesn’t run exactly when the callback is invoked, but at a later, bounded randomized point in time.

Here’s how that would look.

/* -------------------------------------------------------------------------- */
/* calibrate; invoked at a randomized timeout */
static void
do_calibrate(void *arg)
  uint8_t *p = (uint8_t *)arg;
  uint8_t instrument = p[0];
  uint8_t calib_arg1 = p[1];
  uint8_t calib_arg2 = p[2];

  instrument_calibrate(instrument, calib_arg1, calib_arg2);
/* -------------------------------------------------------------------------- */
/* scheduled event; sets random timer to calibrate */
static void
calib_cb(struct thsq_schedule *s, uint8_t *arg, int arglen)
  static uint8_t argbuf[3]; /* three args, one byte each */
  static struct ctimer calibrate_timer;

  /* store the arguments, and set a timer */
  memcpy((void *)argbuf, (const void *)arg, 3);
    do_calibrate, (void *)argbuf);
/* -------------------------------------------------------------------------- */

Your turn

Would you like to hear more about how Thingsquare can help your product come alive? We’re looking forward to talking to you! Drop us an email, or start a chat with us.

Feel free to use the IoT solution planner to get an automated time and cost estimate of your product.

Additional Photo Credits

Get started!

Get your own tailored IoT solution

Instead of recruiting a full development team, let us build a tailored IoT solution for you – from prototype to product. Tailored prototypes at a fixed price!

Get in touch – we'd love to hear how we can help you achieve your goals!

Get in touch!

Be in good company
Get in touch with us!

Get a quote!

Be in good company
Adam Dunkels

Adam Dunkels


Fredrik Rosendal

Fredrik Rosendal


Marcus Linderoth

Marcus Linderoth

VP Engineering

Setting things up...