ESP32 上的警报
Alarms on an ESP32
我正在使用连接到 Google Cloud IoT Core 的 ESP32 来控制灯,并以 raspberry pi 作为集线器通过 IoT Core 向 esp32 发送消息以在设置时打开和关闭它们一天中的时间。
我现在想做的只是消除 raspberry pi 并让 esp32 在需要打开或关闭它们时进行管理,但我看不到找到设置警报或类似警报的方法在几天的特定时间触发。
传入的时间值是 UTC 的毫秒数
是否存在类似的东西,或者我怎样才能在 esp32 上完成警报?
一个real-time clock is what you are looking for. I've never worked with the ESP32 but apparently it has one and it is bad。但是,5% 的误差可能对您来说已经足够了。如果可以的话,我会使用外部的。
我不知道您使用的是 ESP IDF 还是 ESP32 的某种 Arduino 框架(我不熟悉)。这个答案是针对 ESP IDF 的。如果您没有使用 ESP-IDF 并且无法访问相关的头文件和库,一些想法可能仍然相关。
激活 SNTP
按照 https://github.com/espressif/esp-idf/tree/master/examples/protocols/sntp 中的 SNTP 示例确保控制器上的时钟已更新。
使用localtime()
如果您使用 ESP-IDF,您将可以使用 localtime_r()
功能。将此(指针)传递给 'seconds since the Epoch' 的数字 - 由 time(NULL)
返回 - 它将用星期几、月几、小时、分钟填充 struct tm
第二个。
一般程序是(省略错误处理)...
#include <time.h>
// ...
struct tm now = {};
time_t tnow = time(NULL);
localtime_r(&tnow, &now);
// Compare current day, hour, minute, etc. against pre-defined alarms.
注意:如果您更喜欢使用 UTC,可以使用 gmtime_r()
而不是 localtime_r()
。
您可以从主 while()
循环中定期调用它,如下所述。
报警结构
为您的警报信息定义一个结构。您可以使用类似下面的内容 - 根据您需要的粒度级别进行调整。请注意,它包含一个 callback
成员 - 指定程序在触发警报时实际应执行的操作。
typedef struct {
int week_day; // -1 for every day of week
int hour; // -1 for every hour
int minute; // -1 for every minute
void (*callback)(void);
void *callback_arg;
} alarm_t;
定义警报和回调
定义警报数组、回调函数和参数。
static void trigger_relay(const void *arg) {
uint32_t num = *(uint32_t *)arg;
printf("Activating relay %u ...\n", num);
// Handle GPIO etc.
}
static uint32_t relay_1 = 0;
static uint32_t relay_2 = 1;
static alarm_t alarms[] = {
// Trigger relay 1 at 9:00 on Sunday
{0, 9, 0, trigger_relay, (void *)&relay_1},
// Relay 2 at 7:30 every day
{-1, 7, 30, trigger_relay, (void *)&relay_2},
};
定期扫描
定义一个 check_alarms()
例程,它加载一个 struct tm
结构和当前时间,并将其与您定义的每个警报进行比较。如果当前时间与闹钟时间匹配,则调用回调。
static void check_alarms(void) {
time_t tnow = time(NULL);
struct tm now = {};
localtime_r(&tnow, &now);
ESP_LOGI(TAG, "Time: %u:%u:%u", now.tm_hour, now.tm_min, now.tm_sec);
size_t n_alarms = sizeof(alarms) / sizeof(alarms[0]);
for (int i = 0; i < n_alarms; i++) {
alarm_t *alrm = &alarms[i];
if (alrm->week_day != -1 && alrm->week_day != now.tm_wday) {
continue;
}
if (alrm->hour != -1 && alrm->hour != now.tm_hour) {
continue;
}
if (alrm->minute != -1 && alrm->minute != now.tm_min) {
continue;
}
alrm->callback(alrm->callback_arg);
}
}
然后从您的主 while()
循环中调用它,其频率取决于您的警报的粒度。由于上面定义的 alarm_t
具有 1 分钟的粒度,这就是我调用 check_alarms()
的频率。此示例适用于 FreeRTOS,但可以根据需要进行调整。
while (1) {
TickType_t tick = xTaskGetTickCount();
if (tick - g_time_last_check > pdMS_TO_TICKS(60000)) {
g_time_last_check = tick;
check_alarms();
}
}
效率
如果您有很多警报和/或每个警报都应高频触发,则上述方法效率不高。在这种情况下,您可能会考虑根据时间对警报进行排序的算法,直到经过,并且 - 而不是每次都遍历整个数组 - 而是使用 one-shot 软件计时器来调用相关回调。除非你有大量的计时器,否则这可能不值得这么麻烦。
备选方案
FreeRTOS 和 ESP-IDF 都包含 software timers 的 API - 但这些 API 不能(轻松)用于一天中特定时间的警报 - 您将其列为要求。另一方面 - 正如您可能已经知道的那样 - ESP32 system-on-a-chip 确实有一个 built-in RTC,但我认为最好使用 higher-level 接口而不是直接与它通信。
我正在使用连接到 Google Cloud IoT Core 的 ESP32 来控制灯,并以 raspberry pi 作为集线器通过 IoT Core 向 esp32 发送消息以在设置时打开和关闭它们一天中的时间。
我现在想做的只是消除 raspberry pi 并让 esp32 在需要打开或关闭它们时进行管理,但我看不到找到设置警报或类似警报的方法在几天的特定时间触发。
传入的时间值是 UTC 的毫秒数
是否存在类似的东西,或者我怎样才能在 esp32 上完成警报?
一个real-time clock is what you are looking for. I've never worked with the ESP32 but apparently it has one and it is bad。但是,5% 的误差可能对您来说已经足够了。如果可以的话,我会使用外部的。
我不知道您使用的是 ESP IDF 还是 ESP32 的某种 Arduino 框架(我不熟悉)。这个答案是针对 ESP IDF 的。如果您没有使用 ESP-IDF 并且无法访问相关的头文件和库,一些想法可能仍然相关。
激活 SNTP
按照 https://github.com/espressif/esp-idf/tree/master/examples/protocols/sntp 中的 SNTP 示例确保控制器上的时钟已更新。
使用localtime()
如果您使用 ESP-IDF,您将可以使用 localtime_r()
功能。将此(指针)传递给 'seconds since the Epoch' 的数字 - 由 time(NULL)
返回 - 它将用星期几、月几、小时、分钟填充 struct tm
第二个。
一般程序是(省略错误处理)...
#include <time.h>
// ...
struct tm now = {};
time_t tnow = time(NULL);
localtime_r(&tnow, &now);
// Compare current day, hour, minute, etc. against pre-defined alarms.
注意:如果您更喜欢使用 UTC,可以使用 gmtime_r()
而不是 localtime_r()
。
您可以从主 while()
循环中定期调用它,如下所述。
报警结构
为您的警报信息定义一个结构。您可以使用类似下面的内容 - 根据您需要的粒度级别进行调整。请注意,它包含一个 callback
成员 - 指定程序在触发警报时实际应执行的操作。
typedef struct {
int week_day; // -1 for every day of week
int hour; // -1 for every hour
int minute; // -1 for every minute
void (*callback)(void);
void *callback_arg;
} alarm_t;
定义警报和回调
定义警报数组、回调函数和参数。
static void trigger_relay(const void *arg) {
uint32_t num = *(uint32_t *)arg;
printf("Activating relay %u ...\n", num);
// Handle GPIO etc.
}
static uint32_t relay_1 = 0;
static uint32_t relay_2 = 1;
static alarm_t alarms[] = {
// Trigger relay 1 at 9:00 on Sunday
{0, 9, 0, trigger_relay, (void *)&relay_1},
// Relay 2 at 7:30 every day
{-1, 7, 30, trigger_relay, (void *)&relay_2},
};
定期扫描
定义一个 check_alarms()
例程,它加载一个 struct tm
结构和当前时间,并将其与您定义的每个警报进行比较。如果当前时间与闹钟时间匹配,则调用回调。
static void check_alarms(void) {
time_t tnow = time(NULL);
struct tm now = {};
localtime_r(&tnow, &now);
ESP_LOGI(TAG, "Time: %u:%u:%u", now.tm_hour, now.tm_min, now.tm_sec);
size_t n_alarms = sizeof(alarms) / sizeof(alarms[0]);
for (int i = 0; i < n_alarms; i++) {
alarm_t *alrm = &alarms[i];
if (alrm->week_day != -1 && alrm->week_day != now.tm_wday) {
continue;
}
if (alrm->hour != -1 && alrm->hour != now.tm_hour) {
continue;
}
if (alrm->minute != -1 && alrm->minute != now.tm_min) {
continue;
}
alrm->callback(alrm->callback_arg);
}
}
然后从您的主 while()
循环中调用它,其频率取决于您的警报的粒度。由于上面定义的 alarm_t
具有 1 分钟的粒度,这就是我调用 check_alarms()
的频率。此示例适用于 FreeRTOS,但可以根据需要进行调整。
while (1) {
TickType_t tick = xTaskGetTickCount();
if (tick - g_time_last_check > pdMS_TO_TICKS(60000)) {
g_time_last_check = tick;
check_alarms();
}
}
效率
如果您有很多警报和/或每个警报都应高频触发,则上述方法效率不高。在这种情况下,您可能会考虑根据时间对警报进行排序的算法,直到经过,并且 - 而不是每次都遍历整个数组 - 而是使用 one-shot 软件计时器来调用相关回调。除非你有大量的计时器,否则这可能不值得这么麻烦。
备选方案
FreeRTOS 和 ESP-IDF 都包含 software timers 的 API - 但这些 API 不能(轻松)用于一天中特定时间的警报 - 您将其列为要求。另一方面 - 正如您可能已经知道的那样 - ESP32 system-on-a-chip 确实有一个 built-in RTC,但我认为最好使用 higher-level 接口而不是直接与它通信。