Skip to main content

Home Power Consumption Monitoring

https://www.youtube.com/watch?v=TITtBkoaQ_s&t=20s

 current-sendor.png

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


int activeConnection = 1;

// silo
const String ssid1 = "xxxx";
const String password1 = "xxxxx";

// hansen
const String ssid2 = "xxxx";
const String password2 = "xxxx";

const String heartbeatUrl = "https://xxx.xxx.ca/silopower/heartbeat.php";
const String currentUrl = "https://xxx.xxx.ca/silopower/set_house_current.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 prevWattagePerHourText = "";
String prevWattageAverageText = "";
String prevConnectivityText1 = "";
String prevConnectivityText2 = "";

const int potPin = 36;
const int buttonPin = 15;
int buttonState = 0;
int displayMode = 1; 

int ACDCVoltage = 120;

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

float averagePotValue = 0;
float potValue = 0;
float accumulatedPotValues = 0;
int loopCount = 0;

int lcdBacklightOnCounter = 1;


float accumulatedAmps = 0;
float averageCurrentValue = 0;
float averageWattage = 0;

float wattsPerHour = 0;
float ampHoursPerDay = 0;
 
long secondsOfHour = 0;

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

float houryAmpsArray[secondsPerHour];

const int hoursPerDay = 24;
float dailyAmpsArray[hoursPerDay];


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;


Adafruit_ADS1115 ads;

LiquidCrystal_I2C lcd_i2c(0x27, 16, 2);

const float FACTOR = 20; //20A/1V from the CT
const float multiplier = 0.0000472; // increate so bring down read current

void setup() {

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

  //Init EEPROM
  EEPROM.begin(EEPROM_SIZE);

  pinMode(buttonPin, INPUT);

  for (int i = 0; i < secondsPerHour; i++) {
    houryAmpsArray[i] = -1;
  }
  
  for (int i = 0; i < hoursPerDay; i++) {
    dailyAmpsArray[i] = -1;
  }
  
  delay(2000);


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


  // CURRENT
  float amps = getAmps();
  accumulatedAmps += amps;

  cycleHourlyAmpsArray(amps);
  
  float ampsPerHour = getAverageAmps(houryAmpsArray, secondsPerHour);

  // to get average wattage get the last 10 reading divided by 10
  
  float wattsAverage = getLastXWattReadings(5) * ACDCVoltage;
  float wattsPerHour = ampsPerHour * ACDCVoltage;

  float ampHoursPerDay = getAverageAmps(dailyAmpsArray, hoursPerDay);

  if(secondsOfHour % secondsPerHour == 0){
     cycleDailyAmpsArray(ampsPerHour);
  }

  float dailyAmpHours = getDailyAmpHours();

  float wattagePerDay =  dailyAmpHours * ACDCVoltage;
   
  
  String wattsAverageString = String(wattsAverage, 0);
  wattsAverageString.trim();

  String wattsPerHourString = String(wattsPerHour, 0);
  wattsPerHourString.trim();

  String wattsPerDayString = String(wattagePerDay, 0);
  wattsPerDayString.trim();

  String wattagePerHourText = wattsPerHourString + "WH " + wattsPerDayString + "DWH ";
  String wattageAverageText =  wattsAverageString + "W";

  // keep the noise down
  if (loopCount % 10 == 0) {
  
  }

  averageWattage = (accumulatedAmps / loopCount) * ACDCVoltage;
   
  /////////////////////
  // SERVER RELAY 
  

  if (!debug && (loopCount >= samplesPerReading) || serverFailed) {

    accumulatedAmps = 0;
    loopCount = 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 recordedAverageWattage = String(averageWattage, 0);

    recordedAverageWattage.trim();
    
    http.begin(currentUrl + recordedAverageWattage);
    httpCode = http.GET();
    if (httpCode > 0) {
      payload = http.getString();
      Serial.println("HTTP Response: " + payload);
      recordPingSucces();
    } else {
      Serial.println("HTTP Request failed with error code: " + String(httpCode));
      recordPingFailure();
    }
    http.end();
    
    setWifiSleepMode(true);
  }

  loopCount++;
    

  ////////////////////////////
  // LCD DISPLAY

  switch (displayMode) {
  case 1:
    wattagePerHourText.trim();
    wattageAverageText.trim();
      
    if(prevWattagePerHourText != wattagePerHourText || prevWattageAverageText != wattageAverageText ){
       lcd_i2c.clear();
    }

    prevWattagePerHourText = wattagePerHourText;
    prevWattageAverageText = wattageAverageText;

    lcd_i2c.setCursor(0, 0);
    lcd_i2c.print(wattagePerHourText);
    lcd_i2c.setCursor(0, 1);
    lcd_i2c.print(wattageAverageText);
    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;
}

/**
 * Get the current in amps coming from the hall sensor
 * @return float
 */
float getAmps() {
  
  float combinedReading;
  float current;
  float sum = 0;
  long time_check = millis();
  int counter = 0;

  while (millis() - time_check < 1000) {

    combinedReading = ads.readADC_Differential_0_1();
    combinedReading += ads.readADC_Differential_2_3();
    current = combinedReading * FACTOR + (combinedReading * 0.05);

    sum += sq(current);
    counter = counter + 1;
  }

  current = sqrt(sum / counter);
  return (current);
}


/**
 * read the accumulated amps divided by readings
 */
float getAverageAmps(float array[], int size){

  float accumulatedValues = 0;
  int ampCounts = 0;
  
    for (int i = 0; i < size - 1; i++) {
      if(array[i] >= 0){
        ampCounts ++;
        accumulatedValues += array[i];
      }
    }

    return accumulatedValues / ampCounts;
}


/**
 * read the total amp over a 24 hour periods
 */
float getDailyAmpHours(){

  float accumulatedValues = 0;  
    for (int i = 0; i < hoursPerDay - 1; i++) {
      if(dailyAmpsArray[i] >= 0){
        accumulatedValues += dailyAmpsArray[i];
      }
    }

    return accumulatedValues;
}


/**
 * get average wattage from samples
 * @param int sampleCount
 * @return float
 */
float getLastXWattReadings(int sampleCount){

  float accumulatedValues = 0;

    int countedValues = 0;
  
    for (int i = secondsPerHour; i > 0; i--) {
      if(houryAmpsArray[i-1] >= 0){
        accumulatedValues += houryAmpsArray[i -1];
        countedValues ++;
      }
      if(countedValues >= sampleCount){
        break;
      }
    }

    return accumulatedValues / sampleCount;
}



/**
 * remove first item from array, shift all value to left and add new value to end.
 */
void cycleHourlyAmpsArray(float newValue) {

  // this means we the array if full so we can begin shifting
  if(houryAmpsArray[secondsPerHour -1] >= 0){
    for (int i = 0; i < secondsPerHour - 1; i++) {
      houryAmpsArray[i] = houryAmpsArray[i + 1];
    }
    houryAmpsArray[secondsPerHour - 1] = newValue;

  }else {
    // allow the array to initialize with real values
    houryAmpsArray[houryAmpsArrayCurrentIndex] = newValue;
    houryAmpsArrayCurrentIndex ++;
    if(houryAmpsArrayCurrentIndex > secondsPerHour) {
      houryAmpsArrayCurrentIndex = secondsPerHour - 1;
    }
  }
}



/**
 * remove first item from array, shift all value to left and add new value to end.
 */
void cycleDailyAmpsArray(float newValue) {

  // this means we the array if full so we can begin shifting
  if(dailyAmpsArray[hoursPerDay -1] >= 0){    
    for (int i = 0; i < hoursPerDay - 1; i++) {
      dailyAmpsArray[i] = dailyAmpsArray[i + 1];
    }
    dailyAmpsArray[hoursPerDay - 1] = newValue;

  }else {
    // allow the array to initialize with real values
    dailyAmpsArray[dailyAmpsArrayCurrentIndex] = newValue;
    dailyAmpsArrayCurrentIndex ++;
    if(dailyAmpsArrayCurrentIndex > hoursPerDay) {
      dailyAmpsArrayCurrentIndex = hoursPerDay - 1;
    }
  }
}