Skip to content

Instantly share code, notes, and snippets.

@whatnick
Last active October 1, 2022 20:10
  • Star 21 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save whatnick/13e08d40ce0f9a4c7373 to your computer and use it in GitHub Desktop.
ESP8266 Energy Monitor Real Power
/*
* This sketch sends ads1115 current sensor data via HTTP POST request to thingspeak server.
* It needs the following libraries to work (besides the esp8266 standard libraries supplied with the IDE):
*
* - https://github.com/adafruit/Adafruit_ADS1X15
*
* designed to run directly on esp8266-01 module, to where it can be uploaded using this marvelous piece of software:
*
* https://github.com/esp8266/Arduino
*
* 2015 Tisham Dhar
* licensed under GNU GPL
*/
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <Adafruit_ADS1015.h>
// replace with your channel's thingspeak API key,
String apiKey = "XXXXXXXXXXXXXXXX";
//WIFI credentials go here
const char* ssid = "xxxxxxxxxxxxx";
const char* password = "xxxxxxxxxxxxxxxx";
Adafruit_ADS1115 ads; /* Use this for the 16-bit version */
//Maximum value of ADS
#define ADC_COUNTS 32768
#define PHASECAL 1.7
#define VCAL 0.6
#define ICAL 0.003
const char* server = "api.thingspeak.com";
WiFiClient client;
double filteredI;
double lastFilteredV,filteredV; //Filtered_ is the raw analog value minus the DC offset
int sampleV; //sample_ holds the raw analog read value
int sampleI;
double offsetV; //Low-pass filter output
double offsetI; //Low-pass filter output
double realPower,
apparentPower,
powerFactor,
Vrms,
Irms;
double phaseShiftedV; //Holds the calibrated phase shifted voltage.
int startV; //Instantaneous voltage at start of sample window.
double sqV,sumV,sqI,sumI,instP,sumP; //sq = squared, sum = Sum, inst = instantaneous
boolean lastVCross, checkVCross; //Used to measure number of times threshold is crossed.
double squareRoot(double fg)
{
double n = fg / 2.0;
double lstX = 0.0;
while (n != lstX)
{
lstX = n;
n = (n + fg / n) / 2.0;
}
return n;
}
void calcVI(unsigned int crossings, unsigned int timeout)
{
unsigned int crossCount = 0; //Used to measure number of times threshold is crossed.
unsigned int numberOfSamples = 0; //This is now incremented
//-------------------------------------------------------------------------------------------------------------------------
// 1) Waits for the waveform to be close to 'zero' (mid-scale adc) part in sin curve.
//-------------------------------------------------------------------------------------------------------------------------
boolean st=false; //an indicator to exit the while loop
unsigned long start = millis(); //millis()-start makes sure it doesnt get stuck in the loop if there is an error.
while(st==false) //the while loop...
{
startV = ads.readADC_Differential_2_3(); //using the voltage waveform
if ((abs(startV) < (ADC_COUNTS*0.55)) && (abs(startV) > (ADC_COUNTS*0.45))) st=true; //check its within range
if ((millis()-start)>timeout) st = true;
}
//-------------------------------------------------------------------------------------------------------------------------
// 2) Main measurement loop
//-------------------------------------------------------------------------------------------------------------------------
start = millis();
while ((crossCount < crossings) && ((millis()-start)<timeout))
{
numberOfSamples++; //Count number of times looped.
lastFilteredV = filteredV; //Used for delay/phase compensation
//-----------------------------------------------------------------------------
// A) Read in raw voltage and current samples
//-----------------------------------------------------------------------------
sampleV = ads.readADC_Differential_2_3(); //Read in raw voltage signal
sampleI = ads.readADC_Differential_0_1(); //Read in raw current signal
//-----------------------------------------------------------------------------
// B) Apply digital low pass filters to extract the 2.5 V or 1.65 V dc offset,
// then subtract this - signal is now centred on 0 counts.
//-----------------------------------------------------------------------------
offsetV = offsetV + ((sampleV-offsetV)/1024);
filteredV = sampleV - offsetV;
offsetI = offsetI + ((sampleI-offsetI)/1024);
filteredI = sampleI - offsetI;
//-----------------------------------------------------------------------------
// C) Root-mean-square method voltage
//-----------------------------------------------------------------------------
sqV= filteredV * filteredV; //1) square voltage values
sumV += sqV; //2) sum
//-----------------------------------------------------------------------------
// D) Root-mean-square method current
//-----------------------------------------------------------------------------
sqI = filteredI * filteredI; //1) square current values
sumI += sqI; //2) sum
//-----------------------------------------------------------------------------
// E) Phase calibration
//-----------------------------------------------------------------------------
phaseShiftedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV);
//-----------------------------------------------------------------------------
// F) Instantaneous power calc
//-----------------------------------------------------------------------------
instP = phaseShiftedV * filteredI; //Instantaneous Power
sumP +=instP; //Sum
//-----------------------------------------------------------------------------
// G) Find the number of times the voltage has crossed the initial voltage
// - every 2 crosses we will have sampled 1 wavelength
// - so this method allows us to sample an integer number of half wavelengths which increases accuracy
//-----------------------------------------------------------------------------
lastVCross = checkVCross;
if (sampleV > startV) checkVCross = true;
else checkVCross = false;
if (numberOfSamples==1) lastVCross = checkVCross;
if (lastVCross != checkVCross) crossCount++;
}
//-------------------------------------------------------------------------------------------------------------------------
// 3) Post loop calculations
//-------------------------------------------------------------------------------------------------------------------------
//Calculation of the root of the mean of the voltage and current squared (rms)
//Calibration coefficients applied.
float multiplier = 0.125F; /* ADS1115 @ +/- 4.096V gain (16-bit results) */
double V_RATIO = VCAL * multiplier;
Vrms = V_RATIO * squareRoot(sumV / numberOfSamples);
double I_RATIO = ICAL * multiplier;
Irms = I_RATIO * squareRoot(sumI / numberOfSamples);
//Calculation power values
realPower = V_RATIO * I_RATIO * sumP / numberOfSamples;
apparentPower = Vrms * Irms;
powerFactor=realPower / apparentPower;
//Reset accumulators
sumV = 0;
sumI = 0;
sumP = 0;
//--------------------------------------------------------------------------------------
}
double calcIrms(unsigned int Number_of_Samples)
{
/* Be sure to update this value based on the IC and the gain settings! */
float multiplier = 0.125F; /* ADS1115 @ +/- 4.096V gain (16-bit results) */
for (unsigned int n = 0; n < Number_of_Samples; n++)
{
sampleI = ads.readADC_Differential_0_1();
// Digital low pass filter extracts the 2.5 V or 1.65 V dc offset,
// then subtract this - signal is now centered on 0 counts.
offsetI = (offsetI + (sampleI-offsetI)/1024);
filteredI = sampleI - offsetI;
//filteredI = sampleI * multiplier;
// Root-mean-square method current
// 1) square current values
sqI = filteredI * filteredI;
// 2) sum
sumI += sqI;
}
Irms = squareRoot(sumI / Number_of_Samples)*multiplier;
//Reset accumulators
sumI = 0;
//--------------------------------------------------------------------------------------
return Irms;
}
void setup() {
Serial.begin(115200);
delay(10);
// We start by connecting to a WiFi network
//Serial.println();
//Serial.println();
//Serial.print("Connecting to ");
//Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
//Serial.print(".");
}
//Serial.println("");
//Serial.println("WiFi connected");
//Serial.println("IP address: ");
//Serial.println(WiFi.localIP());
ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 0.125mV
ads.begin();
}
void loop() {
//Serial.print("Differential: "); Serial.print(results); Serial.print("("); Serial.print(trans_volt); Serial.println("mV)");
//double current = calcIrms(2048);
//Serial.print("Just Current:");
//Serial.println(current);
calcVI(20,2000);
//Serial.print("Real Power:");
//Serial.println(realPower);
//Serial.print("Irms:");
//Serial.println(Irms);
//Serial.print("Vrms:");
//Serial.println(Vrms);
if (client.connect(server,80)) { // "184.106.153.149" or api.thingspeak.com
String postStr = apiKey;
postStr +="&field1=";
postStr += String(realPower);
postStr +="&field2=";
postStr += String(Vrms);
postStr +="&field3=";
postStr += String(Irms);
postStr +="&field4=";
postStr += String(powerFactor);
postStr += "\r\n\r\n";
client.print("POST /update HTTP/1.1\n");
client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");
client.print("X-THINGSPEAKAPIKEY: "+apiKey+"\n");
client.print("Content-Type: application/x-www-form-urlencoded\n");
client.print("Content-Length: ");
client.print(postStr.length());
client.print("\n\n");
client.print(postStr);
}
client.stop();
//Serial.println("Waiting...");
// thingspeak needs minimum 15 sec delay between updates
delay(20000);
}
@Andsbf
Copy link

Andsbf commented Jan 19, 2018

Hi mate, question, don't you need Wire.begin(0,2) for a ESP-01?
Thanks in advance

@antonmi97
Copy link

Why do you use the double squareRoot(double fg) function instead of sqrt() from math.h? I've tested both on my nodeMCU and your function is way slower.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment