xoseperez / hlw8012

HLW8012 library for Arduino and ESP8266 using the Arduino Core for ESP8266.
GNU General Public License v3.0
126 stars 48 forks source link

Random readings when no load\relay off #11

Open chinswain opened 6 years ago

chinswain commented 6 years ago

I've just uploaded the interrupts example to a Sonoff POW, once or twice a minute I get random readings - is that to be expected? (I want to use the POW to determine if a device is switched on and for how long so was triggering on if current > x).

Most of the time with no load:

t1

Randomly with relay off: t2

1 Hour history: screenshot_20181025-141739_blynk

#define BLYNK_PRINT Serial
#include <Arduino.h>
#include "HLW8012.h"
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

#define SERIAL_BAUDRATE                 115200

// GPIOs
#define RELAY_PIN                       12
#define SEL_PIN                         5
#define CF1_PIN                         13
#define CF_PIN                          14
#define UPDATE_TIME                     5000
#define CURRENT_MODE                    HIGH

// These are the nominal values for the resistors in the circuit
#define CURRENT_RESISTOR                0.001
#define VOLTAGE_RESISTOR_UPSTREAM       ( 5 * 470000 ) // Real: 2280k
#define VOLTAGE_RESISTOR_DOWNSTREAM     ( 1000 ) // Real 1.009k

char auth[] = "";
char ssid[] = "";
char pass[] = "";

HLW8012 hlw8012;

void ICACHE_RAM_ATTR hlw8012_cf1_interrupt() {
  hlw8012.cf1_interrupt();
}
void ICACHE_RAM_ATTR hlw8012_cf_interrupt() {
  hlw8012.cf_interrupt();
}

void setInterrupts() {
  attachInterrupt(CF1_PIN, hlw8012_cf1_interrupt, CHANGE);
  attachInterrupt(CF_PIN, hlw8012_cf_interrupt, CHANGE);
}

void setup() {

  // Init serial port and clean garbage
  Serial.begin(SERIAL_BAUDRATE);
  Blynk.begin(auth, ssid, pass);
  // Close the relay to switch on the load
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, HIGH);

  hlw8012.begin(CF_PIN, CF1_PIN, SEL_PIN, CURRENT_MODE, true);
  hlw8012.setResistors(CURRENT_RESISTOR, VOLTAGE_RESISTOR_UPSTREAM, VOLTAGE_RESISTOR_DOWNSTREAM);

  setInterrupts();

}

char buffer[50];

void loop() {
  Blynk.run();
  static unsigned long last = millis();

  // This UPDATE_TIME should be at least twice the interrupt timeout (2 second by default)
  if ((millis() - last) > UPDATE_TIME) {

    last = millis();

    Blynk.virtualWrite(V1, hlw8012.getActivePower());
    Blynk.virtualWrite(V2, hlw8012.getVoltage());
    Blynk.virtualWrite(V3, hlw8012.getCurrent());
    Blynk.virtualWrite(V4, hlw8012.getApparentPower());
    Blynk.virtualWrite(V5, (int) (100 * hlw8012.getPowerFactor()));
    Blynk.virtualWrite(V6, hlw8012.getEnergy());

  }
}
erniberni commented 6 years ago

Same problem for me.

SergiuToporjinschi commented 6 years ago

Same problem here

erniberni commented 6 years ago

I just did some tests with readings of power as fast as possible. To do this I set a boolean in the ISR and every time this is triggered (as this is an edge on the CF line) I print out the power. When no other device connected (the power should be zero) I saw some pulses with a duration of mostly 200ms to 1000ms but also very seldom pulses with a duration of 1ms (!) with a calculated power of 6000W. But fortunately only single pulses only. To rule out these short pulses I will do 3 measurements in a row and kick out the value with the biggest deviation. And I will only do measurements after triggering the ISR, alll other times the power is 0 anyhow. Remember that with a power of 5W the time between two pulses will be 1000ms with 60W it will be 100ms, so you can do a lot of measurements in a short time.

xoseperez commented 5 years ago

I also noticed these spurious readings with the HLW8012 but couldn't remove them. My approach in ESPurna is the same as yours, @erniberni. I'm not reading so often but I apply a median filter every 3 readings.

chinswain commented 5 years ago

Do you have some example code of your method @erniberni ?

erniberni commented 5 years ago

@chinswain not really an example of code, but I try to explain, what I did. I added to the ISR a boolean flag

void hlw8012_cf_interrupt() {
  hlw8012.cf_interrupt();
  trigd = true;
}

In the main loop, every time when trigd is set I check the power. When the measured power is within a limit bigger or smaller (half or doubled) then the previous value then I trust this value, if not, the value will be ignored. The first trigd set must be ignored. Here is a part of my test sketch.

void loop() {
  yield();
  if (millis() - lasttrigd_time > (PULSE_TIMEOUT / 1000)) {
    timeOutms = PULSE_TIMEOUT / 1000;
    trusted = false;
    firstReading = false;
    firstPulse = true;        // must be true after this if
  }
  if (trigd && millis() - lasttrigd_time > 100) {     // check every trigger or every 100ms
    sinceLastReadingTime = millis() - lasttrigd_time;     // time since last reading ie min 100
    lasttrigd_time = millis();
    if (!firstPulse) {   // not the first pulse after timeout
      actPow = hlw8012.getActivePower();   // measure
      client.print("[HLW] Power(W) trigd      : ");
      client.print(actPow);
      client.print("\tTime ");
      client.print(sinceLastReadingTime);
      client.print("\tlastTime ");
      client.print(sinceLastReadingTimeStored);
      if (!firstReading) {    // timeout was already reduced
        if ((sinceLastReadingTime > sinceLastReadingTimeStored >> 1) && (sinceLastReadingTime < sinceLastReadingTimeStored << 1)) {
          client.println("\ttrusted value\t");
          trusted = true;
        }
        else {
          client.println("\tUNTRUSTED value");    // difference too big
          trusted = false;
        }
      }
      else {
        client.println("\tUNTRUSTED value");  // this is first reading
        trusted = false;
        firstReading = false;
      }
      trigd = false;
      lastmillis = lasttrigd_time;
    }
    else {      // the first pulse
      trigd = false;
      firstReading = true;
      firstPulse = false;
    }
    sinceLastReadingTimeStored = sinceLastReadingTime;
  }
  printStatus();
  manageButton();
}

void printStatus() {
  if (millis() - lastmillis > UPDATE_TIME) {
    if (!client.connected()) {
      if (!client.connect(host, httpPort)) {  // check for reconnection
        Serial.println("connection failed");
      }
      else {
        client.println("[DEBUG] Connection re-opened");
        client.println();
      }
    }
    client.print("[HLW] Voltage (V)         : "); client.print(hlw8012.getVoltage());
    client.print("  timeout: "); client.println(timeOutms);
    if (trusted) {
      client.print("[HLW] Last Active Power (W)    : ");
      client.println(actPow);
      trusted = false;
    }
    lastmillis = millis();
  }
}

The debug messages are printed on a tcp client. I hope this will help.

javier-fg commented 1 year ago

I have been also having the same behaviour, and still looking for a solution.

Has someone find a solution?

image

In my case Voltage seems stable, but on Current and Power, there are crazy 1 sample spikes with no load, as you can see in the image.

erniberni commented 1 year ago

Did you try using my code above?

javier-fg commented 1 year ago

I am going to implemented your idea as part of the library, test it and share the results.

javier-fg commented 1 year ago

It seems that the new filter performs good compared to the original libray code.

In the image, to the left it was the original library code, to the right it is the new filter functionality: image

I also checked the CF signal with an oscilloscope and there were not signal spikes or noise (while there is no load connected), but the software reported this strange and random values. Perhaps the noise it is related to the manufacturer PCB design. In my case, everthing has been tested on a Delock 11827 smart plug metter.

The filter functionalities are implemented in the ISR for the CF line, therefore this will work only using interrupts. Basically, once a timeout is detected, a time delay is added where the readings are ignore, after that there is a counter that counts valid readings, and if during that period no timeout it is detected the measurements will be valid.

void ICACHE_RAM_ATTR HLW8012::cf_interrupt() {

    unsigned long now = micros();

    _power_pulse_width = now - _last_cf_interrupt;
    _last_cf_interrupt = now;

    // Check if timeout event ocurred
    if (_power_pulse_width > _pulse_timeout){
        _notimeout_cf_cntr = 0;  
        _timeout_interrupt = now;
        _power_pulse_width = 0;
        _power = 0;  
    } 

    // Ignore any values during the PULSE_TIMEOUT microsec after first timeout event
    if( (now - _timeout_interrupt) < PULSE_TIMEOUT ){
        _power_pulse_width = 0;
        _power = 0;
    }else{

        // Wait for valid measurements, meantime ignore measurements
        if(_notimeout_cf_cntr <= TIMEOUT_VALID_PULSES){
            _notimeout_cf_cntr++;
            _power_pulse_width = 0;
            _power = 0;
        }
    }

    // Increment energy values only on valid signal
    if (_notimeout_cf_cntr >= TIMEOUT_VALID_PULSES)
        _pulse_count++;
}

The full source code can be find on the following fork repository https://github.com/javier-fg/hlw8012

If someone could also test it on their hardware, I could request a push to the original repository.