288 lines
8.7 KiB
C++
288 lines
8.7 KiB
C++
#include <stdio.h>
|
||
#include <time.h>
|
||
#include <string.h>
|
||
#include "wifi-connect.h"
|
||
#include "esp_lvgl_port.h"
|
||
#include "lvgl_st7789_use.h"
|
||
#include "ui.h"
|
||
#include "vars.h"
|
||
#include "relay_ctrl.h"
|
||
#include "esp_err.h"
|
||
#include "esp_log.h"
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "freertos/semphr.h"
|
||
#include "sntp_time.h"
|
||
#include "esp_mac.h"
|
||
#include "esp_system.h"
|
||
#include "esp_efuse.h"
|
||
#include "esp_efuse_table.h"
|
||
#include "sntp_time.h"
|
||
#include "bh1750_use.h"
|
||
#include "aht30.h"
|
||
#include "MQ-2.h"
|
||
#include "JW01.h"
|
||
#include "human_door.h"
|
||
#include "fire_sensor.h"
|
||
|
||
#define TAG "MAIN"
|
||
#define CO2_SPOILAGE_THRESHOLD_PPM 1500.0f
|
||
#define FIRE_DANGER_THRESHOLD_PERCENT 35.0f
|
||
|
||
typedef struct
|
||
{
|
||
char time_str[32];
|
||
float lux;
|
||
float temp;
|
||
float humidity;
|
||
float gas_percent;
|
||
float tvoc;
|
||
float hcho;
|
||
float co2;
|
||
float fire_percent;
|
||
bool fire_danger;
|
||
bool human_present;
|
||
bool door_closed;
|
||
} env_data_t;
|
||
|
||
static env_data_t s_env_data;
|
||
static SemaphoreHandle_t s_env_data_lock = NULL;
|
||
static volatile bool s_ui_ready = false;
|
||
|
||
static bool wait_for_wifi_connected(TickType_t timeout_ticks)
|
||
{
|
||
const TickType_t start_ticks = xTaskGetTickCount();
|
||
while ((xTaskGetTickCount() - start_ticks) < timeout_ticks)
|
||
{
|
||
if (wifi_connect_get_status() == WIFI_CONNECT_STATUS_CONNECTED)
|
||
return true;
|
||
vTaskDelay(pdMS_TO_TICKS(200));
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static void env_data_update_system_info(void)
|
||
{
|
||
if (s_env_data_lock == NULL)
|
||
return;
|
||
time_t now;
|
||
struct tm timeinfo;
|
||
time(&now);
|
||
localtime_r(&now, &timeinfo);
|
||
char time_buf[32];
|
||
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
|
||
|
||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||
strncpy(s_env_data.time_str, time_buf, sizeof(s_env_data.time_str));
|
||
xSemaphoreGive(s_env_data_lock);
|
||
set_var_local_time(s_env_data.time_str);
|
||
}
|
||
|
||
static void ui_task(void *arg)
|
||
{
|
||
for (;;)
|
||
{
|
||
if (!s_ui_ready)
|
||
{
|
||
vTaskDelay(pdMS_TO_TICKS(20));
|
||
continue;
|
||
}
|
||
env_data_update_system_info();
|
||
lvgl_port_lock(0);
|
||
ui_tick();
|
||
lvgl_port_unlock();
|
||
vTaskDelay(pdMS_TO_TICKS(30));
|
||
}
|
||
}
|
||
|
||
static void ui_init_task(void *arg)
|
||
{
|
||
(void)arg;
|
||
|
||
lvgl_port_lock(100 / portTICK_PERIOD_MS);
|
||
ui_init();
|
||
lvgl_port_unlock();
|
||
|
||
s_ui_ready = true;
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
static void status_task(void *arg)
|
||
{
|
||
(void)arg;
|
||
for (;;)
|
||
{
|
||
human_door_state_t io_state{};
|
||
if (human_door_read(&io_state) == ESP_OK)
|
||
{
|
||
set_var_hum_status(io_state.human_present ? "有人" : "无人");
|
||
set_var_door_status(io_state.door_closed ? "关闭" : "开启");
|
||
|
||
if (s_env_data_lock)
|
||
{
|
||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||
s_env_data.human_present = io_state.human_present;
|
||
s_env_data.door_closed = io_state.door_closed;
|
||
xSemaphoreGive(s_env_data_lock);
|
||
}
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(200));
|
||
}
|
||
}
|
||
|
||
static void sntp_task(void *arg)
|
||
{
|
||
(void)arg;
|
||
|
||
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000))) {
|
||
esp_err_t sntp_ret = sntp_timp_sync_time(10000);
|
||
if (sntp_ret != ESP_OK) {
|
||
ESP_LOGW(TAG, "SNTP sync failed: %s", esp_err_to_name(sntp_ret));
|
||
}
|
||
}
|
||
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
extern "C" void app_main(void)
|
||
{
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
ESP_LOGI(TAG, "--- APP STARTING ---");
|
||
s_env_data_lock = xSemaphoreCreateMutex();
|
||
|
||
// 1. 初始化 Wi-Fi
|
||
ESP_ERROR_CHECK(wifi_connect_init());
|
||
|
||
// 2. 初始化显示屏和 LVGL
|
||
start_lvgl_demo();
|
||
|
||
// 3. 在 CPU1 上执行 UI 初始化,避免 app_main 长时间占用 CPU0
|
||
xTaskCreatePinnedToCore(ui_init_task, "ui_init_task", 8192, NULL, 8, NULL, 1);
|
||
set_var_food_status("良好");
|
||
set_var_fire_status("安全");
|
||
|
||
// 7. 创建 UI 刷新任务并固定在 CPU1(与 ui_init_task 同核)
|
||
xTaskCreatePinnedToCore(ui_task, "ui_task", 8192, NULL, 5, NULL, 1);
|
||
|
||
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000)))
|
||
{
|
||
set_var_system_ip(wifi_connect_get_ip());
|
||
}
|
||
|
||
// 4. 独立 SNTP 对时任务
|
||
xTaskCreate(sntp_task, "sntp_task", 4096, NULL, 4, NULL);
|
||
|
||
// 初始化继电器 (独立配置每个通道)
|
||
const relay_config_t relay_cfg[RELAY_CTRL_ID_MAX] = {
|
||
{.pin = GPIO_NUM_9, .active_high = false},
|
||
{.pin = GPIO_NUM_10, .active_high = false},
|
||
{.pin = GPIO_NUM_11, .active_high = true},
|
||
{.pin = GPIO_NUM_12, .active_high = true},
|
||
};
|
||
ESP_ERROR_CHECK(relay_ctrl_init(relay_cfg));
|
||
|
||
// 5. 初始化 I2C 总线并注册传感器 (共享总线)
|
||
ESP_ERROR_CHECK(bh1750_user_init());
|
||
i2c_master_bus_handle_t i2c_bus = bh1750_get_i2c_bus_handle();
|
||
|
||
// AHT30 挂载到同一条 I2C 总线上
|
||
aht30_handle_t aht30_dev = NULL;
|
||
ESP_ERROR_CHECK(aht30_create(i2c_bus, AHT30_I2C_ADDRESS, &aht30_dev));
|
||
|
||
// MQ-2 使用 ADC(GPIO8)
|
||
ESP_ERROR_CHECK(mq2_init());
|
||
|
||
// JW01 使用 UART0(GPIO43/44)
|
||
ESP_ERROR_CHECK(jw01_init());
|
||
|
||
// 火焰传感器使用 ADC(GPIO3)
|
||
ESP_ERROR_CHECK(fire_sensor_init());
|
||
|
||
// GPIO16: HC-SR312, GPIO17: Door switch(低电平=关门)
|
||
ESP_ERROR_CHECK(human_door_init());
|
||
set_var_hum_status("无人");
|
||
set_var_door_status("关闭");
|
||
|
||
// 6. 创建传感器读取任务
|
||
xTaskCreate([](void *arg)
|
||
{
|
||
aht30_handle_t aht30 = (aht30_handle_t)arg;
|
||
uint32_t log_cnt = 0;
|
||
for (;;) {
|
||
float lux = 0.0f, temp = 0.0f, hum = 0.0f, gas_percent = 0.0f, fire_percent = 0.0f;
|
||
jw01_data_t jw01{};
|
||
esp_err_t bh_ret = ESP_FAIL;
|
||
esp_err_t aht_ret = ESP_FAIL;
|
||
esp_err_t mq2_ret = ESP_FAIL;
|
||
esp_err_t jw_ret = ESP_FAIL;
|
||
esp_err_t fire_ret = ESP_FAIL;
|
||
// 读取 BH1750
|
||
bh_ret = bh1750_user_read(&lux);
|
||
if (bh_ret == ESP_OK) {
|
||
set_var_light_val(lux);
|
||
}
|
||
// 读取 AHT30
|
||
aht_ret = aht30_get_temperature_humidity_value(aht30, &temp, &hum);
|
||
if (aht_ret == ESP_OK) {
|
||
set_var_temp(temp);
|
||
set_var_humity_val(hum);
|
||
}
|
||
|
||
// 读取 MQ-2,更新空气质量变量
|
||
mq2_ret = mq2_read_percent(&gas_percent);
|
||
if (mq2_ret == ESP_OK) {
|
||
set_var_air_quity(gas_percent);
|
||
}
|
||
|
||
// 读取火焰传感器,更新火焰状态
|
||
fire_ret = fire_sensor_read_percent(&fire_percent);
|
||
if (fire_ret == ESP_OK) {
|
||
set_var_fire_status(fire_sensor_is_danger(fire_percent, FIRE_DANGER_THRESHOLD_PERCENT) ? "危险" : "安全");
|
||
}
|
||
|
||
// 读取 JW01(TVOC/HCHO/CO2)
|
||
jw_ret = jw01_read(&jw01, 200);
|
||
if (jw_ret == ESP_OK) {
|
||
if (jw01.co2_valid) {
|
||
if (jw01.co2 >= CO2_SPOILAGE_THRESHOLD_PPM) {
|
||
set_var_food_status("变质");
|
||
} else {
|
||
set_var_food_status("良好");
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
// 每 5 次打印一次综合状态,避免日志刷屏
|
||
if ((log_cnt++ % 10) == 0) {
|
||
ESP_LOGI(TAG,
|
||
"SENS bh=%s lux=%.1f | aht=%s t=%.1f h=%.1f | mq2=%s gas=%.1f | fire=%s fp=%.1f | jw01=%s co2_valid=%d co2=%.1f",
|
||
esp_err_to_name(bh_ret), lux,
|
||
esp_err_to_name(aht_ret), temp, hum,
|
||
esp_err_to_name(mq2_ret), gas_percent,
|
||
esp_err_to_name(fire_ret), fire_percent,
|
||
esp_err_to_name(jw_ret), jw01.co2_valid ? 1 : 0, jw01.co2);
|
||
}
|
||
|
||
|
||
// 数据存入共享结构体
|
||
if (s_env_data_lock) {
|
||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||
s_env_data.lux = lux;
|
||
s_env_data.temp = temp;
|
||
s_env_data.humidity = hum;
|
||
s_env_data.gas_percent = gas_percent;
|
||
s_env_data.fire_percent = fire_percent;
|
||
s_env_data.fire_danger = fire_sensor_is_danger(fire_percent, FIRE_DANGER_THRESHOLD_PERCENT);
|
||
if (jw01.tvoc_valid) s_env_data.tvoc = jw01.tvoc;
|
||
if (jw01.hcho_valid) s_env_data.hcho = jw01.hcho;
|
||
if (jw01.co2_valid) s_env_data.co2 = jw01.co2;
|
||
xSemaphoreGive(s_env_data_lock);
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
} }, "sensor_task", 4096 * 3, (void *)aht30_dev, 6, NULL);
|
||
|
||
// 独立任务:人体与门状态检测
|
||
xTaskCreate(status_task, "status_task", 4096, NULL, 6, NULL);
|
||
}
|