diff --git a/CODEOWNERS b/CODEOWNERS index 4edfde7711..52e80a5822 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -144,7 +144,7 @@ esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/power_supply/* @esphome/core esphome/components/preferences/* @esphome/core esphome/components/psram/* @esphome/core -esphome/components/pulse_meter/* @stevebaxter +esphome/components/pulse_meter/* @cstaahl @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz esphome/components/qr_code/* @wjtje esphome/components/radon_eye_ble/* @jeffeb3 diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index 7d526b241b..f747f9ee40 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -12,18 +12,53 @@ void PulseMeterSensor::setup() { this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->last_detected_edge_us_ = 0; - this->last_valid_edge_us_ = 0; + this->last_valid_low_edge_us_ = 0; + this->last_valid_high_edge_us_ = 0; + this->sensor_is_high_ = this->isr_pin_.digital_read(); } void PulseMeterSensor::loop() { const uint32_t now = micros(); + // Check to see if we should filter this edge out + if (this->filter_mode_ == FILTER_EDGE) { + if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) { + // Don't measure the first valid pulse (we need at least two pulses to measure the width) + if (this->last_valid_high_edge_us_ != 0) { + this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); + } + this->total_pulses_++; + this->last_valid_high_edge_us_ = this->last_detected_edge_us_; + } + } else { + // Make sure the signal has been stable long enough + if ((now - this->last_detected_edge_us_) >= this->filter_us_) { + // Only consider HIGH pulses and "new" edges if sensor state is LOW + if (!this->sensor_is_high_ && this->isr_pin_.digital_read() && + (this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) { + // Don't measure the first valid pulse (we need at least two pulses to measure the width) + if (this->last_valid_high_edge_us_ != 0) { + this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); + } + this->sensor_is_high_ = true; + this->total_pulses_++; + this->last_valid_high_edge_us_ = this->last_detected_edge_us_; + } + // Only consider LOW pulses and "new" edges if sensor state is HIGH + else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() && + (this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) { + this->sensor_is_high_ = false; + this->last_valid_low_edge_us_ = this->last_detected_edge_us_; + } + } + } + // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until // we get at least two valid pulses. - const uint32_t time_since_valid_edge_us = now - this->last_valid_edge_us_; - if ((this->last_valid_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_)) { + const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_; + if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) && + (this->pulse_width_us_ != 0)) { ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000); - this->last_valid_edge_us_ = 0; this->pulse_width_us_ = 0; } @@ -52,7 +87,11 @@ void PulseMeterSensor::set_total_pulses(uint32_t pulses) { this->total_pulses_ = void PulseMeterSensor::dump_config() { LOG_SENSOR("", "Pulse Meter", this); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + if (this->filter_mode_ == FILTER_EDGE) { + ESP_LOGCONFIG(TAG, " Filtering rising edges less than %u µs apart", this->filter_us_); + } else { + ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + } ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); } @@ -62,23 +101,14 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); - // We only look at rising edges - if (!sensor->isr_pin_.digital_read()) { - return; - } - - // Check to see if we should filter this edge out - if ((now - sensor->last_detected_edge_us_) >= sensor->filter_us_) { - // Don't measure the first valid pulse (we need at least two pulses to measure the width) - if (sensor->last_valid_edge_us_ != 0) { - sensor->pulse_width_us_ = (now - sensor->last_valid_edge_us_); + // We only look at rising edges in EDGE mode, and all edges in PULSE mode + if (sensor->filter_mode_ == FILTER_EDGE) { + if (sensor->isr_pin_.digital_read()) { + sensor->last_detected_edge_us_ = now; } - - sensor->total_pulses_++; - sensor->last_valid_edge_us_ = now; + } else { + sensor->last_detected_edge_us_ = now; } - - sensor->last_detected_edge_us_ = now; } } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index 1cebc1748e..cf08f8c92d 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -10,8 +10,14 @@ namespace pulse_meter { class PulseMeterSensor : public sensor::Sensor, public Component { public: + enum InternalFilterMode { + FILTER_EDGE = 0, + FILTER_PULSE, + }; + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_filter_us(uint32_t filter) { this->filter_us_ = filter; } + void set_filter_mode(InternalFilterMode mode) { this->filter_mode_ = mode; } void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } @@ -30,14 +36,17 @@ class PulseMeterSensor : public sensor::Sensor, public Component { uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; sensor::Sensor *total_sensor_ = nullptr; + InternalFilterMode filter_mode_{FILTER_EDGE}; Deduplicator pulse_width_dedupe_; Deduplicator total_dedupe_; volatile uint32_t last_detected_edge_us_ = 0; - volatile uint32_t last_valid_edge_us_ = 0; + volatile uint32_t last_valid_low_edge_us_ = 0; + volatile uint32_t last_valid_high_edge_us_ = 0; volatile uint32_t pulse_width_us_ = 0; volatile uint32_t total_pulses_ = 0; + volatile bool sensor_is_high_ = false; }; } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index fa753b5b05..26bc6b189b 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -5,6 +5,7 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, CONF_INTERNAL_FILTER, + CONF_INTERNAL_FILTER_MODE, CONF_PIN, CONF_NUMBER, CONF_TIMEOUT, @@ -18,14 +19,21 @@ from esphome.const import ( ) from esphome.core import CORE -CODEOWNERS = ["@stevebaxter"] +CODEOWNERS = ["@stevebaxter", "@cstaahl"] pulse_meter_ns = cg.esphome_ns.namespace("pulse_meter") + PulseMeterSensor = pulse_meter_ns.class_( "PulseMeterSensor", sensor.Sensor, cg.Component ) +PulseMeterInternalFilterMode = PulseMeterSensor.enum("InternalFilterMode") +FILTER_MODES = { + "EDGE": PulseMeterInternalFilterMode.FILTER_EDGE, + "PULSE": PulseMeterInternalFilterMode.FILTER_PULSE, +} + SetTotalPulsesAction = pulse_meter_ns.class_("SetTotalPulsesAction", automation.Action) @@ -66,6 +74,9 @@ CONFIG_SCHEMA = sensor.sensor_schema( accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional(CONF_INTERNAL_FILTER_MODE, default="EDGE"): cv.enum( + FILTER_MODES, upper=True + ), } ) @@ -78,6 +89,7 @@ async def to_code(config): cg.add(var.set_pin(pin)) cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER])) cg.add(var.set_timeout_us(config[CONF_TIMEOUT])) + cg.add(var.set_filter_mode(config[CONF_INTERNAL_FILTER_MODE])) if CONF_TOTAL in config: sens = await sensor.new_sensor(config[CONF_TOTAL]) diff --git a/esphome/const.py b/esphome/const.py index 00cc90ad6a..2ec00edf7b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -307,6 +307,7 @@ CONF_INTENSITY = "intensity" CONF_INTERLOCK = "interlock" CONF_INTERNAL = "internal" CONF_INTERNAL_FILTER = "internal_filter" +CONF_INTERNAL_FILTER_MODE = "internal_filter_mode" CONF_INTERRUPT = "interrupt" CONF_INTERVAL = "interval" CONF_INVALID_COOLDOWN = "invalid_cooldown" diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 4ff16ba703..42828450e8 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -954,7 +954,7 @@ class MockObjEnum(MockObj): base = kwargs.pop("base") if self._is_class: base = f"{base}::{self._enum}" - kwargs["op"] = "::" + kwargs["op"] = "::" kwargs["base"] = base MockObj.__init__(self, *args, **kwargs)