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). 

Note to self: avoid non-oem DHT22 modules. You only save a couple of dollars and may more headache than they are worth. They die almost immediately.

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.jpgtemp_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 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 <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <DHT_U.h>
#include <EEPROM.h>

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

int activeConnection = 1;

// silo
const String ssid1 = "hgf";
const String password1 = "i656dfd1396";

// hansen
const String ssid2 = "gfds";
const String password2 = "9645311355";

const String temperatureUrl = "https://homelab.xyz.com/silopower/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 basementTemperaturePin = 16;
const int upstairsTemperaturePin = 17;
const int outdoorTemperaturePin = 23;


const int buttonPin = 15;
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 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;


// TODO; remove anything to do with temperature

DHT_Unified dhtBasement(basementTemperaturePin, DHT11);
DHT_Unified dhtUpstairs(upstairsTemperaturePin, DHT11);
DHT_Unified dhtOutdoor(outdoorTemperaturePin, DHT22);

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;


void setup() {

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

  //Init EEPROM
  EEPROM.begin(EEPROM_SIZE);

  pinMode(buttonPin, INPUT);

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

  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 == HIGH) {
    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);
}

/**
 * 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;
}









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

  */
}