Table of Contents

Solar powered weather station with ESP32

Project Description

This project aims to design and build a truly autonomous weather station by leveraging solar energy harvesting. The weather station will monitor the temperature, the humidity, the atmospheric pressure and the state of charge of the Li-Po battery. The project will use MQTT communication protocol with ESP32 to publish messages and subscripe to topics, making the data accessible for various applications.

Hardware Description

The following parts were used:

The following electric schematic represents the project’s hardware

The prototyping board can be seen bellow

Software Description

The code flowchart can be seen bellow

 
#include <WiFi.h>
#include <AsyncMqttClient.h>
#define WIFI_SSID ""
#define WIFI_PASSWORD ""
#define MQTT_HOST "test.mosquitto.org"
#define MQTT_PORT 1883

This part includes necessary libraries for Wi-Fi and MQTT functionality. It defines the Wi-Fi credentials and the MQTT broker details.

 
#define batPin 4
#define LOW 3300
#define HIGH 4095
#define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  7        /* Time ESP32 will go to sleep (in seconds) */

// Temperature MQTT Topics
#define MQTT_PUB_TEMP "esp32/weather_station/temperature"
#define MQTT_PUB_HUM "esp32/weather_station/humidity"
#define MQTT_PUB_PRES "esp32/weather_station/pressure"
#define MQTT_PUB_BAT "esp32/weather_station/batteryPercentage"
// BME280 I2C
Adafruit_BME280 bme;
// Variables to hold sensor readings
float temp;
float hum;
float pres;
//int publishACK = 0;
int batValue = 0;
//RTC_DATA_ATTR int bootCount = 0;

AsyncMqttClient mqttClient;
TimerHandle_t mqttReconnectTimer;
TimerHandle_t wifiReconnectTimer;

Here, constants and global variables are defined, including the pin for battery measurement, sensor reading variables (temp, hum, pres), and MQTT client and timer objects.

 
void connectToWifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

void connectToMqtt() {
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}

void WiFiEvent(WiFiEvent_t event) {
  Serial.printf("[WiFi-event] event: %d\n", event);
  switch(event) {
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("WiFi connected");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
      connectToMqtt();
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("WiFi lost connection");
      xTimerStop(mqttReconnectTimer, 0); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
      xTimerStart(wifiReconnectTimer, 0);
      break;
  }
}

void onMqttConnect(bool sessionPresent) {
  Serial.println("Connected to MQTT.");
  Serial.print("Session present: ");
  Serial.println(sessionPresent);
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.println("Disconnected from MQTT.");
  if (WiFi.isConnected()) {
    xTimerStart(mqttReconnectTimer, 0);
  }
}

void onMqttPublish(uint16_t packetId) {
  Serial.print("Publish acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
  //publishACK = publishACK + 1;
}

These functions manage Wi-Fi and MQTT connectivity. They handle events like connecting to Wi-Fi, MQTT broker, and publishing messages.

 
void EnterSleep() {
  /*
  First we configure the wake up source
  We set our ESP32 to wake up every 7 seconds
  */
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

  Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
  " Seconds");

  Serial.println("Going to sleep now");
  Serial.flush();

  // force specific powerdown modes for RTC peripherals and RTC memories
  esp_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF);
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);

  // Enter Deep Sleep -- Hibernation
  esp_deep_sleep_start();
}

EnterSleep configures the ESP32 to enter deep sleep mode for power saving, waking up periodically to perform measurements and send data.

 
void setup() {
  Serial.begin(115200);
  Serial.println();
  
  // Initialize BME280 sensor 
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  
  mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt));
  wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToWifi));

  WiFi.onEvent(WiFiEvent);

  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  mqttClient.onPublish(onMqttPublish);
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  connectToWifi();
}
}

In setup, serial communication is started, the BME280 sensor is initialized, Wi-Fi and MQTT timers and event handlers are set up, and the ESP32 connects to Wi-Fi.

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // Save the last time a new reading was published
    previousMillis = currentMillis;
    // New BME280 sensor readings
    temp = bme.readTemperature();
    //temp = 1.8*bme.readTemperature() + 32;
    hum = bme.readHumidity();
    pres = bme.readPressure()/100.0F;
    batValue = (analogRead(batPin) - LOW)* 100 / (HIGH - LOW);
    
    // Publish an MQTT message on topic esp32/BME2800/temperature
    uint16_t packetIdPub1 = mqttClient.publish(MQTT_PUB_TEMP, 1, true, String(temp).c_str());                            
    Serial.printf("Publishing on topic %s at QoS 1, packetId: %i", MQTT_PUB_TEMP, packetIdPub1);
    Serial.printf("Message: %.2f \n", temp);

    // Publish an MQTT message on topic esp32/weather_station/humidity
    uint16_t packetIdPub2 = mqttClient.publish(MQTT_PUB_HUM, 1, true, String(hum).c_str());                            
    Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_HUM, packetIdPub2);
    Serial.printf("Message: %.2f \n", hum);

    // Publish an MQTT message on topic esp32/weather_station/pressure
    uint16_t packetIdPub3 = mqttClient.publish(MQTT_PUB_PRES, 1, true, String(pres).c_str());                            
    Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_PRES, packetIdPub3);
    Serial.printf("Message: %.2f \n", pres);

    uint16_t packetIdPub4 = mqttClient.publish(MQTT_PUB_BAT, 1, true, String(batValue).c_str());                            
    Serial.printf("Publishing on topic %s at QoS 1, packetId: %i", MQTT_PUB_BAT, packetIdPub4);
    Serial.printf("Message: %d \n", batValue);
    delay(3000);
    EnterSleep();
  }
}

The loop function periodically reads data from the BME280 sensor and publishes it to the MQTT broker. After publishing, it puts the ESP32 into deep sleep mode for 7 seconds.

Results

Above we can see a prolonged use time with a lot of data entry points.*

As you can see, the ADC reading for the battery are not that precise. We can think of at least 2 culprits in this case. First would be the ESP32 ADC's poor performance and the second one, the method chosen; because the ESP wakes from deep sleep, the energy draw increases very drastically and the battery voltage might vary.*

*For data visualization and plotting, MQTT Explore was used.

Conclusion

In conclusion, the proposed solution demonstrates how an ESP32, combined with a BME280 sensor, can be used to create a power-efficient, wireless weather station capable of continuously monitoring environmental conditions and transmitting the data in real-time via Wi-Fi and MQTT, showcasing the potential of IoT in remote sensing and data communication. Further improvements will be to find a way to get precise and constant readings of the battery state of charge, although the literature says it isn't quite correct to calculate it based on voltage; the most correct way to solve this problem from an strictly engineering standpoint is to use an dedicated IC such as a fuel gauge or a coulomb counter.

Bibliography

https://ocw.cs.pub.ro/courses/iothings

https://randomnerdtutorials.com/esp32-bme280-arduino-ide-pressure-temperature-humidity/;

https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/;

https://randomnerdtutorials.com/esp32-mqtt-publish-subscribe-arduino-ide/;

https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/;

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html;

https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32/api-reference/peripherals/adc.html.