#include #include "driver/i2c_master.h" #include "ahtxx.h" #include "esp_check.h" #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "i2c_master_messager.h" static const char *TAG = "i2c_master_messager"; #define BH1750_REINIT_INTERVAL_MS (3000) #define AHT30_REINIT_INTERVAL_MS (3000) typedef struct { bool initialized; bool owns_i2c_bus; bool bh1750_ready; bool aht30_ready; i2c_master_messager_config_t config; i2c_master_bus_handle_t i2c_bus; bh1750_handle_t bh1750; ahtxx_handle_t aht30; i2c_master_messager_data_t data; SemaphoreHandle_t lock; TaskHandle_t task_handle; } i2c_master_messager_ctx_t; static i2c_master_messager_ctx_t g_ctx; static esp_err_t i2c_master_messager_try_init_bh1750(void) { if (!g_ctx.config.bh1750_enable) { return ESP_ERR_NOT_SUPPORTED; } if (g_ctx.bh1750 != NULL) { bh1750_delete(g_ctx.bh1750); g_ctx.bh1750 = NULL; } esp_err_t ret = bh1750_create(g_ctx.i2c_bus, BH1750_I2C_ADDRESS_DEFAULT, &g_ctx.bh1750); if (ret != ESP_OK) { return ret; } ret = bh1750_power_on(g_ctx.bh1750); if (ret != ESP_OK) { bh1750_delete(g_ctx.bh1750); g_ctx.bh1750 = NULL; return ret; } ret = bh1750_set_measure_mode(g_ctx.bh1750, g_ctx.config.bh1750_mode); if (ret != ESP_OK) { bh1750_delete(g_ctx.bh1750); g_ctx.bh1750 = NULL; return ret; } g_ctx.bh1750_ready = true; return ESP_OK; } static esp_err_t i2c_master_messager_try_init_aht30(void) { if (!g_ctx.config.aht30_enable) { return ESP_ERR_NOT_SUPPORTED; } if (g_ctx.aht30 != NULL) { ahtxx_delete(g_ctx.aht30); g_ctx.aht30 = NULL; } ahtxx_config_t aht_cfg = I2C_AHT30_CONFIG_DEFAULT; esp_err_t ret = ahtxx_init(g_ctx.i2c_bus, &aht_cfg, &g_ctx.aht30); if (ret != ESP_OK) { return ret; } g_ctx.aht30_ready = true; return ESP_OK; } static void i2c_master_messager_task(void *arg) { (void)arg; int64_t last_bh1750_reinit_ms = 0; int64_t last_bh1750_read_ms = 0; int64_t last_aht30_read_ms = 0; int64_t last_aht30_reinit_ms = 0; while (1) { int64_t now_ms = esp_timer_get_time() / 1000; if (g_ctx.config.bh1750_enable && !g_ctx.bh1750_ready && (now_ms - last_bh1750_reinit_ms) >= BH1750_REINIT_INTERVAL_MS) { last_bh1750_reinit_ms = now_ms; esp_err_t init_ret = i2c_master_messager_try_init_bh1750(); if (init_ret == ESP_OK) { ESP_LOGI(TAG, "BH1750 reinit success"); } else { ESP_LOGW(TAG, "BH1750 reinit failed: %s", esp_err_to_name(init_ret)); if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { g_ctx.data.bh1750.valid = false; g_ctx.data.bh1750.last_error = init_ret; xSemaphoreGive(g_ctx.lock); } } } if (g_ctx.bh1750_ready && g_ctx.bh1750 != NULL && (now_ms - last_bh1750_read_ms) >= g_ctx.config.bh1750_read_period_ms) { float lux = 0.0f; esp_err_t bh1750_ret = bh1750_get_data(g_ctx.bh1750, &lux); last_bh1750_read_ms = now_ms; if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { g_ctx.data.bh1750.valid = (bh1750_ret == ESP_OK); g_ctx.data.bh1750.last_error = bh1750_ret; if (bh1750_ret == ESP_OK) { g_ctx.data.bh1750.lux = lux; g_ctx.data.bh1750.last_update_ms = now_ms; } xSemaphoreGive(g_ctx.lock); } if (bh1750_ret != ESP_OK) { ESP_LOGW(TAG, "bh1750_get_data failed: %s", esp_err_to_name(bh1750_ret)); } } if (g_ctx.config.aht30_enable && !g_ctx.aht30_ready && (now_ms - last_aht30_reinit_ms) >= AHT30_REINIT_INTERVAL_MS) { last_aht30_reinit_ms = now_ms; esp_err_t init_ret = i2c_master_messager_try_init_aht30(); if (init_ret == ESP_OK) { ESP_LOGI(TAG, "AHT30 reinit success"); } else { ESP_LOGW(TAG, "AHT30 reinit failed: %s", esp_err_to_name(init_ret)); if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { g_ctx.data.aht30.valid = false; g_ctx.data.aht30.last_error = init_ret; xSemaphoreGive(g_ctx.lock); } } } if (g_ctx.aht30_ready && g_ctx.aht30 != NULL && (now_ms - last_aht30_read_ms) >= g_ctx.config.aht30_read_period_ms) { float temperature_c = 0.0f; float humidity_rh = 0.0f; esp_err_t aht30_ret = ahtxx_get_measurement(g_ctx.aht30, &temperature_c, &humidity_rh); last_aht30_read_ms = now_ms; if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { g_ctx.data.aht30.valid = (aht30_ret == ESP_OK); g_ctx.data.aht30.last_error = aht30_ret; if (aht30_ret == ESP_OK) { g_ctx.data.aht30.temperature_c = temperature_c; g_ctx.data.aht30.humidity_rh = humidity_rh; g_ctx.data.aht30.last_update_ms = now_ms; } xSemaphoreGive(g_ctx.lock); } if (aht30_ret != ESP_OK) { ESP_LOGW(TAG, "ahtxx_get_measurement failed: %s", esp_err_to_name(aht30_ret)); if (aht30_ret == ESP_ERR_INVALID_STATE || aht30_ret == ESP_ERR_TIMEOUT) { if (g_ctx.aht30 != NULL) { ahtxx_delete(g_ctx.aht30); g_ctx.aht30 = NULL; } g_ctx.aht30_ready = false; } } } vTaskDelay(pdMS_TO_TICKS(I2C_MASTER_MESSAGER_MIN_PERIOD_MS)); } } esp_err_t i2c_master_messager_init(const i2c_master_messager_config_t *config) { ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is null"); ESP_RETURN_ON_FALSE(config->bh1750_enable || config->aht30_enable, ESP_ERR_INVALID_ARG, TAG, "at least one sensor must be enabled"); ESP_RETURN_ON_FALSE(!config->bh1750_enable || config->bh1750_read_period_ms >= I2C_MASTER_MESSAGER_MIN_PERIOD_MS, ESP_ERR_INVALID_ARG, TAG, "bh1750_read_period_ms too small"); ESP_RETURN_ON_FALSE(!config->aht30_enable || config->aht30_read_period_ms >= I2C_MASTER_MESSAGER_MIN_PERIOD_MS, ESP_ERR_INVALID_ARG, TAG, "aht30_read_period_ms too small"); if (g_ctx.initialized) { return ESP_ERR_INVALID_STATE; } memset(&g_ctx, 0, sizeof(g_ctx)); g_ctx.config = *config; g_ctx.lock = xSemaphoreCreateMutex(); ESP_RETURN_ON_FALSE(g_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "failed to create mutex"); const i2c_master_bus_config_t bus_cfg = { .i2c_port = config->i2c_port, .sda_io_num = config->sda_io_num, .scl_io_num = config->scl_io_num, .clk_source = I2C_CLK_SRC_DEFAULT, .glitch_ignore_cnt = 7, .flags.enable_internal_pullup = config->i2c_enable_internal_pullup, }; esp_err_t ret = i2c_new_master_bus(&bus_cfg, &g_ctx.i2c_bus); if (ret != ESP_OK) { if (ret == ESP_ERR_INVALID_STATE) { ESP_LOGW(TAG, "i2c port %d already initialized, trying to reuse existing master bus", config->i2c_port); ret = i2c_master_get_bus_handle(config->i2c_port, &g_ctx.i2c_bus); if (ret != ESP_OK || g_ctx.i2c_bus == NULL) { ESP_LOGE(TAG, "failed to reuse i2c bus on port %d: %s", config->i2c_port, esp_err_to_name(ret)); vSemaphoreDelete(g_ctx.lock); g_ctx.lock = NULL; return (ret == ESP_OK) ? ESP_ERR_INVALID_STATE : ret; } g_ctx.owns_i2c_bus = false; } else { ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(ret)); vSemaphoreDelete(g_ctx.lock); g_ctx.lock = NULL; return ret; } } else { g_ctx.owns_i2c_bus = true; } if (!config->bh1750_enable) { g_ctx.data.bh1750.valid = false; g_ctx.data.bh1750.last_error = ESP_ERR_NOT_SUPPORTED; } else { g_ctx.data.bh1750.valid = false; g_ctx.data.bh1750.last_error = ESP_ERR_INVALID_STATE; } if (!config->aht30_enable) { g_ctx.data.aht30.valid = false; g_ctx.data.aht30.last_error = ESP_ERR_NOT_SUPPORTED; } else { g_ctx.data.aht30.valid = false; g_ctx.data.aht30.last_error = ESP_ERR_INVALID_STATE; } g_ctx.initialized = true; ESP_LOGI(TAG, "initialized: port=%d scl=%d sda=%d pullup_int=%d bh1750(en=%d,ready=%d,default,%ums) aht30(en=%d,ready=%d,default,%ums)", config->i2c_port, config->scl_io_num, config->sda_io_num, config->i2c_enable_internal_pullup, config->bh1750_enable, g_ctx.bh1750_ready, config->bh1750_read_period_ms, config->aht30_enable, g_ctx.aht30_ready, config->aht30_read_period_ms); ESP_LOGI(TAG, "i2c bus is ready; sensor drivers will initialize lazily in task loop"); return ESP_OK; } esp_err_t i2c_master_messager_start(void) { ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); if (g_ctx.task_handle != NULL) { return ESP_OK; } BaseType_t ok = xTaskCreate(i2c_master_messager_task, "i2c_msg_task", 4096, NULL, 5, &g_ctx.task_handle); ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "failed to create task"); return ESP_OK; } esp_err_t i2c_master_messager_stop(void) { ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); if (g_ctx.task_handle != NULL) { vTaskDelete(g_ctx.task_handle); g_ctx.task_handle = NULL; } return ESP_OK; } esp_err_t i2c_master_messager_get_data(i2c_master_messager_data_t *out_data) { ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); ESP_RETURN_ON_FALSE(out_data != NULL, ESP_ERR_INVALID_ARG, TAG, "out_data is null"); ESP_RETURN_ON_FALSE(g_ctx.lock != NULL, ESP_ERR_INVALID_STATE, TAG, "lock not ready"); ESP_RETURN_ON_FALSE(xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE, ESP_ERR_TIMEOUT, TAG, "failed to lock shared data"); *out_data = g_ctx.data; xSemaphoreGive(g_ctx.lock); return ESP_OK; } esp_err_t i2c_master_messager_deinit(void) { if (!g_ctx.initialized) { return ESP_OK; } i2c_master_messager_stop(); if (g_ctx.bh1750 != NULL) { bh1750_delete(g_ctx.bh1750); g_ctx.bh1750 = NULL; } if (g_ctx.aht30 != NULL) { ahtxx_delete(g_ctx.aht30); g_ctx.aht30 = NULL; } if (g_ctx.i2c_bus != NULL && g_ctx.owns_i2c_bus) { i2c_del_master_bus(g_ctx.i2c_bus); } g_ctx.i2c_bus = NULL; g_ctx.owns_i2c_bus = false; if (g_ctx.lock != NULL) { vSemaphoreDelete(g_ctx.lock); g_ctx.lock = NULL; } memset(&g_ctx, 0, sizeof(g_ctx)); return ESP_OK; }