ESPHome  2025.2.0
pulse_meter_sensor.cpp
Go to the documentation of this file.
1 #include "pulse_meter_sensor.h"
2 #include <utility>
3 #include "esphome/core/log.h"
4 
5 namespace esphome {
6 namespace pulse_meter {
7 
8 static const char *const TAG = "pulse_meter";
9 
10 void PulseMeterSensor::set_total_pulses(uint32_t pulses) {
11  this->total_pulses_ = pulses;
12  if (this->total_sensor_ != nullptr) {
14  }
15 }
16 
18  this->pin_->setup();
19  this->isr_pin_ = pin_->to_isr();
20 
21  // Set the last processed edge to now for the first timeout
23 
24  if (this->filter_mode_ == FILTER_EDGE) {
26  } else if (this->filter_mode_ == FILTER_PULSE) {
27  // Set the pin value to the current value to avoid a false edge
31  }
32 }
33 
35  const uint32_t now = micros();
36 
37  // Reset the count in get before we pass it back to the ISR as set
38  this->get_->count_ = 0;
39 
40  // Swap out set and get to get the latest state from the ISR
41  // The ISR could interrupt on any of these lines and the results would be consistent
42  auto *temp = this->set_;
43  this->set_ = this->get_;
44  this->get_ = temp;
45 
46  // If an edge was peeked, repay the debt
47  if (this->peeked_edge_ && this->get_->count_ > 0) {
48  this->peeked_edge_ = false;
49  this->get_->count_--;
50  }
51 
52  // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early
53  if (this->get_->last_rising_edge_us_ != this->get_->last_detected_edge_us_ &&
54  now - this->get_->last_rising_edge_us_ >= this->filter_us_) {
55  this->peeked_edge_ = true;
57  this->get_->count_++;
58  }
59 
60  // Check if we detected a pulse this loop
61  if (this->get_->count_ > 0) {
62  // Keep a running total of pulses if a total sensor is configured
63  if (this->total_sensor_ != nullptr) {
64  this->total_pulses_ += this->get_->count_;
65  const uint32_t total = this->total_pulses_;
66  this->total_sensor_->publish_state(total);
67  }
68 
69  // We need to detect at least two edges to have a valid pulse width
70  switch (this->meter_state_) {
72  case MeterState::TIMED_OUT: {
74  } break;
75  case MeterState::RUNNING: {
76  uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_;
77  float pulse_width_us = delta_us / float(this->get_->count_);
78  ESP_LOGV(TAG, "New pulse, delta: %" PRIu32 " µs, count: %" PRIu32 ", width: %.5f µs", delta_us,
79  this->get_->count_, pulse_width_us);
80  this->publish_state((60.0f * 1000000.0f) / pulse_width_us);
81  } break;
82  }
83 
85  }
86  // No detected edges this loop
87  else {
88  const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_;
89 
90  switch (this->meter_state_) {
91  // Running and initial states can timeout
93  case MeterState::RUNNING: {
94  if (time_since_valid_edge_us > this->timeout_us_) {
96  ESP_LOGD(TAG, "No pulse detected for %" PRIu32 "s, assuming 0 pulses/min",
97  time_since_valid_edge_us / 1000000);
98  this->publish_state(0.0f);
99  }
100  } break;
101  default:
102  break;
103  }
104  }
105 }
106 
108 
110  LOG_SENSOR("", "Pulse Meter", this);
111  LOG_PIN(" Pin: ", this->pin_);
112  if (this->filter_mode_ == FILTER_EDGE) {
113  ESP_LOGCONFIG(TAG, " Filtering rising edges less than %" PRIu32 " µs apart", this->filter_us_);
114  } else {
115  ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %" PRIu32 " µs", this->filter_us_);
116  }
117  ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %" PRIu32 "s",
118  this->timeout_us_ / 1000000);
119 }
120 
122  // This is an interrupt handler - we can't call any virtual method from this method
123  // Get the current time before we do anything else so the measurements are consistent
124  const uint32_t now = micros();
125  auto &state = sensor->edge_state_;
126  auto &set = *sensor->set_;
127 
128  if ((now - state.last_sent_edge_us_) >= sensor->filter_us_) {
129  state.last_sent_edge_us_ = now;
130  set.last_detected_edge_us_ = now;
131  set.last_rising_edge_us_ = now;
132  set.count_++;
133  }
134 }
135 
137  // This is an interrupt handler - we can't call any virtual method from this method
138  // Get the current time before we do anything else so the measurements are consistent
139  const uint32_t now = micros();
140  const bool pin_val = sensor->isr_pin_.digital_read();
141  auto &state = sensor->pulse_state_;
142  auto &set = *sensor->set_;
143 
144  // Filter length has passed since the last interrupt
145  const bool length = now - state.last_intr_ >= sensor->filter_us_;
146 
147  if (length && state.latched_ && !state.last_pin_val_) { // Long enough low edge
148  state.latched_ = false;
149  } else if (length && !state.latched_ && state.last_pin_val_) { // Long enough high edge
150  state.latched_ = true;
151  set.last_detected_edge_us_ = state.last_intr_;
152  set.count_++;
153  }
154 
155  // Due to order of operations this includes
156  // length && latched && rising (just reset from a long low edge)
157  // !latched && (rising || high) (noise on the line resetting the potential rising edge)
158  set.last_rising_edge_us_ = !state.latched_ && pin_val ? now : set.last_detected_edge_us_;
159 
160  state.last_intr_ = now;
161  state.last_pin_val_ = pin_val;
162 }
163 
164 } // namespace pulse_meter
165 } // namespace esphome
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
static void edge_intr(PulseMeterSensor *sensor)
virtual void setup()=0
uint32_t IRAM_ATTR HOT micros()
Definition: core.cpp:27
float state
This member variable stores the last state that has passed through all filters.
Definition: sensor.h:131
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
static void pulse_intr(PulseMeterSensor *sensor)
virtual ISRInternalGPIOPin to_isr() const =0
uint16_t length
Definition: tt21100.cpp:12
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition: gpio.h:88
esphome::sensor::Sensor * sensor
Definition: statsd.h:38