mirror of
https://github.com/esphome/esphome.git
synced 2024-12-24 14:34:54 +01:00
pulse_counter: Restore original pulse counter
This commit is contained in:
parent
2b51fe7f95
commit
7e22e7f813
5 changed files with 457 additions and 0 deletions
0
esphome/components/pulse_counter/__init__.py
Normal file
0
esphome/components/pulse_counter/__init__.py
Normal file
24
esphome/components/pulse_counter/automation.h
Normal file
24
esphome/components/pulse_counter/automation.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/pulse_counter/pulse_counter_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace pulse_counter {
|
||||
|
||||
template<typename... Ts> class SetTotalPulsesAction : public Action<Ts...> {
|
||||
public:
|
||||
SetTotalPulsesAction(PulseCounterSensor *pulse_counter) : pulse_counter_(pulse_counter) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint32_t, total_pulses)
|
||||
|
||||
void play(Ts... x) override { this->pulse_counter_->set_total_pulses(this->total_pulses_.value(x...)); }
|
||||
|
||||
protected:
|
||||
PulseCounterSensor *pulse_counter_;
|
||||
};
|
||||
|
||||
} // namespace pulse_counter
|
||||
} // namespace esphome
|
187
esphome/components/pulse_counter/pulse_counter_sensor.cpp
Normal file
187
esphome/components/pulse_counter/pulse_counter_sensor.cpp
Normal file
|
@ -0,0 +1,187 @@
|
|||
#include "pulse_counter_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pulse_counter {
|
||||
|
||||
static const char *const TAG = "pulse_counter";
|
||||
|
||||
const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"};
|
||||
|
||||
#ifdef HAS_PCNT
|
||||
PulseCounterStorageBase *get_storage(bool hw_pcnt) {
|
||||
return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage)
|
||||
: (PulseCounterStorageBase *) (new BasicPulseCounterStorage));
|
||||
}
|
||||
#else
|
||||
PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; }
|
||||
#endif
|
||||
|
||||
void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) {
|
||||
const uint32_t now = micros();
|
||||
const bool discard = now - arg->last_pulse < arg->filter_us;
|
||||
arg->last_pulse = now;
|
||||
if (discard)
|
||||
return;
|
||||
|
||||
PulseCounterCountMode mode = arg->isr_pin.digital_read() ? arg->rising_edge_mode : arg->falling_edge_mode;
|
||||
switch (mode) {
|
||||
case PULSE_COUNTER_DISABLE:
|
||||
break;
|
||||
case PULSE_COUNTER_INCREMENT:
|
||||
arg->counter++;
|
||||
break;
|
||||
case PULSE_COUNTER_DECREMENT:
|
||||
arg->counter--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
this->pin = pin;
|
||||
this->pin->setup();
|
||||
this->isr_pin = this->pin->to_isr();
|
||||
this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
|
||||
return true;
|
||||
}
|
||||
pulse_counter_t BasicPulseCounterStorage::read_raw_value() {
|
||||
pulse_counter_t counter = this->counter;
|
||||
pulse_counter_t ret = counter - this->last_value;
|
||||
this->last_value = counter;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef HAS_PCNT
|
||||
bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0;
|
||||
this->pin = pin;
|
||||
this->pin->setup();
|
||||
this->pcnt_unit = next_pcnt_unit;
|
||||
next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " PCNT Unit Number: %u", this->pcnt_unit);
|
||||
|
||||
pcnt_count_mode_t rising = PCNT_COUNT_DIS, falling = PCNT_COUNT_DIS;
|
||||
switch (this->rising_edge_mode) {
|
||||
case PULSE_COUNTER_DISABLE:
|
||||
rising = PCNT_COUNT_DIS;
|
||||
break;
|
||||
case PULSE_COUNTER_INCREMENT:
|
||||
rising = PCNT_COUNT_INC;
|
||||
break;
|
||||
case PULSE_COUNTER_DECREMENT:
|
||||
rising = PCNT_COUNT_DEC;
|
||||
break;
|
||||
}
|
||||
switch (this->falling_edge_mode) {
|
||||
case PULSE_COUNTER_DISABLE:
|
||||
falling = PCNT_COUNT_DIS;
|
||||
break;
|
||||
case PULSE_COUNTER_INCREMENT:
|
||||
falling = PCNT_COUNT_INC;
|
||||
break;
|
||||
case PULSE_COUNTER_DECREMENT:
|
||||
falling = PCNT_COUNT_DEC;
|
||||
break;
|
||||
}
|
||||
|
||||
pcnt_config_t pcnt_config = {
|
||||
.pulse_gpio_num = this->pin->get_pin(),
|
||||
.ctrl_gpio_num = PCNT_PIN_NOT_USED,
|
||||
.lctrl_mode = PCNT_MODE_KEEP,
|
||||
.hctrl_mode = PCNT_MODE_KEEP,
|
||||
.pos_mode = rising,
|
||||
.neg_mode = falling,
|
||||
.counter_h_lim = 0,
|
||||
.counter_l_lim = 0,
|
||||
.unit = this->pcnt_unit,
|
||||
.channel = PCNT_CHANNEL_0,
|
||||
};
|
||||
esp_err_t error = pcnt_unit_config(&pcnt_config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Configuring Pulse Counter failed: %s", esp_err_to_name(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->filter_us != 0) {
|
||||
uint16_t filter_val = std::min(static_cast<unsigned int>(this->filter_us * 80u), 1023u);
|
||||
ESP_LOGCONFIG(TAG, " Filter Value: %" PRIu32 "us (val=%u)", this->filter_us, filter_val);
|
||||
error = pcnt_set_filter_value(this->pcnt_unit, filter_val);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Setting filter value failed: %s", esp_err_to_name(error));
|
||||
return false;
|
||||
}
|
||||
error = pcnt_filter_enable(this->pcnt_unit);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Enabling filter failed: %s", esp_err_to_name(error));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = pcnt_counter_pause(this->pcnt_unit);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Pausing pulse counter failed: %s", esp_err_to_name(error));
|
||||
return false;
|
||||
}
|
||||
error = pcnt_counter_clear(this->pcnt_unit);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Clearing pulse counter failed: %s", esp_err_to_name(error));
|
||||
return false;
|
||||
}
|
||||
error = pcnt_counter_resume(this->pcnt_unit);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Resuming pulse counter failed: %s", esp_err_to_name(error));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
pulse_counter_t HwPulseCounterStorage::read_raw_value() {
|
||||
pulse_counter_t counter;
|
||||
pcnt_get_counter_value(this->pcnt_unit, &counter);
|
||||
pulse_counter_t ret = counter - this->last_value;
|
||||
this->last_value = counter;
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void PulseCounterSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str());
|
||||
if (!this->storage_.pulse_counter_setup(this->pin_)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PulseCounterSensor::set_total_pulses(uint32_t pulses) {
|
||||
this->current_total_ = pulses;
|
||||
this->total_sensor_->publish_state(pulses);
|
||||
}
|
||||
|
||||
void PulseCounterSensor::dump_config() {
|
||||
LOG_SENSOR("", "Pulse Counter", this);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " Rising Edge: %s", EDGE_MODE_TO_STRING[this->storage_.rising_edge_mode]);
|
||||
ESP_LOGCONFIG(TAG, " Falling Edge: %s", EDGE_MODE_TO_STRING[this->storage_.falling_edge_mode]);
|
||||
ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %" PRIu32 " µs", this->storage_.filter_us);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void PulseCounterSensor::update() {
|
||||
pulse_counter_t raw = this->storage_.read_raw_value();
|
||||
uint32_t now = millis();
|
||||
if (this->last_time_ != 0) {
|
||||
uint32_t interval = now - this->last_time_;
|
||||
float value = (60000.0f * raw) / float(interval); // per minute
|
||||
ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value);
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
if (this->total_sensor_ != nullptr) {
|
||||
current_total_ += raw;
|
||||
ESP_LOGD(TAG, "'%s': Total : %" PRIu32 " pulses", this->get_name().c_str(), current_total_);
|
||||
this->total_sensor_->publish_state(current_total_);
|
||||
}
|
||||
this->last_time_ = now;
|
||||
}
|
||||
|
||||
} // namespace pulse_counter
|
||||
} // namespace esphome
|
90
esphome/components/pulse_counter/pulse_counter_sensor.h
Normal file
90
esphome/components/pulse_counter/pulse_counter_sensor.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#include <driver/pcnt.h>
|
||||
#define HAS_PCNT
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace pulse_counter {
|
||||
|
||||
enum PulseCounterCountMode {
|
||||
PULSE_COUNTER_DISABLE = 0,
|
||||
PULSE_COUNTER_INCREMENT,
|
||||
PULSE_COUNTER_DECREMENT,
|
||||
};
|
||||
|
||||
#ifdef HAS_PCNT
|
||||
using pulse_counter_t = int16_t;
|
||||
#else
|
||||
using pulse_counter_t = int32_t;
|
||||
#endif
|
||||
|
||||
struct PulseCounterStorageBase {
|
||||
virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0;
|
||||
virtual pulse_counter_t read_raw_value() = 0;
|
||||
|
||||
InternalGPIOPin *pin;
|
||||
PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT};
|
||||
PulseCounterCountMode falling_edge_mode{PULSE_COUNTER_DISABLE};
|
||||
uint32_t filter_us{0};
|
||||
pulse_counter_t last_value{0};
|
||||
};
|
||||
|
||||
struct BasicPulseCounterStorage : public PulseCounterStorageBase {
|
||||
static void gpio_intr(BasicPulseCounterStorage *arg);
|
||||
|
||||
bool pulse_counter_setup(InternalGPIOPin *pin) override;
|
||||
pulse_counter_t read_raw_value() override;
|
||||
|
||||
volatile pulse_counter_t counter{0};
|
||||
volatile uint32_t last_pulse{0};
|
||||
|
||||
ISRInternalGPIOPin isr_pin;
|
||||
};
|
||||
|
||||
#ifdef HAS_PCNT
|
||||
struct HwPulseCounterStorage : public PulseCounterStorageBase {
|
||||
bool pulse_counter_setup(InternalGPIOPin *pin) override;
|
||||
pulse_counter_t read_raw_value() override;
|
||||
|
||||
pcnt_unit_t pcnt_unit;
|
||||
};
|
||||
#endif
|
||||
|
||||
PulseCounterStorageBase *get_storage(bool hw_pcnt = false);
|
||||
|
||||
class PulseCounterSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
explicit PulseCounterSensor(bool hw_pcnt = false) : storage_(*get_storage(hw_pcnt)) {}
|
||||
|
||||
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
||||
void set_rising_edge_mode(PulseCounterCountMode mode) { storage_.rising_edge_mode = mode; }
|
||||
void set_falling_edge_mode(PulseCounterCountMode mode) { storage_.falling_edge_mode = mode; }
|
||||
void set_filter_us(uint32_t filter) { storage_.filter_us = filter; }
|
||||
void set_total_sensor(sensor::Sensor *total_sensor) { total_sensor_ = total_sensor; }
|
||||
|
||||
void set_total_pulses(uint32_t pulses);
|
||||
|
||||
/// Unit of measurement is "pulses/min".
|
||||
void setup() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *pin_;
|
||||
PulseCounterStorageBase &storage_;
|
||||
uint32_t last_time_{0};
|
||||
uint32_t current_total_{0};
|
||||
sensor::Sensor *total_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace pulse_counter
|
||||
} // namespace esphome
|
156
esphome/components/pulse_counter/sensor.py
Normal file
156
esphome/components/pulse_counter/sensor.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation, pins
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_COUNT_MODE,
|
||||
CONF_FALLING_EDGE,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL_FILTER,
|
||||
CONF_PIN,
|
||||
CONF_RISING_EDGE,
|
||||
CONF_NUMBER,
|
||||
CONF_TOTAL,
|
||||
CONF_VALUE,
|
||||
ICON_PULSE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_PULSES_PER_MINUTE,
|
||||
UNIT_PULSES,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
CONF_USE_PCNT = "use_pcnt"
|
||||
|
||||
pulse_counter_ns = cg.esphome_ns.namespace("pulse_counter")
|
||||
PulseCounterCountMode = pulse_counter_ns.enum("PulseCounterCountMode")
|
||||
COUNT_MODES = {
|
||||
"DISABLE": PulseCounterCountMode.PULSE_COUNTER_DISABLE,
|
||||
"INCREMENT": PulseCounterCountMode.PULSE_COUNTER_INCREMENT,
|
||||
"DECREMENT": PulseCounterCountMode.PULSE_COUNTER_DECREMENT,
|
||||
}
|
||||
|
||||
COUNT_MODE_SCHEMA = cv.enum(COUNT_MODES, upper=True)
|
||||
|
||||
PulseCounterSensor = pulse_counter_ns.class_(
|
||||
"PulseCounterSensor", sensor.Sensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
SetTotalPulsesAction = pulse_counter_ns.class_(
|
||||
"SetTotalPulsesAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
def validate_internal_filter(value):
|
||||
use_pcnt = value.get(CONF_USE_PCNT)
|
||||
if CORE.is_esp8266 and use_pcnt:
|
||||
raise cv.Invalid(
|
||||
"Using hardware PCNT is only available on ESP32",
|
||||
[CONF_USE_PCNT],
|
||||
)
|
||||
|
||||
if CORE.is_esp32 and use_pcnt:
|
||||
if value.get(CONF_INTERNAL_FILTER).total_microseconds > 13:
|
||||
raise cv.Invalid(
|
||||
"Maximum internal filter value when using ESP32 hardware PCNT is 13us",
|
||||
[CONF_INTERNAL_FILTER],
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def validate_pulse_counter_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
|
||||
|
||||
|
||||
def validate_count_mode(value):
|
||||
rising_edge = value[CONF_RISING_EDGE]
|
||||
falling_edge = value[CONF_FALLING_EDGE]
|
||||
if rising_edge == "DISABLE" and falling_edge == "DISABLE":
|
||||
raise cv.Invalid(
|
||||
"Can't set both count modes to DISABLE! This means no counting occurs at "
|
||||
"all!"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
PulseCounterSensor,
|
||||
unit_of_measurement=UNIT_PULSES_PER_MINUTE,
|
||||
icon=ICON_PULSE,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_PIN): validate_pulse_counter_pin,
|
||||
cv.Optional(
|
||||
CONF_COUNT_MODE,
|
||||
default={
|
||||
CONF_RISING_EDGE: "INCREMENT",
|
||||
CONF_FALLING_EDGE: "DISABLE",
|
||||
},
|
||||
): cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_RISING_EDGE): COUNT_MODE_SCHEMA,
|
||||
cv.Required(CONF_FALLING_EDGE): COUNT_MODE_SCHEMA,
|
||||
}
|
||||
),
|
||||
validate_count_mode,
|
||||
),
|
||||
cv.SplitDefault(CONF_USE_PCNT, esp32=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_INTERNAL_FILTER, default="13us"
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Optional(CONF_TOTAL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PULSES,
|
||||
icon=ICON_PULSE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
},
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
validate_internal_filter,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config, config.get(CONF_USE_PCNT))
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
count = config[CONF_COUNT_MODE]
|
||||
cg.add(var.set_rising_edge_mode(count[CONF_RISING_EDGE]))
|
||||
cg.add(var.set_falling_edge_mode(count[CONF_FALLING_EDGE]))
|
||||
cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER]))
|
||||
|
||||
if CONF_TOTAL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL])
|
||||
cg.add(var.set_total_sensor(sens))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"pulse_counter.set_total_pulses",
|
||||
SetTotalPulsesAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(PulseCounterSensor),
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.uint32_t),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def set_total_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, int)
|
||||
cg.add(var.set_total_pulses(template_))
|
||||
return var
|
Loading…
Reference in a new issue