ESPHome 2026.5.1
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::pulse_meter {
6
7static const char *const TAG = "pulse_meter";
8
10 this->total_pulses_ = pulses;
11 if (this->total_sensor_ != nullptr) {
13 }
14}
15
17 this->pin_->setup();
18 this->isr_pin_ = pin_->to_isr();
19
20 // Set the pin value to the current value to avoid a false edge
21 this->last_pin_val_ = this->pin_->digital_read();
22
23 // Set the last processed edge to now for the first timeout
25
26 if (this->filter_mode_ == FILTER_EDGE) {
28 } else if (this->filter_mode_ == FILTER_PULSE) {
29 // Set the pin value to the current value to avoid a false edge
32 }
33
34 if (this->total_sensor_ != nullptr) {
36 }
37}
38
41
42 {
43 // Lock the interrupt so the interrupt code doesn't interfere with itself
44 InterruptLock lock;
45
46 // Sometimes ESP devices miss interrupts if the edge rises or falls too slowly.
47 // See https://github.com/espressif/arduino-esp32/issues/4172
48 // If the edges are rising too slowly it also implies that the pulse rate is slow.
49 // Therefore the update rate of the loop is likely fast enough to detect the edges.
50 // When the main loop detects an edge that the ISR didn't it will run the ISR functions directly.
51 bool current = this->pin_->digital_read();
52 if (this->filter_mode_ == FILTER_EDGE && current && !this->last_pin_val_) {
54 } else if (this->filter_mode_ == FILTER_PULSE && current != this->last_pin_val_) {
56 }
57 this->last_pin_val_ = current;
58
59 // Get the latest state from the ISR and reset the count in the ISR
60 state.last_detected_edge_us_ = this->state_.last_detected_edge_us_;
62 state.count_ = this->state_.count_;
63 this->state_.count_ = 0;
64 }
65
66 const uint32_t now = micros();
67
68 // If an edge was peeked, repay the debt
69 if (this->peeked_edge_ && state.count_ > 0) {
70 this->peeked_edge_ = false;
71 state.count_--;
72 }
73
74 // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early.
75 // Wait for the debt to be repaid before counting another unprocessed edge early.
76 if (!this->peeked_edge_ && state.last_rising_edge_us_ != state.last_detected_edge_us_ &&
77 now - state.last_rising_edge_us_ >= this->filter_us_) {
78 this->peeked_edge_ = true;
79 state.last_detected_edge_us_ = state.last_rising_edge_us_;
80 state.count_++;
81 }
82
83 // Check if we detected a pulse this loop
84 if (state.count_ > 0) {
85 // Keep a running total of pulses if a total sensor is configured
86 if (this->total_sensor_ != nullptr) {
87 this->total_pulses_ += state.count_;
88 const uint32_t total = this->total_pulses_;
89 this->total_sensor_->publish_state(total);
90 }
91
92 // We need to detect at least two edges to have a valid pulse width
93 switch (this->meter_state_) {
97 } break;
99 uint32_t delta_us = state.last_detected_edge_us_ - this->last_processed_edge_us_;
100 float pulse_width_us = delta_us / float(state.count_);
101 ESP_LOGV(TAG, "New pulse, delta: %" PRIu32 " µs, count: %" PRIu32 ", width: %.5f µs", delta_us, state.count_,
102 pulse_width_us);
103 this->publish_state((60.0f * 1000000.0f) / pulse_width_us);
104 } break;
105 }
106
108 }
109 // No detected edges this loop
110 else {
111 const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_;
112
113 switch (this->meter_state_) {
114 // Running and initial states can timeout
116 case MeterState::RUNNING: {
117 if (time_since_valid_edge_us > this->timeout_us_) {
119 ESP_LOGD(TAG, "No pulse detected for %" PRIu32 "s, assuming 0 pulses/min",
120 time_since_valid_edge_us / 1000000);
121 this->publish_state(0.0f);
122 }
123 } break;
124 default:
125 break;
126 }
127 }
128}
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 &edge_state = sensor->edge_state_;
147 auto &state = sensor->state_;
148
149 if ((now - edge_state.last_sent_edge_us_) >= sensor->filter_us_) {
150 edge_state.last_sent_edge_us_ = now;
151 state.last_detected_edge_us_ = now;
152 state.last_rising_edge_us_ = now;
153 state.count_ += 1;
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 &pulse_state = sensor->pulse_state_;
166 auto &state = sensor->state_;
167
168 // Filter length has passed since the last interrupt
169 const bool length = now - pulse_state.last_intr_ >= sensor->filter_us_;
170
171 if (length && pulse_state.latched_ && !sensor->last_pin_val_) { // Long enough low edge
172 pulse_state.latched_ = false;
173 } else if (length && !pulse_state.latched_ && sensor->last_pin_val_) { // Long enough high edge
174 pulse_state.latched_ = true;
175 state.last_detected_edge_us_ = pulse_state.last_intr_;
176 state.count_ += 1;
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 state.last_rising_edge_us_ = !pulse_state.latched_ && pin_val ? now : state.last_detected_edge_us_;
183
184 pulse_state.last_intr_ = now;
185 sensor->last_pin_val_ = pin_val;
186}
187
188} // namespace esphome::pulse_meter
virtual void setup()=0
virtual bool digital_read()=0
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:107
virtual ISRInternalGPIOPin to_isr() const =0
Helper class to disable interrupts.
Definition helpers.h:1952
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:68
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:138
@ INTERRUPT_RISING_EDGE
Definition gpio.h:50
@ INTERRUPT_ANY_EDGE
Definition gpio.h:52
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
static void uint32_t
uint16_t length
Definition tt21100.cpp:0