olehs / PZEM004T

Arduino communication library for Peacefair PZEM-004T Energy monitor
MIT License
226 stars 114 forks source link

{ Solved, example is working fine } How to use HardwareSerial, example PZEM_ESP8266HwSerial.ino not working #47

Closed mikekgr closed 6 years ago

mikekgr commented 6 years ago

Dear Sirs, Hardware: ESP-12F/ESP8266 To get the printings of the values ( voltage, current, energy etc ) I use OLED display.

Softwarte: Arduino 1.8.4 running on Windows 7 64bit PC, having ESP8266 Arduino core 2.4.2 installed.

I need to have PZEM004 connected to ESP8266 hardware serial means ESP8266 TX pin (GPIO01) to PZEM004 RX pin and ESP8266 RX pin (GPIO03) to PZEM TX pin. Well, IF I use software serial ( instead hardware serial ) to ESP8266l means ESP8266 TX pin (GPIO13) to PZEM004 RX pin and ESP8266 RX pin (GPIO12) to PZEM TX pin all are ok If I use the HardwareSerial nothing is "printing" at my OLED. In both cases the rest of sketch are the same. Please tell me what to do or what is possible wrong. It will be a big help if you just test the example PZEM_ESP8266HwSerial.ino and see if it working on your end.

Thanks and Best Regards, Mike Kranidis

olehs commented 6 years ago

Hello, @mikekgr. Have you noticed the line with swap() in PZEM_ESP8266HwSerial.ino ? https://github.com/olehs/PZEM004T/blob/807b65fa631114319eb5d0745c6ac4705c9a847f/examples/PZEM_ESP8266HwSerial/PZEM_ESP8266HwSerial.ino#L23

Try to remove (comment) it and check again.

mikekgr commented 6 years ago

Dear @olehs, thanks a lot for your reply. In my test sketch I have the following lines regarding the PZEM operation:

#include <PZEM004T.h> //https://github.com/olehs/PZEM004T
HardwareSerial hwserial(UART0);     // Use hwserial UART0 at pins GPIO1 (TX) and GPIO3 (RX)
PZEM004T pzem(&hwserial);           // Attach PZEM to hwserial
IPAddress ip(192,168,1,1);

float voltage_b = 0.0;
float current_b = 0.0;
float power_b = 0.0;
float energy_b =0.0;

void setup()
{
pzemrdy = pzem.setAddress(ip);
}

void loop()
{
    float v = pzem.voltage(ip);
    if(v >= 0.0){
      voltage_b = v; //V
    }
}

Is there something wrong or something missing in my simplified sketch above? What do you think? Thanks a lot.

mikekgr commented 6 years ago

@olehs Dear Sir, I have tried to debug my problem regarding the not working hardware serial, using software serial. In the sketch that is follows, I measuring the total duration that the function "ReadAndPost" takes. It is very strange that in almost each iteration of this function, the "function duration time" is increasing steadily without any excuse that I can think of except something wrong either my PZEM004 of software serial library. Can you test the follow sketch and let me know if at your end has the same result? Thanks in advance!

My sketch:

/// PLEASE PUT YOUR DATA AT THE LINES: 42, 44, 61, 67, 68, 
///#define hardwareserial
#define softwareserial

#define COMP_DATE __DATE__

//#define BLYNK_PRINT Serial
#define HARDWARE_LED 2 /// WeMos D1 mini Hardware Led at GPIO02

/// V1 Labeled Value widget, Voltage           V1 ///
/// V2 Labeled Value widget, Current           V2 ///
/// V3 Labeled Value widget, Power             V3 ///
/// V4 Labeled Value widget, Energy            V4 ///
/// V5 Labeled Value widget, current Date/Time V5 ///
/// V14 Terminal widget                       V14 ///

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <BlynkSimpleEsp8266.h>
#include <PZEM004T.h> //https://github.com/olehs/PZEM004T
///PZEM004T pzem(&Serial);                                        /// use Serial
IPAddress ip(192,168,1,1);

#ifdef softwareserial /// using softwareserial ///
#include <SoftwareSerial.h>
PZEM004T pzem(12,13);  // (RX,TX) connect to TX,RX of PZEM  PZEM004T pzem(12, 13);
#endif

#ifdef hardwareserial ///using hardwareserial ///
HardwareSerial hwserial(UART0);     // Use hwserial UART0 at pins GPIO1 (TX) and GPIO3 (RX)
PZEM004T pzem(&hwserial);           // Attach PZEM to hwserial
#endif

/// NTP dependencies and declarations///
#include <TimeLib.h>
/// it is used above /// #include <ESP8266WiFi.h>
/// it is used above /// #include <WiFiUdp.h>

// NTP Servers:
static const char ntpServerName[] = "gr.pool.ntp.org";

int timeZone=3; /// this is initial time zone.
/// That is the blynk way to take TZ ( not the case in this sketch ) This takes feed from time input widget V21, V22, V23, V24
/// t.getTZ_Offset() numeric value in seconds that holds the selected at the widget time zone offset e.x. Time zone offset: 10800
WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets

float voltage_blynk=0;
float current_blynk=0;
float power_blynk=0;
float energy_blynk=0;

unsigned long functionDuration = 0;
/// character arrays that keep formated current Date and Time ///
char currentDateTime[25];

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char blynk_auth[] = "PUT YOUR AUTH HERE";

BlynkTimer timer;

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "WiFi SSID";
char pass[] = "WiFi PASSWORD";
unsigned long lastMillis = 0;

// Attach virtual serial terminal to Virtual Pin V14
WidgetTerminal terminal(V14);

void setup()
{
  //No  Debug console      
  pzem.setAddress(ip);
  Blynk.begin(blynk_auth, ssid, pass);

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_SPIFFS
      type = "filesystem";
    }

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    ///Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    ///Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    ///Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    ///Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      ///Serial.println("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      ///Serial.println("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      ///Serial.println("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      ///Serial.println("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      ///Serial.println("End Failed");
    }
  });
  ArduinoOTA.begin();

/// NTP related ///
///Serial.println("Starting UDP");
Udp.begin(localPort);
unsigned long maxMillis=millis() + 10000;
while ((timeStatus() == timeNotSet) && ( millis() <= maxMillis)) {
/// Wait until takes NTP sync or 10 seconds passed without connection ( maxMillis )
yield();
delay(100);
}
setSyncProvider(getNtpTime); /// this acting as "NTP syncNow" !
yield();
delay(200);
yield();
setSyncInterval(600); /// set sync timer at 600 seconds = 10 minutes

timer.setInterval(3000L, ReadAndPost); /// execute the ReadAndPost function each 3 seconds ///
timer.setInterval(60000L, printFunctionDuration); /// execute the printFunctionDuration function each 60 seconds / 1 minute ///
diagnostics(); /// just a first printing after booting ///
}

void loop()
{
  ArduinoOTA.handle();
  Blynk.run();
  timer.run();
}

void ReadAndPost()
{
unsigned long temp;
temp = millis();

float v = pzem.voltage(ip);
if(v >= 0.0){ voltage_blynk = v;} //V

float i = pzem.current(ip);
if(i >= 0.0){ current_blynk = i;}  //A

float p = pzem.power(ip);
if(p >= 0.0){ power_blynk = p;} //kW

float e = pzem.energy(ip);
if(e >= 0.0){  energy_blynk = e;} ///kWh

Blynk.virtualWrite(V1, voltage_blynk);
Blynk.virtualWrite(V2, current_blynk);
Blynk.virtualWrite(V3, power_blynk);
Blynk.virtualWrite(V4, (energy_blynk/1000));
functionDuration = millis() - temp;
}

void printFunctionDuration()
{
  sprintf(currentDateTime, "%02d/%02d/%d %02d:%02d:%02d",day(), month(), year(), hour(), minute(), second());
  // Send time to the App
  Blynk.virtualWrite(V5, currentDateTime);
  terminal.printf("\n %s, func. duration:%d",currentDateTime, functionDuration); terminal.flush();
}

/// START of diagnostics function ///
void diagnostics()
{
      terminal.printf("\nBlynk %s / V:%s\n", BLYNK_VERSION, system_get_sdk_version());
      terminal.print("Last RST?: ");
      terminal.println (ESP.getResetReason());
      uint8_t mac[6];
      char macAddr[14];
      WiFi.macAddress( mac );
      sprintf(macAddr, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); /// capital letters at MAC address
      terminal.printf("ESP ID: %d / MAC: %s\n", ESP.getChipId(),macAddr);  /// mac address here 
      terminal.printf("ESP free heap=%d, time:%02d:%02d:%02d\n", ESP.getFreeHeap(), hour(), minute(), second() );
      terminal.printf("WiFi Signal:%ddBm / IP:", WiFi.RSSI());
      terminal.print(WiFi.localIP());
      terminal.printf("\nSSID: %s", WiFi.SSID().c_str());
      terminal.flush();
}
/// END of diagnostics function ///

/// START of V14 terminal processing function ///
  BLYNK_WRITE(V14) {
  String getString = param.asStr(); /// get the string ///
  getString.trim(); /// cut off the blank spece(s) at the end of the string ///
    if (getString.equalsIgnoreCase("timesync") || getString.equalsIgnoreCase("ts")) {
      terminal.println("\n OK I will do Time Sync as you wish !\n");
      setSyncProvider(getNtpTime); /// this acting as "NTP syncNow" !
      ///local = myTZ.toLocal(now(), &tcr); /// local time with timeZone integrating 
      ///timeZone = (local-now()) / 3600;
      timeZone = 3;
      setSyncProvider(getNtpTime); /// this acting as "NTP syncNow" !
      return;
    } else if (getString.equalsIgnoreCase("filename") || getString.equalsIgnoreCase("fn")) {
      terminal.printf("\n Source File Name:\n%s\n",__FILE__);
      terminal.flush();
      return;
    } else if (getString.equalsIgnoreCase("Clear") || getString.equalsIgnoreCase("Cls")) {
      clearScreen();
      return;
    } else if (getString.equalsIgnoreCase("Diags")) {
      ///terminal.println("You said: Diags , OK!");
      diagnostics();
      return;
    } else if ( getString.startsWith("uptime") || getString.startsWith("ut")) { /// print uptime formated
      int2Time(millis()/1000);
      return;
    } else if (getString.equalsIgnoreCase("restart")) {
      terminal.println("\n OK I do restart !\n");
      terminal.flush();
      yield(); delay(500);
      ESP.restart();
      ///yield();
      delay(1500);
    } else if (getString.equalsIgnoreCase("?") || getString.equalsIgnoreCase("help")) {
      terminal.println("\nThe regognised commands are: ");
      terminal.println("timesync(ts), filename(fn), clear/cls, diags, uptime(ut), restart");
      terminal.flush();
      return;
    } else {
      // Send it back
      terminal.print("\nYou typed:");
      terminal.write(param.getBuffer(), param.getLength());
      terminal.println();
      terminal.println("Type ? or help to get help...");
      terminal.println();
    }
  // Ensure everything is sent
  terminal.flush();
}
/// END of V14 terminal processing function ///

/// START of int2Time convert integer to time format ///
void int2Time(unsigned long duration)
{
  if(duration >= 86400){ /// 86400
     terminal.printf("%dD:", (duration / 86400)); /// how many days ? ///
     duration = duration % 86400;     
  }
  if(duration >= 3600){ /// 3600
     terminal.printf("%02dH:",(duration / 3600)); /// how many hours ? ///
     duration = duration % 3600;     
  }
  if(duration >= 60){ /// 60
     terminal.printf("%02dM:",(duration / 60)); /// how many minutes ? ///
     duration = duration % 60;
  }
  terminal.printf("%02dS",(duration)); /// how many seconds ? ///
  terminal.println();
terminal.flush();
}
/// END of int2Time convert integer to time format ///

/// START of clearScreen function ///
void clearScreen() /// terminal clear ///
{
    for(int i=1; i<8; i++) {
    terminal.println();
    }
    ///terminal.clear();
    terminal.flush();
}
/// END of crearScreen function ///

/*-------- START of NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print("ntp Server Name: ");
  Serial.print(ntpServerName);
  Serial.print("  IP: ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}
/*-------- END of NTP code ----------*/
olehs commented 6 years ago

This is how PZEM works. It can return only 2 readings (any values) within ~1 second and then becomes busy. See the examples: there are no delays in code, but measurements are printed with delay to Serial. That is why PZEM004T library has default timeout of 1000ms. You can decrease it, but you will receieve error reading (-1) while PZEM is busy.

mikekgr commented 6 years ago

Dear @olehs sorry I think you misunderstood me, the problem is that each time the function "ReadAndPost" is calling THEN it takes a little more time progressively, let's say when the ESP8266 is booting then the function "ReadAndPost" it takes around 2000ms and after some hours the same function "ReadAndPost" it takes around 2200ms ... Is that the expected behavior ??? Thanks

olehs commented 6 years ago

To make it clear, you should change this function to measure only PZEM timings without blynk

void ReadAndPost()
{
unsigned long temp;
temp = millis();

float v = pzem.voltage(ip);
if(v >= 0.0){ voltage_blynk = v;} //V

float i = pzem.current(ip);
if(i >= 0.0){ current_blynk = i;}  //A

float p = pzem.power(ip);
if(p >= 0.0){ power_blynk = p;} //kW

float e = pzem.energy(ip);
if(e >= 0.0){  energy_blynk = e;} ///kWh

functionDuration = millis() - temp; // moved this line here

Blynk.virtualWrite(V1, voltage_blynk);
Blynk.virtualWrite(V2, current_blynk);
Blynk.virtualWrite(V3, power_blynk);
Blynk.virtualWrite(V4, (energy_blynk/1000));
}
mikekgr commented 6 years ago

@olehs ok I will do right now and I will report it the result back here. Thanks

mikekgr commented 6 years ago

@olehs still the same, see the attached "screenshot" increasing_time

olehs commented 6 years ago

Well, it's hard to say what causes this extra delay in multitasking environment. There is a call to yield() in a loop (see PZEM004T.receive() method), so some background tasks may be running while libaray is waiting for response.

mikekgr commented 6 years ago

@olehs yes, I agree with you that it is hard to follow timing things in multitasking environment. See some strange in behavior: started at 10:44:43 1906ms 11:17:42 2017ms 11:18:42 2020ms 11:19:42 1524ms <== Strange !!! 11:20:42 1528ms 11:21:42 1531ms 11:22:42 1534ms ...

Please see me sketch above and tell me if it is OK to run on hardware serial, that I did not achieved ...

olehs commented 6 years ago

It's also possible, that PZEM has it's own timings and requests are processed not as fast as we expect.

Please see me sketch above and tell me if it is OK to run on hardware serial, that I did not achieved ...

It looks good but I don't have any ESP8266 board available now to check it in hardware. Maybe @vortigont can help as he is the author of HWSerial example...

vortigont commented 6 years ago

Greetings! If you want to measure PZEM respone time it would be better to use something like this

void ReadAndPost()
{
unsigned long temp;
temp = millis();
float v = pzem.voltage(ip);
float i = pzem.current(ip);
float p = pzem.power(ip);
float e = pzem.energy(ip);
functionDuration = millis() - temp; // moved this line here

if(v >= 0.0){ voltage_blynk = v;} //V
if(i >= 0.0){ current_blynk = i;}  //A
if(p >= 0.0){ power_blynk = p;} //kW
if(e >= 0.0){  energy_blynk = e;} ///kWh

Blynk.virtualWrite(V1, voltage_blynk);
Blynk.virtualWrite(V2, current_blynk);
Blynk.virtualWrite(V3, power_blynk);
Blynk.virtualWrite(V4, (energy_blynk/1000));
}

Otherwise all kind of unknown blink activity may interfere. JFYI: Here is how ESP to PZEM comm works under the oscilloscope So the whole dialog to retreive 4 meterings takes about 90 ms.

pzem_wave

mikekgr commented 6 years ago

Dear @vortigont , thanks a lot for your kind suggestions, I will do the measurements using your related code. By the way when you measure the 4 meterings that for you takes about 90 ms ONLY!, do you use ESP8266 software or hardware serial? Also please see my code above, the section for the hardware serial that I can not get it to work, do you see it right? I will revert back with the new measurements. Thanks

vortigont commented 6 years ago

I use hwserial with ESP8266. Your code is fine, check your circuits. For ex. if you use wemos with onboard usb2seral converter it may prevent using RX/TX pins with PZEM optocouplers - use swapped pins instead. Consult WiKi pages for wiring details and hwserial setup.

mikekgr commented 6 years ago

Dear @vortigont I put here the measurements/results from your code, see bellow. I believe that there is something wrong with esp8266softwareserial library. I will try to run my project using hardware serial, thanks for you suggestion. pzem_reading_1

mikekgr commented 6 years ago

Dear @olehs , Dear @vortigont , finally, after solving the small hardware glitch that blocked the Hardware Serial operation, ( shame on me, I am 33 years hardware engineer... ), now my setup is running on hardware serial !!! Thanks, both of you, for your assistance, and the fine library. I will stay connected here in case of any problem may arise.

Thanks and Best Regards, Mike Kranidis

P.S. the hardware problem was the resistor level shifter... Don't laugh please!

vortigont commented 6 years ago

@mikekgr, that's great! JFYI - SoftwareSerial is indeed has some issues since esp8266 core 2.4.x. There were significant changes in Core code that broke lots of libs based on timings/delay code, new core doesn't like long blocking states in loop(). I think we all better to try writing event driven code without using delays.

olehs commented 6 years ago

I am working on non-blocking calls to the library functions. This will allow to create more responsive sketches. Hope I can keep backward compatibility.

vortigont commented 6 years ago

That's great, @olehs! Event if if not keeping compatibility it would be a great improvement. Feel free to call if beta-testers needed.

mudassar791 commented 5 years ago

hi, Respected Member, how can we connect and take output (Current,Watt,Voltage) from ESP8266 or ESP 32 with Pzem004t, Can any body share coding and wiring schematic?I dont want use OLED,