ESPHome  2022.12.8
pulse_meter_sensor.cpp
Go to the documentation of this file.
1 #include "pulse_meter_sensor.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace pulse_meter {
6 
7 static const char *const TAG = "pulse_meter";
8 
10  this->pin_->setup();
11  this->isr_pin_ = pin_->to_isr();
13 
14  this->pulse_width_us_ = 0;
15  this->last_detected_edge_us_ = 0;
16  this->last_valid_high_edge_us_ = 0;
17  this->last_valid_low_edge_us_ = 0;
18  this->sensor_is_high_ = this->isr_pin_.digital_read();
19  this->has_valid_high_edge_ = false;
20  this->has_valid_low_edge_ = false;
21 }
22 
24  // Get a local copy of the volatile sensor values, to make sure they are not
25  // modified by the ISR. This could cause overflow in the following arithmetic
26  const uint32_t last_valid_high_edge_us = this->last_valid_high_edge_us_;
27  const bool has_valid_high_edge = this->has_valid_high_edge_;
28  const uint32_t now = micros();
29 
30  // If we've exceeded our timeout interval without receiving any pulses, assume
31  // 0 pulses/min until we get at least two valid pulses.
32  const uint32_t time_since_valid_edge_us = now - last_valid_high_edge_us;
33  if ((has_valid_high_edge) && (time_since_valid_edge_us > this->timeout_us_)) {
34  ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
35 
36  this->pulse_width_us_ = 0;
37  this->last_detected_edge_us_ = 0;
38  this->last_valid_high_edge_us_ = 0;
39  this->last_valid_low_edge_us_ = 0;
40  this->has_detected_edge_ = false;
41  this->has_valid_high_edge_ = false;
42  this->has_valid_low_edge_ = false;
43  }
44 
45  // We quantize our pulse widths to 1 ms to avoid unnecessary jitter
46  const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000;
47  if (this->pulse_width_dedupe_.next(pulse_width_ms)) {
48  if (pulse_width_ms == 0) {
49  // Treat 0 pulse width as 0 pulses/min (normally because we've not
50  // detected any pulses for a while)
51  this->publish_state(0);
52  } else {
53  // Calculate pulses/min from the pulse width in ms
54  this->publish_state((60.0f * 1000.0f) / pulse_width_ms);
55  }
56  }
57 
58  if (this->total_sensor_ != nullptr) {
59  const uint32_t total = this->total_pulses_;
60  if (this->total_dedupe_.next(total)) {
61  this->total_sensor_->publish_state(total);
62  }
63  }
64 }
65 
66 void PulseMeterSensor::set_total_pulses(uint32_t pulses) { this->total_pulses_ = pulses; }
67 
69  LOG_SENSOR("", "Pulse Meter", this);
70  LOG_PIN(" Pin: ", this->pin_);
71  if (this->filter_mode_ == FILTER_EDGE) {
72  ESP_LOGCONFIG(TAG, " Filtering rising edges less than %u µs apart", this->filter_us_);
73  } else {
74  ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_);
75  }
76  ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000);
77 }
78 
80  // This is an interrupt handler - we can't call any virtual method from this
81  // method
82 
83  // Get the current time before we do anything else so the measurements are
84  // consistent
85  const uint32_t now = micros();
86 
87  // We only look at rising edges in EDGE mode, and all edges in PULSE mode
88  if (sensor->filter_mode_ == FILTER_EDGE) {
89  if (sensor->isr_pin_.digital_read()) {
90  sensor->last_detected_edge_us_ = now;
91  }
92  }
93 
94  // Check to see if we should filter this edge out
95  if (sensor->filter_mode_ == FILTER_EDGE) {
96  if ((sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_) >= sensor->filter_us_) {
97  // Don't measure the first valid pulse (we need at least two pulses to
98  // measure the width)
99  if (sensor->has_valid_high_edge_) {
101  }
102  sensor->total_pulses_++;
104  sensor->has_valid_high_edge_ = true;
105  }
106  } else {
107  // Filter Mode is PULSE
108  bool pin_val = sensor->isr_pin_.digital_read();
109  // Ignore false edges that may be caused by bouncing and exit the ISR ASAP
110  if (pin_val == sensor->sensor_is_high_) {
111  return;
112  }
113  // Make sure the signal has been stable long enough
114  if (sensor->has_detected_edge_ && (now - sensor->last_detected_edge_us_ >= sensor->filter_us_)) {
115  if (pin_val) {
116  sensor->has_valid_high_edge_ = true;
118  sensor->sensor_is_high_ = true;
119  } else {
120  // Count pulses when a sufficiently long high pulse is concluded.
121  sensor->total_pulses_++;
122  if (sensor->has_valid_low_edge_) {
124  }
125  sensor->has_valid_low_edge_ = true;
127  sensor->sensor_is_high_ = false;
128  }
129  }
130  sensor->has_detected_edge_ = true;
131  sensor->last_detected_edge_us_ = now;
132  }
133 }
134 
135 } // namespace pulse_meter
136 } // namespace esphome
bool next(T value)
Feeds the next item in the series to the deduplicator and returns whether this is a duplicate...
Definition: helpers.h:482
virtual void setup()=0
uint32_t IRAM_ATTR HOT micros()
Definition: core.cpp:28
static void gpio_intr(PulseMeterSensor *sensor)
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:72
virtual ISRInternalGPIOPin to_isr() const =0
Deduplicator< uint32_t > pulse_width_dedupe_
Definition: a4988.cpp:4
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition: gpio.h:81