mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +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/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 | ||||
|   | ||||
| @@ -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_); | ||||
|   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,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 | ||||
|   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_); | ||||
|     } | ||||
|  | ||||
|     sensor->total_pulses_++; | ||||
|     sensor->last_valid_edge_us_ = now; | ||||
|   } | ||||
|  | ||||
|   // 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; | ||||
|     } | ||||
|   } else { | ||||
|     sensor->last_detected_edge_us_ = now; | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace pulse_meter | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -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<uint32_t> pulse_width_dedupe_; | ||||
|   Deduplicator<uint32_t> 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 | ||||
|   | ||||
| @@ -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]) | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user