mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Create GT911 Touchscreen component (#4027)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
		| @@ -119,6 +119,7 @@ esphome/components/graph/* @synco | |||||||
| esphome/components/gree/* @orestismers | esphome/components/gree/* @orestismers | ||||||
| esphome/components/grove_tb6612fng/* @max246 | esphome/components/grove_tb6612fng/* @max246 | ||||||
| esphome/components/growatt_solar/* @leeuwte | esphome/components/growatt_solar/* @leeuwte | ||||||
|  | esphome/components/gt911/* @clydebarrow @jesserockz | ||||||
| esphome/components/haier/* @paveldn | esphome/components/haier/* @paveldn | ||||||
| esphome/components/havells_solar/* @sourabhjaiswal | esphome/components/havells_solar/* @sourabhjaiswal | ||||||
| esphome/components/hbridge/fan/* @WeekendWarrior | esphome/components/hbridge/fan/* @WeekendWarrior | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								esphome/components/gt911/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								esphome/components/gt911/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@jesserockz", "@clydebarrow"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | gt911_ns = cg.esphome_ns.namespace("gt911") | ||||||
							
								
								
									
										31
									
								
								esphome/components/gt911/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/gt911/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import binary_sensor | ||||||
|  | from esphome.const import CONF_INDEX | ||||||
|  |  | ||||||
|  | from .. import gt911_ns | ||||||
|  | from ..touchscreen import GT911Touchscreen, GT911ButtonListener | ||||||
|  |  | ||||||
|  | CONF_GT911_ID = "gt911_id" | ||||||
|  |  | ||||||
|  | GT911Button = gt911_ns.class_( | ||||||
|  |     "GT911Button", | ||||||
|  |     binary_sensor.BinarySensor, | ||||||
|  |     cg.Component, | ||||||
|  |     GT911ButtonListener, | ||||||
|  |     cg.Parented.template(GT911Touchscreen), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(GT911Button).extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_GT911_ID): cv.use_id(GT911Touchscreen), | ||||||
|  |         cv.Optional(CONF_INDEX, default=0): cv.int_range(min=0, max=3), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await binary_sensor.new_binary_sensor(config) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await cg.register_parented(var, config[CONF_GT911_ID]) | ||||||
|  |     cg.add(var.set_index(config[CONF_INDEX])) | ||||||
							
								
								
									
										27
									
								
								esphome/components/gt911/binary_sensor/gt911_button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/gt911/binary_sensor/gt911_button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | #include "gt911_button.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gt911 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "GT911.binary_sensor"; | ||||||
|  |  | ||||||
|  | void GT911Button::setup() { | ||||||
|  |   this->parent_->register_button_listener(this); | ||||||
|  |   this->publish_initial_state(false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GT911Button::dump_config() { | ||||||
|  |   LOG_BINARY_SENSOR("", "GT911 Button", this); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Index: %u", this->index_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GT911Button::update_button(uint8_t index, bool state) { | ||||||
|  |   if (index != this->index_) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   this->publish_state(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace gt911 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										28
									
								
								esphome/components/gt911/binary_sensor/gt911_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/gt911/binary_sensor/gt911_button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #include "esphome/components/gt911/touchscreen/gt911_touchscreen.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gt911 { | ||||||
|  |  | ||||||
|  | class GT911Button : public binary_sensor::BinarySensor, | ||||||
|  |                     public Component, | ||||||
|  |                     public GT911ButtonListener, | ||||||
|  |                     public Parented<GT911Touchscreen> { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   void set_index(uint8_t index) { this->index_ = index; } | ||||||
|  |  | ||||||
|  |   void update_button(uint8_t index, bool state) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t index_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace gt911 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										46
									
								
								esphome/components/gt911/touchscreen/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/gt911/touchscreen/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | 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_INTERRUPT_PIN, CONF_ID, CONF_ROTATION | ||||||
|  | from .. import gt911_ns | ||||||
|  |  | ||||||
|  |  | ||||||
|  | GT911ButtonListener = gt911_ns.class_("GT911ButtonListener") | ||||||
|  | GT911Touchscreen = gt911_ns.class_( | ||||||
|  |     "GT911Touchscreen", | ||||||
|  |     touchscreen.Touchscreen, | ||||||
|  |     cg.Component, | ||||||
|  |     i2c.I2CDevice, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | ROTATIONS = { | ||||||
|  |     0: touchscreen.TouchRotation.ROTATE_0_DEGREES, | ||||||
|  |     90: touchscreen.TouchRotation.ROTATE_90_DEGREES, | ||||||
|  |     180: touchscreen.TouchRotation.ROTATE_180_DEGREES, | ||||||
|  |     270: touchscreen.TouchRotation.ROTATE_270_DEGREES, | ||||||
|  | } | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     touchscreen.TOUCHSCREEN_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(GT911Touchscreen), | ||||||
|  |             cv.Optional(CONF_ROTATION): cv.enum(ROTATIONS), | ||||||
|  |             cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x5D)) | ||||||
|  |     .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)) | ||||||
|  |     if CONF_ROTATION in config: | ||||||
|  |         cg.add(var.set_rotation(ROTATIONS[config[CONF_ROTATION]])) | ||||||
							
								
								
									
										122
									
								
								esphome/components/gt911/touchscreen/gt911_touchscreen.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/gt911/touchscreen/gt911_touchscreen.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | #include "gt911_touchscreen.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gt911 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "gt911.touchscreen"; | ||||||
|  |  | ||||||
|  | static const uint8_t GET_TOUCH_STATE[2] = {0x81, 0x4E}; | ||||||
|  | static const uint8_t CLEAR_TOUCH_STATE[3] = {0x81, 0x4E, 0x00}; | ||||||
|  | static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F}; | ||||||
|  | static const uint8_t GET_SWITCHES[2] = {0x80, 0x4D}; | ||||||
|  | static const size_t MAX_TOUCHES = 5;  // max number of possible touches reported | ||||||
|  |  | ||||||
|  | #define ERROR_CHECK(err) \ | ||||||
|  |   if ((err) != i2c::ERROR_OK) { \ | ||||||
|  |     ESP_LOGE(TAG, "Failed to communicate!"); \ | ||||||
|  |     this->status_set_warning(); \ | ||||||
|  |     return; \ | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | void IRAM_ATTR HOT Store::gpio_intr(Store *store) { store->available = true; } | ||||||
|  |  | ||||||
|  | void GT911Touchscreen::setup() { | ||||||
|  |   i2c::ErrorCode err; | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen..."); | ||||||
|  |   // datasheet says NOT to use pullup/down on the int line. | ||||||
|  |   this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); | ||||||
|  |   this->interrupt_pin_->setup(); | ||||||
|  |  | ||||||
|  |   // check the configuration of the int line. | ||||||
|  |   uint8_t data; | ||||||
|  |   err = this->write(GET_SWITCHES, 2); | ||||||
|  |   if (err == i2c::ERROR_OK) { | ||||||
|  |     err = this->read(&data, 1); | ||||||
|  |     if (err == i2c::ERROR_OK) { | ||||||
|  |       ESP_LOGD(TAG, "Read from switches: 0x%02X", data); | ||||||
|  |       this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, | ||||||
|  |                                              (data & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (err != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to communicate!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GT911Touchscreen::loop() { | ||||||
|  |   i2c::ErrorCode err; | ||||||
|  |   touchscreen::TouchPoint tp; | ||||||
|  |   uint8_t touch_state = 0; | ||||||
|  |   uint8_t data[MAX_TOUCHES + 1][8];  // 8 bytes each for each point, plus extra space for the key byte | ||||||
|  |  | ||||||
|  |   if (!this->store_.available) | ||||||
|  |     return; | ||||||
|  |   this->store_.available = false; | ||||||
|  |  | ||||||
|  |   err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE), false); | ||||||
|  |   ERROR_CHECK(err); | ||||||
|  |   err = this->read(&touch_state, 1); | ||||||
|  |   ERROR_CHECK(err); | ||||||
|  |   this->write(CLEAR_TOUCH_STATE, sizeof(CLEAR_TOUCH_STATE)); | ||||||
|  |  | ||||||
|  |   if ((touch_state & 0x80) == 0) | ||||||
|  |     return; | ||||||
|  |   uint8_t num_of_touches = touch_state & 0x07; | ||||||
|  |   if (num_of_touches == 0) | ||||||
|  |     this->send_release_(); | ||||||
|  |   if (num_of_touches > MAX_TOUCHES)  // should never happen | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false); | ||||||
|  |   ERROR_CHECK(err); | ||||||
|  |   // num_of_touches is guaranteed to be 0..5. Also read the key data | ||||||
|  |   err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1); | ||||||
|  |   ERROR_CHECK(err); | ||||||
|  |  | ||||||
|  |   for (uint8_t i = 0; i != num_of_touches; i++) { | ||||||
|  |     tp.id = data[i][0]; | ||||||
|  |     uint16_t x = encode_uint16(data[i][2], data[i][1]); | ||||||
|  |     uint16_t y = encode_uint16(data[i][4], data[i][3]); | ||||||
|  |  | ||||||
|  |     switch (this->rotation_) { | ||||||
|  |       case touchscreen::ROTATE_0_DEGREES: | ||||||
|  |         tp.x = x; | ||||||
|  |         tp.y = y; | ||||||
|  |         break; | ||||||
|  |       case touchscreen::ROTATE_90_DEGREES: | ||||||
|  |         tp.x = y; | ||||||
|  |         tp.y = this->display_width_ - x; | ||||||
|  |         break; | ||||||
|  |       case touchscreen::ROTATE_180_DEGREES: | ||||||
|  |         tp.x = this->display_width_ - x; | ||||||
|  |         tp.y = this->display_height_ - y; | ||||||
|  |         break; | ||||||
|  |       case touchscreen::ROTATE_270_DEGREES: | ||||||
|  |         tp.x = this->display_height_ - y; | ||||||
|  |         tp.y = x; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     this->defer([this, tp]() { this->send_touch_(tp); }); | ||||||
|  |   } | ||||||
|  |   auto keys = data[num_of_touches][0]; | ||||||
|  |   for (size_t i = 0; i != 4; i++) { | ||||||
|  |     for (auto *listener : this->button_listeners_) | ||||||
|  |       listener->update_button(i, (keys & (1 << i)) != 0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GT911Touchscreen::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "GT911 Touchscreen:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   LOG_PIN("  Interrupt Pin: ", this->interrupt_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Rotation: %d", (int) this->rotation_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace gt911 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										39
									
								
								esphome/components/gt911/touchscreen/gt911_touchscreen.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/gt911/touchscreen/gt911_touchscreen.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/components/touchscreen/touchscreen.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace gt911 { | ||||||
|  |  | ||||||
|  | struct Store { | ||||||
|  |   volatile bool available; | ||||||
|  |  | ||||||
|  |   static void gpio_intr(Store *store); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class GT911ButtonListener { | ||||||
|  |  public: | ||||||
|  |   virtual void update_button(uint8_t index, bool state) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class GT911Touchscreen : public touchscreen::Touchscreen, public Component, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void loop() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   void set_rotation(touchscreen::TouchRotation rotation) { this->rotation_ = rotation; } | ||||||
|  |  | ||||||
|  |   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } | ||||||
|  |   void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   InternalGPIOPin *interrupt_pin_; | ||||||
|  |   Store store_; | ||||||
|  |   std::vector<GT911ButtonListener *> button_listeners_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace gt911 | ||||||
|  | }  // namespace esphome | ||||||
| @@ -18,6 +18,11 @@ void Touchscreen::set_display(display::Display *display) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Touchscreen::send_release_() { | ||||||
|  |   for (auto *listener : this->touch_listeners_) | ||||||
|  |     listener->release(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Touchscreen::send_touch_(TouchPoint tp) { | void Touchscreen::send_touch_(TouchPoint tp) { | ||||||
|   ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); |   ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); | ||||||
|   this->touch_trigger_.trigger(tp); |   this->touch_trigger_.trigger(tp); | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ class Touchscreen { | |||||||
|  protected: |  protected: | ||||||
|   /// Call this function to send touch points to the `on_touch` listener and the binary_sensors. |   /// Call this function to send touch points to the `on_touch` listener and the binary_sensors. | ||||||
|   void send_touch_(TouchPoint tp); |   void send_touch_(TouchPoint tp); | ||||||
|  |   void send_release_(); | ||||||
|  |  | ||||||
|   uint16_t display_width_; |   uint16_t display_width_; | ||||||
|   uint16_t display_height_; |   uint16_t display_height_; | ||||||
|   | |||||||
| @@ -405,6 +405,10 @@ binary_sensor: | |||||||
|     y_max: 100 |     y_max: 100 | ||||||
|     on_press: |     on_press: | ||||||
|       - logger.log: Touched |       - logger.log: Touched | ||||||
|  |   - platform: gt911 | ||||||
|  |     id: touch_key_911 | ||||||
|  |     index: 0 | ||||||
|  |  | ||||||
|  |  | ||||||
|   - platform: gpio |   - platform: gpio | ||||||
|     name: MaxIn Pin 4 |     name: MaxIn Pin 4 | ||||||
| @@ -725,6 +729,10 @@ touchscreen: | |||||||
|       - logger.log: |       - logger.log: | ||||||
|           format: Touch at (%d, %d) |           format: Touch at (%d, %d) | ||||||
|           args: [touch.x, touch.y] |           args: [touch.x, touch.y] | ||||||
|  |   - platform: gt911 | ||||||
|  |     interrupt_pin: GPIO3 | ||||||
|  |     display: inkplate_display | ||||||
|  |  | ||||||
|  |  | ||||||
| i2s_audio: | i2s_audio: | ||||||
|   i2s_lrclk_pin: GPIO26 |   i2s_lrclk_pin: GPIO26 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user