mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
Refactor xpt2046 to be a touchscreen platform (#3793)
This commit is contained in:
parent
786c8b6cfe
commit
3c2766448d
8 changed files with 248 additions and 334 deletions
|
@ -258,4 +258,4 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||||
esphome/components/xiaomi_mhoc303/* @drug123
|
esphome/components/xiaomi_mhoc303/* @drug123
|
||||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||||
esphome/components/xpt2046/* @numo68
|
esphome/components/xpt2046/* @nielsnl68 @numo68
|
||||||
|
|
|
@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
DEPENDENCIES = ["display"]
|
DEPENDENCIES = ["display"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
Animation_ = display.display_ns.class_("Animation")
|
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
|
||||||
|
|
||||||
ANIMATION_SCHEMA = cv.Schema(
|
ANIMATION_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,129 +1,5 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
|
||||||
from esphome import pins
|
|
||||||
from esphome.components import spi
|
|
||||||
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_THRESHOLD, CONF_TRIGGER_ID
|
|
||||||
|
|
||||||
CODEOWNERS = ["@numo68"]
|
CONFIG_SCHEMA = cv.invalid(
|
||||||
AUTO_LOAD = ["binary_sensor"]
|
"This component sould now be used as platform of the Touchscreen component."
|
||||||
DEPENDENCIES = ["spi"]
|
|
||||||
MULTI_CONF = True
|
|
||||||
|
|
||||||
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_DIMENSION_X = "dimension_x"
|
|
||||||
CONF_DIMENSION_Y = "dimension_y"
|
|
||||||
CONF_SWAP_X_Y = "swap_x_y"
|
|
||||||
CONF_IRQ_PIN = "irq_pin"
|
|
||||||
|
|
||||||
xpt2046_ns = cg.esphome_ns.namespace("xpt2046")
|
|
||||||
CONF_XPT2046_ID = "xpt2046_id"
|
|
||||||
|
|
||||||
XPT2046Component = xpt2046_ns.class_(
|
|
||||||
"XPT2046Component", cg.PollingComponent, spi.SPIDevice
|
|
||||||
)
|
)
|
||||||
|
|
||||||
XPT2046OnStateTrigger = xpt2046_ns.class_(
|
|
||||||
"XPT2046OnStateTrigger", automation.Trigger.template(cg.int_, cg.int_, cg.bool_)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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 = cv.All(
|
|
||||||
cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(XPT2046Component),
|
|
||||||
cv.Optional(CONF_IRQ_PIN): pins.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_DIMENSION_X, default=100): cv.positive_not_null_int,
|
|
||||||
cv.Optional(CONF_DIMENSION_Y, default=100): cv.positive_not_null_int,
|
|
||||||
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,
|
|
||||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
|
||||||
{
|
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
|
||||||
XPT2046OnStateTrigger
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.extend(cv.polling_component_schema("50ms"))
|
|
||||||
.extend(spi.spi_device_schema()),
|
|
||||||
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)
|
|
||||||
|
|
||||||
cg.add(var.set_threshold(config[CONF_THRESHOLD]))
|
|
||||||
cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL]))
|
|
||||||
cg.add(var.set_dimensions(config[CONF_DIMENSION_X], config[CONF_DIMENSION_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_SWAP_X_Y in config:
|
|
||||||
cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y]))
|
|
||||||
|
|
||||||
if CONF_IRQ_PIN in config:
|
|
||||||
pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
|
||||||
cg.add(var.set_irq_pin(pin))
|
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_STATE, []):
|
|
||||||
await automation.build_automation(
|
|
||||||
var.get_on_state_trigger(),
|
|
||||||
[(cg.int_, "x"), (cg.int_, "y"), (cg.bool_, "touched")],
|
|
||||||
conf,
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,55 +1,3 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import binary_sensor
|
|
||||||
|
|
||||||
from . import (
|
CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.")
|
||||||
xpt2046_ns,
|
|
||||||
XPT2046Component,
|
|
||||||
CONF_XPT2046_ID,
|
|
||||||
)
|
|
||||||
|
|
||||||
CONF_X_MIN = "x_min"
|
|
||||||
CONF_X_MAX = "x_max"
|
|
||||||
CONF_Y_MIN = "y_min"
|
|
||||||
CONF_Y_MAX = "y_max"
|
|
||||||
|
|
||||||
DEPENDENCIES = ["xpt2046"]
|
|
||||||
XPT2046Button = xpt2046_ns.class_("XPT2046Button", binary_sensor.BinarySensor)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_xpt2046_button(config):
|
|
||||||
if cv.int_(config[CONF_X_MAX]) < cv.int_(config[CONF_X_MIN]) or cv.int_(
|
|
||||||
config[CONF_Y_MAX]
|
|
||||||
) < cv.int_(config[CONF_Y_MIN]):
|
|
||||||
raise cv.Invalid("x_max is less than x_min or y_max is less than y_min")
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
|
||||||
binary_sensor.binary_sensor_schema(XPT2046Button).extend(
|
|
||||||
{
|
|
||||||
cv.GenerateID(CONF_XPT2046_ID): cv.use_id(XPT2046Component),
|
|
||||||
cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095),
|
|
||||||
cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095),
|
|
||||||
cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=4095),
|
|
||||||
cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=4095),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
validate_xpt2046_button,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = await binary_sensor.new_binary_sensor(config)
|
|
||||||
hub = await cg.get_variable(config[CONF_XPT2046_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_button(var))
|
|
||||||
|
|
116
esphome/components/xpt2046/touchscreen.py
Normal file
116
esphome/components/xpt2046/touchscreen.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
CODEOWNERS = ["@numo68", "@nielsnl68"]
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
|
XPT2046_ns = cg.esphome_ns.namespace("xpt2046")
|
||||||
|
XPT2046Component = XPT2046_ns.class_(
|
||||||
|
"XPT2046Component", touchscreen.Touchscreen, cg.PollingComponent, spi.SPIDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_INTERRUPT_PIN = "interrupt_pin"
|
||||||
|
|
||||||
|
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"
|
||||||
|
CONF_IRQ_PIN = "irq_pin"
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
|
@ -9,31 +9,38 @@ namespace xpt2046 {
|
||||||
|
|
||||||
static const char *const TAG = "xpt2046";
|
static const char *const TAG = "xpt2046";
|
||||||
|
|
||||||
|
void XPT2046TouchscreenStore::gpio_intr(XPT2046TouchscreenStore *store) { store->touch = true; }
|
||||||
|
|
||||||
void XPT2046Component::setup() {
|
void XPT2046Component::setup() {
|
||||||
if (this->irq_pin_ != nullptr) {
|
if (this->irq_pin_ != nullptr) {
|
||||||
// The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state
|
// 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
|
// 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.
|
// need careful masking. A GPIO poll is cheap so we'll just use that.
|
||||||
|
|
||||||
this->irq_pin_->setup(); // INPUT
|
this->irq_pin_->setup(); // INPUT
|
||||||
|
this->irq_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||||
|
this->irq_pin_->setup();
|
||||||
|
|
||||||
|
this->store_.pin = this->irq_pin_->to_isr();
|
||||||
|
this->irq_pin_->attach_interrupt(XPT2046TouchscreenStore::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
|
||||||
}
|
}
|
||||||
spi_setup();
|
spi_setup();
|
||||||
read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin
|
read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin
|
||||||
}
|
}
|
||||||
|
|
||||||
void XPT2046Component::loop() {
|
void XPT2046Component::loop() {
|
||||||
if (this->irq_pin_ != nullptr) {
|
if ((this->irq_pin_ == nullptr) || (!this->store_.touch))
|
||||||
// Force immediate update if a falling edge (= touched is seen) Ignore if still active
|
return;
|
||||||
// (that would mean that we missed the release because of a too long update interval)
|
this->store_.touch = false;
|
||||||
bool val = this->irq_pin_->digital_read();
|
check_touch_();
|
||||||
if (!val && this->last_irq_ && !this->touched) {
|
|
||||||
ESP_LOGD(TAG, "Falling penirq edge, forcing update");
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
this->last_irq_ = val;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void XPT2046Component::update() {
|
void XPT2046Component::update() {
|
||||||
|
if (this->irq_pin_ == nullptr)
|
||||||
|
check_touch_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XPT2046Component::check_touch_() {
|
||||||
int16_t data[6];
|
int16_t data[6];
|
||||||
bool touch = false;
|
bool touch = false;
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
@ -42,13 +49,13 @@ void XPT2046Component::update() {
|
||||||
|
|
||||||
// In case the penirq pin is present only do the SPI transaction if it reports a touch (is low).
|
// In case the penirq pin is present only do the SPI transaction if it reports a touch (is low).
|
||||||
// The touch has to be also confirmed with checking the pressure over threshold
|
// The touch has to be also confirmed with checking the pressure over threshold
|
||||||
if (this->irq_pin_ == nullptr || !this->irq_pin_->digital_read()) {
|
if ((this->irq_pin_ == nullptr) || !this->irq_pin_->digital_read()) {
|
||||||
enable();
|
enable();
|
||||||
|
|
||||||
int16_t z1 = read_adc_(0xB1 /* Z1 */);
|
int16_t touch_pressure_1 = read_adc_(0xB1 /* touch_pressure_1 */);
|
||||||
int16_t z2 = read_adc_(0xC1 /* Z2 */);
|
int16_t touch_pressure_2 = read_adc_(0xC1 /* touch_pressure_2 */);
|
||||||
|
|
||||||
this->z_raw = z1 + 4095 - z2;
|
this->z_raw = touch_pressure_1 + 4095 - touch_pressure_2;
|
||||||
|
|
||||||
touch = (this->z_raw >= this->threshold_);
|
touch = (this->z_raw >= this->threshold_);
|
||||||
if (touch) {
|
if (touch) {
|
||||||
|
@ -63,64 +70,73 @@ void XPT2046Component::update() {
|
||||||
data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down
|
data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down
|
||||||
|
|
||||||
disable();
|
disable();
|
||||||
}
|
|
||||||
|
|
||||||
if (touch) {
|
if (touch) {
|
||||||
this->x_raw = best_two_avg(data[0], data[2], data[4]);
|
this->x_raw = best_two_avg(data[0], data[2], data[4]);
|
||||||
this->y_raw = best_two_avg(data[1], data[3], data[5]);
|
this->y_raw = best_two_avg(data[1], data[3], data[5]);
|
||||||
} else {
|
|
||||||
this->x_raw = this->y_raw = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Update [x, y] = [%d, %d], z = %d%s", this->x_raw, this->y_raw, this->z_raw, (touch ? " touched" : ""));
|
ESP_LOGVV(TAG, "Update [x, y] = [%d, %d], z = %d", this->x_raw, this->y_raw, this->z_raw);
|
||||||
|
|
||||||
if (touch) {
|
TouchPoint touchpoint;
|
||||||
// Normalize raw data according to calibration min and max
|
|
||||||
|
|
||||||
int16_t x_val = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_);
|
touchpoint.x = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_);
|
||||||
int16_t y_val = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_);
|
touchpoint.y = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_);
|
||||||
|
|
||||||
if (this->swap_x_y_) {
|
if (this->swap_x_y_) {
|
||||||
std::swap(x_val, y_val);
|
std::swap(touchpoint.x, touchpoint.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->invert_x_) {
|
if (this->invert_x_) {
|
||||||
x_val = 0x7fff - x_val;
|
touchpoint.x = 0xfff - touchpoint.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->invert_y_) {
|
if (this->invert_y_) {
|
||||||
y_val = 0x7fff - y_val;
|
touchpoint.y = 0xfff - touchpoint.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
x_val = (int16_t)((int) x_val * this->x_dim_ / 0x7fff);
|
switch (static_cast<TouchRotation>(this->display_->get_rotation())) {
|
||||||
y_val = (int16_t)((int) y_val * this->y_dim_ / 0x7fff);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
|
touchpoint.x = (int16_t)((int) touchpoint.x * this->display_->get_width() / 0xfff);
|
||||||
ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val);
|
touchpoint.y = (int16_t)((int) touchpoint.y * this->display_->get_height() / 0xfff);
|
||||||
|
|
||||||
this->x = x_val;
|
if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
|
||||||
this->y = y_val;
|
ESP_LOGV(TAG, "Touching at [%03X, %03X] => [%3d, %3d]", this->x_raw, this->y_raw, touchpoint.x, touchpoint.y);
|
||||||
this->touched = true;
|
|
||||||
this->last_pos_ms_ = now;
|
|
||||||
|
|
||||||
this->on_state_trigger_->process(this->x, this->y, true);
|
this->defer([this, touchpoint]() { this->send_touch_(touchpoint); });
|
||||||
for (auto *button : this->buttons_)
|
|
||||||
button->touch(this->x, this->y);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this->touched) {
|
|
||||||
ESP_LOGD(TAG, "Released [%d, %d]", this->x, this->y);
|
|
||||||
|
|
||||||
this->touched = false;
|
this->x = touchpoint.x;
|
||||||
|
this->y = touchpoint.y;
|
||||||
this->on_state_trigger_->process(this->x, this->y, false);
|
this->touched = true;
|
||||||
for (auto *button : this->buttons_)
|
this->last_pos_ms_ = now;
|
||||||
button->release();
|
}
|
||||||
|
} else {
|
||||||
|
this->x_raw = this->y_raw = 0;
|
||||||
|
if (this->touched) {
|
||||||
|
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) {
|
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_min_ = std::min(x_min, x_max);
|
||||||
this->x_raw_max_ = std::max(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_min_ = std::min(y_min, y_max);
|
||||||
|
@ -137,11 +153,11 @@ void XPT2046Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_);
|
ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_);
|
||||||
ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_);
|
ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_);
|
||||||
ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_);
|
ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_);
|
||||||
ESP_LOGCONFIG(TAG, " X dim: %d", this->x_dim_);
|
|
||||||
ESP_LOGCONFIG(TAG, " Y dim: %d", this->y_dim_);
|
ESP_LOGCONFIG(TAG, " Swap X/Y: %s", YESNO(this->swap_x_y_));
|
||||||
if (this->swap_x_y_) {
|
ESP_LOGCONFIG(TAG, " Invert X: %s", YESNO(this->invert_x_));
|
||||||
ESP_LOGCONFIG(TAG, " Swap X/Y");
|
ESP_LOGCONFIG(TAG, " Invert Y: %s", YESNO(this->invert_y_));
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_);
|
ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_);
|
||||||
ESP_LOGCONFIG(TAG, " Report interval: %u", this->report_millis_);
|
ESP_LOGCONFIG(TAG, " Report interval: %u", this->report_millis_);
|
||||||
|
|
||||||
|
@ -150,8 +166,8 @@ void XPT2046Component::dump_config() {
|
||||||
|
|
||||||
float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; }
|
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) {
|
int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) { // NOLINT
|
||||||
int16_t da, db, dc;
|
int16_t da, db, dc; // NOLINT
|
||||||
int16_t reta = 0;
|
int16_t reta = 0;
|
||||||
|
|
||||||
da = (x > y) ? x - y : y - x;
|
da = (x > y) ? x - y : y - x;
|
||||||
|
@ -175,15 +191,15 @@ int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_va
|
||||||
if (val <= min_val) {
|
if (val <= min_val) {
|
||||||
ret = 0;
|
ret = 0;
|
||||||
} else if (val >= max_val) {
|
} else if (val >= max_val) {
|
||||||
ret = 0x7fff;
|
ret = 0xfff;
|
||||||
} else {
|
} else {
|
||||||
ret = (int16_t)((int) 0x7fff * (val - min_val) / (max_val - min_val));
|
ret = (int16_t)((int) 0xfff * (val - min_val) / (max_val - min_val));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t XPT2046Component::read_adc_(uint8_t ctrl) {
|
int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT
|
||||||
uint8_t data[2];
|
uint8_t data[2];
|
||||||
|
|
||||||
write_byte(ctrl);
|
write_byte(ctrl);
|
||||||
|
@ -193,25 +209,5 @@ int16_t XPT2046Component::read_adc_(uint8_t ctrl) {
|
||||||
return ((data[0] << 8) | data[1]) >> 3;
|
return ((data[0] << 8) | data[1]) >> 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
void XPT2046OnStateTrigger::process(int x, int y, bool touched) { this->trigger(x, y, touched); }
|
|
||||||
|
|
||||||
void XPT2046Button::touch(int16_t x, int16_t y) {
|
|
||||||
bool touched = (x >= this->x_min_ && x <= this->x_max_ && y >= this->y_min_ && y <= this->y_max_);
|
|
||||||
|
|
||||||
if (touched) {
|
|
||||||
this->publish_state(true);
|
|
||||||
this->state_ = true;
|
|
||||||
} else {
|
|
||||||
release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XPT2046Button::release() {
|
|
||||||
if (this->state_) {
|
|
||||||
this->publish_state(false);
|
|
||||||
this->state_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace xpt2046
|
} // namespace xpt2046
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -3,42 +3,31 @@
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/components/spi/spi.h"
|
#include "esphome/components/spi/spi.h"
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
#include "esphome/components/touchscreen/touchscreen.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace xpt2046 {
|
namespace xpt2046 {
|
||||||
|
|
||||||
class XPT2046OnStateTrigger : public Trigger<int, int, bool> {
|
using namespace touchscreen;
|
||||||
public:
|
|
||||||
void process(int x, int y, bool touched);
|
struct XPT2046TouchscreenStore {
|
||||||
|
volatile bool touch;
|
||||||
|
ISRInternalGPIOPin pin;
|
||||||
|
|
||||||
|
static void gpio_intr(XPT2046TouchscreenStore *store);
|
||||||
};
|
};
|
||||||
|
|
||||||
class XPT2046Button : public binary_sensor::BinarySensor {
|
class XPT2046Component : public Touchscreen,
|
||||||
public:
|
public PollingComponent,
|
||||||
/// 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(int16_t x, int16_t y);
|
|
||||||
void release();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
int16_t x_min_, x_max_, y_min_, y_max_;
|
|
||||||
bool state_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
class XPT2046Component : public PollingComponent,
|
|
||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
|
||||||
public:
|
public:
|
||||||
/// Set the logical touch screen dimensions.
|
/// Set the logical touch screen dimensions.
|
||||||
void set_dimensions(int16_t x, int16_t y) {
|
void set_dimensions(int16_t x, int16_t y) {
|
||||||
this->x_dim_ = x;
|
this->display_width_ = x;
|
||||||
this->y_dim_ = y;
|
this->display_height_ = y;
|
||||||
}
|
}
|
||||||
/// Set the coordinates for the touch screen edges.
|
/// 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);
|
void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max);
|
||||||
|
@ -47,14 +36,12 @@ class XPT2046Component : public PollingComponent,
|
||||||
|
|
||||||
/// Set the interval to report the touch point perodically.
|
/// Set the interval to report the touch point perodically.
|
||||||
void set_report_interval(uint32_t interval) { this->report_millis_ = interval; }
|
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.
|
/// Set the threshold for the touch detection.
|
||||||
void set_threshold(int16_t threshold) { this->threshold_ = threshold; }
|
void set_threshold(int16_t threshold) { this->threshold_ = threshold; }
|
||||||
/// Set the pin used to detect the touch.
|
/// Set the pin used to detect the touch.
|
||||||
void set_irq_pin(GPIOPin *pin) { this->irq_pin_ = pin; }
|
void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; }
|
||||||
/// Get an access to the on_state automation trigger
|
|
||||||
XPT2046OnStateTrigger *get_on_state_trigger() const { return this->on_state_trigger_; }
|
|
||||||
/// Register a virtual button to the component.
|
|
||||||
void register_button(XPT2046Button *button) { this->buttons_.push_back(button); }
|
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
@ -103,21 +90,19 @@ class XPT2046Component : public PollingComponent,
|
||||||
static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val);
|
static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val);
|
||||||
|
|
||||||
int16_t read_adc_(uint8_t ctrl);
|
int16_t read_adc_(uint8_t ctrl);
|
||||||
|
void check_touch_();
|
||||||
|
|
||||||
int16_t threshold_;
|
int16_t threshold_;
|
||||||
int16_t x_raw_min_, x_raw_max_, y_raw_min_, y_raw_max_;
|
int16_t x_raw_min_, x_raw_max_, y_raw_min_, y_raw_max_;
|
||||||
int16_t x_dim_, y_dim_;
|
|
||||||
bool invert_x_, invert_y_;
|
bool invert_x_, invert_y_;
|
||||||
bool swap_x_y_;
|
bool swap_x_y_;
|
||||||
|
|
||||||
uint32_t report_millis_;
|
uint32_t report_millis_;
|
||||||
uint32_t last_pos_ms_{0};
|
uint32_t last_pos_ms_{0};
|
||||||
|
|
||||||
GPIOPin *irq_pin_{nullptr};
|
InternalGPIOPin *irq_pin_{nullptr};
|
||||||
bool last_irq_{true};
|
XPT2046TouchscreenStore store_;
|
||||||
|
|
||||||
XPT2046OnStateTrigger *on_state_trigger_{new XPT2046OnStateTrigger()};
|
|
||||||
std::vector<XPT2046Button *> buttons_{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xpt2046
|
} // namespace xpt2046
|
||||||
|
|
|
@ -348,15 +348,16 @@ binary_sensor:
|
||||||
on_state:
|
on_state:
|
||||||
then:
|
then:
|
||||||
- lambda: 'ESP_LOGI("ar1:", "%d", x);'
|
- lambda: 'ESP_LOGI("ar1:", "%d", x);'
|
||||||
- platform: xpt2046
|
- platform: touchscreen
|
||||||
xpt2046_id: xpt_touchscreen
|
touchscreen_id: xpt_touchscreen
|
||||||
id: touch_key0
|
id: touch_key0
|
||||||
x_min: 80
|
x_min: 80
|
||||||
x_max: 160
|
x_max: 160
|
||||||
y_min: 106
|
y_min: 106
|
||||||
y_max: 212
|
y_max: 212
|
||||||
on_state:
|
on_press:
|
||||||
- lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));'
|
- logger.log: Touched
|
||||||
|
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: GPIO SX1509 test
|
name: GPIO SX1509 test
|
||||||
pin:
|
pin:
|
||||||
|
@ -598,33 +599,6 @@ external_components:
|
||||||
components: [bh1750]
|
components: [bh1750]
|
||||||
- source: ../esphome/components
|
- source: ../esphome/components
|
||||||
components: [sntp]
|
components: [sntp]
|
||||||
xpt2046:
|
|
||||||
id: xpt_touchscreen
|
|
||||||
cs_pin: 17
|
|
||||||
irq_pin: 16
|
|
||||||
update_interval: 50ms
|
|
||||||
report_interval: 1s
|
|
||||||
threshold: 400
|
|
||||||
dimension_x: 240
|
|
||||||
dimension_y: 320
|
|
||||||
calibration_x_min: 3860
|
|
||||||
calibration_x_max: 280
|
|
||||||
calibration_y_min: 340
|
|
||||||
calibration_y_max: 3860
|
|
||||||
swap_x_y: false
|
|
||||||
on_state:
|
|
||||||
# yamllint disable rule:line-length
|
|
||||||
- lambda: |-
|
|
||||||
ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release"));
|
|
||||||
ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d",
|
|
||||||
id(xpt_touchscreen).x,
|
|
||||||
id(xpt_touchscreen).y,
|
|
||||||
(int) id(xpt_touchscreen).touched,
|
|
||||||
id(xpt_touchscreen).x_raw,
|
|
||||||
id(xpt_touchscreen).y_raw,
|
|
||||||
id(xpt_touchscreen).z_raw
|
|
||||||
);
|
|
||||||
# yamllint enable rule:line-length
|
|
||||||
|
|
||||||
button:
|
button:
|
||||||
- platform: restart
|
- platform: restart
|
||||||
|
@ -648,6 +622,25 @@ touchscreen:
|
||||||
format: Touch at (%d, %d)
|
format: Touch at (%d, %d)
|
||||||
args: [touch.x, touch.y]
|
args: [touch.x, touch.y]
|
||||||
|
|
||||||
|
- platform: xpt2046
|
||||||
|
id: xpt_touchscreen
|
||||||
|
cs_pin: 17
|
||||||
|
interrupt_pin: 16
|
||||||
|
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)
|
||||||
|
args: [touch.x, touch.y]
|
||||||
|
|
||||||
|
|
||||||
- platform: lilygo_t5_47
|
- platform: lilygo_t5_47
|
||||||
id: lilygo_touchscreen
|
id: lilygo_touchscreen
|
||||||
interrupt_pin: GPIO36
|
interrupt_pin: GPIO36
|
||||||
|
|
Loading…
Reference in a new issue