mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add ektf2232 touchscreen support (#3027)
This commit is contained in:
		
							
								
								
									
										80
									
								
								esphome/components/ektf2232/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								esphome/components/ektf2232/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| 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] | ||||
|         ) | ||||
							
								
								
									
										59
									
								
								esphome/components/ektf2232/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								esphome/components/ektf2232/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| import esphome.codegen as cg | ||||
| 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 | ||||
|  | ||||
| DEPENDENCIES = ["ektf2232"] | ||||
|  | ||||
| EKTF2232Button = ektf2232_ns.class_( | ||||
|     "EKTF2232Button", binary_sensor.BinarySensor, TouchListener | ||||
| ) | ||||
|  | ||||
| CONF_X_MIN = "x_min" | ||||
| CONF_X_MAX = "x_max" | ||||
| CONF_Y_MIN = "y_min" | ||||
| CONF_Y_MAX = "y_max" | ||||
|  | ||||
|  | ||||
| def validate_coords(config): | ||||
|     if ( | ||||
|         config[CONF_X_MAX] < config[CONF_X_MIN] | ||||
|         or config[CONF_Y_MAX] < config[CONF_Y_MIN] | ||||
|     ): | ||||
|         raise cv.Invalid( | ||||
|             f"{CONF_X_MAX} is less than {CONF_X_MIN} or {CONF_Y_MAX} is less than {CONF_Y_MIN}" | ||||
|         ) | ||||
|     return 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.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), | ||||
|             cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000), | ||||
|         } | ||||
|     ), | ||||
|     validate_coords, | ||||
| ) | ||||
|  | ||||
|  | ||||
| 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]) | ||||
|     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_listener(var)) | ||||
| @@ -0,0 +1,19 @@ | ||||
| #include "ektf2232_binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ektf2232 { | ||||
|  | ||||
| void EKTF2232Button::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) { | ||||
|     this->publish_state(true); | ||||
|   } else { | ||||
|     release(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void EKTF2232Button::release() { this->publish_state(false); } | ||||
|  | ||||
| }  // namespace ektf2232 | ||||
| }  // namespace esphome | ||||
| @@ -0,0 +1,27 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "../ektf2232.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ektf2232 { | ||||
|  | ||||
| class EKTF2232Button : 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) { | ||||
|     this->x_min_ = x_min; | ||||
|     this->x_max_ = x_max; | ||||
|     this->y_min_ = y_min; | ||||
|     this->y_max_ = y_max; | ||||
|   } | ||||
|  | ||||
|   void touch(TouchPoint tp) override; | ||||
|   void release() override; | ||||
|  | ||||
|  protected: | ||||
|   int16_t x_min_, x_max_, y_min_, y_max_; | ||||
| }; | ||||
|  | ||||
| }  // namespace ektf2232 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										168
									
								
								esphome/components/ektf2232/ektf2232.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								esphome/components/ektf2232/ektf2232.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| #include "ektf2232.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ektf2232 { | ||||
|  | ||||
| static const char *const TAG = "ektf2232"; | ||||
|  | ||||
| static const uint8_t SOFT_RESET_CMD[4] = {0x77, 0x77, 0x77, 0x77}; | ||||
| static const uint8_t HELLO[4] = {0x55, 0x55, 0x55, 0x55}; | ||||
| static const uint8_t GET_X_RES[4] = {0x53, 0x60, 0x00, 0x00}; | ||||
| static const uint8_t GET_Y_RES[4] = {0x53, 0x63, 0x00, 0x00}; | ||||
| static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01}; | ||||
|  | ||||
| void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; } | ||||
|  | ||||
| void EKTF2232Touchscreen::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen..."); | ||||
|   this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); | ||||
|   this->interrupt_pin_->setup(); | ||||
|  | ||||
|   this->store_.pin = this->interrupt_pin_->to_isr(); | ||||
|   this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_, | ||||
|                                          gpio::INTERRUPT_FALLING_EDGE); | ||||
|  | ||||
|   this->rts_pin_->setup(); | ||||
|  | ||||
|   this->hard_reset_(); | ||||
|   if (!this->soft_reset_()) { | ||||
|     ESP_LOGE(TAG, "Failed to soft reset EKT2232!"); | ||||
|     this->interrupt_pin_->detach_interrupt(); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Get touch resolution | ||||
|   uint8_t received[4]; | ||||
|   this->write(GET_X_RES, 4); | ||||
|   if (this->read(received, 4)) { | ||||
|     ESP_LOGE(TAG, "Failed to read X resolution!"); | ||||
|     this->interrupt_pin_->detach_interrupt(); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); | ||||
|  | ||||
|   this->write(GET_Y_RES, 4); | ||||
|   if (this->read(received, 4)) { | ||||
|     ESP_LOGE(TAG, "Failed to read Y resolution!"); | ||||
|     this->interrupt_pin_->detach_interrupt(); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); | ||||
|   this->store_.touch = false; | ||||
|  | ||||
|   this->set_power_state(true); | ||||
| } | ||||
|  | ||||
| void EKTF2232Touchscreen::loop() { | ||||
|   if (!this->store_.touch) | ||||
|     return; | ||||
|   this->store_.touch = false; | ||||
|  | ||||
|   uint8_t touch_count = 0; | ||||
|   std::vector<TouchPoint> touches; | ||||
|  | ||||
|   uint8_t raw[8]; | ||||
|   this->read(raw, 8); | ||||
|   for (int i = 0; i < 8; i++) | ||||
|     if (raw[7] & (1 << i)) | ||||
|       touch_count++; | ||||
|  | ||||
|   if (touch_count == 0) { | ||||
|     for (auto *listener : this->touch_listeners_) | ||||
|       listener->release(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   touch_count = std::min<uint8_t>(touch_count, 2); | ||||
|  | ||||
|   ESP_LOGV(TAG, "Touch count: %d", touch_count); | ||||
|  | ||||
|   for (int i = 0; i < touch_count; i++) { | ||||
|     uint8_t *d = raw + 1 + (i * 3); | ||||
|     uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1]; | ||||
|     uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2]; | ||||
|  | ||||
|     raw_x = raw_x * this->display_height_ - 1; | ||||
|     raw_y = raw_y * this->display_width_ - 1; | ||||
|  | ||||
|     TouchPoint tp; | ||||
|     switch (this->rotation_) { | ||||
|       case ROTATE_0_DEGREES: | ||||
|         tp.y = raw_x / this->x_resolution_; | ||||
|         tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_); | ||||
|         break; | ||||
|       case ROTATE_90_DEGREES: | ||||
|         tp.x = raw_x / this->x_resolution_; | ||||
|         tp.y = raw_y / this->y_resolution_; | ||||
|         break; | ||||
|       case ROTATE_180_DEGREES: | ||||
|         tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_); | ||||
|         tp.x = raw_y / this->y_resolution_; | ||||
|         break; | ||||
|       case ROTATE_270_DEGREES: | ||||
|         tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_); | ||||
|         tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_); | ||||
|         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); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void EKTF2232Touchscreen::set_power_state(bool enable) { | ||||
|   uint8_t data[] = {0x54, 0x50, 0x00, 0x01}; | ||||
|   data[1] |= (enable << 3); | ||||
|   this->write(data, 4); | ||||
| } | ||||
|  | ||||
| bool EKTF2232Touchscreen::get_power_state() { | ||||
|   uint8_t received[4]; | ||||
|   this->write(GET_POWER_STATE_CMD, 4); | ||||
|   this->store_.touch = false; | ||||
|   this->read(received, 4); | ||||
|   return (received[1] >> 3) & 1; | ||||
| } | ||||
|  | ||||
| void EKTF2232Touchscreen::hard_reset_() { | ||||
|   this->rts_pin_->digital_write(false); | ||||
|   delay(15); | ||||
|   this->rts_pin_->digital_write(true); | ||||
|   delay(15); | ||||
| } | ||||
|  | ||||
| bool EKTF2232Touchscreen::soft_reset_() { | ||||
|   auto err = this->write(SOFT_RESET_CMD, 4); | ||||
|   if (err != i2c::ERROR_OK) | ||||
|     return false; | ||||
|  | ||||
|   uint8_t received[4]; | ||||
|   uint16_t timeout = 1000; | ||||
|   while (!this->store_.touch && timeout > 0) { | ||||
|     delay(1); | ||||
|     timeout--; | ||||
|   } | ||||
|   if (timeout > 0) | ||||
|     this->store_.touch = true; | ||||
|   this->read(received, 4); | ||||
|   this->store_.touch = false; | ||||
|  | ||||
|   return !memcmp(received, HELLO, 4); | ||||
| } | ||||
|  | ||||
| void EKTF2232Touchscreen::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "EKT2232 Touchscreen:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   LOG_PIN("  Interrupt Pin: ", this->interrupt_pin_); | ||||
|   LOG_PIN("  RTS Pin: ", this->rts_pin_); | ||||
| } | ||||
|  | ||||
| }  // namespace ektf2232 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										76
									
								
								esphome/components/ektf2232/ektf2232.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								esphome/components/ektf2232/ektf2232.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ektf2232 { | ||||
|  | ||||
| struct EKTF2232TouchscreenStore { | ||||
|   volatile bool touch; | ||||
|   ISRInternalGPIOPin pin; | ||||
|  | ||||
|   static void gpio_intr(EKTF2232TouchscreenStore *store); | ||||
| }; | ||||
|  | ||||
| struct TouchPoint { | ||||
|   uint16_t x; | ||||
|   uint16_t y; | ||||
| }; | ||||
|  | ||||
| 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 { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   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<TouchPoint> *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_(); | ||||
|  | ||||
|   InternalGPIOPin *interrupt_pin_; | ||||
|   GPIOPin *rts_pin_; | ||||
|   EKTF2232TouchscreenStore store_; | ||||
|   uint16_t x_resolution_; | ||||
|   uint16_t y_resolution_; | ||||
|  | ||||
|   uint16_t display_width_; | ||||
|   uint16_t display_height_; | ||||
|   EKTF2232Rotation rotation_; | ||||
|   Trigger<TouchPoint> *touch_trigger_ = new Trigger<TouchPoint>(); | ||||
|   std::vector<TouchListener *> touch_listeners_; | ||||
| }; | ||||
|  | ||||
| }  // namespace ektf2232 | ||||
| }  // namespace esphome | ||||
		Reference in New Issue
	
	Block a user