Scheduling
Internet of Things (IoT) primarily relates to devices connected to Internet and naturally, exchanging data (like telemetry and commands) over the Internet. However, some automation where the device itself acts independently is desired for many applications, such as turning on lights at say 6 p.m. and turning them off at 11 p.m. To support such automations, ESP RainMaker offers a scheduling functionality.
Features of Scheduling
- Perform an action at a given absolute time with configuration for:
- One shot (Eg. 6:30pm)
- Repeat on specific days of week (Eg. 6:30pm on Mon, Tue, Sat)
- Repeat on specific date of specified months (Eg. 6:30pm on 20th of Jan, Aug, Dec)
- Repeat every year (Eg. 6:30 pm of 20th Jan every year)
- One shot (Eg. 6:30pm)
- Perform an action after relative time. (Eg. 3 hours from now)
- Extensible by adding custom info and flags to help apps identify and segregate different types of schedules (Eg. pre-defined schedules, default on-off schedules, etc.)
- Handling of Day Light Savings (DST) offsets as applicable for the configured timezones. Check Managing Daylight Saving Time (DST) section for more information.
In line with the rest of the ESP RainMaker features, scheduling has been implemented completely on the node side in the form of a "service". The cloud backend just acts as a conduit between the node and the clients (like Phone apps).
The entire complexity of the scheduling feature is hidden beneath a simple C API and also abstracted out by the phone apps. Even so, let's first understand the specifications.
Specifications
The scheduling service consists of a single parameter which is an array of objects. Enabling the feature adds the following service in the node config:
{
"name": "Schedule",
"params": [{
"bounds": {
"max": 5
},
"data_type": "array",
"name": "Schedules",
"properties": [
"read",
"write"
],
"type": "esp.param.schedules"
}],
"type": "esp.service.schedule"
}
The schedule parameter is an empty array by default. The "max" bound indicates the maximum number of schedules a node can have.
Adding a New Schedule
A new schedule can be added by passing a value like this in setparams
.
{
"Schedule": {
"Schedules": [{
"name": "Evening",
"id": "8D36",
"operation": "add",
"triggers": [{
"d": 31,
"m": 1110
}],
"action": {
"Light": {
"power": true
}
}
}]
}
}
- name (string): A user friendly name for the schedule.
- id (string): A unique ID for the schedule. (This needs to be unique only for the given user not universally unique). This should be generated by the client while adding a schedule, and then used for any further operations. A shorter ID is desirable.
- operation (string): The operation to be performed. Possible values are add, remove, edit, enable and disable.
- triggers (array of objects): The time at which the schedule should trigger. Either "m" or "rsec" should be provided.
- m (uint16): Minutes since midnight. Eg. 6:30pm = 18 * 60 + 30 = 1110. Either "d" or "dd" should be provided.
- d (uint8): Bitmap for days. LSB is Monday. [ N/A | Sunday | Saturday | Friday | Tuesday | Wednesday | Tuesday | Monday ]. Eg. 0b00011111 (31) means all weekdays. A value of zero means trigger just once.
- dd (uint8): Date (1-31).
- mm (uint16, Optional) : Bitmap for months. LSB is January. Eg.0b00000111 (7) means Jan, Feb, Mar.
- yy (uint16, Optional) : Year. Eg. 2023.
- r (bool, Optional):
true
for repeating every year.
- rsec (int32): Relative seconds starting from now. There is no day/date setting for this. Either "m" or "rsec" should be provided.
- m (uint16): Minutes since midnight. Eg. 6:30pm = 18 * 60 + 30 = 1110. Either "d" or "dd" should be provided.
- action (object): The actual action to be performed at the given time. The value of this object will be same as what you pass while setting the params. Eg. For turning on Light, it will be
"Light": {"power": true}
. - info (string, Optional): Additional info or description as per the clients' requirements.
- flags (uint32, Optional): General purpose flags (unsigned integer value) which can be used as per the clients' requirements.
- validity (object, Optional): To provide a start and end time between which the schedule should be applicable.
- start (uint32): Start time (epoch seconds) from which the schedule should be active.
- end (uint32): End time (epoch seconds) after which the schedule should be inactive.
Once the schedule is added successfully, a get on params will report a similar value like below:
{
"Schedule": {
"Schedules": [{
"name": "Evening",
"id": "8D36",
"enabled": true,
"triggers": [{
"d": 31,
"m": 1110
}],
"action": {
"Light": {
"power": true
}
}
}]
}
}
As you can see, the response includes an "enabled" key, which indicates that the schedule is now enabled.
Note: While adding or updating schedules, a single entry is sent in the schedules array. However, when queried, the node will return information of all the schedules in the array.
Other Operations
Edit
Value passed for editing the schedule will be similar to that passed for adding. The ID should match an existing schedule and the "operation" value will be "edit". You can either send the entire object or only the elements that have changed (name, trigger or action). However, the objects in these keys cannot be partial. For example, if current action is "action":{"Light": {"power": true, "brightness":90}}
, and you need to change brightness to 100, you should pass "action":{"Light": {"power": true, "brightness":100}}
and not just "action":{"Light": {"brightness":100}}
.
Remove
For removing an existing schedule you just need to pass its ID and the operation. An example is provided below.
{
"Schedule": {
"Schedules": [{
"id": "8D36",
"operation": "remove"
}]
}
}
Enable/Disable
The payload is very similar to the remove operation. This is useful when you have to temporarily disable a schedule instead of removing it. An example is provided below.
{
"Schedule": {
"Schedules": [{
"id": "8D36",
"operation": "disable"
}]
}
}
Passing operation as "enable" will re-enable the schedule.
Note: A one-time schedule automatically gets disabled after it has executed.
Usage
Firmware
The ESP RainMaker exposes a single API to enable scheduling.
esp_err_t esp_rmaker_schedule_enable(void);
This API should be called after
esp_rmaker_node_init()
but beforeesp_rmaker_start()
.
The maximum number of schedules supported can be configured by using the CONFIG_ESP_RMAKER_SCHEDULING_MAX_SCHEDULES
config option (idf.py menuconfig -> ESP RainMaker Config -> ESP RainMaker Scheduling -> Maximum Schedules
). However, you may have to also increase CONFIG_ESP_RMAKER_MAX_PARAM_DATA_SIZE
to support larger number of schedules, as the schedules payload size will increase.
Before using scheduling feature, as mentioned earlier, it is recommended to set the timezone first. Please check here for information regarding timezones. The simplest way is to just set the CONFIG_ESP_RMAKER_DEF_TIMEZONE
config option,such as CONFIG_ESP_RMAKER_DEF_TIMEZONE="Asia/Shanghai"
.
You can find this at idf.py menuconfig -> ESP RainMaker Config -> Default Timezone
Phone Apps
The phone applications provide a very simple user interface for various scheduling operations. Please update your phone apps, enable scheduling in the firmware, and get started.
Schedules Across Nodes
As we have seen, scheduling information is maintained by the nodes, and not by the cloud backend. However, it is a common requirement to have multiple nodes as part of a single schedule. For such schedules, the client uses the same schedule ID across multiple nodes. Even while querying, it looks for common schedule IDs across all the nodes and shows them together.
Managing Daylight Saving Time (DST)
Normally, when daylight saving time (DST) starts, the clocks are moved forward by 1 hour and when it ends, they are moved back by 1 hour. Let's consider America/Los_Angeles timezone for example. As per Wikipedia:
In the U.S., daylight saving time starts on the second Sunday in March and ends on the first Sunday in November, with the time changes taking place at 2:00 a.m. local time. With a mnemonic word play referring to seasons, clocks "spring forward, fall back"—that is, in springtime the clocks are moved forward from 2:00 a.m. to 3:00 a.m. and in fall they are moved back from 2:00 a.m. to 1:00 a.m.
On all the days apart from the ones at which the DST switch takes place, the schedule will trigger at the correct scheduled time, since the node automatically adjusts its clock as per the DST info for the specified timezone. However, what happens during the DST switch is something to be considered. Let's now see what happens to schedules during the switch:
Scheduled Time | DST Begins (spring forward from 2:00 to 3:00 | DST ends (move back from 2:00 to 1:00) |
---|---|---|
12:59 a.m. and earlier | No change | No change |
1:00 a.m. to 1:59 a.m. | No change | Triggered only once before the switch. Not triggered again the second time after the switch |
2:00 a.m. to 2:59 a.m. | Delayed by 1hr. Triggered from 3:00 to 3:59 a.m. instead, on the day of switch | No change |
3:00 a.m. and later | No change | No change |