mirror of
https://git.beihong.wang/wangbeihong/iot-bedroom-environment-controller.git
synced 2026-04-23 14:13:05 +08:00
405 lines
13 KiB
C
405 lines
13 KiB
C
#include "time_alarm.h"
|
||
#include "esp_log.h"
|
||
#include "esp_timer.h"
|
||
#include "nvs_flash.h"
|
||
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "freertos/semphr.h"
|
||
#include "esp_sntp.h"
|
||
#include <time.h>
|
||
|
||
#include "mqtt_manager.h"
|
||
#include "common.h"
|
||
#include "app_state.h"
|
||
#include "vars.h"
|
||
#include "wifi-connect.h"
|
||
|
||
static const char *TAG = "time_alarm";
|
||
|
||
#define SNTP_TIME_VALID_UNIX_TS 1700000000
|
||
|
||
typedef struct {
|
||
uint8_t hour;
|
||
uint8_t minute;
|
||
uint8_t second;
|
||
bool enable;
|
||
bool triggered;
|
||
} alarm_t;
|
||
|
||
static alarm_t g_alarms[ALARM_MAX_NUM];
|
||
static TaskHandle_t alarm_task_handle = NULL;
|
||
|
||
// 来自 main.c 的外部符号
|
||
extern SemaphoreHandle_t xControlFlagMutex;
|
||
extern bool buzzer_control_flag;
|
||
extern bool servo_control_flag;
|
||
extern bool light_source_control_flag;
|
||
extern uint8_t led_brightness_value;
|
||
|
||
|
||
// 本地静态函数声明
|
||
static void alarm_save_to_nvs(void);
|
||
extern SemaphoreHandle_t xMqttMessageMutex;
|
||
extern device_message_t g_device_message;
|
||
|
||
// mqtt_manager 提供的反馈接口
|
||
extern void mqtt_manager_publish_feedback(void);
|
||
|
||
static void wait_for_time_sync(void)
|
||
{
|
||
int retry = 0;
|
||
int max_retry = 30;
|
||
|
||
// 等待WiFi连接
|
||
while (retry < max_retry)
|
||
{
|
||
if (wifi_connect_get_status() == WIFI_CONNECT_STATUS_CONNECTED)
|
||
{
|
||
ESP_LOGI(TAG, "WiFi已连接,开始初始化SNTP");
|
||
break;
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
retry++;
|
||
}
|
||
|
||
if (retry >= max_retry)
|
||
{
|
||
ESP_LOGW(TAG, "WiFi连接超时,跳过SNTP时间同步");
|
||
return;
|
||
}
|
||
|
||
// 初始化SNTP
|
||
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||
esp_sntp_setservername(0, "cn.pool.ntp.org");
|
||
esp_sntp_setservername(1, "ntp1.aliyun.com");
|
||
esp_sntp_init();
|
||
|
||
// 等待时间同步(time()返回有效时间戳即表示同步完成)
|
||
retry = 0;
|
||
max_retry = 30;
|
||
while (retry < max_retry)
|
||
{
|
||
time_t now = time(NULL);
|
||
if (now >= SNTP_TIME_VALID_UNIX_TS)
|
||
{
|
||
struct tm timeinfo;
|
||
localtime_r(&now, &timeinfo);
|
||
ESP_LOGI(TAG, "SNTP时间同步成功: %04d-%02d-%02d %02d:%02d:%02d",
|
||
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
|
||
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||
return;
|
||
}
|
||
if (retry % 5 == 0)
|
||
{
|
||
ESP_LOGI(TAG, "等待SNTP时间同步... %d秒", retry);
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
retry++;
|
||
}
|
||
|
||
ESP_LOGW(TAG, "SNTP时间同步超时");
|
||
}
|
||
|
||
// 对外包装:等待 SNTP 同步完成或超时
|
||
void time_alarm_wait_for_sntp_sync(void)
|
||
{
|
||
wait_for_time_sync();
|
||
}
|
||
|
||
void alarm_set_time(uint8_t alarm_idx, uint8_t hour, uint8_t minute, uint8_t second)
|
||
{
|
||
if (alarm_idx >= ALARM_MAX_NUM)
|
||
return;
|
||
g_alarms[alarm_idx].hour = hour;
|
||
g_alarms[alarm_idx].minute = minute;
|
||
g_alarms[alarm_idx].second = second;
|
||
g_alarms[alarm_idx].triggered = false;
|
||
ESP_LOGI(TAG, "Alarm[%d] time set to %02d:%02d:%02d, enable=%d", alarm_idx, hour, minute, second, g_alarms[alarm_idx].enable ? 1 : 0);
|
||
|
||
// 保存到NVS
|
||
alarm_save_to_nvs();
|
||
}
|
||
|
||
void alarm_set_enable(uint8_t alarm_idx, bool enable)
|
||
{
|
||
if (alarm_idx >= ALARM_MAX_NUM)
|
||
return;
|
||
g_alarms[alarm_idx].enable = enable;
|
||
g_alarms[alarm_idx].triggered = false;
|
||
ESP_LOGI(TAG, "Alarm[%d] enable=%d, time=%02d:%02d:%02d", alarm_idx, enable ? 1 : 0,
|
||
g_alarms[alarm_idx].hour, g_alarms[alarm_idx].minute, g_alarms[alarm_idx].second);
|
||
|
||
// 保存到NVS
|
||
alarm_save_to_nvs();
|
||
}
|
||
|
||
void alarm_disable_all(void)
|
||
{
|
||
for (int i = 0; i < ALARM_MAX_NUM; i++)
|
||
{
|
||
g_alarms[i].enable = false;
|
||
g_alarms[i].triggered = false;
|
||
}
|
||
|
||
// 保存到NVS
|
||
alarm_save_to_nvs();
|
||
}
|
||
|
||
// 获取闹钟状态信息
|
||
void alarm_get_status(int alarm_idx, uint8_t *hour, uint8_t *minute, uint8_t *second, bool *enabled, bool *triggered)
|
||
{
|
||
if (alarm_idx >= ALARM_MAX_NUM)
|
||
return;
|
||
|
||
if (hour) *hour = g_alarms[alarm_idx].hour;
|
||
if (minute) *minute = g_alarms[alarm_idx].minute;
|
||
if (second) *second = g_alarms[alarm_idx].second;
|
||
if (enabled) *enabled = g_alarms[alarm_idx].enable;
|
||
if (triggered) *triggered = g_alarms[alarm_idx].triggered;
|
||
}
|
||
|
||
// 自动停止任务函数
|
||
static void alarm_auto_stop_task(void *arg)
|
||
{
|
||
int alarm_idx = (int)arg;
|
||
vTaskDelay(pdMS_TO_TICKS(10000)); // 等待10秒
|
||
|
||
ESP_LOGI(TAG, "Auto-stopping alarm %d actions after 10 seconds", alarm_idx);
|
||
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = false;
|
||
servo_control_flag = false;
|
||
light_source_control_flag = false;
|
||
led_brightness_value = 0;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
|
||
// 更新设备消息中的设备状态
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
strcpy(g_device_message.telemetry.buzzer_state, "close");
|
||
strcpy(g_device_message.telemetry.curtain_state, "close");
|
||
strcpy(g_device_message.telemetry.led_state, "close");
|
||
g_device_message.telemetry.led_power = 0;
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
|
||
// 发布闹钟关闭状态消息
|
||
mqtt_manager_publish_alarm_status(alarm_idx, "stopped", false);
|
||
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
static void alarm_trigger_action(int idx)
|
||
{
|
||
ESP_LOGI(TAG, "Alarm %d triggered", idx);
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = true;
|
||
servo_control_flag = true;
|
||
light_source_control_flag = true;
|
||
led_brightness_value = 100;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
// 更新设备消息中的设备状态
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
strcpy(g_device_message.telemetry.buzzer_state, "open");
|
||
strcpy(g_device_message.telemetry.curtain_state, "open");
|
||
strcpy(g_device_message.telemetry.led_state, "open");
|
||
g_device_message.telemetry.led_power = 100;
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
|
||
// 发布闹钟触发状态消息
|
||
mqtt_manager_publish_alarm_status(idx, "triggered", true);
|
||
|
||
// 创建自动停止任务(延迟10秒后停止)
|
||
TaskHandle_t stop_task_handle = NULL;
|
||
// 调整栈为2048以节省内存(保守测试)并记录堆情况
|
||
ESP_LOGI(TAG, "创建 alarm_auto_stop_task 前堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||
// 降低自动停止任务栈到 1536,释放堆空间(如遇溢出可再调大)
|
||
BaseType_t result = xTaskCreate(alarm_auto_stop_task, "alarm_stop_task", 2048, (void *)idx, 3, &stop_task_handle);
|
||
ESP_LOGI(TAG, "创建 alarm_auto_stop_task 后堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||
|
||
if (result == pdPASS) {
|
||
ESP_LOGI(TAG, "Auto-stop task created for alarm %d", idx);
|
||
} else {
|
||
ESP_LOGE(TAG, "Failed to create auto-stop task for alarm %d", idx);
|
||
}
|
||
}
|
||
|
||
static void alarm_task(void *pvParameters)
|
||
{
|
||
(void)pvParameters;
|
||
ESP_LOGI(TAG, "alarm task started");
|
||
wait_for_time_sync();
|
||
|
||
// 打印当前设置的闹钟状态
|
||
ESP_LOGI(TAG, "当前闹钟设置:");
|
||
for (int i = 0; i < ALARM_MAX_NUM; i++)
|
||
{
|
||
ESP_LOGI(TAG, "Alarm[%d]: %02d:%02d:%02d, enable=%d, triggered=%d",
|
||
i, g_alarms[i].hour, g_alarms[i].minute, g_alarms[i].second,
|
||
g_alarms[i].enable ? 1 : 0, g_alarms[i].triggered ? 1 : 0);
|
||
}
|
||
|
||
while (1)
|
||
{
|
||
time_t now = time(NULL);
|
||
struct tm tm_now;
|
||
localtime_r(&now, &tm_now);
|
||
|
||
// 每5分钟打印一次当前时间
|
||
if (tm_now.tm_min % 5 == 0 && tm_now.tm_sec == 0)
|
||
{
|
||
ESP_LOGI(TAG, "当前系统时间: %02d:%02d:%02d", tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
|
||
}
|
||
|
||
// 每秒更新屏幕时间显示
|
||
char time_str[16];
|
||
snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d", tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
|
||
set_var_time(time_str);
|
||
|
||
for (int i = 0; i < ALARM_MAX_NUM; i++)
|
||
{
|
||
if (g_alarms[i].enable)
|
||
{
|
||
// 如果闹钟已经触发,检查是否已经过了触发时间,以便重置状态
|
||
if (g_alarms[i].triggered)
|
||
{
|
||
// 如果当前时间已经过了闹钟时间至少1分钟,重置触发状态
|
||
if (tm_now.tm_hour > g_alarms[i].hour ||
|
||
(tm_now.tm_hour == g_alarms[i].hour && tm_now.tm_min > g_alarms[i].minute))
|
||
{
|
||
g_alarms[i].triggered = false;
|
||
ESP_LOGI(TAG, "闹钟[%d]触发状态已重置,准备明天再次触发", i);
|
||
}
|
||
}
|
||
else // 闹钟未触发,检查是否应该触发
|
||
{
|
||
// 只检查小时和分钟,秒数不检查,这样更加可靠
|
||
if (tm_now.tm_hour == g_alarms[i].hour && tm_now.tm_min == g_alarms[i].minute)
|
||
{
|
||
ESP_LOGI(TAG, "闹钟[%d]触发! 设置时间: %02d:%02d:%02d, 当前时间: %02d:%02d:%02d",
|
||
i, g_alarms[i].hour, g_alarms[i].minute, g_alarms[i].second,
|
||
tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
|
||
g_alarms[i].triggered = true;
|
||
alarm_trigger_action(i);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
}
|
||
}
|
||
|
||
static void alarm_save_to_nvs(void)
|
||
{
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open("alarm_config", NVS_READWRITE, &nvs_handle);
|
||
if (err != ESP_OK) {
|
||
ESP_LOGE(TAG, "Failed to open NVS for alarm config: %s", esp_err_to_name(err));
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < ALARM_MAX_NUM; i++) {
|
||
char key[16];
|
||
snprintf(key, sizeof(key), "alarm%d_hour", i);
|
||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].hour);
|
||
|
||
snprintf(key, sizeof(key), "alarm%d_min", i);
|
||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].minute);
|
||
|
||
snprintf(key, sizeof(key), "alarm%d_sec", i);
|
||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].second);
|
||
|
||
snprintf(key, sizeof(key), "alarm%d_enable", i);
|
||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].enable ? 1 : 0);
|
||
}
|
||
|
||
nvs_close(nvs_handle);
|
||
ESP_LOGI(TAG, "Alarm config saved to NVS");
|
||
}
|
||
|
||
static void alarm_load_from_nvs(void)
|
||
{
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open("alarm_config", NVS_READONLY, &nvs_handle);
|
||
if (err != ESP_OK) {
|
||
ESP_LOGI(TAG, "No alarm config found in NVS, using defaults");
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < ALARM_MAX_NUM; i++) {
|
||
char key[16];
|
||
uint8_t value;
|
||
|
||
snprintf(key, sizeof(key), "alarm%d_hour", i);
|
||
err = nvs_get_u8(nvs_handle, key, &value);
|
||
if (err == ESP_OK) g_alarms[i].hour = value;
|
||
|
||
snprintf(key, sizeof(key), "alarm%d_min", i);
|
||
err = nvs_get_u8(nvs_handle, key, &value);
|
||
if (err == ESP_OK) g_alarms[i].minute = value;
|
||
|
||
snprintf(key, sizeof(key), "alarm%d_sec", i);
|
||
err = nvs_get_u8(nvs_handle, key, &value);
|
||
if (err == ESP_OK) g_alarms[i].second = value;
|
||
|
||
snprintf(key, sizeof(key), "alarm%d_enable", i);
|
||
err = nvs_get_u8(nvs_handle, key, &value);
|
||
if (err == ESP_OK) g_alarms[i].enable = (value == 1);
|
||
}
|
||
|
||
nvs_close(nvs_handle);
|
||
ESP_LOGI(TAG, "Alarm config loaded from NVS");
|
||
}
|
||
|
||
esp_err_t time_alarm_init(void)
|
||
{
|
||
for (int i = 0; i < ALARM_MAX_NUM; i++)
|
||
{
|
||
g_alarms[i].hour = 0;
|
||
g_alarms[i].minute = 0;
|
||
g_alarms[i].second = 0;
|
||
g_alarms[i].enable = false;
|
||
g_alarms[i].triggered = false;
|
||
}
|
||
|
||
// 从NVS加载闹钟配置
|
||
alarm_load_from_nvs();
|
||
|
||
ESP_LOGI(TAG, "time_alarm initialized");
|
||
return ESP_OK;
|
||
}
|
||
|
||
esp_err_t time_alarm_start(void)
|
||
{
|
||
if (alarm_task_handle != NULL)
|
||
return ESP_OK;
|
||
ESP_LOGI(TAG, "创建 alarm_task 前堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||
// 降低主闹钟任务栈到 2048,释放堆空间(如遇溢出可再调大)
|
||
BaseType_t ok = xTaskCreate(alarm_task, "alarm_clock", 2048, NULL, 5, &alarm_task_handle);
|
||
ESP_LOGI(TAG, "创建 alarm_task 后堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||
if (ok != pdPASS)
|
||
{
|
||
ESP_LOGE(TAG, "Failed to create alarm task");
|
||
return ESP_ERR_NO_MEM;
|
||
}
|
||
return ESP_OK;
|
||
}
|
||
|
||
esp_err_t time_alarm_stop(void)
|
||
{
|
||
if (alarm_task_handle != NULL)
|
||
{
|
||
vTaskDelete(alarm_task_handle);
|
||
alarm_task_handle = NULL;
|
||
}
|
||
return ESP_OK;
|
||
}
|