Pulse_meter seems to be buggy

Hello Everyone,

I have configured a pulse_meter sensor to measure my electricity consumption at home. But I get strange results from pulse_meter I want to share with you.

Please find first my sensor configuration:

sensor:
  # Used for Energy Monitoring
  - platform: pulse_meter
    name: 'Electricity Power Usage'
    id: sensor_pulse_meter

    device_class: power
    state_class: measurement
    internal_filter: 1s
    internal_filter_mode: PULSE
    accuracy_decimals: 3
    pin: D5

    total:
      name: "Electricity Energy Usage"
      accuracy_decimals: 1 

binary_sensor:
  - platform: gpio
    name : "Electricity pulse"
    pin: D6

I have also added a gpio binary sensor for debugging.

First of all, I use the internal filter of the pulse_meter with 1s filtering and PULSE mode (for a very conservative filtering of my signal and be sure, I don’t get a wrong pulse).

My signal is ON for around 5 seconds and OFF for 2 minutes. So I expect to get a signal with a frequency of around 0,5 pulse / min.

The problem is, pulse_meter always report additional pulses that are not possible. Here an example of the log I get from the sensor:

You can notice two different problems. The first one, I get regularly a log for a pulse with a frequency of 0.01397 pulses / min. So with a period of more than 71 min. Which is not possible in the time frame. I just measured for 10 minutes…

The second problem is a pulse with a high frequency. Hier 15673 pulses / min. That means a period of 3,8 ms. Which is again not possible, because my filter configuration doesn’t allow the detection of pulses with a period less than 2s (1s high, 1s low).

Do you have any idea, what could be the origin of this behaviour? It seems for me pulse_meter implementation is somehow buggy…

Thanks for your help!

Have you tried

It is not clear to me which is better or exactly what is different between the two. I tried using one of them months ago on my gas meter, but was not impressed with the results and switched to Tasmota (from my previous custom solution, which was reliable but very custom).

I think all of these have difficulty with the very long pulses that are possible. I suspect what you are seeing is contact bounce that the internal filter is not filtering like it says it should.

Use the pulse_counter and make sure to turn OFF use_pcnt so you can increase the filter time, also read the rest of the documentation on the component.

1 Like

Thanks for your answer.

Yes, I tried pulse_counter, but it doesn’t feature the pulse mode filtering of pulse meter (only the edge mode filtering). And I need the debouncing feature of the pulse mode filtering because my signal is quite noisy.

The difference between pulse_meter and pulse_counter is the way they calculate the pulse frequency. Pulse_meter measures the time between two pulses. Pulse_counter measure the number of pulse in a one minute frame. For pulse frequency measurement, pulse_meter is much more accurate, but pulse_counter tends to be also accurate for high frequency pulses (which is not my case). So pulse_meter should be a better fit for my application.

Signal bouncing is possible, but it should be filtered by the internal filter of the pulse_meter. That’s why I suspect a bug in pulse_meter implementation. And the pulse report with a frequency of 0.01397 pulses/min is physicaly not possible in the measurement time frame, still reported by pulse_meter…

I will give Tasmota a try. Maybe they have a better implementation for such kind of application…

might be what you are seeing.

If you have a slowly moving signal doing it NOT in interrupts might be better. That is what I did previously. Once a second I checked the state of the pin and used that to count the changes. I actually counted both the rising and falling edges. The meter I had was not 50% duty cycle, but it did provide extra resolution.

Send some specs about this meter, I have never seen electricity meter with that kind of pulses.

Yes, I also thought of something like this. But based on the pulse filter truth table in the link you posted and my understanding of the code, there is no chance for a ghost interrupt to be published. They just reset the last_intr_ variable in the sensor state (and potentialy trigger the publishing of a previous non published pulse).

Yes, not using interrupts may be a solution… but I need to take care of the debouncing ā€œmanuallyā€.

I will try first to switch the logger to a more verbose level. Maybe I get more information. I will let you know.

I have an old legacy electricity meter similar to the below picture:

It gives 96 pulses per KWh. So consumming 300 W from the grid give me a pulse each 2 minutes. And the pulse is about 5 seconds high.

Nice meter… :open_mouth:

How does it behave if you use much shorter filtering, let’s say 40ms ?

With 40 ms, I have the exact same problem + some unfiltered bouncing pulses…

I am now a little bit further in my investigation. I set the level log to verbose and I notice the following in the log:

  • pulses with high delta (physicaly not possible):
[13:28:22][V][pulse_meter:099]: New pulse, delta: 4294963626 µs, count: 1, width: 4294963712.00000 µs
[13:28:22][V][sensor:051]: 'Electricity Power Usage': Received new state 0.013970
[13:28:22][D][sensor:103]: 'Electricity Power Usage': Sending state 0.01397 pulses/min with 3 decimals of accuracy
  • and pulses with low delta (should be filtered but are not…):
[13:31:15][V][pulse_meter:099]: New pulse, delta: 9222 µs, count: 1, width: 9222.00000 µs
[13:31:15][V][sensor:051]: 'Electricity Power Usage': Received new state 6506.180664
[13:31:15][D][sensor:103]: 'Electricity Power Usage': Sending state 6506.18066 pulses/min with 3 decimals of accuracy

So I gave a look to the code calculating the delta value:

uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_;

I suspect to have last_detected_edge_us_ very close to last_processed_edge_us_, inducing the very short pulses. Sometimes having last_detected_edge_us_ even smaller than last_processed_edge_us_ (which is as I already said, not possible… How can I have an edge processed that I don’t have even detected yet…) resulting in a negative delta_us value, cast to uint32, resulting in extrem large value (close to UINT32_MAX).

So my question now is: How is it possible for last_detected_edge_us_ to be very close to last_processed_edge_us_, sometimes even smaller? It sounds for me like a race condition…

Any help in the code understanding would be appreciated.

Did you examined the hardware, e.g. with a multi meter?
I didn’t know that that meters even had a pulse output?

I suspect there is a bug in the code. It is complicated and it is doing things in different contexts (interrupt and main).

If I was debugging it, I would copy the code locally and use it as an external component. I would then add logging at each of the decision points in the main loop()

Of course, that might change the timing enough to make it work. That might meet your bar of good enough.

Do not put logging in the ISR.

Yes, I tried to measure with a multimeter. But the response time is slow, so I cannot see any bounce in the signal and the signal appears much nicer than the reality.

This electricity meter does not have a pulse output per se, but I added an infrared line sensor to detect the rotating wheel. The wheel rotates 96 times per kWh.

Yes, there is indeed a bug in the code. And I found it! So let me explain what happends…

The explanation:

On each loop the two pointers set_ and get_ are swapped. Those pointers are used to transfer data between ISR and the main loop(). And loop expect to get always the latest state from the ISR. But it is not the case! It gets the latest state from the ISR only in the case, when ISR has just triggered. If not, swapping set_ and get_ just alternates between the latest state and an older one, which is timely undefined (can be just before last or even older).

So now, what are the consequences on the rest of the code? The code tries to detect edge early, by comparing if the last rising edge is different than the last detected edge and the last rising edge is older than the filter setting. Here the code for it:

// If there is an unprocessed edge, and filter_us_ has passed since, count this edge early
  if (this->get_->last_rising_edge_us_ != this->get_->last_detected_edge_us_ &&
      now - this->get_->last_rising_edge_us_ >= this->filter_us_) {
    this->peeked_edge_ = true;
    this->get_->last_detected_edge_us_ = this->get_->last_rising_edge_us_;
    this->get_->count_++;  // NOLINT(clang-diagnostic-deprecated-volatile)
  }

The problem is, that this is tested with a get_ state, which is not necessarily the latest state of the ISR (see my remark about swapping get_ and set_ each loop). So it potentialy detects an edge for a state, which is not the lastest one. And on the next loop, it swap ā€˜get_’ and ā€˜set_’ again, get the latest state and detect again an edge early.


Why did I notice this problem ?

It is because my signal bounce at rising edge. So I have a high probability to have ā€˜get_’ and ā€˜set_’ having last_rising_edge_us_ different than their respective last_detected_edge_us_ (because the filter latch is still low) and their last_rising_edge_us_ are also different, because the states have been set by the ISR on different rising edge.

Depending if the early edge detection first triggered on the latest ISR state or on the older one, delta_us can be slighty positive or slighty negative. Which is exactly what I observe with my setup.


So how can we correct this bug?

We should ensure, that loop always gets effectively the latest state from ISR. So I suggest to swap set_ and get_ only if ISR has updated the set_ state since the last loop. If not, we don’t swap and keep the latest swap in get_.

So we need a updated_state_ boolean variable in the sensor class, that is set each time by the ISR and reseted by the loop after swapping. A swap occurs only if updated_state_ is true.

I hope you could follow me. I will add a new issue ticket on github and link it to this thread. I hope for a quick implementation.

1 Like

Hier the link to the reported issue:

I made an implementation of this bug correction. You can find it here:

https://github.com/lefevrer/pulse_meter-esphome/tree/patch-pulse_meter/esphome/components/pulse_meter

It has run for me for the last two hours without any problem anymore (before I got wrong pulses approximately each two minutes).

I will push the request later today, if I don’t notice any problem since then.

1 Like

Here is the associated pull request on github:

https://github.com/esphome/esphome/pull/10619

@ Romesnil

I’ve not tried your fix, but Pulse meter doesn’t seem to work reliably in its default / edge detection mode.

What I’m seeing is that about 10% of the pulses are reported as 50% of the actual pulse duration.

In my case I’m detecting the flashing LEDs on my electricity power meter, and the result of the incorrect functionality is that the ā€˜power’ value which is reported by the Pulse Meter is 200% of the correct value.

The problem may be related to the rise of fall time of the edge, because if I connect another ESP32 to my PowerMonitor sustem, I don’t seem to have any problems; however using a phototransistor to sense a flashing LED, which is controller my the other ESP32, I see this problem.

I connected an oscilloscope to the inputs of the PowerMonitor ESP32 and I don’t see any additional pulses, and also I compiled an Arduino test program to sense the change in pin state, using AttachInterrupt, and I also don’t see any problems with the input when using Arduino.

This leads me to believe that there is probably a bug in Pulse Meter which is causing this, but I can’t see a reason why the reported pulse power is 200% of the correct value.
I could understand if an edge was missed, but not that it has dectected and edge which is exactly 50% of the actual pulse duration.

What are the high and low levels of that phototransistor output?
Also, your signal is likely not very clean, use internal_filter: 10ms to see how it behaves.

The levels look OK. Low is around 0.3V and High is nearly 3V. Rise time is 70uS. I think fall time is slower, becuase I’m using a 10k pulldown, whereas the phototransistor ā€˜on’ resistance is 1k or lower dependiong on light levels.

However, I can’t understand why I’m seeing power levels of exactly 2 x the actual amount, reported occasionally.

This would indicate that the pulse was half the length it should be.

My testbed is to drive this from another ESP32 (using Platformio) to generate the pulses for the flashing LEDs.

IIRC, I didn’t get the problem when I directly connect the ā€˜generator’ ESP32 to the ā€˜powermeter’ ESP32.
I only seem to get the problem when I use the LED and phototransistor.

However I will go back and re-test the direct connection, to see if it also has the same problem.

Also, if I change to the Pulse Meter sensor mode to ā€˜pulse’ it doesn’t have this problem. It only occurs in the default / ā€˜edge’ mode.

But I think using ā€˜pulse’ mode may report slower, as I thought it needed a complete cycle before the duration is calculated, but perhaps I’ve got the 2 modes mixed up

BTW.

I’ve tied various filter settings and nothing makes any difference to this problem. i.e I tried values as high as 200mS, even though this would limit the max measurable power, but it didn’t resolve the problem