Skip to main content

Temperature Monitoring (indoor and outdoor)

For this project the goal is to relay indoor and outdoor temperature (and optionally humidity) to a server log. This project is fairly simple because we can use off the shelf DHT11 and DHT22 sensors (DHT22 is for below 0c). 

As you can see in the diagram below the wiring is pretty basic. The coding is a little more complex but not too much effort. 

temp_humidity_citkit_design.png

For the indoor sensor I used this case:

https://www.thingiverse.com/thing:2497711

And for the outdoor sensor I used this case: 

https://www.thingiverse.com/thing:6400888

temperature_sensor_protector.jpg

The end result in my case is a chart showing the temperature over a couple of days. If I see the temperature drop significantly (like pipes freezing zone) I at least can get out there to remedy the situation before is becomes an issue. 

2024-02-19_10-25.png

And the code. It ain't pretty but it works. For the server-side code I am just using free web hosting since the load is amost nothng. For the ESP32 module the code below is about as minimal as I dare go. I do record the server success/fail rates but if everything seems stable after a few weeks I will remove that too.

#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <DHT_U.h>
#include <EEPROM.h>

//////////////////////////////
// EDIT THIS SECTION
  
int activeConnection = 1;

// primary wifi
const String ssid1 = "xxx";
const String password1 = "xxxxx";

// secondary wifi
const String ssid2 = "xxxx";
const String password2 = "xxxxx";

const String heartbeatUrl = "https://xxx.xxx.ca/silopower/heartbeat.php";
const String temperatureUrl = "https://xxx.xxx.ca/silopower/set_temp_humidity.php?data=";

// end edit section
////////////////////

#define EEPROM_SIZE 4
int eepromPingsAddress = 0;
float totalServerPings = 0;
int eepromFailedPingsAddress = 1;
float totalServerPingFails = 0;
int eepromActiveConnection = 1;
      
String prevTemperatureText = "";
String prevHumidifyText = "";
String prevConnectivityText1 = "";
String prevConnectivityText2 = "";

const int indoorTemperaturePin = 16;
const int outdoorTemperaturePin = 17;
const int buttonPin = 15;
int buttonState = 0;
int displayMode = 1; // 1: temp, humidiy  2: internet

const int pulseRate = 1000; // loop runs once per second

const int serverSendInterval = 10; // 10 minutes between sending a voltage update to the server
const int samplesPerReading = 60 * serverSendInterval; // every x minutes send a sample to the server

int temperatureLoopCount = 0;
int lcdBacklightOnCounter = 1;

long secondsOfHour = 0; // used to reset esp32 every 24 hours

float currentIndoorTemperature = 0;
float currentIndoorHumidity = 0;

float currentOutdoorTemperature = 0;
float currentOutdoorHumidity = 0;

String payload = "";
int httpCode = 0;
bool wifiConnected = false;
bool wifiPaused = false;
int wifiPausedTick = 0;
bool wifiSleeping = false;
bool debug = false; // when true server does not update
bool serverFailed = false;
int wifiConnectionAttempts = 0;

HTTPClient http;

DHT_Unified dhtIndoor(indoorTemperaturePin, DHT11);
DHT_Unified dhtOutdoor(outdoorTemperaturePin, DHT22);

Adafruit_ADS1115 ads;

LiquidCrystal_I2C lcd_i2c(0x27, 16, 2);

void setup() {

  Serial.begin(115200); // this needs to match the value in the serial monitor
 
  pinMode(buttonPin, INPUT);

  delay(2000);

  dhtIndoor.begin();

  // Print temperature sensor details.
  sensor_t indoorSensor;
  dhtIndoor.temperature().getSensor( & indoorSensor);
  // Print humidity sensor details.
  dhtIndoor.humidity().getSensor( & indoorSensor);

  dhtOutdoor.begin();
  // Print temperature sensor details.
  sensor_t dhtOutdoor;
  dhtIndoor.temperature().getSensor( & dhtOutdoor);
  // Print humidity sensor details.
  dhtIndoor.humidity().getSensor( & dhtOutdoor);

  EEPROM.begin(EEPROM_SIZE);

  activeConnection = EEPROM.read(eepromActiveConnection);

  Serial.print("eepromActiveConnection :");
  Serial.println(activeConnection);
  
  if (isnan(activeConnection)) {
    activeConnection = 1;
  }

  if(activeConnection == 0){
    activeConnection = 1;
  }
  
  Serial.print("activeConnection: ");
  Serial.println(activeConnection);

  float pingData = EEPROM.readFloat(eepromPingsAddress);
  if (isnan(pingData)) {
    pingData = 0;
  }
  totalServerPings = pingData;
  EEPROM.end();
  EEPROM.begin(EEPROM_SIZE);
  float pingFailData = EEPROM.readFloat(eepromFailedPingsAddress);
  if (isnan(pingFailData)) {
    pingFailData = 0;
  }
  totalServerPingFails = pingFailData;
  EEPROM.end();

  if(!connectToWiFi()){
    delay(2000);
    WiFi.disconnect();
    delay(1000);
  
    if(activeConnection == 1){
      activeConnection = 2; 
    }else {
      activeConnection = 1;
    }
    connectToWiFi();
  }
  
  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    while (1);
  }

  ads.setGain(GAIN_FOUR);

  lcd_i2c.init();
  lcd_i2c.backlight();

}

void loop() {

  secondsOfHour ++;
  // after 24 hours reset this integer
  if(secondsOfHour > 86400){
      secondsOfHour = 1;
      // Serial.println("restarting :");
      ESP.restart();
  }

  if(lcdBacklightOnCounter == 0){
    lcd_i2c.noBacklight();
  }else {
     // if lcb backlight counter is over zero it is active so increment
     lcdBacklightOnCounter ++;
     // turn off backlight after 1800 - 5 mintes of inactivity
     if(lcdBacklightOnCounter > 1800){
          lcd_i2c.noBacklight();
          lcdBacklightOnCounter = 0;
     }
  }
  
  buttonState = digitalRead(buttonPin);
  
  if (buttonState == HIGH) {
    lcd_i2c.clear();  
    lcd_i2c.backlight();

    if(lcdBacklightOnCounter == 0){
      displayMode = 1;
    }else {
      displayMode++;
      if (displayMode > 2) {
        displayMode = 1;
      }   
    }
    lcdBacklightOnCounter = 1;    
  }
 
  sensors_event_t indoorSensorEvent;

   //////////////////////////////
  // TEMPERATURE AND HUMIDITY

  // no need to read the temperature every second
  if (temperatureLoopCount % 10 == 0) {
    dhtIndoor.temperature().getEvent( & indoorSensorEvent);
    if (isnan(indoorSensorEvent.temperature)) {
      Serial.println(F("Error reading indoor temperature!"));
    } else {
      currentIndoorTemperature = indoorSensorEvent.temperature;
    }
    dhtIndoor.humidity().getEvent( & indoorSensorEvent);
    if (isnan(indoorSensorEvent.relative_humidity)) {
      Serial.println(F("Error reading indoor humidity!"));
    } else {
      currentIndoorHumidity = indoorSensorEvent.relative_humidity;
    }
  }

  sensors_event_t outdoorSensorEvent;

  // no need to read the temperature every second
  if (temperatureLoopCount % 10 == 0) {
    dhtOutdoor.temperature().getEvent( & outdoorSensorEvent);
    if (isnan(outdoorSensorEvent.temperature)) {
      Serial.println(F("Error reading outdoor temperature!"));
    } else {
      currentOutdoorTemperature = outdoorSensorEvent.temperature;

    }
    dhtOutdoor.humidity().getEvent( & outdoorSensorEvent);
    if (isnan(outdoorSensorEvent.relative_humidity)) {
      Serial.println(F("Error reading outdoor humidity!"));
    } else {
      currentOutdoorHumidity = outdoorSensorEvent.relative_humidity;
    }
  }

  String temperatureText = "IT:" + String(currentIndoorTemperature, 0) + "c OT:" + String(currentOutdoorTemperature, 0) + "c";
  String humidifyText = "IH:" + String(currentIndoorHumidity, 0) + " OH:" + String(currentOutdoorHumidity, 0) + "";

  if (wifiPaused) {
    if (wifiPausedTick > 60 && wifiConnectionAttempts == 0) { // wait 1 minute before attempting to reconnect 
      wifiPaused = false;
      wifiPausedTick = 0;
      connectToWiFi();
    } else {
      wifiPausedTick++;
    }

  }

  /////////////////////
  // SERVER RELAY 
    
  if (!debug && (temperatureLoopCount >= samplesPerReading) || serverFailed) {

    temperatureLoopCount = 0;

    setWifiSleepMode(false);

    delay(2000);
    
    // do a heartbeat check to see if we are online...
    http.begin(heartbeatUrl);
    httpCode = http.GET();
    if (!httpCode > 0) {
      // wifi may not be alive yet so wait 3 seconds
      delay(3000);
    }
     
    String recordedIndoorTemperature = String(currentIndoorTemperature, 1);
    String recordedIndoorHumidity = String(currentIndoorHumidity, 1);
    String recordedOutdoorTemperature = String(currentOutdoorTemperature, 1);
    String recordedOutdoorHumidity = String(currentOutdoorHumidity, 1);

    recordedIndoorTemperature.trim();
    recordedIndoorHumidity.trim();   
    recordedOutdoorTemperature.trim();
    recordedOutdoorHumidity.trim();
       
    temperatureLoopCount = 0;

    Serial.println("repaying temperature: ");
    Serial.println(temperatureLoopCount);
      
    http.begin(temperatureUrl + recordedIndoorTemperature + "," + recordedIndoorHumidity + "," + recordedOutdoorTemperature + "," + recordedOutdoorHumidity);
    httpCode = http.GET();
    if (httpCode > 0) {
      payload = http.getString();
      Serial.println("HTTP Response: " + payload);
      recordPingSucces();
    } else {
      recordPingFailure();
    }
    http.end();

    setWifiSleepMode(true);
  }

  temperatureLoopCount++;
    
  ////////////////////////////
  // LCD DISPLAY

  switch (displayMode) {
    
  case 1:
    temperatureText.trim();
    humidifyText.trim();

    if(prevTemperatureText != temperatureText || prevHumidifyText != humidifyText){
       lcd_i2c.clear();
    }

    prevTemperatureText = temperatureText;      
    prevHumidifyText = humidifyText;
    
    lcd_i2c.setCursor(0, 0);
    lcd_i2c.print(temperatureText);
    lcd_i2c.setCursor(0, 1);
    lcd_i2c.print(humidifyText);
    break;
    
  case 2:
    String connectivityText1 = "";
    if(wifiConnected){
      connectivityText1 += "WF ON";
    }
    if(wifiConnected && !serverFailed){
        connectivityText1 += " SERVER ON";
    }

    String serverPings = String(totalServerPings, 0);
    serverPings.trim();
    
    String serverPingFails = String(totalServerPingFails, 0);
    serverPingFails.trim();
          
    String connectivityText2 = serverPings + "/" +  serverPingFails + " PINGS";

    connectivityText1.trim();
    connectivityText2.trim();

    if(prevConnectivityText1 != connectivityText1 || prevConnectivityText2 != connectivityText2){
       lcd_i2c.clear();
    }

    prevConnectivityText1 = connectivityText1;
    prevConnectivityText2 = connectivityText2;
    
    lcd_i2c.setCursor(0, 0);
    lcd_i2c.print(connectivityText1);
    lcd_i2c.setCursor(0, 1);
    lcd_i2c.print(connectivityText2);
    break;
  }

  delay(pulseRate);
}

/**
 * connect to wifi
 */
bool connectToWiFi() {

  String activeSsid = "";
  String activePassword = "";
  
  if(activeConnection == 1){
    activeSsid = ssid1;
    activePassword= password1;
  }else if(activeConnection == 2){
    activeSsid = ssid2;
    activePassword= password2; 
  }
  
  Serial.print("Connecting to WiFi: ");
  Serial.println(activeSsid);

  WiFi.begin(activeSsid, activePassword);

  while (WiFi.status() != WL_CONNECTED && wifiConnectionAttempts < 20) {
    delay(500);
    Serial.print(".");
    wifiConnectionAttempts++;
  }

  wifiConnectionAttempts = 0;

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nConnected to WiFi");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());
    wifiConnected = true;

    EEPROM.begin(EEPROM_SIZE);
    EEPROM.write(eepromActiveConnection, activeConnection);
    EEPROM.commit();
    EEPROM.end();

    Serial.print("set activeConnection to : ");
    Serial.println(activeConnection);
    
    return true;
      
  } else {
    Serial.println("Connection failed. Trying alternative");
    return false;
  }
}



/**
 * set wifi sleep mode between data relays to conserve energy
 * @param sleepMode - if true set wifi card to sleep to conserve energy
 */
void setWifiSleepMode(bool sleepMode) {

  wifiSleeping = sleepMode;

  if (sleepMode) {
    WiFi.disconnect();
    WiFi.setSleep(true);
    delay(1000);
    Serial.print("sleep wifi status: ");
    Serial.println(wl_status_to_string(WiFi.status()));
  } else {
    WiFi.setSleep(false);
    WiFi.reconnect();
    delay(1000);
    Serial.print("awaken wifi status: ");
    Serial.println(wl_status_to_string(WiFi.status()));
    // Check if the connection is still active. if not trigger wait for it to come back online
    if (WiFi.status() != WL_CONNECTED && !wifiPaused) {
      Serial.println("Connection lost. Attempting to reconnect in 1 minute ...");
      WiFi.disconnect();
      wifiPaused = true;
      wifiConnected = false;
      connectToWiFi();
    }
  }
}

/**
 * record server ping success in long term memory
 */
void recordPingSucces() {
      totalServerPings++;
      EEPROM.begin(EEPROM_SIZE);
      EEPROM.writeFloat(eepromPingsAddress, totalServerPings);
      EEPROM.commit();
      EEPROM.end();
      wifiConnected = true;
      serverFailed = false;
}

/**
 * record server ping fails in long term memory
 */
void recordPingFailure() {
      totalServerPingFails++;
      EEPROM.begin(EEPROM_SIZE);
      EEPROM.writeFloat(eepromFailedPingsAddress, totalServerPingFails);
      EEPROM.commit();
      EEPROM.end();
      wifiConnected = false;
      serverFailed = true;
}

/**
 * ESP32 wifi card statuses
 * @param status
 * @return string
 */
String wl_status_to_string(wl_status_t status) {

  String response = "";

  switch (status) {
  case WL_NO_SHIELD:
    response = "WL_NO_SHIELD";
    break;
  case WL_IDLE_STATUS:
    response = "WL_IDLE_STATUS";
    break;
  case WL_NO_SSID_AVAIL:
    response = "WL_NO_SSID_AVAIL";
    break;
  case WL_SCAN_COMPLETED:
    response = "WL_SCAN_COMPLETED";
    break;
  case WL_CONNECTED:
    response = "WL_CONNECTED";
    break;
  case WL_CONNECT_FAILED:
    response = "WL_CONNECT_FAILED";
    break;
  case WL_CONNECTION_LOST:
    response = "WL_CONNECTION_LOST";
    break;
  case WL_DISCONNECTED:
    response = "WL_DISCONNECTED";
    break;
  }

  return response;
}