From 3c2766448d7e6f077af42dee7cfcf64c9b4eaa82 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 10 Oct 2022 23:10:22 +0200 Subject: [PATCH] Refactor xpt2046 to be a touchscreen platform (#3793) --- CODEOWNERS | 2 +- esphome/components/animation/__init__.py | 2 +- esphome/components/xpt2046/__init__.py | 128 +-------------- esphome/components/xpt2046/binary_sensor.py | 54 +------ esphome/components/xpt2046/touchscreen.py | 116 ++++++++++++++ esphome/components/xpt2046/xpt2046.cpp | 168 ++++++++++---------- esphome/components/xpt2046/xpt2046.h | 57 +++---- tests/test4.yaml | 55 +++---- 8 files changed, 248 insertions(+), 334 deletions(-) create mode 100644 esphome/components/xpt2046/touchscreen.py diff --git a/CODEOWNERS b/CODEOWNERS index d95ebcce59..b04b480780 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -258,4 +258,4 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_rtcgq02lm/* @jesserockz -esphome/components/xpt2046/* @numo68 +esphome/components/xpt2046/* @nielsnl68 @numo68 diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 3a150146e2..87d72254e8 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["display"] MULTI_CONF = True -Animation_ = display.display_ns.class_("Animation") +Animation_ = display.display_ns.class_("Animation", espImage.Image_) ANIMATION_SCHEMA = cv.Schema( { diff --git a/esphome/components/xpt2046/__init__.py b/esphome/components/xpt2046/__init__.py index 3de89a6425..3b8a925bb2 100644 --- a/esphome/components/xpt2046/__init__.py +++ b/esphome/components/xpt2046/__init__.py @@ -1,129 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation -from esphome import pins -from esphome.components import spi -from esphome.const import CONF_ID, CONF_ON_STATE, CONF_THRESHOLD, CONF_TRIGGER_ID -CODEOWNERS = ["@numo68"] -AUTO_LOAD = ["binary_sensor"] -DEPENDENCIES = ["spi"] -MULTI_CONF = True - -CONF_REPORT_INTERVAL = "report_interval" -CONF_CALIBRATION_X_MIN = "calibration_x_min" -CONF_CALIBRATION_X_MAX = "calibration_x_max" -CONF_CALIBRATION_Y_MIN = "calibration_y_min" -CONF_CALIBRATION_Y_MAX = "calibration_y_max" -CONF_DIMENSION_X = "dimension_x" -CONF_DIMENSION_Y = "dimension_y" -CONF_SWAP_X_Y = "swap_x_y" -CONF_IRQ_PIN = "irq_pin" - -xpt2046_ns = cg.esphome_ns.namespace("xpt2046") -CONF_XPT2046_ID = "xpt2046_id" - -XPT2046Component = xpt2046_ns.class_( - "XPT2046Component", cg.PollingComponent, spi.SPIDevice +CONFIG_SCHEMA = cv.invalid( + "This component sould now be used as platform of the Touchscreen component." ) - -XPT2046OnStateTrigger = xpt2046_ns.class_( - "XPT2046OnStateTrigger", automation.Trigger.template(cg.int_, cg.int_, cg.bool_) -) - - -def validate_xpt2046(config): - if ( - abs( - cv.int_(config[CONF_CALIBRATION_X_MAX]) - - cv.int_(config[CONF_CALIBRATION_X_MIN]) - ) - < 1000 - ): - raise cv.Invalid("Calibration X values difference < 1000") - - if ( - abs( - cv.int_(config[CONF_CALIBRATION_Y_MAX]) - - cv.int_(config[CONF_CALIBRATION_Y_MIN]) - ) - < 1000 - ): - raise cv.Invalid("Calibration Y values difference < 1000") - - return config - - -def report_interval(value): - if value == "never": - return 4294967295 # uint32_t max - return cv.positive_time_period_milliseconds(value) - - -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(XPT2046Component), - cv.Optional(CONF_IRQ_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_DIMENSION_X, default=100): cv.positive_not_null_int, - cv.Optional(CONF_DIMENSION_Y, default=100): cv.positive_not_null_int, - cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095), - cv.Optional(CONF_REPORT_INTERVAL, default="never"): report_interval, - cv.Optional(CONF_SWAP_X_Y, default=False): cv.boolean, - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - XPT2046OnStateTrigger - ), - } - ), - } - ) - .extend(cv.polling_component_schema("50ms")) - .extend(spi.spi_device_schema()), - validate_xpt2046, -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await spi.register_spi_device(var, config) - - cg.add(var.set_threshold(config[CONF_THRESHOLD])) - cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL])) - cg.add(var.set_dimensions(config[CONF_DIMENSION_X], config[CONF_DIMENSION_Y])) - cg.add( - var.set_calibration( - config[CONF_CALIBRATION_X_MIN], - config[CONF_CALIBRATION_X_MAX], - config[CONF_CALIBRATION_Y_MIN], - config[CONF_CALIBRATION_Y_MAX], - ) - ) - - if CONF_SWAP_X_Y in config: - cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y])) - - if CONF_IRQ_PIN in config: - pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) - cg.add(var.set_irq_pin(pin)) - - for conf in config.get(CONF_ON_STATE, []): - await automation.build_automation( - var.get_on_state_trigger(), - [(cg.int_, "x"), (cg.int_, "y"), (cg.bool_, "touched")], - conf, - ) diff --git a/esphome/components/xpt2046/binary_sensor.py b/esphome/components/xpt2046/binary_sensor.py index 6ec09a2295..5a6cfe4919 100644 --- a/esphome/components/xpt2046/binary_sensor.py +++ b/esphome/components/xpt2046/binary_sensor.py @@ -1,55 +1,3 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor -from . import ( - xpt2046_ns, - XPT2046Component, - CONF_XPT2046_ID, -) - -CONF_X_MIN = "x_min" -CONF_X_MAX = "x_max" -CONF_Y_MIN = "y_min" -CONF_Y_MAX = "y_max" - -DEPENDENCIES = ["xpt2046"] -XPT2046Button = xpt2046_ns.class_("XPT2046Button", binary_sensor.BinarySensor) - - -def validate_xpt2046_button(config): - if cv.int_(config[CONF_X_MAX]) < cv.int_(config[CONF_X_MIN]) or cv.int_( - config[CONF_Y_MAX] - ) < cv.int_(config[CONF_Y_MIN]): - raise cv.Invalid("x_max is less than x_min or y_max is less than y_min") - - return config - - -CONFIG_SCHEMA = cv.All( - binary_sensor.binary_sensor_schema(XPT2046Button).extend( - { - cv.GenerateID(CONF_XPT2046_ID): cv.use_id(XPT2046Component), - cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095), - cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095), - cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=4095), - cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=4095), - } - ), - validate_xpt2046_button, -) - - -async def to_code(config): - var = await binary_sensor.new_binary_sensor(config) - hub = await cg.get_variable(config[CONF_XPT2046_ID]) - cg.add( - var.set_area( - config[CONF_X_MIN], - config[CONF_X_MAX], - config[CONF_Y_MIN], - config[CONF_Y_MAX], - ) - ) - - cg.add(hub.register_button(var)) +CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.") diff --git a/esphome/components/xpt2046/touchscreen.py b/esphome/components/xpt2046/touchscreen.py new file mode 100644 index 0000000000..868525ccb1 --- /dev/null +++ b/esphome/components/xpt2046/touchscreen.py @@ -0,0 +1,116 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import spi, touchscreen +from esphome.const import CONF_ID, CONF_THRESHOLD + +CODEOWNERS = ["@numo68", "@nielsnl68"] +DEPENDENCIES = ["spi"] + +XPT2046_ns = cg.esphome_ns.namespace("xpt2046") +XPT2046Component = XPT2046_ns.class_( + "XPT2046Component", touchscreen.Touchscreen, cg.PollingComponent, spi.SPIDevice +) + +CONF_INTERRUPT_PIN = "interrupt_pin" + +CONF_REPORT_INTERVAL = "report_interval" +CONF_CALIBRATION_X_MIN = "calibration_x_min" +CONF_CALIBRATION_X_MAX = "calibration_x_max" +CONF_CALIBRATION_Y_MIN = "calibration_y_min" +CONF_CALIBRATION_Y_MAX = "calibration_y_max" +CONF_SWAP_X_Y = "swap_x_y" + +# obsolete Keys +CONF_DIMENSION_X = "dimension_x" +CONF_DIMENSION_Y = "dimension_y" +CONF_IRQ_PIN = "irq_pin" + + +def validate_xpt2046(config): + if ( + abs( + cv.int_(config[CONF_CALIBRATION_X_MAX]) + - cv.int_(config[CONF_CALIBRATION_X_MIN]) + ) + < 1000 + ): + raise cv.Invalid("Calibration X values difference < 1000") + + if ( + abs( + cv.int_(config[CONF_CALIBRATION_Y_MAX]) + - cv.int_(config[CONF_CALIBRATION_Y_MIN]) + ) + < 1000 + ): + raise cv.Invalid("Calibration Y values difference < 1000") + + return config + + +def report_interval(value): + if value == "never": + return 4294967295 # uint32_t max + return cv.positive_time_period_milliseconds(value) + + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XPT2046Component), + cv.Optional(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range( + min=0, max=4095 + ), + cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range( + min=0, max=4095 + ), + cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range( + min=0, max=4095 + ), + cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range( + min=0, max=4095 + ), + cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095), + cv.Optional(CONF_REPORT_INTERVAL, default="never"): report_interval, + cv.Optional(CONF_SWAP_X_Y, default=False): cv.boolean, + # obsolete Keys + cv.Optional(CONF_IRQ_PIN): cv.invalid("Rename IRQ_PIN to INTERUPT_PIN"), + cv.Optional(CONF_DIMENSION_X): cv.invalid( + "This key is now obsolete, please remove it" + ), + cv.Optional(CONF_DIMENSION_Y): cv.invalid( + "This key is now obsolete, please remove it" + ), + }, + ) + .extend(cv.polling_component_schema("50ms")) + .extend(spi.spi_device_schema()), +).add_extra(validate_xpt2046) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + await touchscreen.register_touchscreen(var, config) + + cg.add(var.set_threshold(config[CONF_THRESHOLD])) + cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL])) + cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y])) + cg.add( + var.set_calibration( + config[CONF_CALIBRATION_X_MIN], + config[CONF_CALIBRATION_X_MAX], + config[CONF_CALIBRATION_Y_MIN], + config[CONF_CALIBRATION_Y_MAX], + ) + ) + + if CONF_INTERRUPT_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_irq_pin(pin)) diff --git a/esphome/components/xpt2046/xpt2046.cpp b/esphome/components/xpt2046/xpt2046.cpp index aaadeea52e..d6cbe39aa0 100644 --- a/esphome/components/xpt2046/xpt2046.cpp +++ b/esphome/components/xpt2046/xpt2046.cpp @@ -9,31 +9,38 @@ namespace xpt2046 { static const char *const TAG = "xpt2046"; +void XPT2046TouchscreenStore::gpio_intr(XPT2046TouchscreenStore *store) { store->touch = true; } + void XPT2046Component::setup() { if (this->irq_pin_ != nullptr) { // The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state // while the channels are read and wiring it as an interrupt is not straightforward and would // need careful masking. A GPIO poll is cheap so we'll just use that. + this->irq_pin_->setup(); // INPUT + this->irq_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->irq_pin_->setup(); + + this->store_.pin = this->irq_pin_->to_isr(); + this->irq_pin_->attach_interrupt(XPT2046TouchscreenStore::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE); } spi_setup(); read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin } void XPT2046Component::loop() { - if (this->irq_pin_ != nullptr) { - // Force immediate update if a falling edge (= touched is seen) Ignore if still active - // (that would mean that we missed the release because of a too long update interval) - bool val = this->irq_pin_->digital_read(); - if (!val && this->last_irq_ && !this->touched) { - ESP_LOGD(TAG, "Falling penirq edge, forcing update"); - update(); - } - this->last_irq_ = val; - } + if ((this->irq_pin_ == nullptr) || (!this->store_.touch)) + return; + this->store_.touch = false; + check_touch_(); } void XPT2046Component::update() { + if (this->irq_pin_ == nullptr) + check_touch_(); +} + +void XPT2046Component::check_touch_() { int16_t data[6]; bool touch = false; uint32_t now = millis(); @@ -42,13 +49,13 @@ void XPT2046Component::update() { // In case the penirq pin is present only do the SPI transaction if it reports a touch (is low). // The touch has to be also confirmed with checking the pressure over threshold - if (this->irq_pin_ == nullptr || !this->irq_pin_->digital_read()) { + if ((this->irq_pin_ == nullptr) || !this->irq_pin_->digital_read()) { enable(); - int16_t z1 = read_adc_(0xB1 /* Z1 */); - int16_t z2 = read_adc_(0xC1 /* Z2 */); + int16_t touch_pressure_1 = read_adc_(0xB1 /* touch_pressure_1 */); + int16_t touch_pressure_2 = read_adc_(0xC1 /* touch_pressure_2 */); - this->z_raw = z1 + 4095 - z2; + this->z_raw = touch_pressure_1 + 4095 - touch_pressure_2; touch = (this->z_raw >= this->threshold_); if (touch) { @@ -63,64 +70,73 @@ void XPT2046Component::update() { data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down disable(); - } - if (touch) { - this->x_raw = best_two_avg(data[0], data[2], data[4]); - this->y_raw = best_two_avg(data[1], data[3], data[5]); - } else { - this->x_raw = this->y_raw = 0; - } + if (touch) { + this->x_raw = best_two_avg(data[0], data[2], data[4]); + this->y_raw = best_two_avg(data[1], data[3], data[5]); - ESP_LOGV(TAG, "Update [x, y] = [%d, %d], z = %d%s", this->x_raw, this->y_raw, this->z_raw, (touch ? " touched" : "")); + ESP_LOGVV(TAG, "Update [x, y] = [%d, %d], z = %d", this->x_raw, this->y_raw, this->z_raw); - if (touch) { - // Normalize raw data according to calibration min and max + TouchPoint touchpoint; - int16_t x_val = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_); - int16_t y_val = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_); + touchpoint.x = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_); + touchpoint.y = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_); - if (this->swap_x_y_) { - std::swap(x_val, y_val); - } + if (this->swap_x_y_) { + std::swap(touchpoint.x, touchpoint.y); + } - if (this->invert_x_) { - x_val = 0x7fff - x_val; - } + if (this->invert_x_) { + touchpoint.x = 0xfff - touchpoint.x; + } - if (this->invert_y_) { - y_val = 0x7fff - y_val; - } + if (this->invert_y_) { + touchpoint.y = 0xfff - touchpoint.y; + } - x_val = (int16_t)((int) x_val * this->x_dim_ / 0x7fff); - y_val = (int16_t)((int) y_val * this->y_dim_ / 0x7fff); + switch (static_cast(this->display_->get_rotation())) { + case ROTATE_0_DEGREES: + break; + case ROTATE_90_DEGREES: + std::swap(touchpoint.x, touchpoint.y); + touchpoint.y = 0xfff - touchpoint.y; + break; + case ROTATE_180_DEGREES: + touchpoint.x = 0xfff - touchpoint.x; + touchpoint.y = 0xfff - touchpoint.y; + break; + case ROTATE_270_DEGREES: + std::swap(touchpoint.x, touchpoint.y); + touchpoint.x = 0xfff - touchpoint.x; + break; + } - if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) { - ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val); + touchpoint.x = (int16_t)((int) touchpoint.x * this->display_->get_width() / 0xfff); + touchpoint.y = (int16_t)((int) touchpoint.y * this->display_->get_height() / 0xfff); - this->x = x_val; - this->y = y_val; - this->touched = true; - this->last_pos_ms_ = now; + if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) { + ESP_LOGV(TAG, "Touching at [%03X, %03X] => [%3d, %3d]", this->x_raw, this->y_raw, touchpoint.x, touchpoint.y); - this->on_state_trigger_->process(this->x, this->y, true); - for (auto *button : this->buttons_) - button->touch(this->x, this->y); - } - } else { - if (this->touched) { - ESP_LOGD(TAG, "Released [%d, %d]", this->x, this->y); + this->defer([this, touchpoint]() { this->send_touch_(touchpoint); }); - this->touched = false; - - this->on_state_trigger_->process(this->x, this->y, false); - for (auto *button : this->buttons_) - button->release(); + this->x = touchpoint.x; + this->y = touchpoint.y; + this->touched = true; + this->last_pos_ms_ = now; + } + } else { + this->x_raw = this->y_raw = 0; + if (this->touched) { + ESP_LOGV(TAG, "Released [%d, %d]", this->x, this->y); + this->touched = false; + for (auto *listener : this->touch_listeners_) + listener->release(); + } } } } -void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { +void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { // NOLINT this->x_raw_min_ = std::min(x_min, x_max); this->x_raw_max_ = std::max(x_min, x_max); this->y_raw_min_ = std::min(y_min, y_max); @@ -137,11 +153,11 @@ void XPT2046Component::dump_config() { ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_); ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_); ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_); - ESP_LOGCONFIG(TAG, " X dim: %d", this->x_dim_); - ESP_LOGCONFIG(TAG, " Y dim: %d", this->y_dim_); - if (this->swap_x_y_) { - ESP_LOGCONFIG(TAG, " Swap X/Y"); - } + + ESP_LOGCONFIG(TAG, " Swap X/Y: %s", YESNO(this->swap_x_y_)); + ESP_LOGCONFIG(TAG, " Invert X: %s", YESNO(this->invert_x_)); + ESP_LOGCONFIG(TAG, " Invert Y: %s", YESNO(this->invert_y_)); + ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_); ESP_LOGCONFIG(TAG, " Report interval: %u", this->report_millis_); @@ -150,8 +166,8 @@ void XPT2046Component::dump_config() { float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; } -int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) { - int16_t da, db, dc; +int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) { // NOLINT + int16_t da, db, dc; // NOLINT int16_t reta = 0; da = (x > y) ? x - y : y - x; @@ -175,15 +191,15 @@ int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_va if (val <= min_val) { ret = 0; } else if (val >= max_val) { - ret = 0x7fff; + ret = 0xfff; } else { - ret = (int16_t)((int) 0x7fff * (val - min_val) / (max_val - min_val)); + ret = (int16_t)((int) 0xfff * (val - min_val) / (max_val - min_val)); } return ret; } -int16_t XPT2046Component::read_adc_(uint8_t ctrl) { +int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT uint8_t data[2]; write_byte(ctrl); @@ -193,25 +209,5 @@ int16_t XPT2046Component::read_adc_(uint8_t ctrl) { return ((data[0] << 8) | data[1]) >> 3; } -void XPT2046OnStateTrigger::process(int x, int y, bool touched) { this->trigger(x, y, touched); } - -void XPT2046Button::touch(int16_t x, int16_t y) { - bool touched = (x >= this->x_min_ && x <= this->x_max_ && y >= this->y_min_ && y <= this->y_max_); - - if (touched) { - this->publish_state(true); - this->state_ = true; - } else { - release(); - } -} - -void XPT2046Button::release() { - if (this->state_) { - this->publish_state(false); - this->state_ = false; - } -} - } // namespace xpt2046 } // namespace esphome diff --git a/esphome/components/xpt2046/xpt2046.h b/esphome/components/xpt2046/xpt2046.h index e7270f7d7d..c3d0462c6a 100644 --- a/esphome/components/xpt2046/xpt2046.h +++ b/esphome/components/xpt2046/xpt2046.h @@ -3,42 +3,31 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/components/spi/spi.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace xpt2046 { -class XPT2046OnStateTrigger : public Trigger { - public: - void process(int x, int y, bool touched); +using namespace touchscreen; + +struct XPT2046TouchscreenStore { + volatile bool touch; + ISRInternalGPIOPin pin; + + static void gpio_intr(XPT2046TouchscreenStore *store); }; -class XPT2046Button : public binary_sensor::BinarySensor { - public: - /// Set the touch screen area where the button will detect the touch. - void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { - this->x_min_ = x_min; - this->x_max_ = x_max; - this->y_min_ = y_min; - this->y_max_ = y_max; - } - - void touch(int16_t x, int16_t y); - void release(); - - protected: - int16_t x_min_, x_max_, y_min_, y_max_; - bool state_{false}; -}; - -class XPT2046Component : public PollingComponent, +class XPT2046Component : public Touchscreen, + public PollingComponent, public spi::SPIDevice { public: /// Set the logical touch screen dimensions. void set_dimensions(int16_t x, int16_t y) { - this->x_dim_ = x; - this->y_dim_ = y; + this->display_width_ = x; + this->display_height_ = y; } /// Set the coordinates for the touch screen edges. void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max); @@ -47,14 +36,12 @@ class XPT2046Component : public PollingComponent, /// Set the interval to report the touch point perodically. void set_report_interval(uint32_t interval) { this->report_millis_ = interval; } + uint32_t get_report_interval() { return this->report_millis_; } + /// Set the threshold for the touch detection. void set_threshold(int16_t threshold) { this->threshold_ = threshold; } /// Set the pin used to detect the touch. - void set_irq_pin(GPIOPin *pin) { this->irq_pin_ = pin; } - /// Get an access to the on_state automation trigger - XPT2046OnStateTrigger *get_on_state_trigger() const { return this->on_state_trigger_; } - /// Register a virtual button to the component. - void register_button(XPT2046Button *button) { this->buttons_.push_back(button); } + void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; } void setup() override; void dump_config() override; @@ -103,21 +90,19 @@ class XPT2046Component : public PollingComponent, static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val); int16_t read_adc_(uint8_t ctrl); + void check_touch_(); int16_t threshold_; int16_t x_raw_min_, x_raw_max_, y_raw_min_, y_raw_max_; - int16_t x_dim_, y_dim_; + bool invert_x_, invert_y_; bool swap_x_y_; uint32_t report_millis_; uint32_t last_pos_ms_{0}; - GPIOPin *irq_pin_{nullptr}; - bool last_irq_{true}; - - XPT2046OnStateTrigger *on_state_trigger_{new XPT2046OnStateTrigger()}; - std::vector buttons_{}; + InternalGPIOPin *irq_pin_{nullptr}; + XPT2046TouchscreenStore store_; }; } // namespace xpt2046 diff --git a/tests/test4.yaml b/tests/test4.yaml index 6293e0f7b7..cf517bb391 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -348,15 +348,16 @@ binary_sensor: on_state: then: - lambda: 'ESP_LOGI("ar1:", "%d", x);' - - platform: xpt2046 - xpt2046_id: xpt_touchscreen + - platform: touchscreen + touchscreen_id: xpt_touchscreen id: touch_key0 x_min: 80 x_max: 160 y_min: 106 y_max: 212 - on_state: - - lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));' + on_press: + - logger.log: Touched + - platform: gpio name: GPIO SX1509 test pin: @@ -598,33 +599,6 @@ external_components: components: [bh1750] - source: ../esphome/components components: [sntp] -xpt2046: - id: xpt_touchscreen - cs_pin: 17 - irq_pin: 16 - update_interval: 50ms - report_interval: 1s - threshold: 400 - dimension_x: 240 - dimension_y: 320 - calibration_x_min: 3860 - calibration_x_max: 280 - calibration_y_min: 340 - calibration_y_max: 3860 - swap_x_y: false - on_state: - # yamllint disable rule:line-length - - lambda: |- - ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release")); - ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d", - id(xpt_touchscreen).x, - id(xpt_touchscreen).y, - (int) id(xpt_touchscreen).touched, - id(xpt_touchscreen).x_raw, - id(xpt_touchscreen).y_raw, - id(xpt_touchscreen).z_raw - ); - # yamllint enable rule:line-length button: - platform: restart @@ -648,6 +622,25 @@ touchscreen: format: Touch at (%d, %d) args: [touch.x, touch.y] + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 17 + interrupt_pin: 16 + display: inkplate_display + update_interval: 50ms + report_interval: 1s + threshold: 400 + calibration_x_min: 3860 + calibration_x_max: 280 + calibration_y_min: 340 + calibration_y_max: 3860 + swap_x_y: false + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] + + - platform: lilygo_t5_47 id: lilygo_touchscreen interrupt_pin: GPIO36