Updating the touchscreen interface structure (#4596)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: NP v/d Spek <github_mail@lumensoft.nl>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Gustavo Ambrozio <gustavo@gustavo.eng.br>
This commit is contained in:
NP v/d Spek 2023-12-12 23:56:01 +01:00 committed by GitHub
parent 8e92bb7958
commit c6dc336c4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 997 additions and 836 deletions

View file

@ -88,7 +88,7 @@ esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/* @jesserockz esphome/components/ektf2232/touchscreen/* @jesserockz
esphome/components/emc2101/* @ellull esphome/components/emc2101/* @ellull
esphome/components/ens160/* @vincentscode esphome/components/ens160/* @vincentscode
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
@ -110,6 +110,8 @@ esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi esphome/components/feedback/* @ianchi
esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/fingerprint_grow/* @OnFreund @loongyh
esphome/components/fs3000/* @kahrendt esphome/components/fs3000/* @kahrendt
esphome/components/ft5x06/* @clydebarrow
esphome/components/ft63x6/* @gpambrozio
esphome/components/gcja5/* @gcormier esphome/components/gcja5/* @gcormier
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
@ -331,7 +333,7 @@ esphome/components/tmp1075/* @sybrenstuvel
esphome/components/tmp117/* @Azimath esphome/components/tmp117/* @Azimath
esphome/components/tof10120/* @wstrzalka esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81 esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz esphome/components/touchscreen/* @jesserockz @nielsnl68
esphome/components/tsl2591/* @wjcarpenter esphome/components/tsl2591/* @wjcarpenter
esphome/components/tt21100/* @kroimon esphome/components/tt21100/* @kroimon
esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/binary_sensor/* @jesserockz
@ -364,6 +366,6 @@ esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97 esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/* @nielsnl68 @numo68 esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/zhlt01/* @cfeenstra1024 esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zio_ultrasonic/* @kahrendt esphome/components/zio_ultrasonic/* @kahrendt

View file

@ -12,7 +12,6 @@ ektf2232_ns = cg.esphome_ns.namespace("ektf2232")
EKTF2232Touchscreen = ektf2232_ns.class_( EKTF2232Touchscreen = ektf2232_ns.class_(
"EKTF2232Touchscreen", "EKTF2232Touchscreen",
touchscreen.Touchscreen, touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice, i2c.I2CDevice,
) )
@ -28,17 +27,14 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
), ),
cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema,
} }
) ).extend(i2c.i2c_device_schema(0x15))
.extend(i2c.i2c_device_schema(0x15))
.extend(cv.COMPONENT_SCHEMA)
) )
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) 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) await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin)) cg.add(var.set_interrupt_pin(interrupt_pin))

View file

@ -15,16 +15,12 @@ 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_Y_RES[4] = {0x53, 0x63, 0x00, 0x00};
static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01}; 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() { void EKTF2232Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen..."); ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen...");
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup(); this->interrupt_pin_->setup();
this->store_.pin = this->interrupt_pin_->to_isr(); this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_,
gpio::INTERRUPT_FALLING_EDGE);
this->rts_pin_->setup(); this->rts_pin_->setup();
@ -45,7 +41,7 @@ void EKTF2232Touchscreen::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
this->write(GET_Y_RES, 4); this->write(GET_Y_RES, 4);
if (this->read(received, 4)) { if (this->read(received, 4)) {
@ -54,19 +50,14 @@ void EKTF2232Touchscreen::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
this->store_.touch = false;
this->set_power_state(true); this->set_power_state(true);
} }
void EKTF2232Touchscreen::loop() { void EKTF2232Touchscreen::update_touches() {
if (!this->store_.touch)
return;
this->store_.touch = false;
uint8_t touch_count = 0; uint8_t touch_count = 0;
std::vector<TouchPoint> touches; int16_t x_raw, y_raw;
uint8_t raw[8]; uint8_t raw[8];
this->read(raw, 8); this->read(raw, 8);
@ -75,45 +66,15 @@ void EKTF2232Touchscreen::loop() {
touch_count++; touch_count++;
} }
if (touch_count == 0) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
}
touch_count = std::min<uint8_t>(touch_count, 2); touch_count = std::min<uint8_t>(touch_count, 2);
ESP_LOGV(TAG, "Touch count: %d", touch_count); ESP_LOGV(TAG, "Touch count: %d", touch_count);
for (int i = 0; i < touch_count; i++) { for (int i = 0; i < touch_count; i++) {
uint8_t *d = raw + 1 + (i * 3); uint8_t *d = raw + 1 + (i * 3);
uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1]; x_raw = (d[0] & 0xF0) << 4 | d[1];
uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2]; y_raw = (d[0] & 0x0F) << 8 | d[2];
this->set_raw_touch_position_(i, x_raw, y_raw);
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;
}
this->defer([this, tp]() { this->send_touch_(tp); });
} }
} }
@ -126,7 +87,7 @@ void EKTF2232Touchscreen::set_power_state(bool enable) {
bool EKTF2232Touchscreen::get_power_state() { bool EKTF2232Touchscreen::get_power_state() {
uint8_t received[4]; uint8_t received[4];
this->write(GET_POWER_STATE_CMD, 4); this->write(GET_POWER_STATE_CMD, 4);
this->store_.touch = false; this->store_.touched = false;
this->read(received, 4); this->read(received, 4);
return (received[1] >> 3) & 1; return (received[1] >> 3) & 1;
} }
@ -145,14 +106,14 @@ bool EKTF2232Touchscreen::soft_reset_() {
uint8_t received[4]; uint8_t received[4];
uint16_t timeout = 1000; uint16_t timeout = 1000;
while (!this->store_.touch && timeout > 0) { while (!this->store_.touched && timeout > 0) {
delay(1); delay(1);
timeout--; timeout--;
} }
if (timeout > 0) if (timeout > 0)
this->store_.touch = true; this->store_.touched = true;
this->read(received, 4); this->read(received, 4);
this->store_.touch = false; this->store_.touched = false;
return !memcmp(received, HELLO, 4); return !memcmp(received, HELLO, 4);
} }

View file

@ -9,19 +9,11 @@
namespace esphome { namespace esphome {
namespace ektf2232 { namespace ektf2232 {
struct EKTF2232TouchscreenStore {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(EKTF2232TouchscreenStore *store);
};
using namespace touchscreen; using namespace touchscreen;
class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { class EKTF2232Touchscreen : public Touchscreen, public i2c::I2CDevice {
public: public:
void setup() override; void setup() override;
void loop() override;
void dump_config() override; void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
@ -33,12 +25,10 @@ class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2
protected: protected:
void hard_reset_(); void hard_reset_();
bool soft_reset_(); bool soft_reset_();
void update_touches() override;
InternalGPIOPin *interrupt_pin_; InternalGPIOPin *interrupt_pin_;
GPIOPin *rts_pin_; GPIOPin *rts_pin_;
EKTF2232TouchscreenStore store_;
uint16_t x_resolution_;
uint16_t y_resolution_;
}; };
} // namespace ektf2232 } // namespace ektf2232

View file

@ -0,0 +1,6 @@
import esphome.codegen as cg
CODEOWNERS = ["@clydebarrow"]
DEPENDENCIES = ["i2c"]
ft5x06_ns = cg.esphome_ns.namespace("ft5x06")

View file

@ -0,0 +1,26 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, touchscreen
from esphome.const import CONF_ID
from .. import ft5x06_ns
FT5x06ButtonListener = ft5x06_ns.class_("FT5x06ButtonListener")
FT5x06Touchscreen = ft5x06_ns.class_(
"FT5x06Touchscreen",
touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice,
)
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(FT5x06Touchscreen),
}
).extend(i2c.i2c_device_schema(0x48))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await i2c.register_i2c_device(var, config)
await touchscreen.register_touchscreen(var, config)

View file

@ -0,0 +1,124 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ft5x06 {
static const char *const TAG = "ft5x06.touchscreen";
enum VendorId {
FT5X06_ID_UNKNOWN = 0,
FT5X06_ID_1 = 0x51,
FT5X06_ID_2 = 0x11,
FT5X06_ID_3 = 0xCD,
};
enum FTCmd : uint8_t {
FT5X06_MODE_REG = 0x00,
FT5X06_ORIGIN_REG = 0x08,
FT5X06_RESOLUTION_REG = 0x0C,
FT5X06_VENDOR_ID_REG = 0xA8,
FT5X06_TD_STATUS = 0x02,
FT5X06_TOUCH_DATA = 0x03,
FT5X06_I_MODE = 0xA4,
FT5X06_TOUCH_MAX = 0x4C,
};
enum FTMode : uint8_t {
FT5X06_OP_MODE = 0,
FT5X06_SYSINFO_MODE = 0x10,
FT5X06_TEST_MODE = 0x40,
};
static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
public:
void setup() override {
esph_log_config(TAG, "Setting up FT5x06 Touchscreen...");
// wait 200ms after reset.
this->set_timeout(200, [this] { this->continue_setup_(); });
}
void continue_setup_(void) {
uint8_t data[4];
if (!this->set_mode_(FT5X06_OP_MODE))
return;
if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
return;
switch (data[0]) {
case FT5X06_ID_1:
case FT5X06_ID_2:
case FT5X06_ID_3:
this->vendor_id_ = (VendorId) data[0];
esph_log_d(TAG, "Read vendor ID 0x%X", data[0]);
break;
default:
esph_log_e(TAG, "Unknown vendor ID 0x%X", data[0]);
this->mark_failed();
return;
}
// reading the chip registers to get max x/y does not seem to work.
this->x_raw_max_ = this->display_->get_width();
this->y_raw_max_ = this->display_->get_height();
esph_log_config(TAG, "FT5x06 Touchscreen setup complete");
}
void update_touches() override {
uint8_t touch_cnt;
uint8_t data[MAX_TOUCHES][6];
if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
esph_log_w(TAG, "Failed to read status");
return;
}
if (touch_cnt == 0)
return;
if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
esph_log_w(TAG, "Failed to read touch data");
return;
}
for (uint8_t i = 0; i != touch_cnt; i++) {
uint8_t status = data[i][0] >> 6;
uint8_t id = data[i][2] >> 3;
uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
esph_log_d(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
if (status == 0 || status == 2) {
this->set_raw_touch_position_(id, x, y);
}
}
}
void dump_config() override {
esph_log_config(TAG, "FT5x06 Touchscreen:");
esph_log_config(TAG, " Address: 0x%02X", this->address_);
esph_log_config(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_);
}
protected:
bool err_check_(i2c::ErrorCode err, const char *msg) {
if (err != i2c::ERROR_OK) {
this->mark_failed();
esph_log_e(TAG, "%s failed - err 0x%X", msg, err);
return false;
}
return true;
}
bool set_mode_(FTMode mode) {
return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
}
VendorId vendor_id_{FT5X06_ID_UNKNOWN};
};
} // namespace ft5x06
} // namespace esphome

View file

@ -0,0 +1 @@
CODEOWNERS = ["@gpambrozio"]

View file

@ -0,0 +1,99 @@
/**************************************************************************/
/*!
Author: Gustavo Ambrozio
Based on work by: Atsushi Sasaki (https://github.com/aselectroworks/Arduino-FT6336U)
*/
/**************************************************************************/
#include "ft63x6.h"
#include "esphome/core/log.h"
// Registers
// Reference: https://focuslcds.com/content/FT6236.pdf
namespace esphome {
namespace ft63x6 {
static const uint8_t FT63X6_ADDR_TOUCH_COUNT = 0x02;
static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05;
static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03;
static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05;
static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B;
static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09;
static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B;
static const char *const TAG = "FT63X6Touchscreen";
void FT63X6Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up FT63X6Touchscreen Touchscreen...");
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
}
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
}
this->hard_reset_();
// Get touch resolution
this->x_raw_max_ = 320;
this->y_raw_max_ = 480;
}
void FT63X6Touchscreen::update_touches() {
int touch_count = this->read_touch_count_();
if (touch_count == 0) {
return;
}
uint8_t touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH1_ID); // id1 = 0 or 1
int16_t x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_X);
int16_t y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_Y);
this->set_raw_touch_position_(touch_id, x, y);
if (touch_count >= 2) {
touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH2_ID); // id2 = 0 or 1(~id1 & 0x01)
x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_X);
y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_Y);
this->set_raw_touch_position_(touch_id, x, y);
}
}
void FT63X6Touchscreen::hard_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
}
}
void FT63X6Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "FT63X6 Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
}
uint8_t FT63X6Touchscreen::read_touch_count_() { return this->read_byte_(FT63X6_ADDR_TOUCH_COUNT); }
// Touch functions
uint16_t FT63X6Touchscreen::read_touch_coordinate_(uint8_t coordinate) {
uint8_t read_buf[2];
read_buf[0] = this->read_byte_(coordinate);
read_buf[1] = this->read_byte_(coordinate + 1);
return ((read_buf[0] & 0x0f) << 8) | read_buf[1];
}
uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t id_address) { return this->read_byte_(id_address) >> 4; }
uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) {
uint8_t byte = 0;
this->read_byte(addr, &byte);
return byte;
}
} // namespace ft63x6
} // namespace esphome

View file

@ -0,0 +1,41 @@
/**************************************************************************/
/*!
Author: Gustavo Ambrozio
Based on work by: Atsushi Sasaki (https://github.com/aselectroworks/Arduino-FT6336U)
*/
/**************************************************************************/
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
namespace esphome {
namespace ft63x6 {
using namespace touchscreen;
class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
protected:
void hard_reset_();
uint8_t read_byte_(uint8_t addr);
void update_touches() override;
InternalGPIOPin *interrupt_pin_{nullptr};
GPIOPin *reset_pin_{nullptr};
uint8_t read_touch_count_();
uint16_t read_touch_coordinate_(uint8_t coordinate);
uint8_t read_touch_id_(uint8_t id_address);
};
} // namespace ft63x6
} // namespace esphome

View file

@ -0,0 +1,44 @@
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, CONF_INTERRUPT_PIN, CONF_RESET_PIN
CODEOWNERS = ["@gpambrozio"]
DEPENDENCIES = ["i2c"]
ft6336u_ns = cg.esphome_ns.namespace("ft63x6")
FT63X6Touchscreen = ft6336u_ns.class_(
"FT63X6Touchscreen",
touchscreen.Touchscreen,
i2c.I2CDevice,
)
CONF_FT63X6_ID = "ft63x6_id"
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(FT63X6Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): cv.All(
pins.internal_gpio_input_pin_schema
),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
).extend(i2c.i2c_device_schema(0x38))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
if interrupt_pin_config := config.get(CONF_INTERRUPT_PIN):
interrupt_pin = await cg.gpio_pin_expression(interrupt_pin_config)
cg.add(var.set_interrupt_pin(interrupt_pin))
if reset_pin_config := config.get(CONF_RESET_PIN):
reset_pin = await cg.gpio_pin_expression(reset_pin_config)
cg.add(var.set_reset_pin(reset_pin))

View file

@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import i2c, touchscreen from esphome.components import i2c, touchscreen
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_ROTATION from esphome.const import CONF_INTERRUPT_PIN, CONF_ID
from .. import gt911_ns from .. import gt911_ns
@ -11,36 +11,21 @@ GT911ButtonListener = gt911_ns.class_("GT911ButtonListener")
GT911Touchscreen = gt911_ns.class_( GT911Touchscreen = gt911_ns.class_(
"GT911Touchscreen", "GT911Touchscreen",
touchscreen.Touchscreen, touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice, i2c.I2CDevice,
) )
ROTATIONS = { CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
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.GenerateID(): cv.declare_id(GT911Touchscreen),
cv.Optional(CONF_ROTATION): cv.enum(ROTATIONS), cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
} }
) ).extend(i2c.i2c_device_schema(0x5D))
.extend(i2c.i2c_device_schema(0x5D))
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) 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) await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(interrupt_pin)) cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if CONF_ROTATION in config:
cg.add(var.set_rotation(ROTATIONS[config[CONF_ROTATION]]))

View file

@ -12,6 +12,7 @@ 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 CLEAR_TOUCH_STATE[3] = {0x81, 0x4E, 0x00};
static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F}; static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F};
static const uint8_t GET_SWITCHES[2] = {0x80, 0x4D}; static const uint8_t GET_SWITCHES[2] = {0x80, 0x4D};
static const uint8_t GET_MAX_VALUES[2] = {0x80, 0x48};
static const size_t MAX_TOUCHES = 5; // max number of possible touches reported static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
#define ERROR_CHECK(err) \ #define ERROR_CHECK(err) \
@ -21,24 +22,35 @@ static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
return; \ return; \
} }
void IRAM_ATTR HOT Store::gpio_intr(Store *store) { store->available = true; }
void GT911Touchscreen::setup() { void GT911Touchscreen::setup() {
i2c::ErrorCode err; i2c::ErrorCode err;
ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen..."); ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen...");
// check the configuration of the int line.
uint8_t data[4];
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[0]);
if (this->interrupt_pin_ != nullptr) {
// datasheet says NOT to use pullup/down on the int line. // datasheet says NOT to use pullup/down on the int line.
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
this->interrupt_pin_->setup(); this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_,
// check the configuration of the int line. (data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
uint8_t data; }
err = this->write(GET_SWITCHES, 2); }
}
if (err == i2c::ERROR_OK) { if (err == i2c::ERROR_OK) {
err = this->read(&data, 1); err = this->write(GET_MAX_VALUES, 2);
if (err == i2c::ERROR_OK) { if (err == i2c::ERROR_OK) {
ESP_LOGD(TAG, "Read from switches: 0x%02X", data); err = this->read(data, sizeof(data));
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, if (err == i2c::ERROR_OK) {
(data & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE); this->x_raw_max_ = encode_uint16(data[1], data[0]);
this->y_raw_max_ = encode_uint16(data[3], data[2]);
esph_log_d(TAG, "Read max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_);
}
} }
} }
if (err != i2c::ERROR_OK) { if (err != i2c::ERROR_OK) {
@ -46,31 +58,28 @@ void GT911Touchscreen::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete"); ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete");
} }
void GT911Touchscreen::loop() { void GT911Touchscreen::update_touches() {
i2c::ErrorCode err; i2c::ErrorCode err;
touchscreen::TouchPoint tp;
uint8_t touch_state = 0; 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 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); err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE), false);
ERROR_CHECK(err); ERROR_CHECK(err);
err = this->read(&touch_state, 1); err = this->read(&touch_state, 1);
ERROR_CHECK(err); ERROR_CHECK(err);
this->write(CLEAR_TOUCH_STATE, sizeof(CLEAR_TOUCH_STATE)); this->write(CLEAR_TOUCH_STATE, sizeof(CLEAR_TOUCH_STATE));
if ((touch_state & 0x80) == 0)
return;
uint8_t num_of_touches = touch_state & 0x07; uint8_t num_of_touches = touch_state & 0x07;
if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) {
this->skip_update_ = true; // skip send touch events, touchscreen is not ready yet.
return;
}
if (num_of_touches == 0) if (num_of_touches == 0)
this->send_release_();
if (num_of_touches > MAX_TOUCHES) // should never happen
return; return;
err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false); err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false);
@ -80,29 +89,10 @@ void GT911Touchscreen::loop() {
ERROR_CHECK(err); ERROR_CHECK(err);
for (uint8_t i = 0; i != num_of_touches; i++) { for (uint8_t i = 0; i != num_of_touches; i++) {
tp.id = data[i][0]; uint16_t id = data[i][0];
uint16_t x = encode_uint16(data[i][2], data[i][1]); uint16_t x = encode_uint16(data[i][2], data[i][1]);
uint16_t y = encode_uint16(data[i][4], data[i][3]); uint16_t y = encode_uint16(data[i][4], data[i][3]);
this->set_raw_touch_position_(id, x, y);
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]; auto keys = data[num_of_touches][0];
for (size_t i = 0; i != 4; i++) { for (size_t i = 0; i != 4; i++) {
@ -115,7 +105,6 @@ void GT911Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "GT911 Touchscreen:"); ESP_LOGCONFIG(TAG, "GT911 Touchscreen:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
ESP_LOGCONFIG(TAG, " Rotation: %d", (int) this->rotation_);
} }
} // namespace gt911 } // namespace gt911

View file

@ -8,30 +8,23 @@
namespace esphome { namespace esphome {
namespace gt911 { namespace gt911 {
struct Store {
volatile bool available;
static void gpio_intr(Store *store);
};
class GT911ButtonListener { class GT911ButtonListener {
public: public:
virtual void update_button(uint8_t index, bool state) = 0; virtual void update_button(uint8_t index, bool state) = 0;
}; };
class GT911Touchscreen : public touchscreen::Touchscreen, public Component, public i2c::I2CDevice { class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
public: public:
void setup() override; void setup() override;
void loop() override;
void dump_config() 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 set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); } void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); }
protected: protected:
InternalGPIOPin *interrupt_pin_; void update_touches() override;
Store store_;
InternalGPIOPin *interrupt_pin_{};
std::vector<GT911ButtonListener *> button_listeners_; std::vector<GT911ButtonListener *> button_listeners_;
}; };

View file

@ -32,7 +32,11 @@ CODEOWNERS = ["@nielsnl68", "@clydebarrow"]
ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx") ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx")
ILI9XXXDisplay = ili9xxx_ns.class_( ILI9XXXDisplay = ili9xxx_ns.class_(
"ILI9XXXDisplay", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer "ILI9XXXDisplay",
cg.PollingComponent,
spi.SPIDevice,
display.Display,
display.DisplayBuffer,
) )
ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode") ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode")

View file

@ -39,7 +39,11 @@ CONF_VCOM_PIN = "vcom_pin"
inkplate6_ns = cg.esphome_ns.namespace("inkplate6") inkplate6_ns = cg.esphome_ns.namespace("inkplate6")
Inkplate6 = inkplate6_ns.class_( Inkplate6 = inkplate6_ns.class_(
"Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer "Inkplate6",
cg.PollingComponent,
i2c.I2CDevice,
display.Display,
display.DisplayBuffer,
) )
InkplateModel = inkplate6_ns.enum("InkplateModel") InkplateModel = inkplate6_ns.enum("InkplateModel")

View file

@ -13,7 +13,6 @@ DEPENDENCIES = ["i2c"]
LilygoT547Touchscreen = lilygo_t5_47_ns.class_( LilygoT547Touchscreen = lilygo_t5_47_ns.class_(
"LilygoT547Touchscreen", "LilygoT547Touchscreen",
touchscreen.Touchscreen, touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice, i2c.I2CDevice,
) )
@ -27,17 +26,14 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
pins.internal_gpio_input_pin_schema pins.internal_gpio_input_pin_schema
), ),
} }
) ).extend(i2c.i2c_device_schema(0x5A))
.extend(i2c.i2c_device_schema(0x5A))
.extend(cv.COMPONENT_SCHEMA)
) )
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) 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) await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin)) cg.add(var.set_interrupt_pin(interrupt_pin))

View file

@ -23,15 +23,12 @@ static const uint8_t READ_TOUCH[1] = {0x07};
return; \ return; \
} }
void Store::gpio_intr(Store *store) { store->touch = true; }
void LilygoT547Touchscreen::setup() { void LilygoT547Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen..."); ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen...");
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup(); this->interrupt_pin_->setup();
this->store_.pin = this->interrupt_pin_->to_isr(); this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
if (this->write(nullptr, 0) != i2c::ERROR_OK) { if (this->write(nullptr, 0) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Failed to communicate!"); ESP_LOGE(TAG, "Failed to communicate!");
@ -41,19 +38,14 @@ void LilygoT547Touchscreen::setup() {
} }
this->write_register(POWER_REGISTER, WAKEUP_CMD, 1); this->write_register(POWER_REGISTER, WAKEUP_CMD, 1);
this->x_raw_max_ = this->get_width_();
this->y_raw_max_ = this->get_height_();
} }
void LilygoT547Touchscreen::loop() { void LilygoT547Touchscreen::update_touches() {
if (!this->store_.touch) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
}
this->store_.touch = false;
uint8_t point = 0; uint8_t point = 0;
uint8_t buffer[40] = {0}; uint8_t buffer[40] = {0};
uint32_t sum_l = 0, sum_h = 0;
i2c::ErrorCode err; i2c::ErrorCode err;
err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1); err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1);
@ -69,102 +61,30 @@ void LilygoT547Touchscreen::loop() {
point = buffer[5] & 0xF; point = buffer[5] & 0xF;
if (point == 0) { if (point == 1) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
} else if (point == 1) {
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
ERROR_CHECK(err); ERROR_CHECK(err);
err = this->read(&buffer[5], 2); err = this->read(&buffer[5], 2);
ERROR_CHECK(err); ERROR_CHECK(err);
sum_l = buffer[5] << 8 | buffer[6];
} else if (point > 1) { } else if (point > 1) {
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
ERROR_CHECK(err); ERROR_CHECK(err);
err = this->read(&buffer[5], 5 * (point - 1) + 3); err = this->read(&buffer[5], 5 * (point - 1) + 3);
ERROR_CHECK(err); ERROR_CHECK(err);
sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2];
} }
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2); this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
for (int i = 0; i < 5 * point; i++) if (point == 0)
sum_h += buffer[i]; point = 1;
if (sum_l != sum_h) uint16_t id, x_raw, y_raw;
point = 0; for (uint8_t i = 0; i < point; i++) {
id = (buffer[i * 5] >> 4) & 0x0F;
if (point) { y_raw = (uint16_t) ((buffer[i * 5 + 1] << 4) | ((buffer[i * 5 + 3] >> 4) & 0x0F));
uint8_t offset; x_raw = (uint16_t) ((buffer[i * 5 + 2] << 4) | (buffer[i * 5 + 3] & 0x0F));
for (int i = 0; i < point; i++) { this->set_raw_touch_position_(id, x_raw, y_raw);
if (i == 0) {
offset = 0;
} else {
offset = 4;
}
TouchPoint tp;
tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F;
tp.state = buffer[i * 5 + offset] & 0x0F;
if (tp.state == 0x06)
tp.state = 0x07;
uint16_t y = (uint16_t) ((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F));
uint16_t x = (uint16_t) ((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F));
switch (this->rotation_) {
case ROTATE_0_DEGREES:
tp.y = this->display_height_ - y;
tp.x = x;
break;
case ROTATE_90_DEGREES:
tp.x = this->display_height_ - y;
tp.y = this->display_width_ - x;
break;
case ROTATE_180_DEGREES:
tp.y = y;
tp.x = this->display_width_ - x;
break;
case ROTATE_270_DEGREES:
tp.x = y;
tp.y = x;
break;
}
this->defer([this, tp]() { this->send_touch_(tp); });
}
} else {
TouchPoint tp;
tp.id = (buffer[0] >> 4) & 0x0F;
tp.state = 0x06;
uint16_t y = (uint16_t) ((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F));
uint16_t x = (uint16_t) ((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F));
switch (this->rotation_) {
case ROTATE_0_DEGREES:
tp.y = this->display_height_ - y;
tp.x = x;
break;
case ROTATE_90_DEGREES:
tp.x = this->display_height_ - y;
tp.y = this->display_width_ - x;
break;
case ROTATE_180_DEGREES:
tp.y = y;
tp.x = this->display_width_ - x;
break;
case ROTATE_270_DEGREES:
tp.x = y;
tp.y = x;
break;
}
this->defer([this, tp]() { this->send_touch_(tp); });
} }
this->status_clear_warning(); this->status_clear_warning();

View file

@ -6,29 +6,25 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include <vector>
namespace esphome { namespace esphome {
namespace lilygo_t5_47 { namespace lilygo_t5_47 {
struct Store {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(Store *store);
};
using namespace touchscreen; using namespace touchscreen;
class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { class LilygoT547Touchscreen : public Touchscreen, public i2c::I2CDevice {
public: public:
void setup() override; void setup() override;
void loop() override;
void dump_config() override; void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected: protected:
void update_touches() override;
InternalGPIOPin *interrupt_pin_; InternalGPIOPin *interrupt_pin_;
Store store_;
}; };
} // namespace lilygo_t5_47 } // namespace lilygo_t5_47

View file

@ -3,44 +3,84 @@ import esphome.codegen as cg
from esphome.components import display from esphome.components import display
from esphome import automation from esphome import automation
from esphome.const import CONF_ON_TOUCH from esphome.const import CONF_ON_TOUCH, CONF_ON_RELEASE
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz", "@nielsnl68"]
DEPENDENCIES = ["display"] DEPENDENCIES = ["display"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
touchscreen_ns = cg.esphome_ns.namespace("touchscreen") touchscreen_ns = cg.esphome_ns.namespace("touchscreen")
Touchscreen = touchscreen_ns.class_("Touchscreen") Touchscreen = touchscreen_ns.class_("Touchscreen", cg.PollingComponent)
TouchRotation = touchscreen_ns.enum("TouchRotation") TouchRotation = touchscreen_ns.enum("TouchRotation")
TouchPoint = touchscreen_ns.struct("TouchPoint") TouchPoint = touchscreen_ns.struct("TouchPoint")
TouchPoints_t = cg.std_vector.template(TouchPoint)
TouchPoints_t_const_ref = TouchPoints_t.operator("ref").operator("const")
TouchListener = touchscreen_ns.class_("TouchListener") TouchListener = touchscreen_ns.class_("TouchListener")
CONF_DISPLAY = "display" CONF_DISPLAY = "display"
CONF_TOUCHSCREEN_ID = "touchscreen_id" CONF_TOUCHSCREEN_ID = "touchscreen_id"
CONF_REPORT_INTERVAL = "report_interval" # not used yet:
CONF_ON_UPDATE = "on_update"
CONF_MIRROR_X = "mirror_x"
CONF_MIRROR_Y = "mirror_y"
CONF_SWAP_XY = "swap_xy"
CONF_TRANSFORM = "transform"
TOUCHSCREEN_SCHEMA = cv.Schema( TOUCHSCREEN_SCHEMA = cv.Schema(
{ {
cv.GenerateID(CONF_DISPLAY): cv.use_id(display.DisplayBuffer), cv.GenerateID(CONF_DISPLAY): cv.use_id(display.Display),
cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), cv.Optional(CONF_TRANSFORM): cv.Schema(
{
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
} }
) ),
cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True),
cv.Optional(CONF_ON_UPDATE): automation.validate_automation(single=True),
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True),
}
).extend(cv.polling_component_schema("50ms"))
async def register_touchscreen(var, config): async def register_touchscreen(var, config):
await cg.register_component(var, config)
disp = await cg.get_variable(config[CONF_DISPLAY]) disp = await cg.get_variable(config[CONF_DISPLAY])
cg.add(var.set_display(disp)) cg.add(var.set_display(disp))
if CONF_TRANSFORM in config:
transform = config[CONF_TRANSFORM]
cg.add(var.set_swap_xy(transform[CONF_SWAP_XY]))
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
if CONF_ON_TOUCH in config: if CONF_ON_TOUCH in config:
await automation.build_automation( await automation.build_automation(
var.get_touch_trigger(), var.get_touch_trigger(),
[(TouchPoint, "touch")], [(TouchPoint, "touch"), (TouchPoints_t_const_ref, "touches")],
config[CONF_ON_TOUCH], config[CONF_ON_TOUCH],
) )
if CONF_ON_UPDATE in config:
await automation.build_automation(
var.get_update_trigger(),
[(TouchPoints_t_const_ref, "touches")],
config[CONF_ON_UPDATE],
)
if CONF_ON_RELEASE in config:
await automation.build_automation(
var.get_release_trigger(),
[],
config[CONF_ON_RELEASE],
)
@coroutine_with_priority(100.0) @coroutine_with_priority(100.0)
async def to_code(config): async def to_code(config):

View file

@ -14,11 +14,10 @@ void TouchscreenBinarySensor::touch(TouchPoint tp) {
if (this->page_ != nullptr) { if (this->page_ != nullptr) {
touched &= this->page_ == this->parent_->get_display()->get_active_page(); touched &= this->page_ == this->parent_->get_display()->get_active_page();
} }
if (touched) { if (touched) {
this->publish_state(true); this->publish_state(true);
} else { } else {
release(); this->release();
} }
} }

View file

@ -7,27 +7,128 @@ namespace touchscreen {
static const char *const TAG = "touchscreen"; static const char *const TAG = "touchscreen";
void Touchscreen::set_display(display::Display *display) { void TouchscreenInterrupt::gpio_intr(TouchscreenInterrupt *store) { store->touched = true; }
this->display_ = display;
this->display_width_ = display->get_width();
this->display_height_ = display->get_height();
this->rotation_ = static_cast<TouchRotation>(display->get_rotation());
if (this->rotation_ == ROTATE_90_DEGREES || this->rotation_ == ROTATE_270_DEGREES) { void Touchscreen::attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::InterruptType type) {
std::swap(this->display_width_, this->display_height_); irq_pin->attach_interrupt(TouchscreenInterrupt::gpio_intr, &this->store_, type);
this->store_.init = true;
this->store_.touched = false;
}
void Touchscreen::update() {
if (!this->store_.init) {
this->store_.touched = true;
} else {
// no need to poll if we have interrupts.
this->stop_poller();
} }
} }
void Touchscreen::send_release_() { void Touchscreen::loop() {
if (this->store_.touched) {
this->first_touch_ = this->touches_.empty();
this->need_update_ = false;
this->is_touched_ = false;
this->skip_update_ = false;
for (auto &tp : this->touches_) {
if (tp.second.state == STATE_PRESSED || tp.second.state == STATE_UPDATED) {
tp.second.state = tp.second.state | STATE_RELEASING;
} else {
tp.second.state = STATE_RELEASED;
}
tp.second.x_prev = tp.second.x;
tp.second.y_prev = tp.second.y;
}
this->update_touches();
if (this->skip_update_) {
for (auto &tp : this->touches_) {
tp.second.state = tp.second.state & -STATE_RELEASING;
}
} else {
this->store_.touched = false;
this->defer([this]() { this->send_touches_(); });
}
}
}
void Touchscreen::set_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_raw, int16_t z_raw) {
TouchPoint tp;
uint16_t x, y;
if (this->touches_.count(id) == 0) {
tp.state = STATE_PRESSED;
tp.id = id;
} else {
tp = this->touches_[id];
tp.state = STATE_UPDATED;
}
tp.x_raw = x_raw;
tp.y_raw = y_raw;
tp.z_raw = z_raw;
x = this->normalize_(x_raw, this->x_raw_min_, this->x_raw_max_, this->invert_x_);
y = this->normalize_(y_raw, this->y_raw_min_, this->y_raw_max_, this->invert_y_);
if (this->swap_x_y_) {
std::swap(x, y);
}
tp.x = (uint16_t) ((int) x * this->get_width_() / 0x1000);
tp.y = (uint16_t) ((int) y * this->get_height_() / 0x1000);
if (tp.state == STATE_PRESSED) {
tp.x_org = tp.x;
tp.y_org = tp.y;
}
this->touches_[id] = tp;
this->is_touched_ = true;
if ((tp.x != tp.x_prev) || (tp.y != tp.y_prev)) {
this->need_update_ = true;
}
}
void Touchscreen::send_touches_() {
if (!this->is_touched_) {
this->release_trigger_.trigger();
for (auto *listener : this->touch_listeners_) for (auto *listener : this->touch_listeners_)
listener->release(); listener->release();
this->touches_.clear();
} else {
TouchPoints_t touches;
for (auto tp : this->touches_) {
touches.push_back(tp.second);
}
if (this->first_touch_) {
TouchPoint tp = this->touches_.begin()->second;
this->touch_trigger_.trigger(tp, touches);
for (auto *listener : this->touch_listeners_) {
listener->touch(tp);
}
}
if (this->need_update_) {
this->update_trigger_.trigger(touches);
for (auto *listener : this->touch_listeners_) {
listener->update(touches);
}
}
}
} }
void Touchscreen::send_touch_(TouchPoint tp) { int16_t Touchscreen::normalize_(int16_t val, int16_t min_val, int16_t max_val, bool inverted) {
ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); int16_t ret;
this->touch_trigger_.trigger(tp);
for (auto *listener : this->touch_listeners_) if (val <= min_val) {
listener->touch(tp); ret = 0;
} else if (val >= max_val) {
ret = 0xfff;
} else {
ret = (int16_t) ((int) 0xfff * (val - min_val) / (max_val - min_val));
}
ret = (inverted) ? 0xfff - ret : ret;
return ret;
} }
} // namespace touchscreen } // namespace touchscreen

View file

@ -1,54 +1,119 @@
#pragma once #pragma once
#include "esphome/components/display/display_buffer.h" #include "esphome/core/defines.h"
#include "esphome/components/display/display.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include <vector> #include <vector>
#include <map>
namespace esphome { namespace esphome {
namespace touchscreen { namespace touchscreen {
static const uint8_t STATE_RELEASED = 0x00;
static const uint8_t STATE_PRESSED = 0x01;
static const uint8_t STATE_UPDATED = 0x02;
static const uint8_t STATE_RELEASING = 0x04;
struct TouchPoint { struct TouchPoint {
uint16_t x;
uint16_t y;
uint8_t id; uint8_t id;
uint8_t state; int16_t x_raw{0}, y_raw{0}, z_raw{0};
uint16_t x_prev{0}, y_prev{0};
uint16_t x_org{0}, y_org{0};
uint16_t x{0}, y{0};
int8_t state{0};
};
using TouchPoints_t = std::vector<TouchPoint>;
struct TouchscreenInterrupt {
volatile bool touched{true};
bool init{false};
static void gpio_intr(TouchscreenInterrupt *store);
}; };
class TouchListener { class TouchListener {
public: public:
virtual void touch(TouchPoint tp) = 0; virtual void touch(TouchPoint tp) {}
virtual void update(const TouchPoints_t &tpoints) {}
virtual void release() {} virtual void release() {}
}; };
enum TouchRotation { class Touchscreen : public PollingComponent {
ROTATE_0_DEGREES = 0,
ROTATE_90_DEGREES = 90,
ROTATE_180_DEGREES = 180,
ROTATE_270_DEGREES = 270,
};
class Touchscreen {
public: public:
void set_display(display::Display *display); void set_display(display::Display *display) { this->display_ = display; }
display::Display *get_display() const { return this->display_; } display::Display *get_display() const { return this->display_; }
Trigger<TouchPoint> *get_touch_trigger() { return &this->touch_trigger_; } void set_mirror_x(bool invert_x) { this->invert_x_ = invert_x; }
void set_mirror_y(bool invert_y) { this->invert_y_ = invert_y; }
void set_swap_xy(bool swap) { this->swap_x_y_ = swap; }
void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
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);
this->y_raw_max_ = std::max(y_min, y_max);
if (x_min > x_max)
this->invert_x_ = true;
if (y_min > y_max)
this->invert_y_ = true;
}
Trigger<TouchPoint, const TouchPoints_t &> *get_touch_trigger() { return &this->touch_trigger_; }
Trigger<const TouchPoints_t &> *get_update_trigger() { return &this->update_trigger_; }
Trigger<> *get_release_trigger() { return &this->release_trigger_; }
void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); }
virtual void update_touches() = 0;
optional<TouchPoint> get_touch() { return this->touches_.begin()->second; }
TouchPoints_t get_touches() {
TouchPoints_t touches;
for (auto i : this->touches_) {
touches.push_back(i.second);
}
return touches;
}
void update() override;
void loop() override;
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_release_();
uint16_t display_width_; void attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::InterruptType type);
uint16_t display_height_;
display::Display *display_; void set_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_raw, int16_t z_raw = 0);
TouchRotation rotation_;
Trigger<TouchPoint> touch_trigger_; void send_touches_();
int16_t normalize_(int16_t val, int16_t min_val, int16_t max_val, bool inverted = false);
uint16_t get_width_() { return this->display_->get_width(); }
uint16_t get_height_() { return this->display_->get_height(); }
display::Display *display_{nullptr};
int16_t x_raw_min_{0}, x_raw_max_{0}, y_raw_min_{0}, y_raw_max_{0};
bool invert_x_{false}, invert_y_{false}, swap_x_y_{false};
Trigger<TouchPoint, const TouchPoints_t &> touch_trigger_;
Trigger<const TouchPoints_t &> update_trigger_;
Trigger<> release_trigger_;
std::vector<TouchListener *> touch_listeners_; std::vector<TouchListener *> touch_listeners_;
std::map<uint8_t, TouchPoint> touches_;
TouchscreenInterrupt store_;
bool first_touch_{true};
bool need_update_{false};
bool is_touched_{false};
bool skip_update_{false};
}; };
} // namespace touchscreen } // namespace touchscreen

View file

@ -12,7 +12,6 @@ DEPENDENCIES = ["i2c"]
TT21100Touchscreen = tt21100_ns.class_( TT21100Touchscreen = tt21100_ns.class_(
"TT21100Touchscreen", "TT21100Touchscreen",
touchscreen.Touchscreen, touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice, i2c.I2CDevice,
) )
TT21100ButtonListener = tt21100_ns.class_("TT21100ButtonListener") TT21100ButtonListener = tt21100_ns.class_("TT21100ButtonListener")
@ -24,17 +23,14 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
} }
) ).extend(i2c.i2c_device_schema(0x24))
.extend(i2c.i2c_device_schema(0x24))
.extend(cv.COMPONENT_SCHEMA)
) )
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) 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) await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin)) cg.add(var.set_interrupt_pin(interrupt_pin))

View file

@ -44,8 +44,6 @@ struct TT21100TouchReport {
TT21100TouchRecord touch_record[MAX_TOUCH_POINTS]; TT21100TouchRecord touch_record[MAX_TOUCH_POINTS];
} __attribute__((packed)); } __attribute__((packed));
void TT21100TouchscreenStore::gpio_intr(TT21100TouchscreenStore *store) { store->touch = true; }
float TT21100Touchscreen::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } float TT21100Touchscreen::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
void TT21100Touchscreen::setup() { void TT21100Touchscreen::setup() {
@ -54,9 +52,8 @@ void TT21100Touchscreen::setup() {
// Register interrupt pin // Register interrupt pin
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup(); this->interrupt_pin_->setup();
this->store_.pin = this->interrupt_pin_->to_isr();
this->interrupt_pin_->attach_interrupt(TT21100TouchscreenStore::gpio_intr, &this->store_, this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
gpio::INTERRUPT_FALLING_EDGE);
// Perform reset if necessary // Perform reset if necessary
if (this->reset_pin_ != nullptr) { if (this->reset_pin_ != nullptr) {
@ -65,19 +62,11 @@ void TT21100Touchscreen::setup() {
} }
// Update display dimensions if they were updated during display setup // Update display dimensions if they were updated during display setup
this->display_width_ = this->display_->get_width(); this->x_raw_max_ = this->get_width_();
this->display_height_ = this->display_->get_height(); this->y_raw_max_ = this->get_height_();
this->rotation_ = static_cast<TouchRotation>(this->display_->get_rotation());
// Trigger initial read to activate the interrupt
this->store_.touch = true;
} }
void TT21100Touchscreen::loop() { void TT21100Touchscreen::update_touches() {
if (!this->store_.touch)
return;
this->store_.touch = false;
// Read report length // Read report length
uint16_t data_len; uint16_t data_len;
this->read((uint8_t *) &data_len, sizeof(data_len)); this->read((uint8_t *) &data_len, sizeof(data_len));
@ -111,12 +100,6 @@ void TT21100Touchscreen::loop() {
uint8_t touch_count = (data_len - (sizeof(*report) - sizeof(report->touch_record))) / sizeof(TT21100TouchRecord); uint8_t touch_count = (data_len - (sizeof(*report) - sizeof(report->touch_record))) / sizeof(TT21100TouchRecord);
if (touch_count == 0) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
}
for (int i = 0; i < touch_count; i++) { for (int i = 0; i < touch_count; i++) {
auto *touch = &report->touch_record[i]; auto *touch = &report->touch_record[i];
@ -126,30 +109,7 @@ void TT21100Touchscreen::loop() {
i, touch->touch_type, touch->tip, touch->event_id, touch->touch_id, touch->x, touch->y, i, touch->touch_type, touch->tip, touch->event_id, touch->touch_id, touch->x, touch->y,
touch->pressure, touch->major_axis_length, touch->orientation); touch->pressure, touch->major_axis_length, touch->orientation);
TouchPoint tp; this->set_raw_touch_position_(touch->tip, touch->x, touch->y, touch->pressure);
switch (this->rotation_) {
case ROTATE_0_DEGREES:
// Origin is top right, so mirror X by default
tp.x = this->display_width_ - touch->x;
tp.y = touch->y;
break;
case ROTATE_90_DEGREES:
tp.x = touch->y;
tp.y = touch->x;
break;
case ROTATE_180_DEGREES:
tp.x = touch->x;
tp.y = this->display_height_ - touch->y;
break;
case ROTATE_270_DEGREES:
tp.x = this->display_height_ - touch->y;
tp.y = this->display_width_ - touch->x;
break;
}
tp.id = touch->tip;
tp.state = touch->pressure;
this->defer([this, tp]() { this->send_touch_(tp); });
} }
} }
} }

View file

@ -5,27 +5,21 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include <vector>
namespace esphome { namespace esphome {
namespace tt21100 { namespace tt21100 {
using namespace touchscreen; using namespace touchscreen;
struct TT21100TouchscreenStore {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(TT21100TouchscreenStore *store);
};
class TT21100ButtonListener { class TT21100ButtonListener {
public: public:
virtual void update_button(uint8_t index, uint16_t state) = 0; virtual void update_button(uint8_t index, uint16_t state) = 0;
}; };
class TT21100Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { class TT21100Touchscreen : public Touchscreen, public i2c::I2CDevice {
public: public:
void setup() override; void setup() override;
void loop() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
@ -37,7 +31,7 @@ class TT21100Touchscreen : public Touchscreen, public Component, public i2c::I2C
protected: protected:
void reset_(); void reset_();
TT21100TouchscreenStore store_; void update_touches() override;
InternalGPIOPin *interrupt_pin_; InternalGPIOPin *interrupt_pin_;
GPIOPin *reset_pin_{nullptr}; GPIOPin *reset_pin_{nullptr};

View file

@ -1,3 +0,0 @@
import esphome.config_validation as cv
CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.")

View file

@ -1,116 +0,0 @@
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_INTERRUPT_PIN, CONF_IRQ_PIN, 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_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"
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))

View file

@ -0,0 +1,93 @@
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, CONF_INTERRUPT_PIN
CODEOWNERS = ["@numo68", "@nielsnl68"]
DEPENDENCIES = ["spi"]
XPT2046_ns = cg.esphome_ns.namespace("xpt2046")
XPT2046Component = XPT2046_ns.class_(
"XPT2046Component",
touchscreen.Touchscreen,
spi.SPIDevice,
)
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"
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
CONFIG_SCHEMA = cv.All(
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),
},
)
).extend(spi.spi_device_schema()),
validate_xpt2046,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await touchscreen.register_touchscreen(var, config)
await spi.register_spi_device(var, config)
cg.add(var.set_threshold(config[CONF_THRESHOLD]))
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))

View file

@ -0,0 +1,113 @@
#include "xpt2046.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <algorithm>
namespace esphome {
namespace xpt2046 {
static const char *const TAG = "xpt2046";
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->attach_interrupt_(this->irq_pin_, gpio::INTERRUPT_FALLING_EDGE);
}
this->spi_setup();
this->read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin
}
void XPT2046Component::update_touches() {
int16_t data[6], x_raw, y_raw, z_raw;
bool touch = false;
enable();
int16_t touch_pressure_1 = this->read_adc_(0xB1 /* touch_pressure_1 */);
int16_t touch_pressure_2 = this->read_adc_(0xC1 /* touch_pressure_2 */);
ESP_LOGVV(TAG, "touch_pressure %d, %d", touch_pressure_1, touch_pressure_2);
z_raw = touch_pressure_1 + 0Xfff - touch_pressure_2;
touch = (z_raw >= this->threshold_);
if (touch) {
read_adc_(0xD1 /* X */); // dummy Y measure, 1st is always noisy
data[0] = this->read_adc_(0x91 /* Y */);
data[1] = this->read_adc_(0xD1 /* X */); // make 3 x-y measurements
data[2] = this->read_adc_(0x91 /* Y */);
data[3] = this->read_adc_(0xD1 /* X */);
data[4] = this->read_adc_(0x91 /* Y */);
}
data[5] = this->read_adc_(0xD0 /* X */); // Last X touch power down
disable();
if (touch) {
x_raw = best_two_avg(data[1], data[3], data[5]);
y_raw = best_two_avg(data[0], data[2], data[4]);
ESP_LOGV(TAG, "Touchscreen Update [%d, %d], z = %d", x_raw, y_raw, z_raw);
this->set_raw_touch_position_(0, x_raw, y_raw, z_raw);
}
}
void XPT2046Component::dump_config() {
ESP_LOGCONFIG(TAG, "XPT2046:");
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
ESP_LOGCONFIG(TAG, " X min: %d", this->x_raw_min_);
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, " 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_);
LOG_UPDATE_INTERVAL(this);
}
float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; }
int16_t XPT2046Component::best_two_avg(int16_t value1, int16_t value2, int16_t value3) {
int16_t delta_a, delta_b, delta_c;
int16_t reta = 0;
delta_a = (value1 > value2) ? value1 - value2 : value2 - value1;
delta_b = (value1 > value3) ? value1 - value3 : value3 - value1;
delta_c = (value3 > value2) ? value3 - value2 : value2 - value3;
if (delta_a <= delta_b && delta_a <= delta_c) {
reta = (value1 + value2) >> 1;
} else if (delta_b <= delta_a && delta_b <= delta_c) {
reta = (value1 + value3) >> 1;
} else {
reta = (value2 + value3) >> 1;
}
return reta;
}
int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT
uint8_t data[2];
this->write_byte(ctrl);
delay(1);
data[0] = this->read_byte();
data[1] = this->read_byte();
return ((data[0] << 8) | data[1]) >> 3;
}
} // namespace xpt2046
} // namespace esphome

View file

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace xpt2046 {
using namespace touchscreen;
class XPT2046Component : public Touchscreen,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
public:
/// 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(InternalGPIOPin *pin) { this->irq_pin_ = pin; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
protected:
static int16_t best_two_avg(int16_t value1, int16_t value2, int16_t value3);
int16_t read_adc_(uint8_t ctrl);
void update_touches() override;
int16_t threshold_;
InternalGPIOPin *irq_pin_{nullptr};
};
} // namespace xpt2046
} // namespace esphome

View file

@ -1,207 +0,0 @@
#include "xpt2046.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <algorithm>
#include <cinttypes>
namespace esphome {
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->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) && (this->store_.touch || this->touched)) {
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();
enable();
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 = touch_pressure_1 + 0Xfff - touch_pressure_2;
touch = (this->z_raw >= this->threshold_);
if (touch) {
read_adc_(0xD1 /* X */); // dummy Y measure, 1st is always noisy
data[0] = read_adc_(0x91 /* Y */);
data[1] = read_adc_(0xD1 /* X */); // make 3 x-y measurements
data[2] = read_adc_(0x91 /* Y */);
data[3] = read_adc_(0xD1 /* X */);
data[4] = read_adc_(0x91 /* Y */);
}
data[5] = read_adc_(0xD0 /* X */); // Last X touch power down
disable();
if (touch) {
this->x_raw = best_two_avg(data[1], data[3], data[5]);
this->y_raw = best_two_avg(data[0], data[2], data[4]);
ESP_LOGVV(TAG, "Update [x, y] = [%d, %d], z = %d", this->x_raw, this->y_raw, this->z_raw);
TouchPoint touchpoint;
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(touchpoint.x, touchpoint.y);
}
if (this->invert_x_) {
touchpoint.x = 0xfff - touchpoint.x;
}
if (this->invert_y_) {
touchpoint.y = 0xfff - touchpoint.y;
}
switch (static_cast<TouchRotation>(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;
}
touchpoint.x = (int16_t) ((int) touchpoint.x * this->display_->get_width() / 0xfff);
touchpoint.y = (int16_t) ((int) touchpoint.y * this->display_->get_height() / 0xfff);
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->defer([this, touchpoint]() { this->send_touch_(touchpoint); });
this->x = touchpoint.x;
this->y = touchpoint.y;
this->touched = true;
this->last_pos_ms_ = now;
}
}
if (!touch && this->touched) {
this->x_raw = this->y_raw = this->z_raw = 0;
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) { // 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);
this->y_raw_max_ = std::max(y_min, y_max);
this->invert_x_ = (x_min > x_max);
this->invert_y_ = (y_min > y_max);
}
void XPT2046Component::dump_config() {
ESP_LOGCONFIG(TAG, "XPT2046:");
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
ESP_LOGCONFIG(TAG, " X min: %d", this->x_raw_min_);
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, " 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: %" PRIu32, this->report_millis_);
LOG_UPDATE_INTERVAL(this);
}
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) { // NOLINT
int16_t da, db, dc; // NOLINT
int16_t reta = 0;
da = (x > y) ? x - y : y - x;
db = (x > z) ? x - z : z - x;
dc = (z > y) ? z - y : y - z;
if (da <= db && da <= dc) {
reta = (x + y) >> 1;
} else if (db <= da && db <= dc) {
reta = (x + z) >> 1;
} else {
reta = (y + z) >> 1;
}
return reta;
}
int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_val) {
int16_t ret;
if (val <= min_val) {
ret = 0;
} else if (val >= max_val) {
ret = 0xfff;
} else {
ret = (int16_t) ((int) 0xfff * (val - min_val) / (max_val - min_val));
}
return ret;
}
int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT
uint8_t data[2];
write_byte(ctrl);
delay(1);
data[0] = read_byte();
data[1] = read_byte();
return ((data[0] << 8) | data[1]) >> 3;
}
} // namespace xpt2046
} // namespace esphome

View file

@ -1,107 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace xpt2046 {
using namespace touchscreen;
struct XPT2046TouchscreenStore {
volatile bool touch;
static void gpio_intr(XPT2046TouchscreenStore *store);
};
class XPT2046Component : public Touchscreen,
public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
public:
/// Set the logical touch screen dimensions.
void set_dimensions(int16_t x, int16_t 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);
/// If true the x and y axes will be swapped
void set_swap_x_y(bool val) { this->swap_x_y_ = val; }
/// 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(InternalGPIOPin *pin) { this->irq_pin_ = pin; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
/** Detect the touch if the irq pin is specified.
*
* If the touch is detected and the component does not already know about it
* the update() is called immediately. If the irq pin is not specified
* the loop() is a no-op.
*/
void loop() override;
/** Read and process the values from the hardware.
*
* Read the raw x, y and touch pressure values from the chip, detect the touch,
* and if touched, transform to the user x and y coordinates. If the state has
* changed or if the value should be reported again due to the
* report interval, run the action and inform the virtual buttons.
*/
void update() override;
/**@{*/
/** Coordinates of the touch position.
*
* The values are set immediately before the on_state action with touched == true
* is triggered. The action with touched == false sends the coordinates of the last
* reported touch.
*/
int16_t x{0}, y{0};
/**@}*/
/// True if the component currently detects the touch
bool touched{false};
/**@{*/
/** Raw sensor values of the coordinates and the pressure.
*
* The values are set each time the update() method is called.
*/
int16_t x_raw{0}, y_raw{0}, z_raw{0};
/**@}*/
protected:
static int16_t best_two_avg(int16_t x, int16_t y, int16_t z);
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_;
bool invert_x_, invert_y_;
bool swap_x_y_;
uint32_t report_millis_;
uint32_t last_pos_ms_{0};
InternalGPIOPin *irq_pin_{nullptr};
XPT2046TouchscreenStore store_;
};
} // namespace xpt2046
} // namespace esphome

View file

@ -464,6 +464,7 @@ binary_sensor:
sx1509: sx1509_hub sx1509: sx1509_hub
number: 3 number: 3
- platform: touchscreen - platform: touchscreen
touchscreen_id: lilygo_touchscreen touchscreen_id: lilygo_touchscreen
id: touch_key1 id: touch_key1
@ -483,6 +484,7 @@ binary_sensor:
pin: pin:
max6956: max6956_1 max6956: max6956_1
number: 4 number: 4
mode: mode:
input: true input: true
pullup: true pullup: true
@ -506,6 +508,7 @@ binary_sensor:
input: true input: true
inverted: false inverted: false
climate: climate:
- platform: tuya - platform: tuya
id: tuya_climate id: tuya_climate
@ -595,6 +598,8 @@ display:
it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green);
it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue);
it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red);
auto touch = id(ft63_touchscreen)->get_touch();
if (touch) { ESP_LOGD("touch", "%d/%d", touch.value().x, touch.value().y); }
rotation: 0° rotation: 0°
update_interval: 16ms update_interval: 16ms
@ -677,53 +682,53 @@ display:
update_interval: 60s update_interval: 60s
display_data_1_pin: display_data_1_pin:
number: 5 number: GPIO5
allow_other_uses: true allow_other_uses: true
display_data_2_pin: display_data_2_pin:
number: 18 number: GPIO18
allow_other_uses: true allow_other_uses: true
display_data_3_pin: display_data_3_pin:
number: 19 number: GPIO19
allow_other_uses: true allow_other_uses: true
display_data_5_pin: display_data_5_pin:
number: 25 number: GPIO25
allow_other_uses: true allow_other_uses: true
display_data_4_pin: display_data_4_pin:
number: 23 number: GPIO23
allow_other_uses: true allow_other_uses: true
display_data_6_pin: display_data_6_pin:
number: 26 number: GPIO26
allow_other_uses: true allow_other_uses: true
display_data_7_pin: display_data_7_pin:
number: 27 number: GPIO27
allow_other_uses: true allow_other_uses: true
ckv_pin: ckv_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
sph_pin: sph_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
gmod_pin: gmod_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
gpio0_enable_pin: gpio0_enable_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
oe_pin: oe_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
spv_pin: spv_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
powerup_pin: powerup_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
wakeup_pin: wakeup_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
vcom_pin: vcom_pin:
allow_other_uses: true
number: GPIO1 number: GPIO1
allow_other_uses: true
number: number:
- platform: tuya - platform: tuya
@ -816,21 +821,16 @@ esp32_camera:
allow_other_uses: true allow_other_uses: true
- number: GPIO35 - number: GPIO35
allow_other_uses: true allow_other_uses: true
- - number: GPIO34
number: GPIO34 - number: GPIO5
-
number: GPIO5
allow_other_uses: true allow_other_uses: true
- - number: GPIO39
number: GPIO39
-
number: GPIO18
allow_other_uses: true allow_other_uses: true
- - number: GPIO18
number: GPIO36
allow_other_uses: true allow_other_uses: true
- - number: GPIO36
number: GPIO19 allow_other_uses: true
- number: GPIO19
allow_other_uses: true allow_other_uses: true
vsync_pin: vsync_pin:
allow_other_uses: true allow_other_uses: true
@ -910,18 +910,16 @@ touchscreen:
spi_id: spi_id_2 spi_id: spi_id_2
cs_pin: cs_pin:
allow_other_uses: true allow_other_uses: true
number: 17 number: GPIO17
interrupt_pin: interrupt_pin:
number: 16 number: GPIO16
display: inkplate_display display: inkplate_display
update_interval: 50ms update_interval: 50ms
report_interval: 1s
threshold: 400 threshold: 400
calibration_x_min: 3860 calibration_x_min: 3860
calibration_x_max: 280 calibration_x_max: 280
calibration_y_min: 340 calibration_y_min: 340
calibration_y_max: 3860 calibration_y_max: 3860
swap_x_y: false
on_touch: on_touch:
- logger.log: - logger.log:
format: Touch at (%d, %d) format: Touch at (%d, %d)
@ -938,10 +936,25 @@ touchscreen:
format: Touch at (%d, %d) format: Touch at (%d, %d)
args: [touch.x, touch.y] args: [touch.x, touch.y]
- platform: gt911 - platform: gt911
interrupt_pin: GPIO3 interrupt_pin:
number: GPIO3
display: inkplate_display display: inkplate_display
- platform: ft63x6
id: ft63_touchscreen
interrupt_pin:
allow_other_uses: true
number: GPIO39
reset_pin:
allow_other_uses: true
number: GPIO5
display: inkplate_display
on_touch:
- logger.log:
format: Touch at (%d, %d)
args: [touch.x, touch.y]
i2s_audio: i2s_audio:
i2s_lrclk_pin: i2s_lrclk_pin:
allow_other_uses: true allow_other_uses: true

View file

@ -54,6 +54,7 @@ spi_device:
display: display:
- platform: ili9xxx - platform: ili9xxx
id: displ8
model: ili9342 model: ili9342
cs_pin: GPIO5 cs_pin: GPIO5
dc_pin: GPIO4 dc_pin: GPIO4
@ -67,6 +68,7 @@ i2c:
touchscreen: touchscreen:
- platform: tt21100 - platform: tt21100
display: displ8
interrupt_pin: interrupt_pin:
number: GPIO3 number: GPIO3
ignore_strapping_warning: true ignore_strapping_warning: true