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

Measurement not that precise?! #3

Open xoseperez opened 6 years ago

xoseperez commented 6 years ago

Originally reported by: Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown)


I'm facing some issues with measurement with my Sonoff POW (which uses this HLW8012 chip). Sometimes the measurement is totally wrong and not realistic. So I decided to test with no load to see if there are also strange values visible. And guess what:

Sometimes there is some current, where not current should be (measurement with 5sec interval):

Bildschirmfoto vom 2017-11-24 14-53-19.png

My guess is, this also happen when there is a load connected, but then the failure is even worse.

Measuring is done via interrupts.

Is anyone else facing those issues?

xoseperez commented 6 years ago

Original comment by ZupoLlask (Bitbucket: ZupoLlask, GitHub: ZupoLlask):


Thanks for your feedback.

Have you ever got 80V-like readings for voltage, in between valid 230V readings?

In this specific situation, it seems that sometimes current signal wrongly feeds getVoltage(), thus generating totally wrong readings.

With the fix above, did you kept your other changes, including the removal of division by 2 for getting more stability, and so on?

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


I think I found a solution that works for me:

I created the following method:

#!arduino

#define TIMEFRAME 100
#define TIMES 10

int _activePower[TIMES];
int _voltage[TIMES];
double _current[TIMES];

int idx = 0;

int _currActivePower = 0;
int _currVoltage = 0;
double _currCurrent = 0;

long lastCheck = millis();

void checkMeasurement() {

  if (millis()-lastCheck>TIMEFRAME) {

    int activePower = _hlw8012.getActivePower();
    int voltage = _hlw8012.getVoltage();
    double current = _hlw8012.getCurrent();

    // do check
    _activePower[idx] = activePower;
    _voltage[idx] = voltage;
    _current[idx] = current;

    bool valid = true;
    int testInt = _activePower[0];
    for(int i=1;i<TIMES;i++) {
      if (testInt!=_activePower[i]) {
        valid = false;
        break;
      }
    }
    if (valid) {
      //Serial.print(micros());
      //Serial.println(" Valid power");
      _currActivePower = activePower;
    }

    valid=true;
    testInt = _voltage[0];
    for(int i=1;i<TIMES;i++) {
      if (testInt!=_voltage[i]) {
        valid = false;
        break;
      }
    }
    if (valid) {
      //Serial.print(micros());
      //Serial.println(" Valid voltage");
      _currVoltage = voltage;
    }
    valid=true;
    double testDouble = _current[0];
    for(int i=1;i<TIMES;i++) {
      if (testDouble!=_current[i]) {
        valid = false;
        break;
      }
    }
    if (valid) {
      //Serial.print(micros());
      //Serial.println(" Valid current");
      _currCurrent = current;
    }

    idx++;
    if (idx>TIMES) {
      idx = 0;
    }

    // save check time
    lastCheck=millis();
  }

}

long lastPrint = millis();

void loop() {

  checkMeasurement();

  if (millis()-lastPrint>500) {
    Serial.print(micros());
    Serial.print(";");
    Serial.print(_currVoltage);
    Serial.print(";");
    Serial.print(_currCurrent);
    Serial.print(";");
    Serial.println(_currActivePower);
    lastPrint=millis();
  }

}

The code is still quick'n'dirty and need some cleanup, but the idea is: I measure with 100ms interval. Every measurement is put into an array with 10 slots (ringbuffer). Only if each slot contains the same value, the measurement is accepted as valid.

To conclude: The measurement has to be "stable" for at least 1sec. Faulty measures will not go through this barrier.

I tested this without any load: Works quite well. I tested with some load: looks also good.

Maybe one could adapt this to the library as an "alternative way" of getting stabalized values.

xoseperez commented 6 years ago

Original comment by ZupoLlask (Bitbucket: ZupoLlask, GitHub: ZupoLlask):


I'm also observing the same behavior with Sonoff POW. Do have any update on this problem?

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


I managed to visualize the measurement: Bildschirmfoto vom 2017-12-21 13-06-35.png

This shows the CF signal with ~100ms sampling and without any load connected to the device.

If one believes the datasheet, the signal should have 50% duty cycle. But the screenshot shows a different behavior. As soon as I turn on a 40W halogen desk-lamp, the "duty cycle" looks as expected (right half of screenshot):

Bildschirmfoto vom 2017-12-21 13-10-06.png

I'm trying to find a solution to check the signal for a more or less correct duty cycle. But this will not be easy. Any help is appreciated.

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


I added debug.output to the CF interrupt handler, as well as to the checkCFSignal() function. I regulary get a checkCFSignal() invocation, and as soon as soon as I get wrong values, I also get an cf_interrupt() invocation upfront. So, either there is some issue with the interrupt-handling, or there are here and then wrong signals coming from HLW chip, which will trigger the time calculation and stop calculation on pulse_timeout.

I'll try to search for a software-solution...

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


I found out, that the wrong numbers occur for about 2sec. This directly correlates to the PULSE_TIMEOUT. If I set this to 4sec, I get for about 4sec a wrong values. Maybe something is wrong with "micros()"?! Still investigating.

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


I tested with an kettle ...

modifications:

#!arduino

// attach interrupts for HLW library --> sample uses CHANGE --> modified to RISING
  attachInterrupt(GPIO_HLW8012_CF1, hlw8012_cf1_interrupt, RISING);
  attachInterrupt(GPIO_HLW8012_CF, hlw8012_cf_interrupt, RISING);

from HLW8012.cpp

and removed from getVoltage(), getCurrent() and getActivePower() the division by 2 ...

Result: Measurement is more stable. But I still get seom faulty values when no load is running. argh

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


The TASMOTA Firmware (which is very common for Sonoff devices), uses "FALLING":

https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/xsns_03_hlw8012.ino#L289

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


Err, does it make sense to register the interrupt on "every change"?

https://bitbucket.org/xoseperez/hlw8012/src/cc3baec18f87779cb60bbc221ae8da5a0c89b94f/examples/HLW8012_Interrupts/HLW8012_Interrupts.cpp?at=master&fileviewer=file-view-default#HLW8012_Interrupts.cpp-39

Wouldn't it make more sense to trigger on every period, so either RISING or FALLING?

I changed it from CHANGE to RISING, and now it seems that the values are "stable". Runs now for a few minutes without one single measurement failure. Before the modification, the failure was reproducible within a few seconds.

But now I have to check if power-consumption is still correct measured.

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


I added additional logoutput to getCurrent() and getActivePower() if current or power is > 0 (still not connected anything):

#!arduino
!!!!!!getActivePower() _power_pulse_width=603303 _power_multiplier=10343611.735122 _power=8
!!!!!!getCurrent() _current_pulse_width=219070 _current_multiplier=14484.492875 _current=0.033059
!!!!!!getCurrent() _current_pulse_width=219070 _current_multiplier=14484.492875 _current=0.033059
!!!!!!getActivePower() _power_pulse_width=603303 _power_multiplier=10343611.735122 _power=8
!!!!!!getCurrent() _current_pulse_width=219070 _current_multiplier=14484.492875 _current=0.033059
!!!!!!getActivePower() _power_pulse_width=603303 _power_multiplier=10343611.735122 _power=8
!!!!!!getCurrent() _current_pulse_width=219070 _current_multiplier=14484.492875 _current=0.033059

Question is: As there is actually some pulse_width: where does this come from? Does the HLW8012 really give some signal which results in triggered interrupt, or does the micros() overflow (have to research this...)?

If someone has any hints about this: please share.

xoseperez commented 6 years ago

Original comment by Alexander Christian (Bitbucket: alexander_christian, GitHub: Unknown):


The jumping voltage is also a bit strange. from 234 volts to 226 volts within 5sec, and after additional 10sec again back to 234 volts.

My voltmeter measures 233,5volts. So 234 volts is the "correct" value. And I cannot see any jump with my voltmeter...

rodri16 commented 6 years ago

Hi I am also having strange readings in voltage. I am using interrupts. My readings fluctuates from 212 to 228 when 218-220V are present. Does change in interrupt from CHANGE to RISING/FALLING solves the problem? I tried setting mode when reading current with from hlw8012.setMode(MODE_CURRENT); and hlw8012.setMode(MODE_VOLTAGE); for voltage but nothing changed. Any idea? Did Alexander Christian solve the problem?

arihantdaga commented 6 years ago

I am having extreme fluctuations in activePower() sometimes its 10 Watts and sometimes it's 1000Watts while i only have a 200Watt bulb connected. I think right now the getActivePower() function is only considering last pulse width, will it be a better thing to use average of few values measured during the timeout period instead of using just the width of last pulse. ?

rrelande commented 1 year ago

i have also similar signals on CF1 pulse. Most signals will fit into the 50% duty ratio but some spurious signals will not.

I assume that the spurious signals not meeting the 50% duty ratio check create the wrong measurements mentioned in the opening of the issue. a solution could be indeed to measure the pulse width on high and pulse width on low. Only those pulses that are similar are a valid measurement. other pulses can be discarded. This is so far an idea which is not implemented.

rrelande commented 1 year ago

I've tried above filrtering solution. it succesfully only considers the pulse width meeting 50% duty ration with 97% exactness. at higher power, 80% of pulses are correct but in lower power only 20%, which means the filter is working correctly in removing the low frequency spurious signals. now the figures at low power are much more reliable.

@xoseperez , let me know if I can submit a PR with the above proposal