ESPHome  2024.9.2
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  this->publish_state((60.0f * 1000000.0f) / pulse_width_us);
79  } break;
80  }
81 
83  }
84  // No detected edges this loop
85  else {
86  const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_;
87 
88  switch (this->meter_state_) {
89  // Running and initial states can timeout
91  case MeterState::RUNNING: {
92  if (time_since_valid_edge_us > this->timeout_us_) {
94  ESP_LOGD(TAG, "No pulse detected for %" PRIu32 "s, assuming 0 pulses/min",
95  time_since_valid_edge_us / 1000000);
96  this->publish_state(0.0f);
97  }
98  } break;
99  default:
100  break;
101  }
102  }
103 }
104 
106 
108  LOG_SENSOR("", "Pulse Meter", this);
109  LOG_PIN(" Pin: ", this->pin_);
110  if (this->filter_mode_ == FILTER_EDGE) {
111  ESP_LOGCONFIG(TAG, " Filtering rising edges less than %" PRIu32 " µs apart", this->filter_us_);
112  } else {
113  ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %" PRIu32 " µs", this->filter_us_);
114  }
115  ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %" PRIu32 "s",
116  this->timeout_us_ / 1000000);
117 }
118 
120  // This is an interrupt handler - we can't call any virtual method from this method
121  // Get the current time before we do anything else so the measurements are consistent
122  const uint32_t now = micros();
123  auto &state = sensor->edge_state_;
124  auto &set = *sensor->set_;
125 
126  if ((now - state.last_sent_edge_us_) >= sensor->filter_us_) {
127  state.last_sent_edge_us_ = now;
128  set.last_detected_edge_us_ = now;
129  set.last_rising_edge_us_ = now;
130  set.count_++;
131  }
132 }
133 
135  // This is an interrupt handler - we can't call any virtual method from this method
136  // Get the current time before we do anything else so the measurements are consistent
137  const uint32_t now = micros();
138  const bool pin_val = sensor->isr_pin_.digital_read();
139  auto &state = sensor->pulse_state_;
140  auto &set = *sensor->set_;
141 
142  // Filter length has passed since the last interrupt
143  const bool length = now - state.last_intr_ >= sensor->filter_us_;
144 
145  if (length && state.latched_ && !state.last_pin_val_) { // Long enough low edge
146  state.latched_ = false;
147  } else if (length && !state.latched_ && state.last_pin_val_) { // Long enough high edge
148  state.latched_ = true;
149  set.last_detected_edge_us_ = state.last_intr_;
150  set.count_++;
151  }
152 
153  // Due to order of operations this includes
154  // length && latched && rising (just reset from a long low edge)
155  // !latched && (rising || high) (noise on the line resetting the potential rising edge)
156  set.last_rising_edge_us_ = !state.latched_ && pin_val ? now : set.last_detected_edge_us_;
157 
158  state.last_intr_ = now;
159  state.last_pin_val_ = pin_val;
160 }
161 
162 } // namespace pulse_meter
163 } // 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:81
esphome::sensor::Sensor * sensor
Definition: statsd.h:37