Last active
October 1, 2022 20:10
-
-
Save whatnick/13e08d40ce0f9a4c7373 to your computer and use it in GitHub Desktop.
ESP8266 Energy Monitor Real Power
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} |
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
Hi mate, question, don't you need
Wire.begin(0,2)
for a ESP-01?Thanks in advance