mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Pulse meter internal filter mode (#3082)
Co-authored-by: Paul Daumlechner <paul.daumlechner@live.de> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
		| @@ -144,7 +144,7 @@ esphome/components/pn532_spi/* @OttoWinter @jesserockz | |||||||
| esphome/components/power_supply/* @esphome/core | esphome/components/power_supply/* @esphome/core | ||||||
| esphome/components/preferences/* @esphome/core | esphome/components/preferences/* @esphome/core | ||||||
| esphome/components/psram/* @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/pvvx_mithermometer/* @pasiz | ||||||
| esphome/components/qr_code/* @wjtje | esphome/components/qr_code/* @wjtje | ||||||
| esphome/components/radon_eye_ble/* @jeffeb3 | esphome/components/radon_eye_ble/* @jeffeb3 | ||||||
|   | |||||||
| @@ -12,18 +12,53 @@ void PulseMeterSensor::setup() { | |||||||
|   this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); |   this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); | ||||||
|  |  | ||||||
|   this->last_detected_edge_us_ = 0; |   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() { | void PulseMeterSensor::loop() { | ||||||
|   const uint32_t now = micros(); |   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 |   // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until | ||||||
|   // we get at least two valid pulses. |   // we get at least two valid pulses. | ||||||
|   const uint32_t time_since_valid_edge_us = now - this->last_valid_edge_us_; |   const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_; | ||||||
|   if ((this->last_valid_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_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); |     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; |     this->pulse_width_us_ = 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -52,7 +87,11 @@ void PulseMeterSensor::set_total_pulses(uint32_t pulses) { this->total_pulses_ = | |||||||
| void PulseMeterSensor::dump_config() { | void PulseMeterSensor::dump_config() { | ||||||
|   LOG_SENSOR("", "Pulse Meter", this); |   LOG_SENSOR("", "Pulse Meter", this); | ||||||
|   LOG_PIN("  Pin: ", this->pin_); |   LOG_PIN("  Pin: ", this->pin_); | ||||||
|  |   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, "  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); |   ESP_LOGCONFIG(TAG, "  Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -62,24 +101,15 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { | |||||||
|   // Get the current time before we do anything else so the measurements are consistent |   // Get the current time before we do anything else so the measurements are consistent | ||||||
|   const uint32_t now = micros(); |   const uint32_t now = micros(); | ||||||
|  |  | ||||||
|   // We only look at rising edges |   // We only look at rising edges in EDGE mode, and all edges in PULSE mode | ||||||
|   if (!sensor->isr_pin_.digital_read()) { |   if (sensor->filter_mode_ == FILTER_EDGE) { | ||||||
|     return; |     if (sensor->isr_pin_.digital_read()) { | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // 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_); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     sensor->total_pulses_++; |  | ||||||
|     sensor->last_valid_edge_us_ = now; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|       sensor->last_detected_edge_us_ = now; |       sensor->last_detected_edge_us_ = now; | ||||||
|     } |     } | ||||||
|  |   } else { | ||||||
|  |     sensor->last_detected_edge_us_ = now; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace pulse_meter | }  // namespace pulse_meter | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -10,8 +10,14 @@ namespace pulse_meter { | |||||||
|  |  | ||||||
| class PulseMeterSensor : public sensor::Sensor, public Component { | class PulseMeterSensor : public sensor::Sensor, public Component { | ||||||
|  public: |  public: | ||||||
|  |   enum InternalFilterMode { | ||||||
|  |     FILTER_EDGE = 0, | ||||||
|  |     FILTER_PULSE, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } |   void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } | ||||||
|   void set_filter_us(uint32_t filter) { this->filter_us_ = filter; } |   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_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } | ||||||
|   void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } |   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 filter_us_ = 0; | ||||||
|   uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; |   uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; | ||||||
|   sensor::Sensor *total_sensor_ = nullptr; |   sensor::Sensor *total_sensor_ = nullptr; | ||||||
|  |   InternalFilterMode filter_mode_{FILTER_EDGE}; | ||||||
|  |  | ||||||
|   Deduplicator<uint32_t> pulse_width_dedupe_; |   Deduplicator<uint32_t> pulse_width_dedupe_; | ||||||
|   Deduplicator<uint32_t> total_dedupe_; |   Deduplicator<uint32_t> total_dedupe_; | ||||||
|  |  | ||||||
|   volatile uint32_t last_detected_edge_us_ = 0; |   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 pulse_width_us_ = 0; | ||||||
|   volatile uint32_t total_pulses_ = 0; |   volatile uint32_t total_pulses_ = 0; | ||||||
|  |   volatile bool sensor_is_high_ = false; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace pulse_meter | }  // namespace pulse_meter | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ from esphome.components import sensor | |||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_INTERNAL_FILTER, |     CONF_INTERNAL_FILTER, | ||||||
|  |     CONF_INTERNAL_FILTER_MODE, | ||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
|     CONF_NUMBER, |     CONF_NUMBER, | ||||||
|     CONF_TIMEOUT, |     CONF_TIMEOUT, | ||||||
| @@ -18,14 +19,21 @@ from esphome.const import ( | |||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
|  |  | ||||||
| CODEOWNERS = ["@stevebaxter"] | CODEOWNERS = ["@stevebaxter", "@cstaahl"] | ||||||
|  |  | ||||||
| pulse_meter_ns = cg.esphome_ns.namespace("pulse_meter") | pulse_meter_ns = cg.esphome_ns.namespace("pulse_meter") | ||||||
|  |  | ||||||
|  |  | ||||||
| PulseMeterSensor = pulse_meter_ns.class_( | PulseMeterSensor = pulse_meter_ns.class_( | ||||||
|     "PulseMeterSensor", sensor.Sensor, cg.Component |     "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) | SetTotalPulsesAction = pulse_meter_ns.class_("SetTotalPulsesAction", automation.Action) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -66,6 +74,9 @@ CONFIG_SCHEMA = sensor.sensor_schema( | |||||||
|             accuracy_decimals=0, |             accuracy_decimals=0, | ||||||
|             state_class=STATE_CLASS_TOTAL_INCREASING, |             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_pin(pin)) | ||||||
|     cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER])) |     cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER])) | ||||||
|     cg.add(var.set_timeout_us(config[CONF_TIMEOUT])) |     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: |     if CONF_TOTAL in config: | ||||||
|         sens = await sensor.new_sensor(config[CONF_TOTAL]) |         sens = await sensor.new_sensor(config[CONF_TOTAL]) | ||||||
|   | |||||||
| @@ -307,6 +307,7 @@ CONF_INTENSITY = "intensity" | |||||||
| CONF_INTERLOCK = "interlock" | CONF_INTERLOCK = "interlock" | ||||||
| CONF_INTERNAL = "internal" | CONF_INTERNAL = "internal" | ||||||
| CONF_INTERNAL_FILTER = "internal_filter" | CONF_INTERNAL_FILTER = "internal_filter" | ||||||
|  | CONF_INTERNAL_FILTER_MODE = "internal_filter_mode" | ||||||
| CONF_INTERRUPT = "interrupt" | CONF_INTERRUPT = "interrupt" | ||||||
| CONF_INTERVAL = "interval" | CONF_INTERVAL = "interval" | ||||||
| CONF_INVALID_COOLDOWN = "invalid_cooldown" | CONF_INVALID_COOLDOWN = "invalid_cooldown" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user