/* * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include #include #include /* 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); } }