mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
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:
parent
8e92bb7958
commit
c6dc336c4a
35 changed files with 997 additions and 836 deletions
|
@ -88,7 +88,7 @@ esphome/components/ds1307/* @badbadc0ffee
|
|||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ektf2232/touchscreen/* @jesserockz
|
||||
esphome/components/emc2101/* @ellull
|
||||
esphome/components/ens160/* @vincentscode
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
|
@ -110,6 +110,8 @@ esphome/components/fastled_base/* @OttoWinter
|
|||
esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/fs3000/* @kahrendt
|
||||
esphome/components/ft5x06/* @clydebarrow
|
||||
esphome/components/ft63x6/* @gpambrozio
|
||||
esphome/components/gcja5/* @gcormier
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gp8403/* @jesserockz
|
||||
|
@ -331,7 +333,7 @@ esphome/components/tmp1075/* @sybrenstuvel
|
|||
esphome/components/tmp117/* @Azimath
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/toshiba/* @kbx81
|
||||
esphome/components/touchscreen/* @jesserockz
|
||||
esphome/components/touchscreen/* @jesserockz @nielsnl68
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
esphome/components/tt21100/* @kroimon
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
|
@ -364,6 +366,6 @@ esphome/components/xiaomi_mhoc303/* @drug123
|
|||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
esphome/components/xl9535/* @mreditor97
|
||||
esphome/components/xpt2046/* @nielsnl68 @numo68
|
||||
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
|
||||
esphome/components/zhlt01/* @cfeenstra1024
|
||||
esphome/components/zio_ultrasonic/* @kahrendt
|
||||
|
|
|
@ -12,7 +12,6 @@ ektf2232_ns = cg.esphome_ns.namespace("ektf2232")
|
|||
EKTF2232Touchscreen = ektf2232_ns.class_(
|
||||
"EKTF2232Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
|
@ -28,17 +27,14 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
|||
),
|
||||
cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x15))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(i2c.i2c_device_schema(0x15))
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
|
@ -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_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01};
|
||||
|
||||
void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; }
|
||||
|
||||
void EKTF2232Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen...");
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_,
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
this->rts_pin_->setup();
|
||||
|
||||
|
@ -45,7 +41,7 @@ void EKTF2232Touchscreen::setup() {
|
|||
this->mark_failed();
|
||||
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);
|
||||
if (this->read(received, 4)) {
|
||||
|
@ -54,19 +50,14 @@ void EKTF2232Touchscreen::setup() {
|
|||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
this->store_.touch = false;
|
||||
this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
|
||||
this->set_power_state(true);
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::loop() {
|
||||
if (!this->store_.touch)
|
||||
return;
|
||||
this->store_.touch = false;
|
||||
|
||||
void EKTF2232Touchscreen::update_touches() {
|
||||
uint8_t touch_count = 0;
|
||||
std::vector<TouchPoint> touches;
|
||||
int16_t x_raw, y_raw;
|
||||
|
||||
uint8_t raw[8];
|
||||
this->read(raw, 8);
|
||||
|
@ -75,45 +66,15 @@ void EKTF2232Touchscreen::loop() {
|
|||
touch_count++;
|
||||
}
|
||||
|
||||
if (touch_count == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
|
||||
touch_count = std::min<uint8_t>(touch_count, 2);
|
||||
|
||||
ESP_LOGV(TAG, "Touch count: %d", touch_count);
|
||||
|
||||
for (int i = 0; i < touch_count; i++) {
|
||||
uint8_t *d = raw + 1 + (i * 3);
|
||||
uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1];
|
||||
uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2];
|
||||
|
||||
raw_x = raw_x * this->display_height_ - 1;
|
||||
raw_y = raw_y * this->display_width_ - 1;
|
||||
|
||||
TouchPoint tp;
|
||||
switch (this->rotation_) {
|
||||
case ROTATE_0_DEGREES:
|
||||
tp.y = raw_x / this->x_resolution_;
|
||||
tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
case ROTATE_90_DEGREES:
|
||||
tp.x = raw_x / this->x_resolution_;
|
||||
tp.y = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_180_DEGREES:
|
||||
tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.x = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_270_DEGREES:
|
||||
tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
}
|
||||
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
x_raw = (d[0] & 0xF0) << 4 | d[1];
|
||||
y_raw = (d[0] & 0x0F) << 8 | d[2];
|
||||
this->set_raw_touch_position_(i, x_raw, y_raw);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,7 +87,7 @@ void EKTF2232Touchscreen::set_power_state(bool enable) {
|
|||
bool EKTF2232Touchscreen::get_power_state() {
|
||||
uint8_t received[4];
|
||||
this->write(GET_POWER_STATE_CMD, 4);
|
||||
this->store_.touch = false;
|
||||
this->store_.touched = false;
|
||||
this->read(received, 4);
|
||||
return (received[1] >> 3) & 1;
|
||||
}
|
||||
|
@ -145,14 +106,14 @@ bool EKTF2232Touchscreen::soft_reset_() {
|
|||
|
||||
uint8_t received[4];
|
||||
uint16_t timeout = 1000;
|
||||
while (!this->store_.touch && timeout > 0) {
|
||||
while (!this->store_.touched && timeout > 0) {
|
||||
delay(1);
|
||||
timeout--;
|
||||
}
|
||||
if (timeout > 0)
|
||||
this->store_.touch = true;
|
||||
this->store_.touched = true;
|
||||
this->read(received, 4);
|
||||
this->store_.touch = false;
|
||||
this->store_.touched = false;
|
||||
|
||||
return !memcmp(received, HELLO, 4);
|
||||
}
|
|
@ -9,19 +9,11 @@
|
|||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
struct EKTF2232TouchscreenStore {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(EKTF2232TouchscreenStore *store);
|
||||
};
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
|
||||
class EKTF2232Touchscreen : public Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
|
@ -33,12 +25,10 @@ class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2
|
|||
protected:
|
||||
void hard_reset_();
|
||||
bool soft_reset_();
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
GPIOPin *rts_pin_;
|
||||
EKTF2232TouchscreenStore store_;
|
||||
uint16_t x_resolution_;
|
||||
uint16_t y_resolution_;
|
||||
};
|
||||
|
||||
} // namespace ektf2232
|
6
esphome/components/ft5x06/__init__.py
Normal file
6
esphome/components/ft5x06/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ft5x06_ns = cg.esphome_ns.namespace("ft5x06")
|
26
esphome/components/ft5x06/touchscreen/__init__.py
Normal file
26
esphome/components/ft5x06/touchscreen/__init__.py
Normal 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)
|
124
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h
Normal file
124
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h
Normal 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
|
1
esphome/components/ft63x6/__init__.py
Normal file
1
esphome/components/ft63x6/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@gpambrozio"]
|
99
esphome/components/ft63x6/ft63x6.cpp
Normal file
99
esphome/components/ft63x6/ft63x6.cpp
Normal 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
|
41
esphome/components/ft63x6/ft63x6.h
Normal file
41
esphome/components/ft63x6/ft63x6.h
Normal 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
|
44
esphome/components/ft63x6/touchscreen.py
Normal file
44
esphome/components/ft63x6/touchscreen.py
Normal 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))
|
|
@ -3,7 +3,7 @@ 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 esphome.const import CONF_INTERRUPT_PIN, CONF_ID
|
||||
from .. import gt911_ns
|
||||
|
||||
|
||||
|
@ -11,36 +11,21 @@ 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)
|
||||
)
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GT911Touchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x5D))
|
||||
|
||||
|
||||
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)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
if CONF_ROTATION in config:
|
||||
cg.add(var.set_rotation(ROTATIONS[config[CONF_ROTATION]]))
|
||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
||||
|
|
|
@ -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 GET_TOUCHES[2] = {0x81, 0x4F};
|
||||
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
|
||||
|
||||
#define ERROR_CHECK(err) \
|
||||
|
@ -21,24 +22,35 @@ static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
|
|||
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;
|
||||
uint8_t data[4];
|
||||
err = this->write(GET_SWITCHES, 2);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->read(&data, 1);
|
||||
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);
|
||||
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.
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_,
|
||||
(data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->write(GET_MAX_VALUES, 2);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->read(data, sizeof(data));
|
||||
if (err == i2c::ERROR_OK) {
|
||||
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) {
|
||||
|
@ -46,31 +58,28 @@ void GT911Touchscreen::setup() {
|
|||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void GT911Touchscreen::loop() {
|
||||
void GT911Touchscreen::update_touches() {
|
||||
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 ((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)
|
||||
this->send_release_();
|
||||
if (num_of_touches > MAX_TOUCHES) // should never happen
|
||||
return;
|
||||
|
||||
err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false);
|
||||
|
@ -80,29 +89,10 @@ void GT911Touchscreen::loop() {
|
|||
ERROR_CHECK(err);
|
||||
|
||||
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 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); });
|
||||
this->set_raw_touch_position_(id, x, y);
|
||||
}
|
||||
auto keys = data[num_of_touches][0];
|
||||
for (size_t i = 0; i != 4; i++) {
|
||||
|
@ -115,7 +105,6 @@ 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
|
||||
|
|
|
@ -8,30 +8,23 @@
|
|||
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 {
|
||||
class GT911Touchscreen : public touchscreen::Touchscreen, 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_;
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
std::vector<GT911ButtonListener *> button_listeners_;
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,11 @@ CODEOWNERS = ["@nielsnl68", "@clydebarrow"]
|
|||
|
||||
ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx")
|
||||
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")
|
||||
|
|
|
@ -39,7 +39,11 @@ CONF_VCOM_PIN = "vcom_pin"
|
|||
|
||||
inkplate6_ns = cg.esphome_ns.namespace("inkplate6")
|
||||
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")
|
||||
|
|
|
@ -13,7 +13,6 @@ DEPENDENCIES = ["i2c"]
|
|||
LilygoT547Touchscreen = lilygo_t5_47_ns.class_(
|
||||
"LilygoT547Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
|
@ -27,17 +26,14 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
|||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x5A))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(i2c.i2c_device_schema(0x5A))
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
|
|
|
@ -23,15 +23,12 @@ static const uint8_t READ_TOUCH[1] = {0x07};
|
|||
return; \
|
||||
}
|
||||
|
||||
void Store::gpio_intr(Store *store) { store->touch = true; }
|
||||
|
||||
void LilygoT547Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen...");
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
if (this->write(nullptr, 0) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to communicate!");
|
||||
|
@ -41,19 +38,14 @@ void LilygoT547Touchscreen::setup() {
|
|||
}
|
||||
|
||||
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() {
|
||||
if (!this->store_.touch) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
this->store_.touch = false;
|
||||
|
||||
void LilygoT547Touchscreen::update_touches() {
|
||||
uint8_t point = 0;
|
||||
uint8_t buffer[40] = {0};
|
||||
uint32_t sum_l = 0, sum_h = 0;
|
||||
|
||||
i2c::ErrorCode err;
|
||||
err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1);
|
||||
|
@ -69,102 +61,30 @@ void LilygoT547Touchscreen::loop() {
|
|||
|
||||
point = buffer[5] & 0xF;
|
||||
|
||||
if (point == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
} else if (point == 1) {
|
||||
if (point == 1) {
|
||||
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&buffer[5], 2);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
sum_l = buffer[5] << 8 | buffer[6];
|
||||
} else if (point > 1) {
|
||||
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&buffer[5], 5 * (point - 1) + 3);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2];
|
||||
}
|
||||
|
||||
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
|
||||
|
||||
for (int i = 0; i < 5 * point; i++)
|
||||
sum_h += buffer[i];
|
||||
if (point == 0)
|
||||
point = 1;
|
||||
|
||||
if (sum_l != sum_h)
|
||||
point = 0;
|
||||
|
||||
if (point) {
|
||||
uint8_t offset;
|
||||
for (int i = 0; i < point; i++) {
|
||||
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); });
|
||||
uint16_t id, x_raw, y_raw;
|
||||
for (uint8_t i = 0; i < point; i++) {
|
||||
id = (buffer[i * 5] >> 4) & 0x0F;
|
||||
y_raw = (uint16_t) ((buffer[i * 5 + 1] << 4) | ((buffer[i * 5 + 3] >> 4) & 0x0F));
|
||||
x_raw = (uint16_t) ((buffer[i * 5 + 2] << 4) | (buffer[i * 5 + 3] & 0x0F));
|
||||
this->set_raw_touch_position_(id, x_raw, y_raw);
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
|
|
|
@ -6,29 +6,25 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace lilygo_t5_47 {
|
||||
|
||||
struct Store {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(Store *store);
|
||||
};
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
|
||||
class LilygoT547Touchscreen : public Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
|
||||
protected:
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
Store store_;
|
||||
};
|
||||
|
||||
} // namespace lilygo_t5_47
|
||||
|
|
|
@ -3,44 +3,84 @@ import esphome.codegen as cg
|
|||
|
||||
from esphome.components import display
|
||||
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
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CODEOWNERS = ["@jesserockz", "@nielsnl68"]
|
||||
DEPENDENCIES = ["display"]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
touchscreen_ns = cg.esphome_ns.namespace("touchscreen")
|
||||
|
||||
Touchscreen = touchscreen_ns.class_("Touchscreen")
|
||||
Touchscreen = touchscreen_ns.class_("Touchscreen", cg.PollingComponent)
|
||||
TouchRotation = touchscreen_ns.enum("TouchRotation")
|
||||
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")
|
||||
|
||||
CONF_DISPLAY = "display"
|
||||
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(
|
||||
{
|
||||
cv.GenerateID(CONF_DISPLAY): cv.use_id(display.DisplayBuffer),
|
||||
cv.GenerateID(CONF_DISPLAY): cv.use_id(display.Display),
|
||||
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):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
disp = await cg.get_variable(config[CONF_DISPLAY])
|
||||
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:
|
||||
await automation.build_automation(
|
||||
var.get_touch_trigger(),
|
||||
[(TouchPoint, "touch")],
|
||||
[(TouchPoint, "touch"), (TouchPoints_t_const_ref, "touches")],
|
||||
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)
|
||||
async def to_code(config):
|
||||
|
|
|
@ -14,11 +14,10 @@ void TouchscreenBinarySensor::touch(TouchPoint tp) {
|
|||
if (this->page_ != nullptr) {
|
||||
touched &= this->page_ == this->parent_->get_display()->get_active_page();
|
||||
}
|
||||
|
||||
if (touched) {
|
||||
this->publish_state(true);
|
||||
} else {
|
||||
release();
|
||||
this->release();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,27 +7,128 @@ namespace touchscreen {
|
|||
|
||||
static const char *const TAG = "touchscreen";
|
||||
|
||||
void Touchscreen::set_display(display::Display *display) {
|
||||
this->display_ = display;
|
||||
this->display_width_ = display->get_width();
|
||||
this->display_height_ = display->get_height();
|
||||
this->rotation_ = static_cast<TouchRotation>(display->get_rotation());
|
||||
void TouchscreenInterrupt::gpio_intr(TouchscreenInterrupt *store) { store->touched = true; }
|
||||
|
||||
if (this->rotation_ == ROTATE_90_DEGREES || this->rotation_ == ROTATE_270_DEGREES) {
|
||||
std::swap(this->display_width_, this->display_height_);
|
||||
void Touchscreen::attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::InterruptType type) {
|
||||
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_() {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->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::send_touch_(TouchPoint tp) {
|
||||
ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y);
|
||||
this->touch_trigger_.trigger(tp);
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->touch(tp);
|
||||
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_)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int16_t Touchscreen::normalize_(int16_t val, int16_t min_val, int16_t max_val, bool inverted) {
|
||||
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));
|
||||
}
|
||||
|
||||
ret = (inverted) ? 0xfff - ret : ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace touchscreen
|
||||
|
|
|
@ -1,54 +1,119 @@
|
|||
#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/hal.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
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 {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
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 {
|
||||
public:
|
||||
virtual void touch(TouchPoint tp) = 0;
|
||||
virtual void touch(TouchPoint tp) {}
|
||||
virtual void update(const TouchPoints_t &tpoints) {}
|
||||
virtual void release() {}
|
||||
};
|
||||
|
||||
enum TouchRotation {
|
||||
ROTATE_0_DEGREES = 0,
|
||||
ROTATE_90_DEGREES = 90,
|
||||
ROTATE_180_DEGREES = 180,
|
||||
ROTATE_270_DEGREES = 270,
|
||||
};
|
||||
|
||||
class Touchscreen {
|
||||
class Touchscreen : public PollingComponent {
|
||||
public:
|
||||
void set_display(display::Display *display);
|
||||
void set_display(display::Display *display) { this->display_ = 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); }
|
||||
|
||||
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:
|
||||
/// 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_;
|
||||
uint16_t display_height_;
|
||||
display::Display *display_;
|
||||
TouchRotation rotation_;
|
||||
Trigger<TouchPoint> touch_trigger_;
|
||||
void attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::InterruptType type);
|
||||
|
||||
void set_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_raw, int16_t z_raw = 0);
|
||||
|
||||
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::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
|
||||
|
|
|
@ -12,7 +12,6 @@ DEPENDENCIES = ["i2c"]
|
|||
TT21100Touchscreen = tt21100_ns.class_(
|
||||
"TT21100Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
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.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x24))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(i2c.i2c_device_schema(0x24))
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
|
|
|
@ -44,8 +44,6 @@ struct TT21100TouchReport {
|
|||
TT21100TouchRecord touch_record[MAX_TOUCH_POINTS];
|
||||
} __attribute__((packed));
|
||||
|
||||
void TT21100TouchscreenStore::gpio_intr(TT21100TouchscreenStore *store) { store->touch = true; }
|
||||
|
||||
float TT21100Touchscreen::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||
|
||||
void TT21100Touchscreen::setup() {
|
||||
|
@ -54,9 +52,8 @@ void TT21100Touchscreen::setup() {
|
|||
// Register interrupt pin
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(TT21100TouchscreenStore::gpio_intr, &this->store_,
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
// Perform reset if necessary
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
|
@ -65,19 +62,11 @@ void TT21100Touchscreen::setup() {
|
|||
}
|
||||
|
||||
// Update display dimensions if they were updated during display setup
|
||||
this->display_width_ = this->display_->get_width();
|
||||
this->display_height_ = this->display_->get_height();
|
||||
this->rotation_ = static_cast<TouchRotation>(this->display_->get_rotation());
|
||||
|
||||
// Trigger initial read to activate the interrupt
|
||||
this->store_.touch = true;
|
||||
this->x_raw_max_ = this->get_width_();
|
||||
this->y_raw_max_ = this->get_height_();
|
||||
}
|
||||
|
||||
void TT21100Touchscreen::loop() {
|
||||
if (!this->store_.touch)
|
||||
return;
|
||||
this->store_.touch = false;
|
||||
|
||||
void TT21100Touchscreen::update_touches() {
|
||||
// Read report length
|
||||
uint16_t 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);
|
||||
|
||||
if (touch_count == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < touch_count; 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,
|
||||
touch->pressure, touch->major_axis_length, touch->orientation);
|
||||
|
||||
TouchPoint tp;
|
||||
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); });
|
||||
this->set_raw_touch_position_(touch->tip, touch->x, touch->y, touch->pressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,27 +5,21 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace tt21100 {
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
struct TT21100TouchscreenStore {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(TT21100TouchscreenStore *store);
|
||||
};
|
||||
|
||||
class TT21100ButtonListener {
|
||||
public:
|
||||
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:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
|
@ -37,7 +31,7 @@ class TT21100Touchscreen : public Touchscreen, public Component, public i2c::I2C
|
|||
protected:
|
||||
void reset_();
|
||||
|
||||
TT21100TouchscreenStore store_;
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import esphome.config_validation as cv
|
||||
|
||||
CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.")
|
|
@ -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))
|
93
esphome/components/xpt2046/touchscreen/__init__.py
Normal file
93
esphome/components/xpt2046/touchscreen/__init__.py
Normal 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))
|
113
esphome/components/xpt2046/touchscreen/xpt2046.cpp
Normal file
113
esphome/components/xpt2046/touchscreen/xpt2046.cpp
Normal 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
|
41
esphome/components/xpt2046/touchscreen/xpt2046.h
Normal file
41
esphome/components/xpt2046/touchscreen/xpt2046.h
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -464,6 +464,7 @@ binary_sensor:
|
|||
sx1509: sx1509_hub
|
||||
number: 3
|
||||
|
||||
|
||||
- platform: touchscreen
|
||||
touchscreen_id: lilygo_touchscreen
|
||||
id: touch_key1
|
||||
|
@ -483,6 +484,7 @@ binary_sensor:
|
|||
pin:
|
||||
max6956: max6956_1
|
||||
number: 4
|
||||
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
|
@ -506,6 +508,7 @@ binary_sensor:
|
|||
input: true
|
||||
inverted: false
|
||||
|
||||
|
||||
climate:
|
||||
- platform: tuya
|
||||
id: tuya_climate
|
||||
|
@ -595,6 +598,8 @@ display:
|
|||
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(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°
|
||||
update_interval: 16ms
|
||||
|
||||
|
@ -677,53 +682,53 @@ display:
|
|||
update_interval: 60s
|
||||
|
||||
display_data_1_pin:
|
||||
number: 5
|
||||
number: GPIO5
|
||||
allow_other_uses: true
|
||||
display_data_2_pin:
|
||||
number: 18
|
||||
number: GPIO18
|
||||
allow_other_uses: true
|
||||
display_data_3_pin:
|
||||
number: 19
|
||||
number: GPIO19
|
||||
allow_other_uses: true
|
||||
display_data_5_pin:
|
||||
number: 25
|
||||
number: GPIO25
|
||||
allow_other_uses: true
|
||||
display_data_4_pin:
|
||||
number: 23
|
||||
number: GPIO23
|
||||
allow_other_uses: true
|
||||
display_data_6_pin:
|
||||
number: 26
|
||||
number: GPIO26
|
||||
allow_other_uses: true
|
||||
display_data_7_pin:
|
||||
number: 27
|
||||
number: GPIO27
|
||||
allow_other_uses: true
|
||||
ckv_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
sph_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
gmod_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
gpio0_enable_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
oe_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
spv_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
powerup_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
wakeup_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
vcom_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO1
|
||||
allow_other_uses: true
|
||||
|
||||
number:
|
||||
- platform: tuya
|
||||
|
@ -816,21 +821,16 @@ esp32_camera:
|
|||
allow_other_uses: true
|
||||
- number: GPIO35
|
||||
allow_other_uses: true
|
||||
-
|
||||
number: GPIO34
|
||||
-
|
||||
number: GPIO5
|
||||
- number: GPIO34
|
||||
- number: GPIO5
|
||||
allow_other_uses: true
|
||||
-
|
||||
number: GPIO39
|
||||
-
|
||||
number: GPIO18
|
||||
- number: GPIO39
|
||||
allow_other_uses: true
|
||||
-
|
||||
number: GPIO36
|
||||
- number: GPIO18
|
||||
allow_other_uses: true
|
||||
-
|
||||
number: GPIO19
|
||||
- number: GPIO36
|
||||
allow_other_uses: true
|
||||
- number: GPIO19
|
||||
allow_other_uses: true
|
||||
vsync_pin:
|
||||
allow_other_uses: true
|
||||
|
@ -910,18 +910,16 @@ touchscreen:
|
|||
spi_id: spi_id_2
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: 17
|
||||
number: GPIO17
|
||||
interrupt_pin:
|
||||
number: 16
|
||||
number: GPIO16
|
||||
display: inkplate_display
|
||||
update_interval: 50ms
|
||||
report_interval: 1s
|
||||
threshold: 400
|
||||
calibration_x_min: 3860
|
||||
calibration_x_max: 280
|
||||
calibration_y_min: 340
|
||||
calibration_y_max: 3860
|
||||
swap_x_y: false
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
|
@ -938,10 +936,25 @@ touchscreen:
|
|||
format: Touch at (%d, %d)
|
||||
args: [touch.x, touch.y]
|
||||
- platform: gt911
|
||||
interrupt_pin: GPIO3
|
||||
interrupt_pin:
|
||||
number: GPIO3
|
||||
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_lrclk_pin:
|
||||
allow_other_uses: true
|
||||
|
|
|
@ -54,6 +54,7 @@ spi_device:
|
|||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: displ8
|
||||
model: ili9342
|
||||
cs_pin: GPIO5
|
||||
dc_pin: GPIO4
|
||||
|
@ -67,6 +68,7 @@ i2c:
|
|||
|
||||
touchscreen:
|
||||
- platform: tt21100
|
||||
display: displ8
|
||||
interrupt_pin:
|
||||
number: GPIO3
|
||||
ignore_strapping_warning: true
|
||||
|
|
Loading…
Reference in a new issue