mirror of
https://github.com/esphome/esphome.git
synced 2024-11-12 18:27:46 +01:00
Implement pulse_meter as an improvement on pulse_counter and pulse_width for meters (#1434)
This commit is contained in:
parent
f63f9168ff
commit
a77784a6da
7 changed files with 196 additions and 1 deletions
|
@ -67,6 +67,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||||
esphome/components/power_supply/* @esphome/core
|
esphome/components/power_supply/* @esphome/core
|
||||||
|
esphome/components/pulse_meter/* @stevebaxter
|
||||||
esphome/components/rc522/* @glmnet
|
esphome/components/rc522/* @glmnet
|
||||||
esphome/components/rc522_i2c/* @glmnet
|
esphome/components/rc522_i2c/* @glmnet
|
||||||
esphome/components/rc522_spi/* @glmnet
|
esphome/components/rc522_spi/* @glmnet
|
||||||
|
|
0
esphome/components/pulse_meter/__init__.py
Normal file
0
esphome/components/pulse_meter/__init__.py
Normal file
87
esphome/components/pulse_meter/pulse_meter_sensor.cpp
Normal file
87
esphome/components/pulse_meter/pulse_meter_sensor.cpp
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
#include "pulse_meter_sensor.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pulse_meter {
|
||||||
|
|
||||||
|
static const char *TAG = "pulse_meter";
|
||||||
|
|
||||||
|
void PulseMeterSensor::setup() {
|
||||||
|
this->pin_->setup();
|
||||||
|
this->isr_pin_ = pin_->to_isr();
|
||||||
|
this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, CHANGE);
|
||||||
|
|
||||||
|
this->last_detected_edge_us_ = 0;
|
||||||
|
this->last_valid_edge_us_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseMeterSensor::loop() {
|
||||||
|
const uint32_t now = micros();
|
||||||
|
|
||||||
|
// 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_)) {
|
||||||
|
ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
|
||||||
|
this->last_detected_edge_us_ = 0;
|
||||||
|
this->last_valid_edge_us_ = 0;
|
||||||
|
this->pulse_width_us_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We quantize our pulse widths to 1 ms to avoid unnecessary jitter
|
||||||
|
const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000;
|
||||||
|
if (this->pulse_width_dedupe_.next(pulse_width_ms)) {
|
||||||
|
if (pulse_width_ms == 0) {
|
||||||
|
// Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while)
|
||||||
|
this->publish_state(0);
|
||||||
|
} else {
|
||||||
|
// Calculate pulses/min from the pulse width in ms
|
||||||
|
this->publish_state((60.0 * 1000.0) / pulse_width_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->total_sensor_ != nullptr) {
|
||||||
|
const uint32_t total = this->total_pulses_;
|
||||||
|
if (this->total_dedupe_.next(total)) {
|
||||||
|
this->total_sensor_->publish_state(total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICACHE_RAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
|
||||||
|
// This is an interrupt handler - we can't call any virtual method from this method
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the first detected pulse (we need at least two pulses to measure the width)
|
||||||
|
if (sensor->last_detected_edge_us_ != 0) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pulse_meter
|
||||||
|
} // namespace esphome
|
42
esphome/components/pulse_meter/pulse_meter_sensor.h
Normal file
42
esphome/components/pulse_meter/pulse_meter_sensor.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/esphal.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pulse_meter {
|
||||||
|
|
||||||
|
class PulseMeterSensor : public sensor::Sensor, public Component {
|
||||||
|
public:
|
||||||
|
void set_pin(GPIOPin *pin) { this->pin_ = pin; }
|
||||||
|
void set_filter_us(uint32_t filter) { this->filter_us_ = filter; }
|
||||||
|
void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; }
|
||||||
|
void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void loop() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void gpio_intr(PulseMeterSensor *sensor);
|
||||||
|
|
||||||
|
GPIOPin *pin_ = nullptr;
|
||||||
|
ISRInternalGPIOPin *isr_pin_;
|
||||||
|
uint32_t filter_us_ = 0;
|
||||||
|
uint32_t timeout_us_ = 1000000UL * 60UL * 5UL;
|
||||||
|
sensor::Sensor *total_sensor_ = nullptr;
|
||||||
|
|
||||||
|
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 pulse_width_us_ = 0;
|
||||||
|
volatile uint32_t total_pulses_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pulse_meter
|
||||||
|
} // namespace esphome
|
58
esphome/components/pulse_meter/sensor.py
Normal file
58
esphome/components/pulse_meter/sensor.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import CONF_ID, CONF_INTERNAL_FILTER, \
|
||||||
|
CONF_PIN, CONF_NUMBER, CONF_TIMEOUT, CONF_TOTAL, \
|
||||||
|
ICON_PULSE, UNIT_PULSES, UNIT_PULSES_PER_MINUTE
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
|
CODEOWNERS = ['@stevebaxter']
|
||||||
|
|
||||||
|
pulse_meter_ns = cg.esphome_ns.namespace('pulse_meter')
|
||||||
|
|
||||||
|
PulseMeterSensor = pulse_meter_ns.class_('PulseMeterSensor',
|
||||||
|
sensor.Sensor,
|
||||||
|
cg.Component)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_internal_filter(value):
|
||||||
|
return cv.positive_time_period_microseconds(value)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_timeout(value):
|
||||||
|
value = cv.positive_time_period_microseconds(value)
|
||||||
|
if value.total_minutes > 70:
|
||||||
|
raise cv.Invalid("Maximum timeout is 70 minutes")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def validate_pulse_meter_pin(value):
|
||||||
|
value = pins.internal_gpio_input_pin_schema(value)
|
||||||
|
if CORE.is_esp8266 and value[CONF_NUMBER] >= 16:
|
||||||
|
raise cv.Invalid("Pins GPIO16 and GPIO17 cannot be used as pulse counters on ESP8266.")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PULSES_PER_MINUTE, ICON_PULSE, 2).extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(PulseMeterSensor),
|
||||||
|
cv.Required(CONF_PIN): validate_pulse_meter_pin,
|
||||||
|
cv.Optional(CONF_INTERNAL_FILTER, default='13us'): validate_internal_filter,
|
||||||
|
cv.Optional(CONF_TIMEOUT, default='5min'): validate_timeout,
|
||||||
|
cv.Optional(CONF_TOTAL): sensor.sensor_schema(UNIT_PULSES, ICON_PULSE, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
pin = yield cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
|
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]))
|
||||||
|
|
||||||
|
if CONF_TOTAL in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_TOTAL])
|
||||||
|
cg.add(var.set_total_sensor(sens))
|
|
@ -36,7 +36,7 @@ esphome/core/* @esphome/core
|
||||||
|
|
||||||
parts = [BASE]
|
parts = [BASE]
|
||||||
|
|
||||||
# Fake some diretory so that get_component works
|
# Fake some directory so that get_component works
|
||||||
CORE.config_path = str(root)
|
CORE.config_path = str(root)
|
||||||
|
|
||||||
codeowners = defaultdict(list)
|
codeowners = defaultdict(list)
|
||||||
|
|
|
@ -626,6 +626,13 @@ sensor:
|
||||||
falling_edge: DECREMENT
|
falling_edge: DECREMENT
|
||||||
internal_filter: 13us
|
internal_filter: 13us
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
|
- platform: pulse_meter
|
||||||
|
name: 'Pulse Meter'
|
||||||
|
pin: GPIO12
|
||||||
|
internal_filter: 100ms
|
||||||
|
timeout: 2 min
|
||||||
|
total:
|
||||||
|
name: 'Pulse Meter Total'
|
||||||
- platform: rotary_encoder
|
- platform: rotary_encoder
|
||||||
name: 'Rotary Encoder'
|
name: 'Rotary Encoder'
|
||||||
id: rotary_encoder1
|
id: rotary_encoder1
|
||||||
|
|
Loading…
Reference in a new issue