ESP32_DHT_Node/main/main.c

349 lines
10 KiB
C

/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* ESP drivers */
#include "driver/gpio.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_app_desc.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_sleep.h"
#include "esp_sntp.h"
/* FreeRTOS */
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
/* NVS */
#include "nvs_flash.h"
/* Config */
#include "sdkconfig.h"
/* App */
#include "app_dht.h"
#include "app_mqtt.h"
#include "app_report_rb.h"
#include "app_wifi.h"
#define LOG_TAG "APP_MAIN"
#define APP_WIFI_TIMEOUT_SECS 120
#define APP_MQTT_TIMEOUT_SECS 10
#define APP_SNTP_TIMEOUT_SECS 60
#define APP_SENSOR_INTERVAL_SECS 1
#define APP_SENSOR_REPORT_TRIGGER_POINTS 4 /* Count of the trigger points which triggers telemetry reporting */
#define APP_SENSOR_REPORT_WATERMARK_PERCENT 80 /* when the buffer reaches this watermark in each trigger point */
#define APP_SENSOR_REPORT_CRITICAL_PERCENT 95 /* Critical level, when this is reached, reports will be flushed */
/* Report interval: (RB Size / Nr. Trigger Points) * (Watermark / 100) * Interval secs */
#define APP_LED_PIN 16
#define APP_ADC_CH ADC_CHANNEL_6
#define APP_TELEMETRY_TOPIC "v1/devices/me/telemetry"
#define APP_ATTRIBUTE_TOPIC "v1/devices/me/attributes"
#define APP_ATTRIBUTE_INIT_JSON_STRING \
"{\"firmwareVersion\":\"%s\", \"macAddress\":\"" MACSTR \
"\",\"hasHumidity\":true,\"hasPressure\":false,\"batteryPowered\":true,\"batteryType\":\"Li-ion\"," \
"\"batteryVoltage\":%ld}"
#define APP_ATTRIBUTE_JSON_STRING "{\"batteryVoltage\":%ld}"
#define APP_TELEMETRY_JSON_STRING "{\"ts\":%lld,\"values\":{\"temperature\":%.2f,\"humidity\":%.2f}}"
static void app_report_attributes(bool init);
static void app_report_task(void *pvParameters);
static uint64_t app_get_msec_timestamp(void);
static void app_led_init(void);
static void app_led_set(uint8_t on);
static void app_adc_init(void);
static uint32_t app_adc_read(void);
static volatile uint8_t s_report_task_running = 0;
static adc_oneshot_unit_handle_t s_adc_handle;
static adc_cali_handle_t s_adc_cal_handle = NULL;
void app_main(void) {
esp_err_t ret;
ESP_LOGI(LOG_TAG, "Main application started.");
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_LOGW(LOG_TAG, "NVS content appears to be corrupted or outdated, erase and recreate NVS structure.");
/* Erase NVS region and give it another try. */
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
/* NVS initialization reported failure, this should not happen. In this case, abort! */
ESP_ERROR_CHECK(ret);
/* Some indication... */
app_led_init();
app_adc_init();
ESP_ERROR_CHECK(app_wifi_init());
app_wifi_event_t a_wifi_evt = app_wifi_wait_event(APP_WIFI_TIMEOUT_SECS * 1000);
if (a_wifi_evt != APP_WIFI_EVENT_CONNECTED) {
ESP_LOGE(LOG_TAG, "WiFi failed to come up within time limit.");
esp_restart();
}
/* On first start, we need to know the actual start time for timestamp tracking in report */
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, CONFIG_APP_SNTP_POOL_ADDR);
sntp_init();
uint16_t sntp_trials = APP_SNTP_TIMEOUT_SECS;
/* If the NTP does not finish synchronization in 10 seconds... Required for initial boot up */
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
vTaskDelay(pdMS_TO_TICKS(1000));
sntp_trials--;
if (sntp_trials == 0U) {
ESP_LOGE(LOG_TAG, "Failed to synchronize NTP in-time.");
esp_restart();
}
}
sntp_stop();
ESP_ERROR_CHECK(app_mqtt_init(APP_MQTT_TIMEOUT_SECS * 1000));
app_report_attributes(true);
ESP_ERROR_CHECK(app_mqtt_deinit());
/*
* We have acquired NTP time, de-init Wi-Fi.
* Something terribly wrong happens if this function is not completed successfully.
*/
ESP_ERROR_CHECK(app_wifi_deinit());
/* Initialize DHT sensors, abort if error occurs */
ESP_ERROR_CHECK(app_dht_init());
if (app_report_rb_init() < 0) {
ESP_LOGE(LOG_TAG, "Failed to init RB");
esp_restart();
}
app_report_rb_t rpt;
for (;;) {
app_led_set(1);
ret = app_dht_read(&rpt.temperature, &rpt.humidity);
if (ret != ESP_OK) {
continue;
}
app_led_set(0);
rpt.ts = app_get_msec_timestamp();
app_report_rb_append(&rpt);
uint32_t current_count = app_report_rb_get_count();
uint32_t rb_size = app_report_rb_get_total_size();
if (current_count % 10 == 0) {
ESP_LOGI(LOG_TAG, "Last: %.2fC, %.2f%%", rpt.temperature, rpt.humidity);
ESP_LOGI(LOG_TAG, "RB level: %lu%%", current_count * 100 / rb_size);
}
/**
* Trigger report task at each threshold point or above the last threshold point.
* If the last threshold trigger failed, the ring buffer will be cleared.
*/
uint32_t rb_trigger_level = rb_size / APP_SENSOR_REPORT_TRIGGER_POINTS;
uint32_t rb_trigger_watermark = rb_trigger_level * APP_SENSOR_REPORT_WATERMARK_PERCENT / 100;
uint32_t rb_critical_point = rb_size * APP_SENSOR_REPORT_CRITICAL_PERCENT / 100;
if (((current_count % rb_trigger_level) == rb_trigger_watermark) || (current_count > rb_critical_point)) {
if (!s_report_task_running) {
xTaskCreate(app_report_task, "RPT_TASK", 4096, NULL, 4, NULL);
s_report_task_running = 1U;
}
}
if (s_report_task_running) {
vTaskDelay(pdMS_TO_TICKS(1000));
} else {
esp_sleep_enable_timer_wakeup(APP_SENSOR_INTERVAL_SECS * 1000 * 1000);
esp_light_sleep_start();
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER);
}
}
}
static void app_report_task(void *pvParameters) {
app_report_rb_t rpt;
char *rpt_buffer;
ESP_ERROR_CHECK(app_wifi_init());
app_wifi_event_t a_wifi_evt = app_wifi_wait_event(APP_WIFI_TIMEOUT_SECS * 1000);
if (a_wifi_evt != APP_WIFI_EVENT_CONNECTED) {
ESP_LOGE(LOG_TAG, "WiFi failed to come up within time limit.");
goto drop_data_exit;
}
sntp_init();
uint16_t sntp_trials = APP_SNTP_TIMEOUT_SECS;
/* If the NTP does not finish synchronization in 10 seconds... Required for initial boot up */
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
vTaskDelay(pdMS_TO_TICKS(1000));
sntp_trials--;
if (sntp_trials == 0U) {
ESP_LOGE(LOG_TAG, "Failed to synchronize NTP in-time.");
goto stop_sntp_exit;
}
}
ESP_ERROR_CHECK(app_mqtt_init(APP_MQTT_TIMEOUT_SECS * 1000));
app_report_attributes(false);
rpt_buffer = malloc(512);
while (app_report_rb_get_count() > 0) {
app_report_rb_consume(&rpt);
snprintf(rpt_buffer, 512, APP_TELEMETRY_JSON_STRING, rpt.ts, rpt.temperature, rpt.humidity);
app_mqtt_publish(APP_TELEMETRY_TOPIC, rpt_buffer);
}
free(rpt_buffer);
ESP_ERROR_CHECK(app_mqtt_deinit());
sntp_stop();
ESP_ERROR_CHECK(app_wifi_deinit());
s_report_task_running = 0U;
vTaskDelete(NULL);
return;
stop_sntp_exit:
sntp_stop();
drop_data_exit:
ESP_ERROR_CHECK(app_wifi_deinit());
/* Flush ring buffer */
if (app_report_rb_get_count() > (app_report_rb_get_total_size() * APP_SENSOR_REPORT_CRITICAL_PERCENT / 100)) {
app_report_rb_flush();
}
s_report_task_running = 0U;
vTaskDelete(NULL);
}
static void app_report_attributes(bool init) {
char rpt_buf[256];
uint8_t mac_addr[6];
uint32_t batt_voltage = app_adc_read() * 2;
if (init) {
const esp_app_desc_t *app_descr = esp_app_get_description();
esp_read_mac(mac_addr, ESP_MAC_WIFI_STA);
snprintf(rpt_buf, 256, APP_ATTRIBUTE_INIT_JSON_STRING, app_descr->version, MAC2STR(mac_addr), batt_voltage);
} else {
snprintf(rpt_buf, 256, APP_ATTRIBUTE_JSON_STRING, batt_voltage);
}
app_mqtt_publish(APP_ATTRIBUTE_TOPIC, rpt_buf);
}
static uint64_t app_get_msec_timestamp(void) {
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
uint64_t time_ms = (int64_t)tv_now.tv_sec * 1000 + (int64_t)tv_now.tv_usec / 1000;
return time_ms;
}
static void app_adc_init(void) {
adc_oneshot_unit_init_cfg_t init_cfg = {
.unit_id = ADC_UNIT_1,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_cfg, &s_adc_handle));
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = ADC_ATTEN_DB_11,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(s_adc_handle, APP_ADC_CH, &chan_cfg));
adc_cali_line_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.atten = ADC_ATTEN_DB_11,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_config, &s_adc_cal_handle));
}
static uint32_t app_adc_read(void) {
int result;
int voltage;
ESP_ERROR_CHECK(adc_oneshot_read(s_adc_handle, APP_ADC_CH, &result));
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(s_adc_cal_handle, result, &voltage));
return voltage;
}
static void app_led_init(void) {
gpio_config_t io_cfg = {
.mode = GPIO_MODE_OUTPUT_OD,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pin_bit_mask = (1U << APP_LED_PIN),
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_cfg);
gpio_set_level(APP_LED_PIN, 1);
}
static void app_led_set(uint8_t on) {
if (on) {
gpio_set_level(APP_LED_PIN, 0);
} else {
gpio_set_level(APP_LED_PIN, 1);
}
}