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 接口而不是直接与它通信。