ESPHome 2025.9.0-dev
Loading...
Searching...
No Matches
pulse_meter_sensor.cpp
Go to the documentation of this file.
2#include <utility>
3#include "esphome/core/log.h"
4
5namespace esphome {
6namespace pulse_meter {
7
8static const char *const TAG = "pulse_meter";
9
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 pin value to the current value to avoid a false edge
22 this->last_pin_val_ = this->pin_->digital_read();
23
24 // Set the last processed edge to now for the first timeout
26
27 if (this->filter_mode_ == FILTER_EDGE) {
29 } else if (this->filter_mode_ == FILTER_PULSE) {
30 // Set the pin value to the current value to avoid a false edge
33 }
34
35 if (this->total_sensor_ != nullptr) {
37 }
38}
39
41 // Reset the count in get before we pass it back to the ISR as set
42 this->get_->count_ = 0;
43
44 {
45 // Lock the interrupt so the interrupt code doesn't interfere with itself
46 InterruptLock lock;
47
48 // Sometimes ESP devices miss interrupts if the edge rises or falls too slowly.
49 // See https://github.com/espressif/arduino-esp32/issues/4172
50 // If the edges are rising too slowly it also implies that the pulse rate is slow.
51 // Therefore the update rate of the loop is likely fast enough to detect the edges.
52 // When the main loop detects an edge that the ISR didn't it will run the ISR functions directly.
53 bool current = this->pin_->digital_read();
54 if (this->filter_mode_ == FILTER_EDGE && current && !this->last_pin_val_) {
56 } else if (this->filter_mode_ == FILTER_PULSE && current != this->last_pin_val_) {
58 }
59 this->last_pin_val_ = current;
60
61 // Swap out set and get to get the latest state from the ISR
62 std::swap(this->set_, this->get_);
63 }
64
65 const uint32_t now = micros();
66
67 // If an edge was peeked, repay the debt
68 if (this->peeked_edge_ && this->get_->count_ > 0) {
69 this->peeked_edge_ = false;
70 this->get_->count_--; // NOLINT(clang-diagnostic-deprecated-volatile)
71 }
72
73 // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early
74 if (this->get_->last_rising_edge_us_ != this->get_->last_detected_edge_us_ &&
75 now - this->get_->last_rising_edge_us_ >= this->filter_us_) {
76 this->peeked_edge_ = true;
78 this->get_->count_++; // NOLINT(clang-diagnostic-deprecated-volatile)
79 }
80
81 // Check if we detected a pulse this loop
82 if (this->get_->count_ > 0) {
83 // Keep a running total of pulses if a total sensor is configured
84 if (this->total_sensor_ != nullptr) {
85 this->total_pulses_ += this->get_->count_;
86 const uint32_t total = this->total_pulses_;
87 this->total_sensor_->publish_state(total);
88 }
89
90 // We need to detect at least two edges to have a valid pulse width
91 switch (this->meter_state_) {
95 } break;
97 uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_;
98 float pulse_width_us = delta_us / float(this->get_->count_);
99 ESP_LOGV(TAG, "New pulse, delta: %" PRIu32 " µs, count: %" PRIu32 ", width: %.5f µs", delta_us,
100 this->get_->count_, pulse_width_us);
101 this->publish_state((60.0f * 1000000.0f) / pulse_width_us);
102 } break;
103 }
104
106 }
107 // No detected edges this loop
108 else {
109 const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_;
110
111 switch (this->meter_state_) {
112 // Running and initial states can timeout
114 case MeterState::RUNNING: {
115 if (time_since_valid_edge_us > this->timeout_us_) {
117 ESP_LOGD(TAG, "No pulse detected for %" PRIu32 "s, assuming 0 pulses/min",
118 time_since_valid_edge_us / 1000000);
119 this->publish_state(0.0f);
120 }
121 } break;
122 default:
123 break;
124 }
125 }
126}
127
129
131 LOG_SENSOR("", "Pulse Meter", this);
132 LOG_PIN(" Pin: ", this->pin_);
133 if (this->filter_mode_ == FILTER_EDGE) {
134 ESP_LOGCONFIG(TAG, " Filtering rising edges less than %" PRIu32 " µs apart", this->filter_us_);
135 } else {
136 ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %" PRIu32 " µs", this->filter_us_);
137 }
138 ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %" PRIu32 "s",
139 this->timeout_us_ / 1000000);
140}
141
143 // This is an interrupt handler - we can't call any virtual method from this method
144 // Get the current time before we do anything else so the measurements are consistent
145 const uint32_t now = micros();
146 auto &state = sensor->edge_state_;
147 auto &set = *sensor->set_;
148
149 if ((now - state.last_sent_edge_us_) >= sensor->filter_us_) {
150 state.last_sent_edge_us_ = now;
151 set.last_detected_edge_us_ = now;
152 set.last_rising_edge_us_ = now;
153 set.count_++; // NOLINT(clang-diagnostic-deprecated-volatile)
154 }
155
156 // This ISR is bound to rising edges, so the pin is high
157 sensor->last_pin_val_ = true;
158}
159
161 // This is an interrupt handler - we can't call any virtual method from this method
162 // Get the current time before we do anything else so the measurements are consistent
163 const uint32_t now = micros();
164 const bool pin_val = sensor->isr_pin_.digital_read();
165 auto &state = sensor->pulse_state_;
166 auto &set = *sensor->set_;
167
168 // Filter length has passed since the last interrupt
169 const bool length = now - state.last_intr_ >= sensor->filter_us_;
170
171 if (length && state.latched_ && !sensor->last_pin_val_) { // Long enough low edge
172 state.latched_ = false;
173 } else if (length && !state.latched_ && sensor->last_pin_val_) { // Long enough high edge
174 state.latched_ = true;
175 set.last_detected_edge_us_ = state.last_intr_;
176 set.count_++; // NOLINT(clang-diagnostic-deprecated-volatile)
177 }
178
179 // Due to order of operations this includes
180 // length && latched && rising (just reset from a long low edge)
181 // !latched && (rising || high) (noise on the line resetting the potential rising edge)
182 set.last_rising_edge_us_ = !state.latched_ && pin_val ? now : set.last_detected_edge_us_;
183
184 state.last_intr_ = now;
185 sensor->last_pin_val_ = pin_val;
186}
187
188} // namespace pulse_meter
189} // namespace esphome
virtual void setup()=0
virtual bool digital_read()=0
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:88
virtual ISRInternalGPIOPin to_isr() const =0
Helper class to disable interrupts.
Definition helpers.h:732
bool last_pin_val_
The last pin value seen.
static void edge_intr(PulseMeterSensor *sensor)
static void pulse_intr(PulseMeterSensor *sensor)
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:45
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:133
@ INTERRUPT_RISING_EDGE
Definition gpio.h:41
@ INTERRUPT_ANY_EDGE
Definition gpio.h:43
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:50
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT micros()
Definition core.cpp:30
uint16_t length
Definition tt21100.cpp:0