Files
iot-bedroom-environment-con…/main/time_alarm.c

405 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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;
}