IDE version:2.3.2
Microcontroller: ESP32-S [NODEMCU/WROOM-32]
An inexplicable issue has arose with my Emon Power Monitor program.
The Emon data displays for voltage and current appear normally on the Serial Monitor as long as the 'Wifi.begin' statement is not added to the sketch.
Before adding WiFi code:
Connected to WiFi network with IP Address: 0.0.0.0
Attempting to connect to local WPA network...
WiFi Status: 254
ESP Board MAC Address: 00:00:00:00:00:00
Phase 1 Voltage: 749.60 Phase 1 Current: 4.72
Phase 2 Voltage: 720.99 Phase 2 Current: 9.75
Phase 1 Voltage: 135.80 Phase 1 Current: 0.62
Phase 2 Voltage: 131.09 Phase 2 Current: 0.95
Phase 1 Voltage: 121.57 Phase 1 Current: 0.41
Phase 2 Voltage: 120.97 Phase 2 Current: 0.69
Phase 1 Voltage: 120.89 Phase 1 Current: 0.43
Phase 2 Voltage: 120.88 Phase 2 Current: 0.66
After adding WiFi code:
Connected to WiFi network with IP Address: 192.168.1.148
Attempting to connect to local WPA network...
WiFi Status: 3
ESP Board MAC Address: C0:49:EF:D1:01:E4
Phase 1 Voltage: 422.39 Phase 1 Current: 2.58
Phase 2 Voltage: 403.45 Phase 2 Current: 5.27
Phase 1 Voltage: 0.05 Phase 1 Current: 0.45
Phase 2 Voltage: 0.04 Phase 2 Current: 0.68
Phase 1 Voltage: 0.00 Phase 1 Current: 0.44
Phase 2 Voltage: 0.00 Phase 2 Current: 0.70
Phase 1 Voltage: 0.00 Phase 1 Current: 0.44
Phase 2 Voltage: 0.00 Phase 2 Current: 0.67
Phase 1 Voltage: 0.00 Phase 1 Current: 0.44
Phase 2 Voltage: 0.00 Phase 2 Current: 0.70
I have isolated it down to the Wifi.begin statement:
WiFi.begin(wifissid, wifipass);
I do not see an inter-connection between Wifi.begin and Emon but something is happening that is interfering with Emon. You can see with the Wifi code installed
the first Serial Monitor output has a high voltage value. This is typical for both the voltage and current as the processor first starts. But then ONLY the voltage values drop off to zero while the current data continues to display normally.
Is this possibly a memory shortage issue? I suppose I could comment out the second Emon instance and see if anything changes.
Is there other wifi code available that would accomplish the same server/client goal that might not create this issue? How I read the ESP data doesn't matter to me as long as it available through a browser request.
Stripped down main sketch:
```cpp
#include "EmonLib.h" // Include Emon Library
#include "hashtag_includes.h"
#include "constants.h"
EnergyMonitor emon1; // Phase 1
EnergyMonitor emon2; // Phase 2
void setup() {
startup_Millis = millis();
pinMode(reset_switch_input, INPUT_PULLUP); // reset_switch_input = GIOP23;
pinMode(ONBOARD_LED, OUTPUT);
// delay(300000); // 5 minute startup delay to allow routers to initialize after power outage
Serial.begin(115200);
while (WiFi.status() != WL_CONNECTED) {
Initial_Connect_Attempts++;
Serial.println(F("Attempting to Connect To Local Network: "));
Serial.println(wifissid);
WiFi.begin(wifissid, wifipass);
delay(4000);
if (Initial_Connect_Attempts == 5) {
Serial.println(F("5 LAN connect attempts unsuccessful; going to connectToWifi()"));
// connectToWiFi();
}
Serial.print(F("."));
}
Serial.println(F(""));
Serial.print(F("Connected to WiFi network with IP Address: "));
Serial.println(WiFi.localIP());
Serial.println(F("Attempting to connect to local WPA network..."));
server.begin();
Serial.print(F("WiFi Status: "));
Serial.println(WiFi.status());
Serial.println();
Serial.print(F("ESP Board MAC Address: "));
Serial.println(WiFi.macAddress());
analogSetPinAttenuation(V1, ADC_0db);
analogSetPinAttenuation(V2, ADC_0db);
analogSetPinAttenuation(I1, ADC_0db);
analogSetPinAttenuation(I2, ADC_0db);
// Split-Phase 1:
emon1.voltage(V1, CV1, 1.732); // Voltage: input pin, calibration, phase_shift; Mr. Hall recomends between 0 and 1
emon1.current(I1, CI1); // Current: input pin, calibration.
// Split-Phase 2:
emon2.voltage(V2, CV2, 1.732); // Voltage: input pin, calibration, phase_shift
emon2.current(I2, CI2); // Current: input pin, calibration.
}
void loop() {
// Phase 1:
emon1.calcVI(120, 2000); // Calculate all. No.of half wavelengths (crossings), time-out
// emon1.serialprint(); // Print out all variables (realpower, apparent power, Vrms, Irms, power factor)
ph1_realPower = emon1.realPower; //extract Real Power into variable
ph1_apparentPower = emon1.apparentPower; //extract Apparent Power into variable
ph1_powerFactor = emon1.powerFactor; //extract Power Factor into Variable
ph1_supplyVoltage = emon1.Vrms; //extract Vrms into Variable
ph1_Irms = emon1.Irms;
ph1_supVolt = ph1_supplyVoltage;
Serial.print("Phase 1 Voltage: ");
Serial.print(ph1_supplyVoltage);
Serial.print(" Phase 1 Current: ");
Serial.println(ph1_Irms);
// Phase 2:
emon2.calcVI(120, 2000); // Calculate all. No.of half wavelengths (crossings), time-out
// emon2.serialprint(); // Print out all variables (realpower, apparent power, Vrms, Irms, power factor)
ph2_realPower = emon2.realPower;
ph2_apparentPower = emon2.apparentPower;
ph2_powerFactor = emon2.powerFactor;
ph2_supplyVoltage = emon2.Vrms;
ph2_Irms = emon2.Irms; // extract Irms into Variable
ph2_supVolt = ph2_supplyVoltage;
Serial.print("Phase 2 Voltage: ");
Serial.print(ph2_supplyVoltage);
Serial.print(" Phase 2 Current: ");
Serial.println(ph2_Irms);
}
Support files:
// hashtag_includes.h
#include <WiFi.h>
#include <math.h>
#include <esp_sntp.h>
#include <StackString.hpp> // save memory using this function: character arrays
using namespace Stack;
#define ONBOARD_LED 2
#define ESP32
// Pin configuration
#define V1 15
#define V2 4
#define I1 34
#define I2 33
// Calibration settings (allways start with 1000)
#define CV1 1093 // 1086; original program vaue: 1000
#define CV2 1044 // 1039
#define CI1 67 // 67
#define CI2 142 // 142
int status = WL_IDLE_STATUS; // the Wifi radio's status
WiFiClient client;
WiFiServer server(80); // added for testing 2/4
char serverName[19] = "api.pushingbox.com"; // [ api.pushingbox.com ]
int Initial_Connect_Attempts = 0;
int Try_Count = 0;
char wifissid[18] = "Homestead-LAN_EXT"; // "Homestead-LAN_EXT" for in house testing
char wifipass[13] = "xxxxxxxx"; //
char INO_Version[3] = "2A";
char MAC_Address[18] = "C0-49-EF-D1-01-E4";
char DEVID1[] = ""; // high voltage level alarm
char DEVID2[] = ""; // low voltage level alarm
char DEVID3[] = ""; // return of Entergy line power
char DEVID4[] = ""; // midnite counter reset
char entergy_Power[9] = "Normal";
bool entergyFlag = false;
bool entergy_Restored = false;
bool entergyEmail = false;
char high_voltAlm[4] = "OFF";
char low_voltAlm[4] = "OFF";
char systemAlm[4] = "OFF";
unsigned long UNO_run_cycle_start = 0;
;
unsigned long UNO_run_cycle_stop = 0;
;
int UNO_cycle_time = 0;
bool system_alm = false;
bool sysAlmFlag = false;
bool hi_voltageLevel = false;
bool hi_voltFlag = false;
bool hiVoltRptFlag = false;
bool low_voltageLevel = false;
bool low_voltFlag = false;
bool lowVoltRptFlag = false;
unsigned long startup_Millis = millis(); // static uint32_t last_Connection_Attempt_At_Ms = millis();
unsigned long current_Millis = 0;
int wait_interval = 300000; // 5 minutes of no wifi connection
int hour = 3600000; // 3,600,000 millis per hr
int minute = 60000;
// int min = 60; // 60,000 millis per minute
// int hr = 60;
int time_month = 0;
int time_day = 0;
int time_hour = 0;
int time_min = 0;
int time_sec = 0;
int timeStamp_hr = 0;
int timeStamp_min = 0;
int timeStamp_sec = 0;
int hour_trigger = 23; // hour to reset pump counters to zero
int min_trigger = 59; // minute (23 : 59 : 59)
int sec_trigger = 59; // second HR MIN SEC
int min_trigger1 = 58;
int sec_trigger1 = 1;
unsigned long request_time_Millis = 30000; // 1 hour = 3,600,000
unsigned long timeHack_Millis = 0;
unsigned long previous_LED_Millis = 0;
const int LED_interval = 1000; // Run LED Blink rate
const int LED_alm_interval= 300; // System Alarm LED blink rate
// unsigned long system_alm_LED_Millis = 0;
int reset_switch;
const int reset_switch_input = 23;
const int entergy_V = 13; // future point for monitoring Entergy power return after outage
int entergy_Volts = 0;
float total_Voltage = 0; // phase 1 + phase 2 [not true phases but same phase tapped off different location on transformer winding]
float total_Current = 0;
float max_total_I;
float min_total_I;
float ph1_realPower = 0;
float ph1_apparentPower = 0;
float ph1_powerFactor = 0;
float ph1_supplyVoltage = 0;
float ph1_Irms = 0;
float ph1_supVolt = 0;
float ph1_RP = 0;
float ph1_AP = 0;
float ph1_PF = 0;
float ph1_I = 0;
float ph2_realPower = 0;
float ph2_apparentPower = 0;
float ph2_powerFactor = 0;
float ph2_supplyVoltage = 0;
float ph2_Irms = 0;
float ph2_supVolt = 0;
float ph2_RP = 0;
float ph2_AP = 0;
float ph2_PF = 0;
float ph2_I = 0;
float ph1_Daily_Volt_Hi = 0;
float ph1_Daily_Volt_Lo = 0;
float ph2_Daily_Volt_Hi = 0;
float ph2_Daily_Volt_Lo = 0;
float ph1_Daily_I_Hi = 0;
float ph1_Daily_I_Lo = 0;
float ph2_Daily_I_Hi = 0;
float ph2_Daily_I_Lo = 0;
int ledState = LOW;
int Sys_Alm_ledState = LOW;
static bool restart_Flag = false;
bool midnite_reset = false;
boolean DEBUG = true; // Debug mode !!!!
boolean currentLineIsBlank = false; //added 10/31/22 debugging
boolean lastConnected = false;
/*
Emon.h - Library for openenergymonitor
Created by Trystan Lea, April 27 2010
GNU GPL
modified to use up to 12 bits ADC resolution (ex. Arduino Due)
by boredman@boredomprojects.net 26.12.2013
Low Pass filter for offset removal replaces HP filter 1/1/2015 - RW
Updated for ESP32 (2020)
*/
#ifndef EmonLib_h
#define EmonLib_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
// define theoretical vref calibration constant for use in readvcc()
// 1100mV*1024 ADC steps http://openenergymonitor.org/emon/node/1186
// override in your code with value for your specific AVR chip
// determined by procedure described under "Calibrating the internal reference voltage" at
// http://openenergymonitor.org/emon/buildingblocks/calibration
#ifndef READVCC_CALIBRATION_CONST
#define READVCC_CALIBRATION_CONST 1126400L
#endif
// to enable 12-bit ADC resolution on Arduino Due,
// include the following line in main sketch inside setup() function:
// analogReadResolution(ADC_BITS);
// otherwise will default to 10 bits, as in regular Arduino-based boards.
#if defined(__arm__)
#define ADC_BITS 12
#else
#define ADC_BITS 10
#endif
#if defined(ESP32)
#define ADC_BITS 12
#endif
#define ADC_COUNTS (1<<ADC_BITS)
class EnergyMonitor
{
public:
void voltage(unsigned int _inPinV, double _VCAL, double _PHASECAL);
void current(unsigned int _inPinI, double _ICAL);
void voltageTX(double _VCAL, double _PHASECAL);
void currentTX(unsigned int _channel, double _ICAL);
void calcVI(unsigned int crossings, unsigned int timeout);
double calcIrms(unsigned int NUMBER_OF_SAMPLES);
void serialprint();
long readVcc();
//Useful value variables
double realPower,
apparentPower,
powerFactor,
Vrms,
Irms;
private:
//Set Voltage and current input pins
unsigned int inPinV;
unsigned int inPinI;
//Calibration coefficients
//These need to be set in order to obtain accurate results
double VCAL;
double ICAL;
double PHASECAL;
//--------------------------------------------------------------------------------------
// Variable declaration for emon_calc procedure
//--------------------------------------------------------------------------------------
int sampleV; //sample_ holds the raw analog read value
int sampleI;
double lastFilteredV,filteredV; //Filtered_ is the raw analog value minus the DC offset
double filteredI;
double offsetV; //Low-pass filter output
double offsetI; //Low-pass filter output
double phaseShiftedV; //Holds the calibrated phase shifted voltage.
double sqV,sumV,sqI,sumI,instP,sumP; //sq = squared, sum = Sum, inst = instantaneous
int startV; //Instantaneous voltage at start of sample window.
boolean lastVCross, checkVCross; //Used to measure number of times threshold is crossed.
};
#endif
/*
Emon.cpp - Library for openenergymonitor
Created by Trystan Lea, April 27 2010
GNU GPL
modified to use up to 12 bits ADC resolution (ex. Arduino Due)
by boredman@boredomprojects.net 26.12.2013
Low Pass filter for offset removal replaces HP filter 1/1/2015 - RW
Proboscide99 10/08/2016 - Added ADMUX settings for ATmega1284 e 1284P (644 / 644P also, but not tested) in readVcc function
Updated for ESP32 (2020)
*/
#include "EmonLib.h"
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
//--------------------------------------------------------------------------------------
// Sets the pins to be used for voltage and current sensors
//--------------------------------------------------------------------------------------
void EnergyMonitor::voltage(unsigned int _inPinV, double _VCAL, double _PHASECAL)
{
inPinV = _inPinV;
VCAL = _VCAL;
PHASECAL = _PHASECAL;
offsetV = ADC_COUNTS>>1;
}
void EnergyMonitor::current(unsigned int _inPinI, double _ICAL)
{
inPinI = _inPinI;
ICAL = _ICAL;
offsetI = ADC_COUNTS>>1;
}
//--------------------------------------------------------------------------------------
// Sets the pins to be used for voltage and current sensors based on emontx pin map
//--------------------------------------------------------------------------------------
void EnergyMonitor::voltageTX(double _VCAL, double _PHASECAL)
{
inPinV = 2;
VCAL = _VCAL;
PHASECAL = _PHASECAL;
offsetV = ADC_COUNTS>>1;
}
void EnergyMonitor::currentTX(unsigned int _channel, double _ICAL)
{
if (_channel == 1) inPinI = 3;
if (_channel == 2) inPinI = 0;
if (_channel == 3) inPinI = 1;
ICAL = _ICAL;
offsetI = ADC_COUNTS>>1;
}
//--------------------------------------------------------------------------------------
// emon_calc procedure
// Calculates realPower,apparentPower,powerFactor,Vrms,Irms,kWh increment
// From a sample window of the mains AC voltage and current.
// The Sample window length is defined by the number of half wavelengths or crossings we choose to measure.
//--------------------------------------------------------------------------------------
void EnergyMonitor::calcVI(unsigned int crossings, unsigned int timeout)
{
#if defined emonTxV3
int SupplyVoltage=3300;
#else
int SupplyVoltage = readVcc();
#endif
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.
//-------------------------------------------------------------------------------------------------------------------------
unsigned long start = millis(); //millis()-start makes sure it doesnt get stuck in the loop if there is an error.
while(1) //the while loop...
{
startV = analogRead(inPinV); //using the voltage waveform
if ((startV < (ADC_COUNTS*0.55)) && (startV > (ADC_COUNTS*0.45))) break; //check its within range
if ((millis()-start)>timeout) break;
}
//-------------------------------------------------------------------------------------------------------------------------
// 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 = analogRead(inPinV); //Read in raw voltage signal
sampleI = analogRead(inPinI); //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)/ADC_COUNTS);
filteredV = sampleV - offsetV;
offsetI = offsetI + ((sampleI-offsetI)/ADC_COUNTS);
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.
double V_RATIO = VCAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
Vrms = V_RATIO * sqrt(sumV / numberOfSamples);
double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
Irms = I_RATIO * sqrt(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 EnergyMonitor::calcIrms(unsigned int Number_of_Samples)
{
#if defined emonTxV3
int SupplyVoltage=3300;
#else
int SupplyVoltage = readVcc();
#endif
for (unsigned int n = 0; n < Number_of_Samples; n++)
{
sampleI = analogRead(inPinI);
// 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)/ADC_COUNTS);
filteredI = sampleI - offsetI;
// Root-mean-square method current
// 1) square current values
sqI = filteredI * filteredI;
// 2) sum
sumI += sqI;
}
double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
Irms = I_RATIO * sqrt(sumI / Number_of_Samples);
//Reset accumulators
sumI = 0;
//--------------------------------------------------------------------------------------
return Irms;
}
void EnergyMonitor::serialprint()
{
Serial.print(realPower);
Serial.print(' ');
Serial.print(apparentPower);
Serial.print(' ');
Serial.print(Vrms);
Serial.print(' ');
Serial.print(Irms);
Serial.print(' ');
Serial.print(powerFactor);
Serial.println(' ');
delay(100);
}
//thanks to http://hacking.majenko.co.uk/making-accurate-adc-readings-on-arduino
//and Jérôme who alerted us to http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
long EnergyMonitor::readVcc() {
long result;
//not used on emonTx V3 - as Vcc is always 3.3V - eliminates bandgap error and need for calibration http://harizanov.com/2013/09/thoughts-on-avr-adc-accuracy/
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined (__AVR_ATmega328P__)
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_AT90USB1286__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
ADCSRB &= ~_BV(MUX5); // Without this the function always returns -1 on the ATmega2560 http://openenergymonitor.org/emon/node/2253#comment-11432
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#endif
#if defined(__AVR__)
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = READVCC_CALIBRATION_CONST / result; //1100mV*1024 ADC steps http://openenergymonitor.org/emon/node/1186
return result;
#elif defined(__arm__)
return (3300); //Arduino Due
#else
return (3300); //Guess that other un-supported architectures will be running a 3.3V!
#endif
}