ESP32 T-CALL TTGO (SIM800L) and EmonLib - Spikes in the measurement

Hi, I have a problem with spikes in my measurement. I am using ESP32 T-CALL TTGO (SIM800L) and EmonLib. I am using standart CT Sensor (YHDC SCT-013).
a screenshot to the measurement:

I am attaching my code also (It send the data via mqtt):

/*
 * Project SM_Photon
 * Description:
 * Author:
 * Date:16.09.2021
 */



// Please select the corresponding model
#define SIM800L_IP5306_VERSION_20190610
#define TINY_GSM_MODEM_SIM800
#include <ArduinoJson.h>
#include <Wire.h>
#include <TinyGsmClient.h>

#include "EmonLib.h"

#include <vector>
#include <sstream>
#include <locale>
#include <iomanip>
#include <map>
#include <string>
#include <cstdio>
#include <algorithm>

// Set serial for debug console (to the Serial Monitor, default speed 115200)
#define SerialMon Serial
// Set serial for AT commands
#define SerialAT Serial1

#define TINY_GSM_DEBUG SerialMon

// Your GPRS credentials, if any
const char apn[] = "internet.vivacom.bg"; // APN (example: internet.vodafone.pt) use https://wiki.apnchanger.org
const char gprsUser[] = "";
const char gprsPass[] = "";

// SIM card PIN (leave empty, if not defined)
const char simPIN[]   = ""; 

// set GSM PIN, if any
#define GSM_PIN ""

#ifdef DUMP_AT_COMMANDS
  #include <StreamDebugger.h>
  StreamDebugger debugger(SerialAT, SerialMon);
  TinyGsm modem(debugger);
#else
  TinyGsm modem(SerialAT);
#endif

#include <PubSubClient.h>

TinyGsmClient client(modem);
PubSubClient mqtt(client);

// TTGO T-Call pins
#define MODEM_RST            5
#define MODEM_PWKEY          4
#define MODEM_POWER_ON       23
#define MODEM_TX             27
#define MODEM_RX             26
#define I2C_SDA              21
#define I2C_SCL              22


uint32_t lastReconnectAttempt = 0;

// I2C for SIM800 (to keep it running when powered from battery)
TwoWire I2CPower = TwoWire(0);

TwoWire I2CBME = TwoWire(1);


#define IP5306_ADDR          0x75
#define IP5306_REG_SYS_CTL0  0x00

bool setPowerBoostKeepOn(int en){
  I2CPower.beginTransmission(IP5306_ADDR);
  I2CPower.write(IP5306_REG_SYS_CTL0);
  if (en) {
    I2CPower.write(0x37); // Set bit1: 1 enable 0 disable boost keep on
  } else {
    I2CPower.write(0x35); // 0x37 is default reg value
  }
  return I2CPower.endTransmission() == 0;
}

// MQTT details
const char* broker = "159.89.103.242";                    // Public IP address or domain name
const char* mqttUsername = "REPLACE_WITH_YOUR_MQTT_USER";  // MQTT username
const char* mqttPassword = "REPLACE_WITH_YOUR_MQTT_PASS";  // MQTT password
char auth[] = "ag1p2OnHsvHusY3-ZsOYAjuOgTsMXyxS";



// variable for storing the potentiometer value
int potValue = 0;


int ver = 3;
EnergyMonitor emon1;  
EnergyMonitor emon2;
EnergyMonitor emon3; 
float blynkPublish;
float accumulatePow;
int nextPeriod;
float average;
long interval = 2000;
long previousMillis = 0; 

float hourBegining;
float hourConsumption;
float dayBegining;
float dayConsumption;
float monthBegining;
float monthConsumption;

float currPriceH;
float currPriceD;
float currPriceM;
float currPrice;

long currentMillisSend = 0;
long previousMillisSend = 0;
long intervalSend = 5000;
long intervalSendBlynk = 60000;
long previousMillisSendBlynk = 0;
int reportFreq;

int currMin;
int currHour;
int currDay;
int currMonth;
int blynkGridButton;
int16_t currYear;
int16_t prevYear;
uint8_t prevMin;
uint8_t prevDay;
uint8_t prevHour;
uint8_t prevMonth;

#define NUMSAMPLES 5
float tenSec[NUMSAMPLES];


float sixty[60];
float fifteen[15];

float avTenSec = 0;

std::map<String, float>powerMap; 



int trigger = 0;
char data[80]; 

double Irms1; 
double Irms2;
double Irms3; 

int j;

String stampRecieved;

 StaticJsonDocument<200>parser;
 

const char* deviceID = "sm-0002";


const char* authStr = "/auth";

String blynkAuth = String(deviceID)+String(authStr);

const char* blynkAuthChar = blynkAuth.c_str();

const char* timestampStr = "/timestamp";

String timestampReceived = String(deviceID)+String(timestampStr);

const char* timestampRe = timestampReceived.c_str();

const char* initChr = "init/";

String initStr = String(initChr)+String(deviceID);

const char* initial = initStr.c_str();

const char* dashCh = "dash/";
const char* currP = "/currpower/currpower";
String pingDev = String(dashCh)+String(deviceID)+String(currP);
const char* ping = pingDev.c_str();


const char* dataChar = "data/";
const char* fifteenToChar = "/fifteen";
String dataSend = String(dataChar)+String(deviceID)+String(fifteenToChar);
const char* sendFifteen = dataSend.c_str();

const char* er = "error/check/";
String error = String(er)+String(deviceID);
const char* errorSendDash = error.c_str();

const char* blh = "blynkHourConsumption/";
String blynkh = String(blh)+String(deviceID);
const char* blynkHourCons = blynkh.c_str();

const char* bld = "blynkDayConsumption/";
String blynkd = String(bld)+String(deviceID);
const char* blynkDayCons = blynkd.c_str();

const char* blM = "blynkMonthConsumption/";
String blynkm = String(blM)+String(deviceID);
const char* blynkMonthCons = blynkm.c_str();

const char* blPh = "blynkHourPrice/";
String blynkPh = String(blPh)+String(deviceID);
const char* blynkPriceHour = blynkPh.c_str();

const char* blPd = "blynkDayPrice/";
String blynkPd = String(blPd)+String(deviceID);
const char* blynkPriceDay = blynkPd.c_str();

const char* blPm = "blynkMonthPrice/";
String blynkPm = String(blPm)+String(deviceID);
const char* blynkPriceMonth = blynkPm.c_str();

const char* blynkPrice = "blynkPrice/";
String blynkP = String(blynkPrice)+String(deviceID);
const char* blynkCurrPrice = blynkP.c_str();

const char* powtoBl = "blynkPower/";
String blynkPow = String(powtoBl)+String(deviceID);
const char* currPowerToBlynk = blynkPow.c_str();


String authToken; 

String subscribeTopic = "meter/ibexIn";


//BlynkTimer timer;

// recieve message
void mqttCallback(char* topic, byte* message, unsigned int len) {
    Serial.print("Message arrived on topic: ");
    Serial.print(topic);
    Serial.print(". Message: ");
    String p = "";
      for (int i=0;i<len;i++)
      {
        p += (char)message[i];        
      }
      
    Serial.println();

    if (String(topic) == subscribeTopic)
    {
      currPrice = p.toFloat();
      Serial.println(currPrice);
    }
    
    //test topic
    if (String(topic) == "RED")
    {
       
        mqtt.publish("size_plusOnemin", "ECHO ECHO");       
    }


    if (String(topic) == String(deviceID)+"/timestamp")
    {
        stampRecieved = String(p);
        DeserializationError error = deserializeJson(parser, stampRecieved);
        if (error) {
          Serial.print(F("deserializeJson() failed: "));
          Serial.println(error.f_str());
          return;
        }
        String strValue = parser["time"];;
        float powValue = parser["pow"];
        mqtt.publish("parserPow", (char*) strValue.c_str());
        std::map<String,float>::iterator it;
        it = powerMap.find(strValue);
        if (it != powerMap.end())
        {
            powerMap.erase (it);  
            trigger = 1;   
        } 
        else {
            //Particle.publish("empty","empty");
        }        
    }
}



boolean mqttConnect() {
  SerialMon.print("Connecting to ");
  SerialMon.print(broker);

  // Connect to MQTT Broker without username and password
  //boolean status = mqtt.connect("GsmClientN");

  // Or, if you want to authenticate MQTT:
  boolean status = mqtt.connect(deviceID);

  if (status == false) {
    SerialMon.println(" fail");
    ESP.restart();
    return false;
  }
  SerialMon.println(" success");
  mqtt.subscribe("meter/ibexIn");
  mqtt.subscribe(blynkAuthChar);
  mqtt.subscribe(timestampRe);
  mqtt.subscribe("RED");
  mqtt.publish(initial,deviceID);
  return mqtt.connected();
}


void setup() { 
  SerialMon.begin(115200);
  delay(10);
  

  // Keep power when running from battery
  bool isOk = setPowerBoostKeepOn(1);
  SerialMon.println(String("IP5306 KeepOn ") + (isOk ? "OK" : "FAIL"));

  // Set modem reset, enable, power pins
  
  pinMode(MODEM_PWKEY, OUTPUT);
  pinMode(MODEM_RST, OUTPUT);
  pinMode(MODEM_POWER_ON, OUTPUT);
  digitalWrite(MODEM_PWKEY, LOW);
  digitalWrite(MODEM_RST, HIGH);
  digitalWrite(MODEM_POWER_ON, HIGH);  
 
  SerialMon.println("Wait...");

  // Set GSM module baud rate and UART pins
  SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
  delay(6000);

  // Restart takes quite some time
  // To skip it, call init() instead of restart()
  SerialMon.println("Initializing modem...");
  modem.restart();
  // modem.init();

  String modemInfo = modem.getModemInfo();
  SerialMon.print("Modem Info: ");
  SerialMon.println(modemInfo);

  // Unlock your SIM card with a PIN if needed
  if ( GSM_PIN && modem.getSimStatus() != 3 ) {
    modem.simUnlock(GSM_PIN);
  }


  SerialMon.print("Connecting to APN: ");
  SerialMon.print(apn);
  if (!modem.gprsConnect(apn, gprsUser, gprsPass)) {
    SerialMon.println(" fail");
    ESP.restart();
  }
  else {
    SerialMon.println(" OK");
  }
  
  if (modem.isGprsConnected()) {
    SerialMon.println("GPRS connected");
  }

  
      // MQTT Broker setup
    mqtt.setServer(broker, 1883);
    mqtt.setCallback(mqttCallback);


    emon1.current(33, 90.9); //22 ohm 90.9
    emon2.current(32, 90.9);
    emon3.current(34, 90.9);
    analogReadResolution(ADC_BITS);
 
    if (!mqtt.connected()) {
    SerialMon.println("=== MQTT NOT CONNECTED ===");
    // Reconnect every 10 seconds
    uint32_t t = millis();
    if (t - lastReconnectAttempt > 10000L) {
      lastReconnectAttempt = t;
      if (mqttConnect()) {
        lastReconnectAttempt = 0;
      }
    }
    delay(100);
    return;
    }  
    timeConvert();
//    currHour = Time.hour(Time.now());
//    currDay = Time.day(Time.now());
//    currMonth = Time.month(Time.now());
    prevHour = currHour;
    prevDay = currDay;
    prevMonth = currMonth;
    powerMap.clear();
   
}


void blynkUpdate()
{
      
      char blynkPub[8];
      
      dtostrf(blynkPublish, 1, 2, blynkPub);
      mqtt.publish(ping,blynkPub);
      //Serial.println(blynkPublish);

      
}

 void measure()
{
    unsigned long currentMillis = millis();  
    
    if(currentMillis - previousMillis > interval)
    { 
        Irms1 = emon1.calcIrms(1480);
        Irms2 = emon2.calcIrms(1480);
        Irms3 = emon3.calcIrms(1480);
        double Irms = Irms1 + Irms2 + Irms3;
        double power = Irms*230;      
        tenSec[j] = power;         
        j++;        
            if (j > 4)
            {
                for (int k=0; k < 5; k++) 
                {      
                //if (client.isConnected())
                
           
                average += tenSec[k];           
                }
          
                average /= 5;
                //avTenSec = average/360; //Wh
                blynkPublish = average/1000; //kW  
                //accumulatePow = accumulatePow + avTenSec;                
                average = 0;
                j = 0;
            }
             

        previousMillis = currentMillis;
    }   
}


 
void timeConvert (){

String time = modem.getGSMDateTime(DATE_FULL);
int splitT = time.indexOf(",");
int splitY = time.indexOf("/");
String timeStamp = time.substring(splitT+1, time.length());
String timeStampY = time.substring(splitY, 0);
currYear = timeStampY.toInt();
String currHourStr = timeStamp.substring(0,2);
currHour = currHourStr.toInt();

int splitM = timeStamp.indexOf(":");
String timeStampM = timeStamp.substring(splitM+1, timeStamp.length());
String currMinString = timeStampM.substring(0,2);
currMin = currMinString.toInt();
String timeStampMonth = time.substring(splitY+1, time.length());
String currMonthString = timeStampMonth.substring(0,2);
currMonth = currMonthString.toInt();
int splitDay = timeStampMonth.indexOf("/");
String timeStampDay = timeStampMonth.substring(splitDay+1, timeStampMonth.length());
String currDayString = timeStampDay.substring(0,2);
currDay = currDayString.toInt();

 //delay(2000);

}



  String test(struct tm t)
  {

    t = {0};
    t.tm_year = (currYear+2000) - 1900;
    t.tm_mon = currMonth - 1;
    t.tm_mday = currDay;
    t.tm_hour = currHour;
    t.tm_min = currMin;
    t.tm_sec = 00;
    time_t timeSinceEpoch = mktime(&t);    
    int test = int(timeSinceEpoch);
    String timestamp = String(test);
    return timestamp;    
  } 

  struct timestampPower { 
     void myFunc()
    {
        for (std::map<String, float>::iterator it = powerMap.begin(); it != powerMap.end(); ++it) 
        {
            String timeSt = "\"timestamp\": " + String((*it).first);
            String powerSt = "\"power\": " + String((*it).second);
            String jObj = timeSt +','+ powerSt;
            String payload = "{ \"payload\": {" + jObj + "}}";
            payload.toCharArray(data, (payload.length() + 1));
            mqtt.publish(sendFifteen, data);  
        }
    }  

  void checkDb(){
  
    if(!powerMap.empty())
    {
    String mapKey = (--powerMap.end())->first;
    float mapValue = (--powerMap.end())->second;
    String timeSt = "\"timestamp\": " + mapKey;
    String powerSt = "\"power\": " + String(mapValue);
    String jObj = timeSt +','+ powerSt;
    String payload = "{ \"payload\": {" + jObj + "}}";
    payload.toCharArray(data, (payload.length() + 1));
    mqtt.publish(errorSendDash, data);
    }

}
}tp;



void loop() {
  
      timeConvert();
      if (!mqtt.connected()) {
        SerialMon.println("=== MQTT NOT CONNECTED ===");
        // Reconnect every 10 seconds
        uint32_t t = millis();
        if (t - lastReconnectAttempt > 10000L) {
          lastReconnectAttempt = t;
        if (mqttConnect()) {
          lastReconnectAttempt = 0;
        }
      }
        delay(100);
        return;
      }  
       
    unsigned long currentMillisSend = millis();  
  
    if(currentMillisSend - previousMillisSend > intervalSend)
    {           
      
        if (trigger == 1 && powerMap.size() > 0)
        {
     
            tp.checkDb();
   
            trigger = 0;
     
        }
        previousMillisSend = currentMillisSend;
    }   

    measure();  

    if(currentMillisSend - previousMillisSendBlynk > intervalSendBlynk)
    {
//        Serial.print(currYear);
//        Serial.print(currMonth);
//        Serial.print(currDay);
//        Serial.print(currHour);
        Serial.print(currMin);
        
        blynkUpdate();
        previousMillisSendBlynk = currentMillisSend;
    } 
     

    if (currMin != prevMin)   
    {
        
        accumulatePow = accumulatePow + blynkPublish;
        
        hourConsumption = (accumulatePow - hourBegining)/60;
        Serial.print(currMin);
        char hourConsumptionBlynk[8];
        dtostrf(hourConsumption, 1, 2, hourConsumptionBlynk);
        mqtt.publish(blynkHourCons, hourConsumptionBlynk); 
        
        dayConsumption = (accumulatePow - dayBegining )/60;

        char dayConsumptionBlynk[8];
        dtostrf(dayConsumption, 1, 2, dayConsumptionBlynk);
        mqtt.publish(blynkDayCons, dayConsumptionBlynk);
        
        monthConsumption = (accumulatePow - monthBegining)/60;
        char monthConsumptionBlynk[8];
        dtostrf(monthConsumption, 1, 2, monthConsumptionBlynk);
        mqtt.publish(blynkMonthCons, monthConsumptionBlynk);
        

        currPriceH = (hourConsumption*currPrice)/1000;
        char hourPriceBlynk[8];
        dtostrf(currPriceH, 1, 2, hourPriceBlynk);
        mqtt.publish(blynkPriceHour, hourPriceBlynk);
        
        currPriceD = (dayConsumption*currPrice)/1000;

        char dayPriceBlynk[8];
        dtostrf(currPriceD, 1, 2, dayPriceBlynk);
        mqtt.publish(blynkPriceDay, dayPriceBlynk);

       
        currPriceM = (monthConsumption*currPrice)/1000;

        char monthPriceBlynk[8];
        dtostrf(currPriceM, 1, 2, monthPriceBlynk);
        mqtt.publish(blynkPriceMonth, monthPriceBlynk);

        char currentBlynkPrice[8];
        dtostrf(currPrice, 1, 2, currentBlynkPrice);
        mqtt.publish(blynkCurrPrice, currentBlynkPrice);

        char powerToBlynk[8];
        dtostrf(blynkPublish, 1, 2, powerToBlynk);
        mqtt.publish(currPowerToBlynk, powerToBlynk);        

        if (currMin == 0)
        { 
          sixty[59] = blynkPublish;
        }
        else
        {
          sixty[currMin-1] = blynkPublish;
        }
              

        if (currMin % 3 == 0 && currMin != 0)
        {   
             
            float averageM = 0;
            nextPeriod = currMin + 1; 
            int countValues = 0;  
             
            for (int l = currMin - 3; l < currMin; l++)
            {              
                if (sixty[l] != 0)
                {          
                    countValues++;            
                }          
                averageM += sixty[l];                        
            }
                
            averageM /= countValues;         
             
            struct tm t0;
            String timestamp = test(t0);        
            if (!isnan(averageM) && !isinf(averageM))
            {
            powerMap.insert(std::pair<String, float>(timestamp,averageM));
            tp.myFunc();   
            }
        
        }
        else if (currMin == 0)
        {
          float averageM = 0;
          int countValues = 0;
          for (int l = 57; l < 60; l++)
          {              
                if (sixty[l] != 0)
                {          
                    countValues++;            
                }          
                averageM += sixty[l];                        
          }
          averageM /= countValues;
          struct tm t0;
          String timestamp = test(t0);        
          if (!isnan(averageM) && !isinf(averageM))
          {
            powerMap.insert(std::pair<String, float>(timestamp,averageM));
            tp.myFunc();   
          }          
        }  

        if (currMin == nextPeriod)
        {
            
            int err_size = powerMap.size();
            String err_string = String(err_size);
                       
            mqtt.publish("boron", (char*) err_string.c_str());

            if (err_size > 100)
            {
              powerMap.clear();
            }
                             
            tp.checkDb();
            trigger = 1;       
        }    
        prevMin = currMin;   
    }

    if (currHour != prevHour)
    {
       // mqtt.publish("meter/trigger", "ibexTrigg");
        hourBegining = accumulatePow;
        currPriceH = 0;
        prevHour = currHour;
    }
    if (prevDay != currDay)
    {
        dayBegining = accumulatePow;
        currPriceD = 0;
        prevDay = currDay;
    }
    if (prevMonth != currMonth)
    {
        monthBegining = accumulatePow;
        currPriceM = 0;
        prevMonth = currMonth;
    }
      
    mqtt.loop();   
       
}

emonLib was written specifically for the Atmel ATMega 328P processor. You will need to adjust the timings within your local copy of emonLib to suit the different processor speed and therefore the different sample rate of the ESP32.

In particular, you must change calcIrms(1480) so that the number of samples is as near as possible an exact number of whole cycles of your mains supply.

The way to measure this is to measure the time taken for (say) calcIrms(1480) to execute, then take the time for (say) calcIrms(2480) to execute. The difference is the time for 1000 samples. This is enough information because you know one cycle of mains takes 20 ms for you in the 50 Hz part of the world, or 16.66 ms for those who live in the 60 Hz part.

Thank you very much for your suggestions. I used this code:

unsigned long first, now = millis();
double Irms = emon1.calcIrms(1480); // Calculate Irms only
first = millis() - now;

now = millis();
Irms = emon1.calcIrms(5500); // Calculate Irms only
Serial.print(5500); Serial.print(" << This many samples took this many Ms > ");Serial.println(millis() - now - first);

and received 5500 samples for 250ms. As I read you suggest 250ms for 50Hz is fine so I used emon1.calcIrms(5500) in my code. The measurements are correct, but the problem are this random peaks that occurred.

Read again my suggestion for obtaining the time for 1000 samples. I cannot understand how your code can obtain this value.

I found your code for 1000 samples:

unsigned long first, now = millis();
double Irms = emon1.calcIrms(1480); // Calculate Irms only
first = millis() - now;

now = millis();
Irms = emon1.calcIrms(2480); // Calculate Irms only
Serial.print("1000 samples = ");Serial.println(millis() - now - first);

and I read in serial monitor - 1000samples = 64ms

So for my system - 50Hz, one cycle takes 20ms, so as I read your suggestion I need 6 cycles for example or 120ms which is 1875. So I need to use calcIrms(1875). Am I correct?

That looks OK.

I knew your ESP would be faster than the '328P. You measured 64 µs for your ESP to obtain and use a sample in the calculations, compared to the approximately 192 µs that the '328P takes.

EmonLib has only given a problem like that when someone has used a different processor or ADC, which runs at a different speed to the '328P. The result is it is calculating the rms value over a few complete cycles plus only part of a cycle - and because you have no control over where on the mains cycle it starts sampling, you will get a different average each time even though the true rms current remains constant, because the last part-cycle can happen anywhere on the wave.

Thank you very much for your help!

1 Like