reads data every 15 minutes saves data persistently every 24h writes out the contents every measure. goes in deep sleep so very low power consumption. (reason removal task system)
312 lines
8.8 KiB
C
312 lines
8.8 KiB
C
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <inttypes.h>
|
|
#include <locale.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
|
|
#include "sdkconfig.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/queue.h"
|
|
#include "freertos/semphr.h"
|
|
|
|
#include "esp_chip_info.h"
|
|
#include "esp_flash.h"
|
|
#include "esp_system.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_spiffs.h"
|
|
#include "esp_sleep.h"
|
|
|
|
#include "driver/gpio.h"
|
|
#include "driver/rtc_io.h"
|
|
|
|
#define PRINT_DEBUG false
|
|
#define WIPE_MEMORY false
|
|
|
|
#define DHT11_GPIO 0
|
|
#define BLINK_GPIO 8
|
|
|
|
#define DATA_BUFFER_SIZE 100
|
|
|
|
typedef struct {
|
|
char timestamp[64];
|
|
float humidity;
|
|
float temperature;
|
|
} SensorData;
|
|
|
|
// RTC buffer to store time
|
|
RTC_DATA_ATTR time_t rtc_unix_time = 0;
|
|
RTC_DATA_ATTR bool rtc_time_initialized = false;
|
|
|
|
// RTC buffer to store measurements
|
|
RTC_DATA_ATTR SensorData rtc_measurement_buffer[DATA_BUFFER_SIZE];
|
|
RTC_DATA_ATTR int rtc_buffer_index = 0;
|
|
|
|
// ############### Base Setup ###############
|
|
|
|
void set_system_time() {
|
|
if (!rtc_time_initialized) {
|
|
// First boot: use compile time
|
|
struct tm tm = {0};
|
|
char month[4];
|
|
int day, year, hour, min, sec;
|
|
|
|
sscanf(__DATE__, "%3s %d %d", month, &day, &year);
|
|
sscanf(__TIME__, "%d:%d:%d", &hour, &min, &sec);
|
|
|
|
const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
for (int i = 0; i < 12; i++) {
|
|
if (strcmp(month, months[i]) == 0) {
|
|
tm.tm_mon = i;
|
|
break;
|
|
}
|
|
}
|
|
tm.tm_mday = day;
|
|
tm.tm_year = year - 1900;
|
|
tm.tm_hour = hour;
|
|
tm.tm_min = min;
|
|
tm.tm_sec = sec;
|
|
tm.tm_isdst = -1;
|
|
|
|
rtc_unix_time = mktime(&tm);
|
|
rtc_time_initialized = true;
|
|
}
|
|
|
|
// Set system time from RTC
|
|
struct timeval tv = { .tv_sec = rtc_unix_time, .tv_usec = 0 };
|
|
settimeofday(&tv, NULL);
|
|
}
|
|
|
|
void get_current_time(char* time_string, size_t size) {
|
|
time_t now;
|
|
struct tm timeinfo;
|
|
time(&now);
|
|
localtime_r(&now, &timeinfo);
|
|
strftime(time_string, size, "%Y-%m-%d %H:%M:%S", &timeinfo);
|
|
}
|
|
|
|
void print_chip_info() {
|
|
esp_chip_info_t chip_info;
|
|
uint32_t flash_size;
|
|
esp_chip_info(&chip_info);
|
|
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
|
|
CONFIG_IDF_TARGET,
|
|
chip_info.cores,
|
|
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
|
|
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
|
|
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
|
|
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
|
|
|
|
unsigned major_rev = chip_info.revision / 100;
|
|
unsigned minor_rev = chip_info.revision % 100;
|
|
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
|
|
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
|
printf("Get flash size failed");
|
|
return;
|
|
}
|
|
printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
|
|
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
|
|
printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
|
|
}
|
|
|
|
// ############### Status indicator ###############
|
|
|
|
void init_led() {
|
|
gpio_reset_pin(BLINK_GPIO);
|
|
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
|
|
gpio_set_level(BLINK_GPIO, 1);
|
|
}
|
|
|
|
void led_flicker(int times, bool fast) {
|
|
for(u_int8_t i = 0; i < times; i++) {
|
|
gpio_set_level(BLINK_GPIO, 0);
|
|
vTaskDelay(pdMS_TO_TICKS(fast ? 250 : 500));
|
|
gpio_set_level(BLINK_GPIO, 1);
|
|
vTaskDelay(pdMS_TO_TICKS(fast ? 250 : 500));
|
|
}
|
|
}
|
|
|
|
// ############### Measurement ###############
|
|
|
|
void read_dht11(float *humidity, float *temperature) {
|
|
uint8_t data[5] = {0};
|
|
|
|
gpio_set_direction(DHT11_GPIO, GPIO_MODE_OUTPUT);
|
|
gpio_set_level(DHT11_GPIO, 0);
|
|
vTaskDelay(pdMS_TO_TICKS(20));
|
|
gpio_set_level(DHT11_GPIO, 1);
|
|
gpio_set_direction(DHT11_GPIO, GPIO_MODE_INPUT);
|
|
|
|
while (gpio_get_level(DHT11_GPIO) == 1) vTaskDelay(pdMS_TO_TICKS(0.1));
|
|
while (gpio_get_level(DHT11_GPIO) == 0) vTaskDelay(pdMS_TO_TICKS(0.1));
|
|
|
|
for (int i = 0; i < 40; i++) {
|
|
while (gpio_get_level(DHT11_GPIO) == 1) vTaskDelay(pdMS_TO_TICKS(0.1));
|
|
while (gpio_get_level(DHT11_GPIO) == 0) vTaskDelay(pdMS_TO_TICKS(0.1));
|
|
|
|
uint32_t pulse_start = esp_timer_get_time();
|
|
while (gpio_get_level(DHT11_GPIO) == 1) vTaskDelay(pdMS_TO_TICKS(0.1));
|
|
uint32_t pulse_end = esp_timer_get_time();
|
|
|
|
uint8_t bit = (pulse_end - pulse_start) > 50 ? 1 : 0;
|
|
data[i / 8] <<= 1;
|
|
data[i / 8] |= bit;
|
|
}
|
|
|
|
uint8_t humidity_int = data[0];
|
|
uint8_t humidity_dec = data[1];
|
|
int8_t temperature_int = (data[2]);
|
|
uint8_t temperature_dec = data[3];
|
|
uint8_t checksum = data[4];
|
|
|
|
uint8_t sum = humidity_int + humidity_dec + temperature_int + temperature_dec;
|
|
if (sum != checksum) {
|
|
#if PRINT_DEBUG
|
|
printf("Checksum failed! Retrying...\n");
|
|
#endif
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
read_dht11(humidity, temperature);
|
|
return;
|
|
}
|
|
|
|
*humidity = (float)humidity_int + (float)humidity_dec / 10.0f;
|
|
*temperature = (float)temperature_int + (float)temperature_dec / 10.0f;
|
|
}
|
|
|
|
void make_measurement() {
|
|
float humidity, temperature;
|
|
read_dht11(&humidity, &temperature);
|
|
|
|
char time_string[64] = {0};
|
|
get_current_time(time_string, sizeof(time_string));
|
|
|
|
#if PRINT_DEBUG
|
|
printf("Measurement: [%s, %.1f%%, %.1f°C]\n", time_string, humidity, temperature);
|
|
#endif
|
|
|
|
// Store in RTC buffer
|
|
if (rtc_buffer_index < DATA_BUFFER_SIZE) {
|
|
strncpy(rtc_measurement_buffer[rtc_buffer_index].timestamp, time_string, sizeof(time_string));
|
|
rtc_measurement_buffer[rtc_buffer_index].humidity = humidity;
|
|
rtc_measurement_buffer[rtc_buffer_index].temperature = temperature;
|
|
rtc_buffer_index++;
|
|
}
|
|
}
|
|
|
|
// ############### IO operations ###############
|
|
|
|
void init_filesystem() {
|
|
esp_vfs_spiffs_conf_t conf = {
|
|
.base_path = "/spiffs",
|
|
.partition_label = "storage",
|
|
.max_files = 5,
|
|
.format_if_mount_failed = true
|
|
};
|
|
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
|
if (ret != ESP_OK) {
|
|
#if PRINT_DEBUG
|
|
printf("Failed to initialize SPIFFS (%s)\n", esp_err_to_name(ret));
|
|
#endif
|
|
return;
|
|
}
|
|
#if PRINT_DEBUG
|
|
printf("SPIFFS initialized successfully\n");
|
|
#endif
|
|
}
|
|
|
|
void save_buffer_to_persistent(){
|
|
FILE *f = fopen("/spiffs/measurements.csv", "a");
|
|
if (f == NULL) {
|
|
printf("Failed to open CSV file!\n");
|
|
return;
|
|
}
|
|
|
|
// Write header if file is empty
|
|
fseek(f, 0, SEEK_END);
|
|
if (ftell(f) == 0) {
|
|
fprintf(f, "Timestamp,Humidity,Temperature\n");
|
|
}
|
|
|
|
// Write all buffered measurements
|
|
for (int i = 0; i < rtc_buffer_index; i++) {
|
|
fprintf(f, "%s,%.1f,%.1f\n",
|
|
rtc_measurement_buffer[i].timestamp,
|
|
rtc_measurement_buffer[i].humidity,
|
|
rtc_measurement_buffer[i].temperature);
|
|
}
|
|
fclose(f);
|
|
rtc_buffer_index = 0; // Reset buffer
|
|
#if PRINT_DEBUG
|
|
printf("CSV file updated with %d measurements!\n", DATA_BUFFER_SIZE);
|
|
#endif
|
|
}
|
|
|
|
void clear_file(){
|
|
led_flicker(10, false);
|
|
FILE *f = fopen("/spiffs/measurements.csv", "w");
|
|
if (f) {
|
|
fprintf(f, "Timestamp,Humidity,Temperature\n");
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
void readout_persistent_as_csv(){
|
|
FILE *f = fopen("/spiffs/measurements.csv", "r");
|
|
if (f) {
|
|
char line[256];
|
|
while (fgets(line, sizeof(line), f)) {
|
|
printf("%s", line);
|
|
}
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
// ############### Buffer operations ###############
|
|
|
|
void readout_buffer_as_csv(){
|
|
for (int i = 0; i < rtc_buffer_index; i++) {
|
|
printf("%s,%.1f,%.1f\n",
|
|
rtc_measurement_buffer[i].timestamp,
|
|
rtc_measurement_buffer[i].humidity,
|
|
rtc_measurement_buffer[i].temperature);
|
|
}
|
|
}
|
|
|
|
// ############### Main thread ###############
|
|
|
|
const uint16_t sleep_time_seconds = 15 * 60; // Measurement frequency
|
|
|
|
void app_main() {
|
|
setlocale(LC_ALL, "C");
|
|
set_system_time();
|
|
|
|
init_led();
|
|
init_filesystem();
|
|
|
|
led_flicker(3, true);
|
|
|
|
#if PRINT_DEBUG
|
|
printf("DHT11 temperature and humidity sensor\n");
|
|
char start_time[64] = {0};
|
|
get_current_time(start_time, sizeof(start_time));
|
|
printf("Program started at: %s\n", start_time);
|
|
print_chip_info();
|
|
#endif
|
|
|
|
make_measurement();
|
|
if (rtc_buffer_index >= DATA_BUFFER_SIZE) {
|
|
save_buffer_to_persistent();
|
|
}
|
|
readout_persistent_as_csv();
|
|
readout_buffer_as_csv();
|
|
#if WIPE_MEMORY
|
|
clear_file();
|
|
#endif
|
|
|
|
rtc_unix_time += sleep_time_seconds;
|
|
esp_sleep_enable_timer_wakeup(sleep_time_seconds * 1000000);
|
|
esp_deep_sleep_start();
|
|
} |