diff --git a/CODEOWNERS b/CODEOWNERS index 56899be01a..efc00db94d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -188,6 +188,7 @@ esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 +esphome/components/touchscreen/* @jesserockz esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index b275b43b0e..096aaace4a 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -341,15 +341,15 @@ class DisplayBuffer { // Internal method to set display auto clearing. void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + virtual int get_height_internal() = 0; + virtual int get_width_internal() = 0; + DisplayRotation get_rotation() const { return this->rotation_; } + protected: void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; - virtual int get_height_internal() = 0; - - virtual int get_width_internal() = 0; - void init_internal_(uint32_t buffer_length); void do_update_(); diff --git a/esphome/components/ektf2232/__init__.py b/esphome/components/ektf2232/__init__.py index 0427bda4fb..e69de29bb2 100644 --- a/esphome/components/ektf2232/__init__.py +++ b/esphome/components/ektf2232/__init__.py @@ -1,80 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv - -from esphome import pins, automation -from esphome.components import i2c -from esphome.const import CONF_HEIGHT, CONF_ID, CONF_ROTATION, CONF_WIDTH - -CODEOWNERS = ["@jesserockz"] -DEPENDENCIES = ["i2c"] - -ektf2232_ns = cg.esphome_ns.namespace("ektf2232") -EKTF2232Touchscreen = ektf2232_ns.class_( - "EKTF2232Touchscreen", cg.Component, i2c.I2CDevice -) -TouchPoint = ektf2232_ns.struct("TouchPoint") -TouchListener = ektf2232_ns.class_("TouchListener") - -EKTF2232Rotation = ektf2232_ns.enum("EKTF2232Rotation") - -CONF_EKTF2232_ID = "ektf2232_id" -CONF_INTERRUPT_PIN = "interrupt_pin" -CONF_RTS_PIN = "rts_pin" -CONF_ON_TOUCH = "on_touch" - -ROTATIONS = { - 0: EKTF2232Rotation.ROTATE_0_DEGREES, - 90: EKTF2232Rotation.ROTATE_90_DEGREES, - 180: EKTF2232Rotation.ROTATE_180_DEGREES, - 270: EKTF2232Rotation.ROTATE_270_DEGREES, -} - - -def validate_rotation(value): - value = cv.string(value) - if value.endswith("°"): - value = value[:-1] - return cv.enum(ROTATIONS, int=True)(value) - - -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen), - cv.Required(CONF_INTERRUPT_PIN): cv.All( - pins.internal_gpio_input_pin_schema - ), - cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_HEIGHT, default=758): cv.int_, - cv.Optional(CONF_WIDTH, default=1024): cv.int_, - cv.Optional(CONF_ROTATION, default=0): validate_rotation, - cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), - } - ) - .extend(i2c.i2c_device_schema(0x15)) - .extend(cv.COMPONENT_SCHEMA) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await i2c.register_i2c_device(var, config) - - interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) - cg.add(var.set_interrupt_pin(interrupt_pin)) - rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN]) - cg.add(var.set_rts_pin(rts_pin)) - - cg.add( - var.set_display_details( - config[CONF_WIDTH], - config[CONF_HEIGHT], - config[CONF_ROTATION], - ) - ) - - if CONF_ON_TOUCH in config: - await automation.build_automation( - var.get_touch_trigger(), [(TouchPoint, "touch")], config[CONF_ON_TOUCH] - ) diff --git a/esphome/components/ektf2232/ektf2232.cpp b/esphome/components/ektf2232/ektf2232.cpp index f96928a8de..8df25fce24 100644 --- a/esphome/components/ektf2232/ektf2232.cpp +++ b/esphome/components/ektf2232/ektf2232.cpp @@ -111,10 +111,7 @@ void EKTF2232Touchscreen::loop() { break; } - ESP_LOGV(TAG, "Touch %d: (x=%d, y=%d)", i, tp.x, tp.y); - this->touch_trigger_->trigger(tp); - for (auto *listener : this->touch_listeners_) - listener->touch(tp); + this->defer([this, tp]() { this->send_touch_(tp); }); } } diff --git a/esphome/components/ektf2232/ektf2232.h b/esphome/components/ektf2232/ektf2232.h index 0d6fb7a699..e880b77f99 100644 --- a/esphome/components/ektf2232/ektf2232.h +++ b/esphome/components/ektf2232/ektf2232.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -15,25 +16,9 @@ struct EKTF2232TouchscreenStore { static void gpio_intr(EKTF2232TouchscreenStore *store); }; -struct TouchPoint { - uint16_t x; - uint16_t y; -}; +using namespace touchscreen; -class TouchListener { - public: - virtual void touch(TouchPoint tp) = 0; - virtual void release(); -}; - -enum EKTF2232Rotation : uint8_t { - ROTATE_0_DEGREES = 0, - ROTATE_90_DEGREES, - ROTATE_180_DEGREES, - ROTATE_270_DEGREES, -}; - -class EKTF2232Touchscreen : public Component, public i2c::I2CDevice { +class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { public: void setup() override; void loop() override; @@ -42,19 +27,9 @@ class EKTF2232Touchscreen : public Component, public i2c::I2CDevice { void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; } - void set_display_details(uint16_t width, uint16_t height, EKTF2232Rotation rotation) { - this->display_width_ = width; - this->display_height_ = height; - this->rotation_ = rotation; - } - void set_power_state(bool enable); bool get_power_state(); - Trigger *get_touch_trigger() const { return this->touch_trigger_; } - - void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } - protected: void hard_reset_(); bool soft_reset_(); @@ -64,12 +39,6 @@ class EKTF2232Touchscreen : public Component, public i2c::I2CDevice { EKTF2232TouchscreenStore store_; uint16_t x_resolution_; uint16_t y_resolution_; - - uint16_t display_width_; - uint16_t display_height_; - EKTF2232Rotation rotation_; - Trigger *touch_trigger_ = new Trigger(); - std::vector touch_listeners_; }; } // namespace ektf2232 diff --git a/esphome/components/ektf2232/touchscreen.py b/esphome/components/ektf2232/touchscreen.py new file mode 100644 index 0000000000..b3513b2670 --- /dev/null +++ b/esphome/components/ektf2232/touchscreen.py @@ -0,0 +1,48 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +ektf2232_ns = cg.esphome_ns.namespace("ektf2232") +EKTF2232Touchscreen = ektf2232_ns.class_( + "EKTF2232Touchscreen", + touchscreen.Touchscreen, + cg.Component, + i2c.I2CDevice, +) + +CONF_EKTF2232_ID = "ektf2232_id" +CONF_INTERRUPT_PIN = "interrupt_pin" +CONF_RTS_PIN = "rts_pin" + + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen), + cv.Required(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(i2c.i2c_device_schema(0x15)) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await touchscreen.register_touchscreen(var, config) + + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) + rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN]) + cg.add(var.set_rts_pin(rts_pin)) diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py new file mode 100644 index 0000000000..8246b95187 --- /dev/null +++ b/esphome/components/touchscreen/__init__.py @@ -0,0 +1,39 @@ +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.components import display +from esphome import automation +from esphome.const import CONF_ON_TOUCH + +CODEOWNERS = ["@jesserockz"] +IS_PLATFORM_COMPONENT = True + +touchscreen_ns = cg.esphome_ns.namespace("touchscreen") + +Touchscreen = touchscreen_ns.class_("Touchscreen") +TouchRotation = touchscreen_ns.enum("TouchRotation") +TouchPoint = touchscreen_ns.struct("TouchPoint") +TouchListener = touchscreen_ns.class_("TouchListener") + +CONF_DISPLAY = "display" +CONF_TOUCHSCREEN_ID = "touchscreen_id" + + +TOUCHSCREEN_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DISPLAY): cv.use_id(display.DisplayBuffer), + cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), + } +) + + +async def register_touchscreen(var, config): + disp = await cg.get_variable(config[CONF_DISPLAY]) + cg.add(var.set_display(disp)) + + if CONF_ON_TOUCH in config: + await automation.build_automation( + var.get_touch_trigger(), + [(TouchPoint, "touch")], + config[CONF_ON_TOUCH], + ) diff --git a/esphome/components/ektf2232/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py similarity index 74% rename from esphome/components/ektf2232/binary_sensor/__init__.py rename to esphome/components/touchscreen/binary_sensor/__init__.py index 349c45b31c..cbd03c0a32 100644 --- a/esphome/components/ektf2232/binary_sensor/__init__.py +++ b/esphome/components/touchscreen/binary_sensor/__init__.py @@ -4,12 +4,12 @@ import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import CONF_ID -from .. import ektf2232_ns, CONF_EKTF2232_ID, EKTF2232Touchscreen, TouchListener +from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener -DEPENDENCIES = ["ektf2232"] +DEPENDENCIES = ["touchscreen"] -EKTF2232Button = ektf2232_ns.class_( - "EKTF2232Button", binary_sensor.BinarySensor, TouchListener +TouchscreenBinarySensor = touchscreen_ns.class_( + "TouchscreenBinarySensor", binary_sensor.BinarySensor, TouchListener ) CONF_X_MIN = "x_min" @@ -32,8 +32,8 @@ def validate_coords(config): CONFIG_SCHEMA = cv.All( binary_sensor.BINARY_SENSOR_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(EKTF2232Button), - cv.GenerateID(CONF_EKTF2232_ID): cv.use_id(EKTF2232Touchscreen), + cv.GenerateID(): cv.declare_id(TouchscreenBinarySensor), + cv.GenerateID(CONF_TOUCHSCREEN_ID): cv.use_id(Touchscreen), cv.Required(CONF_X_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), @@ -47,7 +47,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await binary_sensor.register_binary_sensor(var, config) - hub = await cg.get_variable(config[CONF_EKTF2232_ID]) + hub = await cg.get_variable(config[CONF_TOUCHSCREEN_ID]) cg.add( var.set_area( config[CONF_X_MIN], diff --git a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp similarity index 52% rename from esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp rename to esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp index a6fdf8b76c..ba12aeeae0 100644 --- a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.cpp +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -1,9 +1,9 @@ -#include "ektf2232_binary_sensor.h" +#include "touchscreen_binary_sensor.h" namespace esphome { -namespace ektf2232 { +namespace touchscreen { -void EKTF2232Button::touch(TouchPoint tp) { +void TouchscreenBinarySensor::touch(TouchPoint tp) { bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); if (touched) { @@ -13,7 +13,7 @@ void EKTF2232Button::touch(TouchPoint tp) { } } -void EKTF2232Button::release() { this->publish_state(false); } +void TouchscreenBinarySensor::release() { this->publish_state(false); } -} // namespace ektf2232 +} // namespace touchscreen } // namespace esphome diff --git a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h similarity index 72% rename from esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h rename to esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index 170dfcdebb..8fb7749766 100644 --- a/esphome/components/ektf2232/binary_sensor/ektf2232_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -1,12 +1,12 @@ #pragma once -#include "../ektf2232.h" +#include "esphome/components/touchscreen/touchscreen.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { -namespace ektf2232 { +namespace touchscreen { -class EKTF2232Button : public binary_sensor::BinarySensor, public TouchListener { +class TouchscreenBinarySensor : public binary_sensor::BinarySensor, public TouchListener { 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) { @@ -23,5 +23,5 @@ class EKTF2232Button : public binary_sensor::BinarySensor, public TouchListener int16_t x_min_, x_max_, y_min_, y_max_; }; -} // namespace ektf2232 +} // namespace touchscreen } // namespace esphome diff --git a/esphome/components/touchscreen/touchscreen.cpp b/esphome/components/touchscreen/touchscreen.cpp new file mode 100644 index 0000000000..9b337fc02c --- /dev/null +++ b/esphome/components/touchscreen/touchscreen.cpp @@ -0,0 +1,18 @@ +#include "touchscreen.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace touchscreen { + +static const char *const TAG = "touchscreen"; + +void Touchscreen::send_touch_(TouchPoint tp) { + ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); + this->touch_trigger_.trigger(tp); + for (auto *listener : this->touch_listeners_) + listener->touch(tp); +} + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h new file mode 100644 index 0000000000..2c0ec9e268 --- /dev/null +++ b/esphome/components/touchscreen/touchscreen.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/automation.h" +#include "esphome/core/hal.h" + +#include + +namespace esphome { +namespace touchscreen { + +struct TouchPoint { + uint16_t x; + uint16_t y; + uint8_t id; + uint8_t state; +}; + +class TouchListener { + public: + virtual void touch(TouchPoint tp) = 0; + virtual void release() {} +}; + +enum TouchRotation { + ROTATE_0_DEGREES = 0, + ROTATE_90_DEGREES = 90, + ROTATE_180_DEGREES = 180, + ROTATE_270_DEGREES = 270, +}; + +class Touchscreen { + public: + void set_display(display::DisplayBuffer *display) { + this->display_ = display; + this->display_width_ = display->get_width_internal(); + this->display_height_ = display->get_height_internal(); + this->rotation_ = static_cast(display->get_rotation()); + } + + Trigger *get_touch_trigger() { return &this->touch_trigger_; } + + void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } + + protected: + /// Call this function to send touch points to the `on_touch` listener and the binary_sensors. + void send_touch_(TouchPoint tp); + + uint16_t display_width_; + uint16_t display_height_; + display::DisplayBuffer *display_; + TouchRotation rotation_; + Trigger touch_trigger_; + std::vector touch_listeners_; +}; + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 136d88173c..2d2f5f1da0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -439,6 +439,7 @@ CONF_ON_TAG = "on_tag" CONF_ON_TAG_REMOVED = "on_tag_removed" CONF_ON_TIME = "on_time" CONF_ON_TIME_SYNC = "on_time_sync" +CONF_ON_TOUCH = "on_touch" CONF_ON_TURN_OFF = "on_turn_off" CONF_ON_TURN_ON = "on_turn_on" CONF_ON_VALUE = "on_value" diff --git a/script/ci-custom.py b/script/ci-custom.py index 2beba3483d..956716e5fa 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -20,7 +20,7 @@ def find_all(a_str, sub): # Optimization: If str is not in whole text, then do not try # on each line return - for i, line in enumerate(a_str.split('\n')): + for i, line in enumerate(a_str.split("\n")): column = 0 while True: column = line.find(sub, column) @@ -592,6 +592,8 @@ def lint_inclusive_language(fname, match): include=["*.h", "*.tcc"], exclude=[ "esphome/components/binary_sensor/binary_sensor.h", + "esphome/components/button/button.h", + "esphome/components/climate/climate.h", "esphome/components/cover/cover.h", "esphome/components/display/display_buffer.h", "esphome/components/fan/fan.h", @@ -606,8 +608,6 @@ def lint_inclusive_language(fname, match): "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", "esphome/components/text_sensor/text_sensor.h", - "esphome/components/climate/climate.h", - "esphome/components/button/button.h", "esphome/core/component.h", "esphome/core/gpio.h", "esphome/core/log.h", @@ -666,7 +666,10 @@ run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): bold = functools.partial(styled, colorama.Style.BRIGHT) bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) - err_str = (f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" for lineno, col, msg in errs) + err_str = ( + f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" + for lineno, col, msg in errs + ) print_error_for_file(f, "\n".join(err_str)) if args.print_slowest: diff --git a/tests/test4.yaml b/tests/test4.yaml index eec1c2eb5e..de641d92ff 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -313,7 +313,7 @@ binary_sensor: then: - lambda: 'ESP_LOGI("ar1:", "%d", x);' - platform: xpt2046 - xpt2046_id: touchscreen + xpt2046_id: xpt_touchscreen id: touch_key0 x_min: 80 x_max: 160 @@ -327,6 +327,16 @@ binary_sensor: sx1509: sx1509_hub number: 3 + - platform: touchscreen + id: touch_key1 + x_min: 0 + x_max: 100 + y_min: 0 + y_max: 100 + on_press: + - logger.log: "Touched" + + climate: - platform: tuya id: tuya_climate @@ -509,7 +519,7 @@ external_components: - source: ../esphome/components components: ["sntp"] xpt2046: - id: touchscreen + id: xpt_touchscreen cs_pin: 17 irq_pin: 16 update_interval: 50ms @@ -526,12 +536,12 @@ xpt2046: - 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(touchscreen).x, - id(touchscreen).y, - (int) id(touchscreen).touched, - id(touchscreen).x_raw, - id(touchscreen).y_raw, - id(touchscreen).z_raw + 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 ); button: @@ -541,3 +551,13 @@ button: name: Safe Mode Button - platform: shutdown name: Shutdown Button + +touchscreen: + - platform: ektf2232 + interrupt_pin: GPIO36 + rts_pin: GPIO5 + display: inkplate_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: ["touch.x", "touch.y"] diff --git a/tests/test5.yaml b/tests/test5.yaml index 55786050b3..9bfd395538 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -231,12 +231,3 @@ switch: register_type: coil address: 2 bitmask: 1 - -ektf2232: - interrupt_pin: GPIO36 - rts_pin: GPIO5 - rotation: 90 - on_touch: - - logger.log: - format: Touch at (%d, %d) - args: ["touch.x", "touch.y"]