ESPHome 2025.5.0
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
37 // Reset the count in get before we pass it back to the ISR as set
38 this->get_->count_ = 0;
39
40 {
41 // Lock the interrupt so the interrupt code doesn't interfere with itself
42 InterruptLock lock;
43
44 // Sometimes ESP devices miss interrupts if the edge rises or falls too slowly.
45 // See https://github.com/espressif/arduino-esp32/issues/4172
46 // If the edges are rising too slowly it also implies that the pulse rate is slow.
47 // Therefore the update rate of the loop is likely fast enough to detect the edges.
48 // When the main loop detects an edge that the ISR didn't it will run the ISR functions directly.
49 bool current = this->pin_->digital_read();
50 if (this->filter_mode_ == FILTER_EDGE && current && !this->last_pin_val_) {
52 } else if (this->filter_mode_ == FILTER_PULSE && current != this->last_pin_val_) {
54 }
55 this->last_pin_val_ = current;
56
57 // Swap out set and get to get the latest state from the ISR
58 std::swap(this->set_, this->get_);
59 }
60
61 const uint32_t now = micros();
62
63 // If an edge was peeked, repay the debt
64 if (this->peeked_edge_ && this->get_->count_ > 0) {
65 this->peeked_edge_ = false;
66 this->get_->count_--;
67 }
68
69 // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early
70 if (this->get_->last_rising_edge_us_ != this->get_->last_detected_edge_us_ &&
71 now - this->get_->last_rising_edge_us_ >= this->filter_us_) {
72 this->peeked_edge_ = true;
74 this->get_->count_++;
75 }
76
77 // Check if we detected a pulse this loop
78 if (this->get_->count_ > 0) {
79 // Keep a running total of pulses if a total sensor is configured
80 if (this->total_sensor_ != nullptr) {
81 this->total_pulses_ += this->get_->count_;
82 const uint32_t total = this->total_pulses_;
83 this->total_sensor_->publish_state(total);
84 }
85
86 // We need to detect at least two edges to have a valid pulse width
87 switch (this->meter_state_) {
91 } break;
93 uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_;
94 float pulse_width_us = delta_us / float(this->get_->count_);
95 ESP_LOGV(TAG, "New pulse, delta: %" PRIu32 " µs, count: %" PRIu32 ", width: %.5f µs", delta_us,
96 this->get_->count_, pulse_width_us);
97 this->publish_state((60.0f * 1000000.0f) / pulse_width_us);
98 } break;
99 }
100
102 }
103 // No detected edges this loop
104 else {
105 const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_;
106
107 switch (this->meter_state_) {
108 // Running and initial states can timeout
110 case MeterState::RUNNING: {
111 if (time_since_valid_edge_us > this->timeout_us_) {
113 ESP_LOGD(TAG, "No pulse detected for %" PRIu32 "s, assuming 0 pulses/min",
114 time_since_valid_edge_us / 1000000);
115 this->publish_state(0.0f);
116 }
117 } break;
118 default:
119 break;
120 }
121 }
122}
123
125
127 LOG_SENSOR("", "Pulse Meter", this);
128 LOG_PIN(" Pin: ", this->pin_);
129 if (this->filter_mode_ == FILTER_EDGE) {
130 ESP_LOGCONFIG(TAG, " Filtering rising edges less than %" PRIu32 " µs apart", this->filter_us_);
131 } else {
132 ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %" PRIu32 " µs", this->filter_us_);
133 }
134 ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %" PRIu32 "s",
135 this->timeout_us_ / 1000000);
136}
137
139 // This is an interrupt handler - we can't call any virtual method from this method
140 // Get the current time before we do anything else so the measurements are consistent
141 const uint32_t now = micros();
142 auto &state = sensor->edge_state_;
143 auto &set = *sensor->set_;
144
145 if ((now - state.last_sent_edge_us_) >= sensor->filter_us_) {
146 state.last_sent_edge_us_ = now;
147 set.last_detected_edge_us_ = now;
148 set.last_rising_edge_us_ = now;
149 set.count_++;
150 }
151
152 // This ISR is bound to rising edges, so the pin is high
153 sensor->last_pin_val_ = true;
154}
155
157 // This is an interrupt handler - we can't call any virtual method from this method
158 // Get the current time before we do anything else so the measurements are consistent
159 const uint32_t now = micros();
160 const bool pin_val = sensor->isr_pin_.digital_read();
161 auto &state = sensor->pulse_state_;
162 auto &set = *sensor->set_;
163
164 // Filter length has passed since the last interrupt
165 const bool length = now - state.last_intr_ >= sensor->filter_us_;
166
167 if (length && state.latched_ && !sensor->last_pin_val_) { // Long enough low edge
168 state.latched_ = false;
169 } else if (length && !state.latched_ && sensor->last_pin_val_) { // Long enough high edge
170 state.latched_ = true;
171 set.last_detected_edge_us_ = state.last_intr_;
172 set.count_++;
173 }
174
175 // Due to order of operations this includes
176 // length && latched && rising (just reset from a long low edge)
177 // !latched && (rising || high) (noise on the line resetting the potential rising edge)
178 set.last_rising_edge_us_ = !state.latched_ && pin_val ? now : set.last_detected_edge_us_;
179
180 state.last_intr_ = now;
181 sensor->last_pin_val_ = pin_val;
182}
183
184} // namespace pulse_meter
185} // 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:614
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:39
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:131
@ 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:19
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT micros()
Definition core.cpp:29
uint16_t length
Definition tt21100.cpp:0