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