mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Add ektf2232 touchscreen support (#3027)
This commit is contained in:
parent
ec769ccf72
commit
6f8c7d9ec4
8 changed files with 439 additions and 0 deletions
|
@ -54,6 +54,7 @@ esphome/components/dfplayer/* @glmnet
|
|||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
|
|
80
esphome/components/ektf2232/__init__.py
Normal file
80
esphome/components/ektf2232/__init__.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome import pins, automation
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_HEIGHT, CONF_ID, CONF_ROTATION, CONF_WIDTH
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ektf2232_ns = cg.esphome_ns.namespace("ektf2232")
|
||||
EKTF2232Touchscreen = ektf2232_ns.class_(
|
||||
"EKTF2232Touchscreen", cg.Component, i2c.I2CDevice
|
||||
)
|
||||
TouchPoint = ektf2232_ns.struct("TouchPoint")
|
||||
TouchListener = ektf2232_ns.class_("TouchListener")
|
||||
|
||||
EKTF2232Rotation = ektf2232_ns.enum("EKTF2232Rotation")
|
||||
|
||||
CONF_EKTF2232_ID = "ektf2232_id"
|
||||
CONF_INTERRUPT_PIN = "interrupt_pin"
|
||||
CONF_RTS_PIN = "rts_pin"
|
||||
CONF_ON_TOUCH = "on_touch"
|
||||
|
||||
ROTATIONS = {
|
||||
0: EKTF2232Rotation.ROTATE_0_DEGREES,
|
||||
90: EKTF2232Rotation.ROTATE_90_DEGREES,
|
||||
180: EKTF2232Rotation.ROTATE_180_DEGREES,
|
||||
270: EKTF2232Rotation.ROTATE_270_DEGREES,
|
||||
}
|
||||
|
||||
|
||||
def validate_rotation(value):
|
||||
value = cv.string(value)
|
||||
if value.endswith("°"):
|
||||
value = value[:-1]
|
||||
return cv.enum(ROTATIONS, int=True)(value)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen),
|
||||
cv.Required(CONF_INTERRUPT_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_HEIGHT, default=758): cv.int_,
|
||||
cv.Optional(CONF_WIDTH, default=1024): cv.int_,
|
||||
cv.Optional(CONF_ROTATION, default=0): validate_rotation,
|
||||
cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True),
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x15))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN])
|
||||
cg.add(var.set_rts_pin(rts_pin))
|
||||
|
||||
cg.add(
|
||||
var.set_display_details(
|
||||
config[CONF_WIDTH],
|
||||
config[CONF_HEIGHT],
|
||||
config[CONF_ROTATION],
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_ON_TOUCH in config:
|
||||
await automation.build_automation(
|
||||
var.get_touch_trigger(), [(TouchPoint, "touch")], config[CONF_ON_TOUCH]
|
||||
)
|
59
esphome/components/ektf2232/binary_sensor/__init__.py
Normal file
59
esphome/components/ektf2232/binary_sensor/__init__.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from .. import ektf2232_ns, CONF_EKTF2232_ID, EKTF2232Touchscreen, TouchListener
|
||||
|
||||
DEPENDENCIES = ["ektf2232"]
|
||||
|
||||
EKTF2232Button = ektf2232_ns.class_(
|
||||
"EKTF2232Button", binary_sensor.BinarySensor, TouchListener
|
||||
)
|
||||
|
||||
CONF_X_MIN = "x_min"
|
||||
CONF_X_MAX = "x_max"
|
||||
CONF_Y_MIN = "y_min"
|
||||
CONF_Y_MAX = "y_max"
|
||||
|
||||
|
||||
def validate_coords(config):
|
||||
if (
|
||||
config[CONF_X_MAX] < config[CONF_X_MIN]
|
||||
or config[CONF_Y_MAX] < config[CONF_Y_MIN]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_X_MAX} is less than {CONF_X_MIN} or {CONF_Y_MAX} is less than {CONF_Y_MIN}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EKTF2232Button),
|
||||
cv.GenerateID(CONF_EKTF2232_ID): cv.use_id(EKTF2232Touchscreen),
|
||||
cv.Required(CONF_X_MIN): cv.int_range(min=0, max=2000),
|
||||
cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000),
|
||||
cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000),
|
||||
cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000),
|
||||
}
|
||||
),
|
||||
validate_coords,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
hub = await cg.get_variable(config[CONF_EKTF2232_ID])
|
||||
cg.add(
|
||||
var.set_area(
|
||||
config[CONF_X_MIN],
|
||||
config[CONF_X_MAX],
|
||||
config[CONF_Y_MIN],
|
||||
config[CONF_Y_MAX],
|
||||
)
|
||||
)
|
||||
cg.add(hub.register_listener(var))
|
|
@ -0,0 +1,19 @@
|
|||
#include "ektf2232_binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
void EKTF2232Button::touch(TouchPoint tp) {
|
||||
bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_);
|
||||
|
||||
if (touched) {
|
||||
this->publish_state(true);
|
||||
} else {
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
void EKTF2232Button::release() { this->publish_state(false); }
|
||||
|
||||
} // namespace ektf2232
|
||||
} // namespace esphome
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "../ektf2232.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
class EKTF2232Button : public binary_sensor::BinarySensor, public TouchListener {
|
||||
public:
|
||||
/// Set the touch screen area where the button will detect the touch.
|
||||
void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
|
||||
this->x_min_ = x_min;
|
||||
this->x_max_ = x_max;
|
||||
this->y_min_ = y_min;
|
||||
this->y_max_ = y_max;
|
||||
}
|
||||
|
||||
void touch(TouchPoint tp) override;
|
||||
void release() override;
|
||||
|
||||
protected:
|
||||
int16_t x_min_, x_max_, y_min_, y_max_;
|
||||
};
|
||||
|
||||
} // namespace ektf2232
|
||||
} // namespace esphome
|
168
esphome/components/ektf2232/ektf2232.cpp
Normal file
168
esphome/components/ektf2232/ektf2232.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
#include "ektf2232.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
static const char *const TAG = "ektf2232";
|
||||
|
||||
static const uint8_t SOFT_RESET_CMD[4] = {0x77, 0x77, 0x77, 0x77};
|
||||
static const uint8_t HELLO[4] = {0x55, 0x55, 0x55, 0x55};
|
||||
static const uint8_t GET_X_RES[4] = {0x53, 0x60, 0x00, 0x00};
|
||||
static const uint8_t GET_Y_RES[4] = {0x53, 0x63, 0x00, 0x00};
|
||||
static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01};
|
||||
|
||||
void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; }
|
||||
|
||||
void EKTF2232Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen...");
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_,
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
this->rts_pin_->setup();
|
||||
|
||||
this->hard_reset_();
|
||||
if (!this->soft_reset_()) {
|
||||
ESP_LOGE(TAG, "Failed to soft reset EKT2232!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get touch resolution
|
||||
uint8_t received[4];
|
||||
this->write(GET_X_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read X resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
|
||||
this->write(GET_Y_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read Y resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
this->store_.touch = false;
|
||||
|
||||
this->set_power_state(true);
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::loop() {
|
||||
if (!this->store_.touch)
|
||||
return;
|
||||
this->store_.touch = false;
|
||||
|
||||
uint8_t touch_count = 0;
|
||||
std::vector<TouchPoint> touches;
|
||||
|
||||
uint8_t raw[8];
|
||||
this->read(raw, 8);
|
||||
for (int i = 0; i < 8; i++)
|
||||
if (raw[7] & (1 << i))
|
||||
touch_count++;
|
||||
|
||||
if (touch_count == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
|
||||
touch_count = std::min<uint8_t>(touch_count, 2);
|
||||
|
||||
ESP_LOGV(TAG, "Touch count: %d", touch_count);
|
||||
|
||||
for (int i = 0; i < touch_count; i++) {
|
||||
uint8_t *d = raw + 1 + (i * 3);
|
||||
uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1];
|
||||
uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2];
|
||||
|
||||
raw_x = raw_x * this->display_height_ - 1;
|
||||
raw_y = raw_y * this->display_width_ - 1;
|
||||
|
||||
TouchPoint tp;
|
||||
switch (this->rotation_) {
|
||||
case ROTATE_0_DEGREES:
|
||||
tp.y = raw_x / this->x_resolution_;
|
||||
tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
case ROTATE_90_DEGREES:
|
||||
tp.x = raw_x / this->x_resolution_;
|
||||
tp.y = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_180_DEGREES:
|
||||
tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.x = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_270_DEGREES:
|
||||
tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Touch %d: (x=%d, y=%d)", i, tp.x, tp.y);
|
||||
this->touch_trigger_->trigger(tp);
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->touch(tp);
|
||||
}
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::set_power_state(bool enable) {
|
||||
uint8_t data[] = {0x54, 0x50, 0x00, 0x01};
|
||||
data[1] |= (enable << 3);
|
||||
this->write(data, 4);
|
||||
}
|
||||
|
||||
bool EKTF2232Touchscreen::get_power_state() {
|
||||
uint8_t received[4];
|
||||
this->write(GET_POWER_STATE_CMD, 4);
|
||||
this->store_.touch = false;
|
||||
this->read(received, 4);
|
||||
return (received[1] >> 3) & 1;
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::hard_reset_() {
|
||||
this->rts_pin_->digital_write(false);
|
||||
delay(15);
|
||||
this->rts_pin_->digital_write(true);
|
||||
delay(15);
|
||||
}
|
||||
|
||||
bool EKTF2232Touchscreen::soft_reset_() {
|
||||
auto err = this->write(SOFT_RESET_CMD, 4);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
uint8_t received[4];
|
||||
uint16_t timeout = 1000;
|
||||
while (!this->store_.touch && timeout > 0) {
|
||||
delay(1);
|
||||
timeout--;
|
||||
}
|
||||
if (timeout > 0)
|
||||
this->store_.touch = true;
|
||||
this->read(received, 4);
|
||||
this->store_.touch = false;
|
||||
|
||||
return !memcmp(received, HELLO, 4);
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "EKT2232 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" RTS Pin: ", this->rts_pin_);
|
||||
}
|
||||
|
||||
} // namespace ektf2232
|
||||
} // namespace esphome
|
76
esphome/components/ektf2232/ektf2232.h
Normal file
76
esphome/components/ektf2232/ektf2232.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
struct EKTF2232TouchscreenStore {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(EKTF2232TouchscreenStore *store);
|
||||
};
|
||||
|
||||
struct TouchPoint {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
};
|
||||
|
||||
class TouchListener {
|
||||
public:
|
||||
virtual void touch(TouchPoint tp) = 0;
|
||||
virtual void release();
|
||||
};
|
||||
|
||||
enum EKTF2232Rotation : uint8_t {
|
||||
ROTATE_0_DEGREES = 0,
|
||||
ROTATE_90_DEGREES,
|
||||
ROTATE_180_DEGREES,
|
||||
ROTATE_270_DEGREES,
|
||||
};
|
||||
|
||||
class EKTF2232Touchscreen : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
|
||||
|
||||
void set_display_details(uint16_t width, uint16_t height, EKTF2232Rotation rotation) {
|
||||
this->display_width_ = width;
|
||||
this->display_height_ = height;
|
||||
this->rotation_ = rotation;
|
||||
}
|
||||
|
||||
void set_power_state(bool enable);
|
||||
bool get_power_state();
|
||||
|
||||
Trigger<TouchPoint> *get_touch_trigger() const { return this->touch_trigger_; }
|
||||
|
||||
void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); }
|
||||
|
||||
protected:
|
||||
void hard_reset_();
|
||||
bool soft_reset_();
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
GPIOPin *rts_pin_;
|
||||
EKTF2232TouchscreenStore store_;
|
||||
uint16_t x_resolution_;
|
||||
uint16_t y_resolution_;
|
||||
|
||||
uint16_t display_width_;
|
||||
uint16_t display_height_;
|
||||
EKTF2232Rotation rotation_;
|
||||
Trigger<TouchPoint> *touch_trigger_ = new Trigger<TouchPoint>();
|
||||
std::vector<TouchListener *> touch_listeners_;
|
||||
};
|
||||
|
||||
} // namespace ektf2232
|
||||
} // namespace esphome
|
|
@ -199,3 +199,12 @@ script:
|
|||
count: 5
|
||||
then:
|
||||
- logger.log: "looping!"
|
||||
|
||||
ektf2232:
|
||||
interrupt_pin: GPIO36
|
||||
rts_pin: GPIO5
|
||||
rotation: 90
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: ["touch.x", "touch.y"]
|
||||
|
|
Loading…
Reference in a new issue