#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_sleep.h"
#include <stdarg.h> // Required for va_list, vsnprintf
// Pin definitions
#define DHT_PIN GPIO_NUM_4
#define CAPACITIVE_RAIN_SWITCH_PIN GPIO_NUM_2
#define TIPPING_BUCKET_PIN GPIO_NUM_18
#define CAPACITIVE_LED_PIN GPIO_NUM_5 // LED for capacitive rain sensor
#define TIPPING_BUCKET_LED_PIN GPIO_NUM_19 // LED for tipping bucket
// I2C OLED SSD1306 pins (using I2C)
#define I2C_MASTER_SCL_IO GPIO_NUM_22 // SCL pin
#define I2C_MASTER_SDA_IO GPIO_NUM_21 // SDA pin
#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_FREQ_HZ 100000
#define OLED_I2C_ADDRESS 0x3C // I2C address for SSD1306 OLED (common is 0x3C or 0x3D)
// SSD1306 OLED dimensions
#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 64
// SSD1306 Commands (subset for basic operation)
#define SSD1306_SET_CONTRAST_CONTROL 0x81
#define SSD1306_DISPLAY_ALL_ON_RESUME 0xA4
#define SSD1306_DISPLAY_ALL_ON 0xA5
#define SSD1306_NORMAL_DISPLAY 0xA6
#define SSD1306_INVERT_DISPLAY 0xA7
#define SSD1306_SET_DISPLAY_ON 0xAF
#define SSD1306_SET_DISPLAY_OFF 0xAE
#define SSD1306_SET_MEMORY_ADDR_MODE 0x20
#define SSD1306_SET_COLUMN_ADDR 0x21
#define SSD1306_SET_PAGE_ADDR 0x22
#define SSD1306_SET_START_LINE 0x40
#define SSD1306_CHARGE_PUMP 0x8D
#define SSD1306_SEG_REMAP_INV 0xA1
#define SSD1306_COM_SCAN_DIR_INV 0xC8
#define SSD1306_SET_COM_PINS 0xDA
#define SSD1306_SET_DISPLAY_CLOCK_DIV 0xD5
#define SSD1306_SET_PRECHARGE_PERIOD 0xD9
#define SSD1306_SET_VCOM_DESELECT 0xDB
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_MULTIPLEX 0xA8
#define SSD1306_SET_DISPLAY_OFFSET 0xD3
// SSD1306 display properties
#define SSD1306_DATA_MODE 0x40
#define SSD1306_COMMAND_MODE 0x00
// DHT22 timing constants (microseconds)
#define DHT_START_SIGNAL_LOW 1000
#define DHT_START_SIGNAL_HIGH 30
#define DHT_RESPONSE_TIMEOUT 100
#define DHT_DATA_TIMEOUT 50
// Variables
static volatile int tipCount = 0;
static float rainfallMM = 0.0;
static const float MM_PER_TIP = 0.2;
static int64_t lastTipTime = 0;
static const int64_t DEBOUNCE_TIME = 100000; // 100ms in microseconds
// Capacitive rain sensor variables
static bool rainDetectedByCapacitive = false;
static bool lastCapacitiveSwitchState = true;
static int64_t lastCapacitiveSwitchTime = 0;
static bool rainSensorEnabled = false;
// Tipping bucket LED variables
static bool tippingBucketLedState = false; // Controls the LED flash
static int64_t tippingBucketLedTimer = 0;
static const int64_t LED_FLASH_DURATION = 5000000; // 500ms flash duration
// Flag for persistent tip indication for display logic
static bool tip_occurred_for_display = false;
// How long the tip_occurred_for_display flag remains true (e.g., 5 seconds)
static const int64_t TIP_DISPLAY_HOLD_DURATION = 5000000;
// OLED variables
static uint8_t oled_display_page = 0; // 0=main, 1=detailed
static int64_t last_oled_update = 0;
static const int64_t OLED_UPDATE_INTERVAL = 6000000; // 6 seconds in microseconds (for page switch)
// Task handles
static TaskHandle_t sensor_task_handle = NULL;
static TaskHandle_t capacitive_sensor_task_handle = NULL;
static TaskHandle_t oled_display_task_handle = NULL; // Renamed from lcd_task_handle
static TaskHandle_t led_control_task_handle = NULL;
static const char* TAG = "WeatherMonitor";
// DHT22 data structure
typedef struct {
float temperature;
float humidity;
bool valid;
} dht_data_t;
// Global data for OLED display
static dht_data_t current_dht_data = {0};
// Basic 5x7 font (ASCII 32-126) for SSD1306 - Each character is 5x7 pixels + 1 pixel column space
// This is a simplified example, a full font would be much larger
const unsigned char font[][5] = {
{0x00, 0x00, 0x00, 0x00, 0x00}, // 32: Space
{0x00, 0x00, 0x5F, 0x00, 0x00}, // 33: !
{0x00, 0x07, 0x00, 0x07, 0x00}, // 34: "
{0x14, 0x7F, 0x14, 0x7F, 0x14}, // 35: #
{0x24, 0x2A, 0x7F, 0x2A, 0x12}, // 36: $
{0x23, 0x13, 0x08, 0x64, 0x62}, // 37: %
{0x36, 0x49, 0x55, 0x22, 0x50}, // 38: &
{0x00, 0x05, 0x03, 0x00, 0x00}, // 39: '
{0x00, 0x1C, 0x22, 0x41, 0x00}, // 40: (
{0x00, 0x41, 0x22, 0x1C, 0x00}, // 41: )
{0x14, 0x08, 0x3E, 0x08, 0x14}, // 42: *
{0x08, 0x08, 0x3E, 0x08, 0x08}, // 43: +
{0x00, 0x50, 0x30, 0x00, 0x00}, // 44: ,
{0x08, 0x08, 0x08, 0x08, 0x08}, // 45: -
{0x00, 0x60, 0x60, 0x00, 0x00}, // 46: .
{0x20, 0x10, 0x08, 0x04, 0x02}, // 47: /
{0x3E, 0x51, 0x49, 0x45, 0x3E}, // 48: 0
{0x00, 0x42, 0x7F, 0x40, 0x00}, // 49: 1
{0x42, 0x61, 0x51, 0x49, 0x46}, // 50: 2
{0x21, 0x41, 0x45, 0x4B, 0x31}, // 51: 3
{0x18, 0x14, 0x12, 0x7F, 0x10}, // 52: 4
{0x27, 0x45, 0x45, 0x45, 0x39}, // 53: 5
{0x3C, 0x4A, 0x49, 0x49, 0x30}, // 54: 6
{0x01, 0x71, 0x09, 0x05, 0x03}, // 55: 7
{0x36, 0x49, 0x49, 0x49, 0x36}, // 56: 8
{0x26, 0x49, 0x49, 0x49, 0x3E}, // 57: 9
{0x00, 0x36, 0x36, 0x00, 0x00}, // 58: :
{0x00, 0x56, 0x36, 0x00, 0x00}, // 59: ;
{0x08, 0x14, 0x22, 0x41, 0x00}, // 60: <
{0x14, 0x14, 0x14, 0x14, 0x14}, // 61: =
{0x00, 0x41, 0x22, 0x14, 0x08}, // 62: >
{0x02, 0x01, 0x51, 0x09, 0x06}, // 63: ?
{0x3E, 0x41, 0x5D, 0x59, 0x4E}, // 64: @
{0x7E, 0x11, 0x11, 0x11, 0x7E}, // 65: A
{0x7F, 0x49, 0x49, 0x49, 0x36}, // 66: B
{0x3E, 0x41, 0x41, 0x41, 0x22}, // 67: C
{0x7F, 0x41, 0x41, 0x22, 0x1C}, // 68: D
{0x7F, 0x49, 0x49, 0x49, 0x41}, // 69: E
{0x7F, 0x09, 0x09, 0x09, 0x01}, // 70: F
{0x3E, 0x41, 0x49, 0x49, 0x7A}, // 71: G
{0x7F, 0x08, 0x08, 0x08, 0x7F}, // 72: H
{0x00, 0x41, 0x7F, 0x41, 0x00}, // 73: I
{0x20, 0x40, 0x41, 0x3F, 0x01}, // 74: J
{0x7F, 0x08, 0x14, 0x22, 0x41}, // 75: K
{0x7F, 0x40, 0x40, 0x40, 0x40}, // 76: L
{0x7F, 0x02, 0x0C, 0x02, 0x7F}, // 77: M
{0x7F, 0x04, 0x08, 0x10, 0x7F}, // 78: N
{0x3E, 0x41, 0x41, 0x41, 0x3E}, // 79: O
{0x7F, 0x09, 0x09, 0x09, 0x06}, // 80: P
{0x3E, 0x41, 0x51, 0x21, 0x5E}, // 81: Q
{0x7F, 0x09, 0x19, 0x29, 0x46}, // 82: R
{0x46, 0x49, 0x49, 0x49, 0x31}, // 83: S
{0x01, 0x01, 0x7F, 0x01, 0x01}, // 84: T
{0x3F, 0x40, 0x40, 0x40, 0x3F}, // 85: U
{0x1F, 0x20, 0x40, 0x20, 0x1F}, // 86: V
{0x3F, 0x40, 0x38, 0x40, 0x3F}, // 87: W
{0x63, 0x14, 0x08, 0x14, 0x63}, // 88: X
{0x03, 0x04, 0x78, 0x04, 0x03}, // 89: Y
{0x61, 0x51, 0x49, 0x45, 0x43}, // 90: Z
{0x00, 0x7F, 0x41, 0x41, 0x00}, // 91: [
{0x02, 0x04, 0x08, 0x10, 0x20}, // 92: "\"
{0x00, 0x41, 0x41, 0x7F, 0x00}, // 93: ]
{0x04, 0x02, 0x01, 0x02, 0x04}, // 94: ^
{0x40, 0x40, 0x40, 0x40, 0x40} // 95: _
};
// Function prototypes
static void tip_isr_handler(void* arg);
static dht_data_t read_dht22(void);
static void sensor_task(void* pvParameters);
static void capacitive_sensor_task(void* pvParameters);
static void oled_display_task(void* pvParameters); // Renamed from lcd_task
static void led_control_task(void* pvParameters);
static void print_weather_data(dht_data_t dht_data);
static void create_json_data(dht_data_t dht_data);
static const char* get_rain_status_string(void);
// OLED functions
static esp_err_t i2c_master_init(void); // Still needed for I2C communication
static void ssd1306_command(uint8_t command);
static void ssd1306_data(uint8_t data);
static void ssd1306_init(void);
static void ssd1306_clear(void);
static void ssd1306_draw_char(uint8_t x, uint8_t y, char c);
static void ssd1306_draw_string(uint8_t x, uint8_t y, const char* str);
static void ssd1306_printf(uint8_t x, uint8_t y, const char* format, ...);
static void oled_display_main_page(void); // Adapted from lcd_display_main_page
static void oled_display_detailed_page(void); // Adapted from lcd_display_detailed_page
static float calculate_heat_index(float temp_c, float humidity);
// Calculate heat index
static float calculate_heat_index(float temp_c, float humidity) {
float temp_f = temp_c * 9.0/5.0 + 32.0; // Convert to Fahrenheit
// No heat index calculation needed if temp_f is below 80.0
if (temp_f < 80.0) {
return temp_c;
}
// Simplified heat index calculation (Steadman's formula approximation)
float hi = -42.379 + 2.04901523 * temp_f + 10.14333127 * humidity;
hi -= 0.22475541 * temp_f * humidity;
hi -= 0.00683783 * temp_f * temp_f;
hi -= 0.05481717 * humidity * humidity;
hi += 0.00122874 * temp_f * temp_f * humidity;
hi += 0.00085282 * temp_f * humidity * humidity;
hi -= 0.00000199 * temp_f * temp_f * humidity * humidity;
return (hi - 32.0) * 5.0/9.0; // Convert back to Celsius
}
// I2C master initialization (remains similar)
static esp_err_t i2c_master_init(void) {
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
esp_err_t err = i2c_param_config(I2C_MASTER_NUM, &conf);
if (err != ESP_OK) return err;
return i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}
// SSD1306 low-level command and data write
static void ssd1306_command(uint8_t command) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, SSD1306_COMMAND_MODE, true); // Command mode
i2c_master_write_byte(cmd, command, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(10));
i2c_cmd_link_delete(cmd);
}
static void ssd1306_data(uint8_t data) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, SSD1306_DATA_MODE, true); // Data mode
i2c_master_write_byte(cmd, data, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(10));
i2c_cmd_link_delete(cmd);
}
// SSD1306 Initialization sequence
static void ssd1306_init(void) {
vTaskDelay(pdMS_TO_TICKS(100)); // Power-up delay
ssd1306_command(SSD1306_SET_DISPLAY_OFF); // 0xAE
ssd1306_command(SSD1306_SET_DISPLAY_CLOCK_DIV); // 0xD5
ssd1306_command(0x80); // the suggested ratio 0x80
ssd1306_command(SSD1306_SET_MULTIPLEX); // 0xA8
ssd1306_command(SSD1306_HEIGHT - 1); // set number of rows
ssd1306_command(SSD1306_SET_DISPLAY_OFFSET); // 0xD3
ssd1306_command(0x0); // no offset
ssd1306_command(SSD1306_SET_START_LINE | 0x0); // line #0
ssd1306_command(SSD1306_CHARGE_PUMP); // 0x8D
ssd1306_command(0x14); // Enable charge pump
ssd1306_command(SSD1306_SET_MEMORY_ADDR_MODE); // 0x20
ssd1306_command(0x00); // 0x00 for horizontal addressing mode
ssd1306_command(SSD1306_SEG_REMAP_INV); // 0xA1, column address 127 is mapped to SEG0
ssd1306_command(SSD1306_COM_SCAN_DIR_INV); // 0xC8, Remapped com scan direction
ssd1306_command(SSD1306_SET_COM_PINS); // 0xDA
ssd1306_command(0x12);
ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); // 0x81
ssd1306_command(0xCF); // Set contrast (0x00 to 0xFF)
ssd1306_command(SSD1306_SET_PRECHARGE_PERIOD); // 0xD9
ssd1306_command(0xF1);
ssd1306_command(SSD1306_SET_VCOM_DESELECT); // 0xDB
ssd1306_command(0x40);
ssd1306_command(SSD1306_DISPLAY_ALL_ON_RESUME); // 0xA4
ssd1306_command(SSD1306_NORMAL_DISPLAY); // 0xA6
ssd1306_command(SSD1306_DEACTIVATE_SCROLL); // 0x2E
ssd1306_command(SSD1306_SET_DISPLAY_ON); // 0xAF
ESP_LOGI(TAG, "SSD1306 OLED initialized successfully");
}
// Clear the entire OLED display
static void ssd1306_clear(void) {
ssd1306_command(SSD1306_SET_COLUMN_ADDR); // Set column address
ssd1306_command(0x00); // Start Column 0
ssd1306_command(SSD1306_WIDTH - 1); // End Column 127
ssd1306_command(SSD1306_SET_PAGE_ADDR); // Set page address
ssd1306_command(0x00); // Start Page 0
ssd1306_command(7); // End Page 7 (for 64-pixel height)
// Fill display with 0s (off)
for (int i = 0; i < (SSD1306_WIDTH * SSD1306_HEIGHT / 8); i++) {
ssd1306_data(0x00);
}
}
// Draw a single character at a specific pixel coordinate (x, y)
// y is the row in 8-pixel chunks (0-7 for 64-pixel height)
static void ssd1306_draw_char(uint8_t x, uint8_t y, char c) {
if (c < 32 || c > 95) { // Only supports ASCII 32-95 (Space to _)
c = 32; // Default to space if character is out of range
}
// Cast c to uint8_t to avoid -Werror=char-subscripts
uint8_t char_index = (uint8_t)c - 32; // Adjust for font array index
// Set page and column for drawing
ssd1306_command(0xB0 + (y / 8)); // Set page address (0-7)
ssd1306_command(0x00 + (x % 16)); // Set lower column start address
ssd1306_command(0x10 + (x / 16)); // Set upper column start address
for (int i = 0; i < 5; i++) {
ssd1306_data(font[char_index][i]); // Use char_index here
}
ssd1306_data(0x00); // 1 pixel column spacing between characters
}
// Draw a string starting at pixel coordinate (x, y)
static void ssd1306_draw_string(uint8_t x, uint8_t y, const char* str) {
uint8_t current_x = x;
uint8_t current_y = y;
while (*str) {
// Wrap text if it exceeds screen width
if (current_x + 5 >= SSD1306_WIDTH) { // 5 is char width
current_x = 0;
current_y += 8; // Move to next line (8 pixels per line)
if (current_y >= SSD1306_HEIGHT) {
// Out of screen bounds, stop drawing
break;
}
}
ssd1306_draw_char(current_x, current_y, *str++);
current_x += 6; // 5 pixels for char + 1 pixel for space
}
}
// Print formatted string on OLED
static void ssd1306_printf(uint8_t x, uint8_t y, const char* format, ...) {
char buffer[SSD1306_WIDTH / 6 * (SSD1306_HEIGHT / 8) + 1]; // Max chars for OLED
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
ssd1306_draw_string(x, y, buffer);
}
// OLED display pages
static void oled_display_main_page(void) {
ssd1306_clear();
// Line 0 (approx y=0)
ssd1306_draw_string(0, 0, "Weather Monitor");
// Line 1 (approx y=16) - Temperature and Humidity
if (current_dht_data.valid) {
ssd1306_printf(0, 16, "T:%.1fC H:%.1f%%", current_dht_data.temperature, current_dht_data.humidity);
} else {
ssd1306_draw_string(0, 16, "T:-- H:--%");
}
// Line 2 (approx y=32) - Rain Status (using the combined logic)
ssd1306_printf(0, 32, "Rain: %s", get_rain_status_string());
// Line 3 (approx y=48) - Rainfall amount and LED status
ssd1306_printf(0, 48, "Rain:%.1fmm LED:%s", rainfallMM, tippingBucketLedState ? "ON" : "OFF");
}
static void oled_display_detailed_page(void) {
ssd1306_clear();
// Line 0 (approx y=0) - Heat Index
if (current_dht_data.valid) {
float heat_index = calculate_heat_index(current_dht_data.temperature, current_dht_data.humidity);
ssd1306_printf(0, 0, "Heat Idx: %.1fC", heat_index);
} else {
ssd1306_draw_string(0, 0, "Heat Idx: --C");
}
// Line 1 (approx y=16) - Tip Count
ssd1306_printf(0, 16, "Tips: %d", tipCount);
// Line 2 (approx y=32) - Sensor Status (reflects main rain sensor enable switch)
ssd1306_printf(0, 32, "Capacitive: %s", rainSensorEnabled ? "RAIN" : "NO RAIN");
// Line 3 (approx y=48) - System Status
ssd1306_draw_string(0, 48, "System: RUNNING");
}
// Interrupt handler for tipping bucket
static void IRAM_ATTR tip_isr_handler(void* arg) {
int64_t currentTime = esp_timer_get_time();
if (currentTime - lastTipTime > DEBOUNCE_TIME) {
tipCount++;
rainfallMM = tipCount * MM_PER_TIP;
lastTipTime = currentTime;
// Trigger LED flash for tipping bucket
tippingBucketLedState = true;
tippingBucketLedTimer = currentTime;
gpio_set_level(TIPPING_BUCKET_LED_PIN, 1); // Turn on tipping bucket LED
// Set the flag for OLED display logic
tip_occurred_for_display = true;
}
}
// Read DHT22 sensor (simplified version)
static dht_data_t read_dht22(void) {
dht_data_t result = {0};
// Set pin as output and send start signal
gpio_set_direction(DHT_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(DHT_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(1)); // 1ms low
gpio_set_level(DHT_PIN, 1);
esp_rom_delay_us(30); // 30us high
// Switch to input mode
gpio_set_direction(DHT_PIN, GPIO_MODE_INPUT);
// Simplified reading - in real implementation you'd need precise timing
// For simulation, we'll generate dummy data
result.humidity = 45.0 + (esp_timer_get_time() % 100) / 10.0; // 45-55%
result.temperature = 20.0 + (esp_timer_get_time() % 200) / 10.0; // 20-40°C
result.valid = true;
ESP_LOGI(TAG, "DHT22 Read - Temp: %.1f°C, Humidity: %.1f%%",
result.temperature, result.humidity);
return result;
}
// Get rain status string for display (MODIFIED)
static const char* get_rain_status_string(void) {
bool capacitive_active = rainDetectedByCapacitive;
bool tipping_active = tip_occurred_for_display; // Use the persistent flag for display
const char* status_str; // Declare a local variable to hold the string
if (!rainSensorEnabled) { // Overall sensor enable switch is OFF
status_str = "FALSE TIPPING";
} else {
if (capacitive_active && tipping_active) {
status_str = "ACTUAL RAIN"; // Both indicators point to rain
} else if (capacitive_active && !tipping_active) {
status_str = "NO TIPPING"; // Capacitive active, but no recent tip
} else if (!capacitive_active && tipping_active) {
status_str = "FALSE TIPPING"; // Recent tip, but capacitive says no rain
} else {
status_str = "No Rain"; // Neither is active, or conditions don't meet special states
}
}
ESP_LOGI(TAG, "get_rain_status_string returning: %s", status_str); // Log to serial monitor
return status_str;
}
// Print weather data to console
static void print_weather_data(dht_data_t dht_data) {
printf("\n=== Weather Data Reading ===\n");
printf("============================\n");
if (dht_data.valid) {
printf("Temperature: %.1f°C\n", dht_data.temperature);
printf("Humidity: %.1f%%\n", dht_data.humidity);
float heat_index = calculate_heat_index(dht_data.temperature, dht_data.humidity);
printf("Heat Index: %.1f°C\n", heat_index);
} else {
printf("Failed to read DHT22 sensor!\n");
}
printf("Rain Sensor: %s\n", rainSensorEnabled ? "ENABLED" : "DISABLED");
printf("Rain Status: %s\n", get_rain_status_string()); // Using modified string
printf("Total Rainfall: %.1f mm\n", rainfallMM);
printf("Tip Count: %d\n", tipCount);
printf("============================\n\n");
}
// Create JSON data string
static void create_json_data(dht_data_t dht_data) {
printf("=== JSON Data ===\n");
printf("{\n");
printf(" \"timestamp\": %lld,\n", esp_timer_get_time() / 1000);
if (dht_data.valid) {
printf(" \"temperature\": %.1f,\n", dht_data.temperature);
printf(" \"humidity\": %.1f,\n", dht_data.humidity);
printf(" \"heat_index\": %.1f,\n", calculate_heat_index(dht_data.temperature, dht_data.humidity));
} else {
printf(" \"temperature\": null,\n");
printf(" \"humidity\": null,\n");
printf(" \"heat_index\": null,\n");
}
printf(" \"rainfall_mm\": %.1f,\n", rainfallMM);
printf(" \"rain_sensor_enabled\": %s,\n", rainSensorEnabled ? "true" : "false");
// Updated JSON to reflect the new combined rain status
printf(" \"rain_status_combined\": \"%s\",\n", get_rain_status_string());
printf(" \"rain_detected_capacitive\": %s,\n", rainDetectedByCapacitive ? "true" : "false");
printf(" \"tip_occurred_recently\": %s,\n", tip_occurred_for_display ? "true" : "false");
printf(" \"tip_count\": %d\n", tipCount);
printf("}\n\n");
}
// LED control task
static void led_control_task(void* pvParameters) {
const TickType_t led_delay = pdMS_TO_TICKS(50); // Check every 50ms
while (1) {
int64_t currentTime = esp_timer_get_time();
// Handle tipping bucket LED flash timer
if (tippingBucketLedState && (currentTime - tippingBucketLedTimer) > LED_FLASH_DURATION) {
tippingBucketLedState = false;
gpio_set_level(TIPPING_BUCKET_LED_PIN, 0); // Turn off tipping bucket LED
}
// Reset tip_occurred_for_display after its hold duration
if (tip_occurred_for_display && (currentTime - tippingBucketLedTimer) > TIP_DISPLAY_HOLD_DURATION) {
tip_occurred_for_display = false;
}
vTaskDelay(led_delay);
}
}
// OLED display task (Renamed from lcd_task)
static void oled_display_task(void* pvParameters) {
last_oled_update = esp_timer_get_time(); // Initialize last update time
bool display_on_previous_cycle = false; // Track display state
while (1) {
int64_t currentTime = esp_timer_get_time();
if (tip_occurred_for_display) {
// If display was off, turn it on
if (!display_on_previous_cycle) {
ssd1306_command(SSD1306_SET_DISPLAY_ON);
display_on_previous_cycle = true;
// Force an immediate update when turning on
oled_display_page = 0; // Start with main page
oled_display_main_page();
last_oled_update = currentTime;
} else {
// Switch between display pages based on OLED_UPDATE_INTERVAL
if ((currentTime - last_oled_update) >= OLED_UPDATE_INTERVAL) {
oled_display_page = (oled_display_page == 0) ? 1 : 0; // Toggle page
if (oled_display_page == 0) {
oled_display_main_page();
} else {
oled_display_detailed_page();
}
last_oled_update = currentTime; // Update last update time
}
}
} else {
// If display was on, turn it off and clear
if (display_on_previous_cycle) {
ssd1306_clear(); // Clear content before turning off
ssd1306_command(SSD1306_SET_DISPLAY_OFF);
display_on_previous_cycle = false;
}
}
vTaskDelay(pdMS_TO_TICKS(500)); // Check more frequently, like every 500ms
}
}
static void sensor_task(void* pvParameters) {
const TickType_t sensor_delay = pdMS_TO_TICKS(30000); // 30 seconds
while (1) {
current_dht_data = read_dht22(); // Update global data for OLED
print_weather_data(current_dht_data);
create_json_data(current_dht_data);
vTaskDelay(sensor_delay);
}
}
// Capacitive rain sensor monitoring task
static void capacitive_sensor_task(void* pvParameters) {
const TickType_t sensor_delay = pdMS_TO_TICKS(100); // Check every 100ms
while (1) {
bool currentSwitchState = gpio_get_level(CAPACITIVE_RAIN_SWITCH_PIN);
int64_t currentTime = esp_timer_get_time();
// Check if switch state changed and enough time has passed (debouncing)
if (currentSwitchState != lastCapacitiveSwitchState &&
(currentTime - lastCapacitiveSwitchTime) > DEBOUNCE_TIME) {
if (currentSwitchState == 0) { // Switch to LOW position (rain detected/sensor enabled)
rainSensorEnabled = true;
rainDetectedByCapacitive = true;
gpio_set_level(CAPACITIVE_LED_PIN, 1); // Turn on capacitive sensor LED
ESP_LOGI(TAG, "Capacitive Rain Sensor: ENABLED - Rain detected!");
} else { // Switch to HIGH position (no rain/sensor disabled)
rainSensorEnabled = false;
rainDetectedByCapacitive = false;
gpio_set_level(CAPACITIVE_LED_PIN, 0); // Turn off capacitive sensor LED
ESP_LOGI(TAG, "Capacitive Rain Sensor: DISABLED");
}
lastCapacitiveSwitchState = currentSwitchState;
lastCapacitiveSwitchTime = currentTime;
}
vTaskDelay(sensor_delay);
}
}
void app_main(void) {
ESP_LOGI(TAG, "Weather Monitoring System with OLED Display Started");
ESP_LOGI(TAG, "===================================================");
ESP_LOGI(TAG, "LED Indicators:");
ESP_LOGI(TAG, " GPIO 5 = Capacitive Rain Sensor LED (Solid ON/OFF)");
ESP_LOGI(TAG, " GPIO 19 = Tipping Bucket LED (Flashes on tip)");
ESP_LOGI(TAG, "===================================================");
// Initialize I2C for OLED
esp_err_t ret = i2c_master_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize I2C: %s", esp_err_to_name(ret));
} else {
ESP_LOGI(TAG, "I2C initialized successfully");
}
// Configure GPIO pins
gpio_config_t io_conf = {};
// Configure LED pins as output
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << CAPACITIVE_LED_PIN) | (1ULL << TIPPING_BUCKET_LED_PIN); // Use specific LED pins
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
// Configure slide switch and tipping bucket pins as input with pullup
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << CAPACITIVE_RAIN_SWITCH_PIN) | (1ULL << TIPPING_BUCKET_PIN);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 1; // Pull-up for input pins
gpio_config(&io_conf);
// Configure DHT pin
gpio_set_direction(DHT_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(DHT_PIN, 1); // Ensure DHT pin is initially high
// Install interrupt for tipping bucket
gpio_install_isr_service(0);
gpio_isr_handler_add(TIPPING_BUCKET_PIN, tip_isr_handler, NULL);
gpio_set_intr_type(TIPPING_BUCKET_PIN, GPIO_INTR_NEGEDGE); // Interrupt on falling edge
// Initialize OLED
ssd1306_init();
// Display startup message
ssd1306_clear();
ssd1306_draw_string(0, 0, "Weather Monitor");
ssd1306_draw_string(0, 16, "Starting...");
ssd1306_draw_string(0, 32, "Please wait");
vTaskDelay(pdMS_TO_TICKS(2000));
// Initialize capacitive sensor state based on physical switch
lastCapacitiveSwitchState = gpio_get_level(CAPACITIVE_RAIN_SWITCH_PIN);
if (lastCapacitiveSwitchState == 0) { // If switch is initially low, rain sensor is enabled and detecting rain
rainSensorEnabled = true;
rainDetectedByCapacitive = true;
gpio_set_level(CAPACITIVE_LED_PIN, 1); // Turn on capacitive sensor LED
ESP_LOGI(TAG, "Initial state: Rain sensor ENABLED");
} else { // If switch is initially high, rain sensor is disabled
rainSensorEnabled = false;
rainDetectedByCapacitive = false;
gpio_set_level(CAPACITIVE_LED_PIN, 0); // Turn off capacitive sensor LED
ESP_LOGI(TAG, "Initial state: Rain sensor DISABLED");
}
// Create tasks
xTaskCreate(sensor_task, "sensor_task", 4096, NULL, 5, &sensor_task_handle);
xTaskCreate(capacitive_sensor_task, "capacitive_sensor_task", 2048, NULL, 5, &capacitive_sensor_task_handle);
xTaskCreate(oled_display_task, "oled_display_task", 3072, NULL, 4, &oled_display_task_handle); // Renamed task
xTaskCreate(led_control_task, "led_control_task", 2048, NULL, 5, &led_control_task_handle);
ESP_LOGI(TAG, "All tasks created successfully");
ESP_LOGI(TAG, "System ready - OLED displaying weather data!");
}