Skip to main content

Home Power Consumption Monitoring

For this project the goal is to know exactly how much current is being used for the entire property. To collect this data I placed 2 SCT013 non-invasive split core current transformers around the phase 1 and 2 wires in the main electrical panel. Using some simple math this can convert the small voltage in the transformers into an accurate current reading. 

current-sendor.png

I used aA clamp meter was used to confirm the calculated values were valid for a wide range of current (1 amp up to 60)

act1300.jpg 20240225_112755.jpg

And of course the code to make it all happen:

#include <SPI.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_ADS1X15.Adafruit_GFX.h>
#include <LiquidCrystal_I2C.Adafruit_SSD1306.h>
#include <Adafruit_ADS1X15.h>
#include <EEPROM.h>
#include "splashscreenbitmap.h" // just make it look fun on bootup

int activeConnection = 1;

// silonetwork 1
const String ssid1 = "xxxx"xxx";
const String password1 = "xxxxx"xxxxxxx";

// hansennetwork 2
const String ssid2 = "xxxx"xxx";
const String password2 = "xxxx"xxxxx";

const String heartbeatUrl = "https://xxx.xxx.ca/com/silopower/heartbeat.php";
const String currentUrl = "https://xxx.xxx.ca/com/silopower/set_house_current.set_hydro_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#define prevWattagePerHourTextSCREEN_WIDTH =128 ""// OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define LOGO_HEIGHT 128
#define LOGO_WIDTH 64

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET - 1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, & Wire, OLED_RESET);

StringAdafruit_ADS1115 prevWattageAverageTextads;

=long "";
String prevConnectivityText1 = "";
String prevConnectivityText2 = "";

const int potPin = 36;
const int buttonPin = 15;
int buttonStatesecondsOfHour = 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 voltagepressure update to the server
const int samplesPerReading = 6012 * serverSendInterval; // everyreading xcurrent minutestakes send5 aseconds sampleso to60 the/ server

float averagePotValue5 = 0;
float potValue = 0;
float accumulatedPotValues = 0;12 

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;

HTTPClientfloat http;basePressureVoltage Adafruit_ADS1115= ads;0.46;
LiquidCrystal_I2Cfloat lcd_i2c(0x27,totalledAveragePressure 16,= 2);0;
float averagePressure = 0;

const float FACTOR = 20; //20A/1V from the CT9;

const floatint multipliersecondsPerHour = 0.0000472;3600; // increateset soto bring60 downfor readdebugging

currentHTTPClient http;

void setup() {
  
  Serial.begin(115200);


  //if this(!display.begin(SSD1306_SWITCHCAPVCC, needs0x3C)) to{
    matchSerial.println(F("SSD1306 theallocation value in the serial monitor

  //Init EEPROM
  EEPROM.begin(EEPROM_SIZE);

  pinMode(buttonPin, INPUT)failed"));
    for (int;;); i// =Don't 0;proceed, iloop <forever
  secondsPerHour;}

  i++if (!ads.begin()) {
    houryAmpsArray[i]Serial.println("Failed =to -1;initialize ADS.");
    while (1);
  }

 for// (intads.setGain(GAIN_FOUR);
  
  idisplay.clearDisplay();
  =display.drawBitmap(0, 0;0, iepd_bitmap_annie, <128, hoursPerDay;64, i++1);
  display.display() {
    dailyAmpsArray[i] = -1;
  };
  delay(2000)3000);

  EEPROM.begin(EEPROM_SIZE);

  activeConnection = EEPROM.read(eepromActiveConnection);

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

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

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

   if (activeConnection > 2) {
    activeConnection = 2;
  }

  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(if (!connectToWiFi()) {
    delay(2000);
    WiFi.disconnect();
    delay(1000);
    if(if (activeConnection == 1) {
      activeConnection = 2;
    } else {
      activeConnection = 1;
    }
    connectToWiFi();
  }

  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.")delay(2000); while// (1);Pause }for ads.setGain(GAIN_FOUR);2 lcd_i2c.init();
  lcd_i2c.backlight();seconds

}

void loop() {

  secondsOfHour +secondsOfHour++;

  // after 24 hours reset this integer
  if(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 ampsPerHourwatts = getAverageAmps(houryAmpsArray, secondsPerHour);

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

  float wattsPerHour = ampsPerHour * ACDCVoltage;

  float ampHoursPerDay = getAverageAmps(dailyAmpsArray, hoursPerDay);

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

  float dailyAmpHours = getDailyAmpHours(display.clearDisplay();
  floatdisplay.setTextSize(2); 
  wattagePerDaydisplay.setTextColor(WHITE);
  =  dailyAmpHours * ACDCVoltage;
   
  
  String wattsAverageString = String(wattsAverage,display.setCursor(0, 0);
  wattsAverageString.trim(display.print("W:");
  String wattsPerHourString = String(wattsPerHour,display.println(watts, 0);
  wattsPerHourString.trim(display.print("A:");
  String wattsPerDayString = String(wattagePerDay,display.println(amps, 0);
  wattsPerDayString.trim(display.setTextSize(1); 
  display.println("internet:");
  String wattagePerHourText = wattsPerHourString + "WH " + wattsPerDayString + "DWH "display.println(wl_status_to_string(WiFi.status()));
  String wattageAverageText =  wattsAverageString + "W"display.display();

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

  averageWattage = (accumulatedAmps / loopCount) * ACDCVoltage;

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

    accumulatedAmpsloopCount = 0;
    loopCounttotalledAveragePressure = 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 recordedAverageWattagerecordedWatts = String(averageWattage,watts, 0)1);
  
    recordedAverageWattage.recordedWatts.trim();

    loopCount = 0;

    http.begin(currentUrl + recordedAverageWattage)recordedWatts);
    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(if (activeConnection == 1) {
    activeSsid = ssid1;
    activePassword=activePassword = password1;
  } else if(if (activeConnection == 2) {
    activeSsid = ssid2;
    activePassword=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.print("Connection to ");
    Serial.print(activeSsid);
    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;sensor1Reading;
  float current;sensor2Reading;
  float amps1 = 0;
  float amps2 = 0;
    
  float combinedReading;
  float sum = 0;
  long time_check = millis();
  int counter = 0;

  while (millis() - time_check < 1000)5000) {

    combinedReadingsensor1Reading = ads.readADC_Differential_0_1();
    combinedReadingsensor2Reading += ads.readADC_Differential_2_3();

    // ac current flows in 2 directions so grab the flow in each direction
    if(sensor1Reading < 0) {
      sensor1Reading = combinedReadingsensor1Reading * FACTOR-1;
    +}
    
    (combinedReadingif(sensor2Reading < 0) {
      sensor2Reading = sensor2Reading * 0.05);-1;
    sum}

    if(sensor1Reading < 2) {
      sensor1Reading = 0;
    }
    
    if(sensor2Reading < 2) {
      sensor2Reading = 0;
    }
   
    amps1 += sq(current);sensor1Reading; 
    amps2 += sensor2Reading;
        
    combinedReading = amps1 + amps2;

   counter = counter + 1;
  }
 
  currentfloat reading = sqrt(sum(combinedReading / counter);

  returnfloat comindedAmps1 = (current)amps1 / counter);
  }float comindedAmps2 = (amps2 /** counter);

  float divider = .155;
  comindedAmps1 = comindedAmps1 * readdivider the+ accumulated amps divided by readings(comindedAmps1 */ float0.015);
  getAverageAmps(float array[], int size){

  float accumulatedValuescomindedAmps2 = 0;comindedAmps2 int* ampCountsdivider = 0;
  
    for+ (intcomindedAmps2 i* = 0; i < size - 1; i++) {
      if(array[i] >= 0){
        ampCounts ++0.015);
        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[icomindedAmps1 + 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;
    }
  }comindedAmps2;

}