mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01: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:
parent
d26141151a
commit
07c1cf7137
6 changed files with 76 additions and 24 deletions
|
@ -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_);
|
||||||
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);
|
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
|
// 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()) {
|
||||||
}
|
sensor->last_detected_edge_us_ = now;
|
||||||
|
|
||||||
// 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_);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
sensor->total_pulses_++;
|
sensor->last_detected_edge_us_ = now;
|
||||||
sensor->last_valid_edge_us_ = now;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sensor->last_detected_edge_us_ = now;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace pulse_meter
|
} // namespace pulse_meter
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -954,7 +954,7 @@ class MockObjEnum(MockObj):
|
||||||
base = kwargs.pop("base")
|
base = kwargs.pop("base")
|
||||||
if self._is_class:
|
if self._is_class:
|
||||||
base = f"{base}::{self._enum}"
|
base = f"{base}::{self._enum}"
|
||||||
kwargs["op"] = "::"
|
kwargs["op"] = "::"
|
||||||
kwargs["base"] = base
|
kwargs["base"] = base
|
||||||
MockObj.__init__(self, *args, **kwargs)
|
MockObj.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue