Skip to main content

Temperature Monitoring (indoor and outdoor)

This project aims 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). 

Note to self: avoid non-oem DHT22 modules. You only save a couple of dollars and they are in my experience completely unreliable. The cheap clones either don't work or die after a few hours. I pay about $6 per unit for OEM (original) AM2302 chips, which have been running without issue for months.

The diagram below includes a 10k resistor between VIN (5v) and GPIO 0 because the esp32 unit I was using would not boot on power-up without having to hit the reset button. This is a common flaw with the esp32 units but so far for me only happened with one of the dozens I've used so far.  

temp_humidity_citkit_design.jpg

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 it 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 almost nothing. 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 <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <DHT_U.h>
#include <EEPROM.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"


//////////////////////////////
// EDIT THIS SECTION
// last updated June 23, 2024

int activeConnection = 1;

// lab
const String ssid1 = "asiuy2G";
const String password1 = "654765";

// other place
const String ssid2 = "cheytr";
const String password2 = "87654";

const String temperatureUrl = "https://homelab.htd.org/set_temp_humidity.php?data=";

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

#define EEPROM_SIZE 48
int eepromPingsAddress = 0;
float totalServerPings = 0;
int eepromFailedPingsAddress = 1;
float totalServerPingFails = 0;
int eepromActiveConnectionAddress = 1;

String prevTemperatureText = "";
String prevHumidifyText = "";
String prevConnectivityText1 = "";
String prevConnectivityText2 = "";

const int basementTemperaturePin = 16;
const int upstairsTemperaturePin = 17;
const int outdoorTemperaturePin = 23;


const int buttonPin = 14;
int buttonState = 0;
int displayMode = 1;  // 1: temp, humidiy  2: internet

const int pulseRate = 2;  // 2 seconds

const int temperatureServerSendInterval = 1;                                                // 10 minutes between sending a temperature update to the server
const int temperatureSamplesPerReading = (60 * temperatureServerSendInterval) / pulseRate;  // every x minutes send a sample to the server

int temperatureLoopCount = 0;

int lcdBacklightOnCounter = 0;


long secondsOfHour = 0;

const int secondsPerHour = 3600;  // set to 60 for debugging

const int hoursPerDay = 24;






float currentBasementTemperature = 0;
float currentUpstairsTemperature = 0;
float currentOutdoorTemperature = 0;

float currentBasementHumidity = 0;
float currentUpstairsHumidity = 0;
float currentOutdoorHumidity = 0;


bool connectingToWifi = false;
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 dhtBasement(basementTemperaturePin, DHT11);
DHT_Unified dhtUpstairs(upstairsTemperaturePin, DHT11);
DHT_Unified dhtOutdoor(outdoorTemperaturePin, DHT11);  //DHT22 is actually in ouse outside

LiquidCrystal_I2C lcd_i2c(0x27, 16, 2);

sensors_event_t basementSensorEvent;
sensor_t basementSensor;
sensors_event_t upstairsSensorEvent;
sensor_t upstairsSensor;
sensors_event_t outdoorSensorEvent;
sensor_t outdoorSensor;

#define LED_PIN 18  // ESP32 pin GPIO18 connected to LED



void setup() {

  // We sometimes run into brownouts due to main hydro line voltage drops. For this situation set BOD to 3 volts
  // WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);  //disable brownout detector
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0x03);  // 3 volts
  //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0x04); // set brownout detector to 3.2 volts
  // WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0x07); // Set 3.3V as BOD level

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

  //Init EEPROM
  EEPROM.begin(EEPROM_SIZE);

  pinMode(buttonPin, INPUT); // button for displaying info on LCD
  pinMode(LED_PIN, OUTPUT); // led to indicate internet connectivity

  delay(2000);

  dhtBasement.begin();
  dhtUpstairs.begin();
  dhtOutdoor.begin();

  dhtBasement.temperature().getSensor(&basementSensor);
  dhtBasement.humidity().getSensor(&basementSensor);

  dhtUpstairs.temperature().getSensor(&upstairsSensor);
  dhtUpstairs.humidity().getSensor(&upstairsSensor);

  dhtOutdoor.temperature().getSensor(&outdoorSensor);
  dhtOutdoor.humidity().getSensor(&outdoorSensor);

  getSensorReadings();

  EEPROM.begin(EEPROM_SIZE);

  activeConnection = EEPROM.read(eepromActiveConnectionAddress);

  Serial.print("eepromActiveConnection :");
  Serial.println(activeConnection);

  if (isnan(activeConnection)) {
    activeConnection = 1;
  }

  if (activeConnection == 0) {
    activeConnection = 1;
  }

  if (activeConnection > 2) {
    activeConnection = 2;
  }
  Serial.print("activeConnection: ");
  Serial.println(activeConnection);

  if (!connectToWiFi()) {
    delay(2000);
    WiFi.disconnect();
    delay(1000);
    if (activeConnection == 1) {
      activeConnection = 2;
    } else {
      activeConnection = 1;
    }
    connectToWiFi();
  }

  // in case we did a reboot allow remote server a moment
  delay(5000);

  // if wifi connect failed again just reboot
  if (WiFi.status() != WL_CONNECTED) {
    ESP.restart();
  }

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


void loop() {

  secondsOfHour += pulseRate;

  // 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 300 5 mintes of inactivity
    if (lcdBacklightOnCounter % 300 == 0) {
      lcd_i2c.noBacklight();
      lcdBacklightOnCounter = 0;
    }
  }

  buttonState = digitalRead(buttonPin);

  if (buttonState == LOW) {
    lcd_i2c.clear();
    lcd_i2c.backlight();

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

  sensors_event_t basementSensorEvent;

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

  // no need to read the temperature every second
  if (temperatureLoopCount % 10 == 0) {
    getSensorReadings();
  }


  String temperatureText = "T U:" + String(currentUpstairsTemperature, 0) + " B:" + String(currentBasementTemperature, 0) + " E:" + String(currentOutdoorTemperature, 0) + "c";
  String humidifyText = "H U:" + String(currentUpstairsHumidity, 0) + " B:" + String(currentBasementHumidity, 0) + " E:" + 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 >= temperatureSamplesPerReading) || serverFailed) {
    String recordedUpstairsTemperature = String(currentUpstairsTemperature, 1);
    String recordedUpstairsHumidity = String(currentUpstairsHumidity, 1);
    String recordedBasementTemperature = String(currentBasementTemperature, 1);
    String recordedBasementHumidity = String(currentBasementHumidity, 1);
    String recordedOutdoorTemperature = String(currentOutdoorTemperature, 1);
    String recordedOutdoorHumidity = String(currentOutdoorHumidity, 1);

    recordedBasementTemperature.trim();
    recordedBasementHumidity.trim();
    recordedOutdoorTemperature.trim();
    recordedOutdoorHumidity.trim();

    temperatureLoopCount = 0;
    setWifiSleepMode(false);
    http.begin(temperatureUrl + recordedBasementTemperature + "," + recordedBasementHumidity + "," + recordedUpstairsTemperature + "," + recordedUpstairsHumidity + "," + recordedOutdoorTemperature + "," + recordedOutdoorHumidity);
    int httpCode = http.GET();
    if (httpCode > 0) {
      String 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 * 1000);
  pulseLed(!wifiPaused);

  // analogWrite(LED_PIN, 255);
}




/**
* pulse the led in and out over a span of 2 seconds
*
*/
void pulseLed(bool active) {

  // analogWrite(LED_PIN, 255);

  int dutyCycle = 166;
  int delayMs = pulseRate * 1000 / dutyCycle;

  // Fade in the LED
  for (int brightness = 0; brightness <= dutyCycle; brightness++) {
    if (active) {
      analogWrite(LED_PIN, brightness);
    }
    delay(delayMs);  // Adjust this value to change the fade duration
  }

  // Fade out the LED
  for (int brightness = dutyCycle; brightness >= 0; brightness--) {
    if (active) {
      analogWrite(LED_PIN, brightness);
    }
    delay(delayMs);  // Adjust this value to change the fade duration
  }

  // delay(1000);
}

bool connectToWiFi() {

  if (connectingToWifi || wifiConnected) {
    return true;
  }

  connectingToWifi = true;

  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());

    EEPROM.write(eepromActiveConnectionAddress, activeConnection);
    EEPROM.commit();

    Serial.print("set activeConnection to : ");
    Serial.println(activeConnection);

    // 5 flashes means intenet is connected
    for (int i = 0; i < 5; i++) {
      digitalWrite(LED_PIN, HIGH);
      delay(10);
      digitalWrite(LED_PIN, LOW);
      delay(200);
    }

    wifiConnected = true;
    connectingToWifi = false;
    return true;

  } else {
    Serial.print("Connection to ");
    Serial.print(activeSsid);
    Serial.println(" failed. Trying alternative");

    wifiConnected = false;
    connectingToWifi = false;

    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);
    wifiConnected = false;
    delay(1000);
    Serial.print("sleep wifi status: ");
    Serial.println(wl_status_to_string(WiFi.status()));
  } else {
    WiFi.setSleep(false);
    WiFi.reconnect();
    delay(2000);
    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;
}



void getSensorReadings() {


  float newTempReading;
  float newHumidityReading;


  // BASEMENT

  dhtBasement.temperature().getEvent(&basementSensorEvent);
  if (isnan(basementSensorEvent.temperature)) {
    Serial.println(F("Error reading basement temperature!"));
  } else {
    newTempReading = basementSensorEvent.temperature;
    if (newTempReading > -10 && newTempReading < 50) {
      currentBasementTemperature = basementSensorEvent.temperature;
    }
    Serial.print("basement temp: ");
    Serial.println(currentBasementTemperature);
  }


  dhtBasement.humidity().getEvent(&basementSensorEvent);
  if (isnan(basementSensorEvent.relative_humidity)) {
    Serial.println(F("Error reading basement humidity!"));
  } else {

    newHumidityReading = basementSensorEvent.relative_humidity;
    if (newHumidityReading > -10 && newHumidityReading < 100) {
      currentBasementHumidity = basementSensorEvent.relative_humidity;
    }

    Serial.print("basement humidity: ");
    Serial.println(currentBasementHumidity);
  }


  // UPSTAIRS
  dhtUpstairs.temperature().getEvent(&upstairsSensorEvent);
  if (isnan(upstairsSensorEvent.temperature)) {
    Serial.println(F("Error reading upstairs temperature!"));
  } else {
    newTempReading = upstairsSensorEvent.temperature;
    if (newTempReading > -10 && newTempReading < 50) {
      currentUpstairsTemperature = upstairsSensorEvent.temperature;
    }
    Serial.print("upstairs temp: ");
    Serial.println(currentUpstairsTemperature);
  }


  dhtUpstairs.humidity().getEvent(&upstairsSensorEvent);
  if (isnan(upstairsSensorEvent.relative_humidity)) {
    Serial.println(F("Error reading upstairs humidity!"));
  } else {

    newHumidityReading = upstairsSensorEvent.relative_humidity;
    if (newHumidityReading > -10 && newHumidityReading < 100) {
      currentUpstairsHumidity = upstairsSensorEvent.relative_humidity;
    }

    Serial.print("upstairs humidity: ");
    Serial.println(currentUpstairsHumidity);
  }



  // OUTDOOR

  dhtOutdoor.temperature().getEvent(&outdoorSensorEvent);
  if (isnan(outdoorSensorEvent.temperature)) {
    Serial.println(F("Error reading outdoor temperature!"));
  } else {
    newTempReading = outdoorSensorEvent.temperature;
    if (newTempReading > -10 && newTempReading < 50) {
      currentOutdoorTemperature = outdoorSensorEvent.temperature;
    }
    Serial.print("outdoor temp: ");
    Serial.println(currentOutdoorTemperature);
  }


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

    newHumidityReading = outdoorSensorEvent.relative_humidity;
    if (newHumidityReading > -10 && newHumidityReading < 100) {
      currentOutdoorHumidity = outdoorSensorEvent.relative_humidity;
    }

    Serial.print("outdoor humidity: ");
    Serial.println(currentOutdoorHumidity);
  }
}