Self calibration for ESP32 touch controls

This commit is contained in:
tetele 2024-11-23 01:13:25 +00:00
parent ef7c5c6055
commit 344992c670
3 changed files with 142 additions and 5 deletions

View file

@ -1,27 +1,74 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_INITIAL_VALUE,
CONF_MODE,
CONF_PIN,
CONF_THRESHOLD,
CONF_ID,
CONF_VALUE,
)
from . import esp32_touch_ns, ESP32TouchComponent, validate_touch_pad
from . import ESP32TouchComponent, esp32_touch_ns, validate_touch_pad
DEPENDENCIES = ["esp32_touch", "esp32"]
CONF_ESP32_TOUCH_ID = "esp32_touch_id"
CONF_WAKEUP_THRESHOLD = "wakeup_threshold"
CONF_LOOKBACK_NUM_VALUES = "lookback_num_values"
CONF_SCAN_INTERVAL = "scan_interval"
CONF_MAX_DEVIATION = "max_deviation"
CONF_MAX_CONSECUTIVE_ANOMALIES = "max_consecutive_anomalies"
THRESHOLD_MODE_STATIC = "static"
THRESHOLD_MODE_DYNAMIC = "dynamic"
ESP32TouchBinarySensor = esp32_touch_ns.class_(
"ESP32TouchBinarySensor", binary_sensor.BinarySensor
)
THRESHOLD_SCHEMA_STATIC = cv.Schema(
{
cv.Required(CONF_MODE): THRESHOLD_MODE_STATIC,
cv.Required(CONF_VALUE): cv.uint32_t,
}
)
THRESHOLD_SCHEMA_DYNAMIC = cv.Schema(
{
cv.Required(CONF_MODE): THRESHOLD_MODE_DYNAMIC,
cv.Optional(CONF_INITIAL_VALUE, default=0): cv.uint32_t,
cv.Optional(CONF_LOOKBACK_NUM_VALUES, default=5): cv.int_range(min=1),
cv.Optional(
CONF_SCAN_INTERVAL, default="1s"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_MAX_DEVIATION, default="0.5%"): cv.percentage,
cv.Optional(CONF_MAX_CONSECUTIVE_ANOMALIES, default=10): cv.positive_int,
}
)
def _convert_int_to_threshold(config):
"""Convert legacy threshold value to new format."""
try:
threshold_value = cv.uint32_t(config)
return {CONF_MODE: THRESHOLD_MODE_STATIC, CONF_VALUE: threshold_value}
except cv.Invalid:
return config
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(ESP32TouchBinarySensor).extend(
{
cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent),
cv.Required(CONF_PIN): validate_touch_pad,
cv.Required(CONF_THRESHOLD): cv.uint32_t,
cv.Required(CONF_THRESHOLD): cv.All(
_convert_int_to_threshold,
cv.Any(
THRESHOLD_SCHEMA_STATIC.schema,
THRESHOLD_SCHEMA_DYNAMIC.schema,
),
),
cv.Optional(CONF_WAKEUP_THRESHOLD, default=0): cv.uint32_t,
}
)
@ -32,8 +79,26 @@ async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_PIN],
config[CONF_THRESHOLD],
(
config[CONF_THRESHOLD][CONF_VALUE]
if config[CONF_THRESHOLD][CONF_MODE] == THRESHOLD_MODE_STATIC
else config[CONF_THRESHOLD][CONF_INITIAL_VALUE]
),
config[CONF_WAKEUP_THRESHOLD],
)
await binary_sensor.register_binary_sensor(var, config)
cg.add(hub.register_touch_pad(var))
if config[CONF_THRESHOLD][CONF_MODE] == THRESHOLD_MODE_DYNAMIC:
cg.add(var.set_max_deviation(config[CONF_THRESHOLD][CONF_MAX_DEVIATION]))
cg.add(
var.set_max_consecutive_anomalies(
config[CONF_THRESHOLD][CONF_MAX_CONSECUTIVE_ANOMALIES]
)
)
cg.add(
var.start_calibration(
config[CONF_THRESHOLD][CONF_SCAN_INTERVAL],
config[CONF_THRESHOLD][CONF_LOOKBACK_NUM_VALUES],
)
)

View file

@ -287,6 +287,10 @@ void ESP32TouchComponent::loop() {
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
for (auto *child : this->children_) {
child->value_ = this->component_touch_pad_read(child->get_touch_pad());
if (child->dynamic_calibration_ && (now - child->last_calibration_timestamp_ > child->calibration_interval_)) {
child->insert_value_();
child->last_calibration_timestamp_ += child->last_calibration_timestamp_ ? child->calibration_interval_ : now;
}
#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
child->publish_state(child->value_ < child->get_threshold());
#else
@ -340,6 +344,57 @@ void ESP32TouchComponent::on_shutdown() {
ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold)
: touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
void ESP32TouchBinarySensor::set_max_deviation(float max_deviation) { this->max_deviation_ = max_deviation; }
void ESP32TouchBinarySensor::set_max_consecutive_anomalies(float max_consecutive_anomalies) {
this->max_consecutive_anomalies_ = max_consecutive_anomalies;
}
void ESP32TouchBinarySensor::start_calibration(uint32_t interval, uint16_t num_values) {
this->dynamic_calibration_ = true;
this->max_prev_values_ = num_values;
this->prev_values_.resize(0);
this->prev_values_.push_front(this->threshold_);
this->sum_values_ = this->threshold_;
this->calibration_interval_ = interval;
this->last_calibration_timestamp_ = 0;
this->consecutive_anomalies_ = 0;
}
float ESP32TouchBinarySensor::get_average_value_() {
if (this->prev_values_.size() == 0)
return 0;
return uint32_t(this->sum_values_ / float(this->prev_values_.size()));
}
void ESP32TouchBinarySensor::insert_value_() {
float avg = this->get_average_value_();
if (fabs(float(this->value_) - avg) / avg > this->max_deviation_) {
this->consecutive_anomalies_++;
if (this->consecutive_anomalies_ < this->max_consecutive_anomalies_)
return;
}
this->consecutive_anomalies_ = 0;
this->prev_values_.push_front(this->value_);
this->sum_values_ += this->value_;
while (this->prev_values_.size() > this->max_prev_values_) {
this->sum_values_ -= this->prev_values_.back();
this->prev_values_.pop_back();
}
#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
this->threshold_ =
uint32_t(float(this->sum_values_) / float(this->prev_values_.size()) * (1.0 - this->max_deviation_));
#else
this->threshold_ =
uint32_t(float(this->sum_values_) / float(this->prev_values_.size()) * (1.0 + this->max_deviation_));
#endif
}
} // namespace esp32_touch
} // namespace esphome

View file

@ -7,6 +7,7 @@
#include <esp_idf_version.h>
#include <vector>
#include <deque>
#include <driver/touch_sensor.h>
@ -106,6 +107,9 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
void set_threshold(uint32_t threshold) { this->threshold_ = threshold; }
uint32_t get_value() const { return this->value_; }
uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; }
void set_max_deviation(float max_deviation);
void set_max_consecutive_anomalies(float max_consecutive_anomalies);
void start_calibration(uint32_t interval, uint16_t num_values);
protected:
friend ESP32TouchComponent;
@ -114,6 +118,19 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
uint32_t threshold_{0};
uint32_t value_{0};
const uint32_t wakeup_threshold_{0};
bool dynamic_calibration_{false};
float max_deviation_{0};
std::deque<uint32_t> prev_values_;
uint32_t sum_values_{0};
uint16_t max_prev_values_{0};
uint32_t last_calibration_timestamp_{0};
uint32_t calibration_interval_{0};
uint16_t consecutive_anomalies_{0};
uint16_t max_consecutive_anomalies_{0};
float get_average_value_();
void insert_value_();
};
} // namespace esp32_touch