349 lines
10 KiB
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);
|
|
}
|
|
} |