mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Add support for the XPT2046 touchscreen controller (#1542)
This commit is contained in:
parent
9ecead2645
commit
d0eaebe19f
6 changed files with 562 additions and 0 deletions
|
@ -123,3 +123,4 @@ esphome/components/web_server_base/* @OttoWinter
|
||||||
esphome/components/whirlpool/* @glmnet
|
esphome/components/whirlpool/* @glmnet
|
||||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||||
|
esphome/components/xpt2046/* @numo68
|
||||||
|
|
129
esphome/components/xpt2046/__init__.py
Normal file
129
esphome/components/xpt2046/__init__.py
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
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"]
|
||||||
|
AUTO_LOAD = ["binary_sensor"]
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield 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 = yield cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||||
|
cg.add(var.set_irq_pin(pin))
|
||||||
|
|
||||||
|
for conf in config.get(CONF_ON_STATE, []):
|
||||||
|
yield automation.build_automation(
|
||||||
|
var.get_on_state_trigger(),
|
||||||
|
[(cg.int_, "x"), (cg.int_, "y"), (cg.bool_, "touched")],
|
||||||
|
conf,
|
||||||
|
)
|
57
esphome/components/xpt2046/binary_sensor.py
Normal file
57
esphome/components/xpt2046/binary_sensor.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
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 (
|
||||||
|
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.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(XPT2046Button),
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield binary_sensor.register_binary_sensor(var, config)
|
||||||
|
hub = yield 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))
|
217
esphome/components/xpt2046/xpt2046.cpp
Normal file
217
esphome/components/xpt2046/xpt2046.cpp
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
#include "xpt2046.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace xpt2046 {
|
||||||
|
|
||||||
|
static const char *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
|
||||||
|
}
|
||||||
|
spi_setup();
|
||||||
|
read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin
|
||||||
|
}
|
||||||
|
|
||||||
|
void XPT2046Component::loop() {
|
||||||
|
if (this->irq_pin_ != nullptr) {
|
||||||
|
// Force immediate update if a falling edge (= touched is seen) Ignore if still active
|
||||||
|
// (that would mean that we missed the release because of a too long update interval)
|
||||||
|
bool val = this->irq_pin_->digital_read();
|
||||||
|
if (!val && this->last_irq_ && !this->touched) {
|
||||||
|
ESP_LOGD(TAG, "Falling penirq edge, forcing update");
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
this->last_irq_ = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void XPT2046Component::update() {
|
||||||
|
int16_t data[6];
|
||||||
|
bool touch = false;
|
||||||
|
unsigned long now = millis();
|
||||||
|
|
||||||
|
this->z_raw = 0;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if (this->irq_pin_ == nullptr || !this->irq_pin_->digital_read()) {
|
||||||
|
enable();
|
||||||
|
|
||||||
|
int16_t z1 = read_adc_(0xB1 /* Z1 */);
|
||||||
|
int16_t z2 = read_adc_(0xC1 /* Z2 */);
|
||||||
|
|
||||||
|
this->z_raw = z1 + 4095 - z2;
|
||||||
|
|
||||||
|
touch = (this->z_raw >= this->threshold_);
|
||||||
|
if (touch) {
|
||||||
|
read_adc_(0x91 /* Y */); // dummy Y measure, 1st is always noisy
|
||||||
|
data[0] = read_adc_(0xD1 /* X */);
|
||||||
|
data[1] = read_adc_(0x91 /* Y */); // make 3 x-y measurements
|
||||||
|
data[2] = read_adc_(0xD1 /* X */);
|
||||||
|
data[3] = read_adc_(0x91 /* Y */);
|
||||||
|
data[4] = read_adc_(0xD1 /* X */);
|
||||||
|
}
|
||||||
|
|
||||||
|
data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down
|
||||||
|
|
||||||
|
disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (touch) {
|
||||||
|
this->x_raw = best_two_avg(data[0], data[2], data[4]);
|
||||||
|
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" : ""));
|
||||||
|
|
||||||
|
if (touch) {
|
||||||
|
// 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_);
|
||||||
|
int16_t y_val = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_);
|
||||||
|
|
||||||
|
if (this->swap_x_y_) {
|
||||||
|
std::swap(x_val, y_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->invert_x_) {
|
||||||
|
x_val = 0x7fff - x_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->invert_y_) {
|
||||||
|
y_val = 0x7fff - y_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
x_val = (int16_t)((int) x_val * this->x_dim_ / 0x7fff);
|
||||||
|
y_val = (int16_t)((int) y_val * this->y_dim_ / 0x7fff);
|
||||||
|
|
||||||
|
if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
|
||||||
|
ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val);
|
||||||
|
|
||||||
|
this->x = x_val;
|
||||||
|
this->y = y_val;
|
||||||
|
this->touched = true;
|
||||||
|
this->last_pos_ms_ = now;
|
||||||
|
|
||||||
|
this->on_state_trigger_->process(this->x, this->y, true);
|
||||||
|
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->on_state_trigger_->process(this->x, this->y, false);
|
||||||
|
for (auto *button : this->buttons_)
|
||||||
|
button->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void XPT2046Component::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);
|
||||||
|
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, " X dim: %d", this->x_dim_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Y dim: %d", this->y_dim_);
|
||||||
|
if (this->swap_x_y_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Swap X/Y");
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Report interval: %u", 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) {
|
||||||
|
int16_t da, db, dc;
|
||||||
|
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 = 0x7fff;
|
||||||
|
} else {
|
||||||
|
ret = (int16_t)((int) 0x7fff * (val - min_val) / (max_val - min_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t XPT2046Component::read_adc_(uint8_t ctrl) {
|
||||||
|
uint8_t data[2];
|
||||||
|
|
||||||
|
write_byte(ctrl);
|
||||||
|
data[0] = read_byte();
|
||||||
|
data[1] = read_byte();
|
||||||
|
|
||||||
|
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 esphome
|
124
esphome/components/xpt2046/xpt2046.h
Normal file
124
esphome/components/xpt2046/xpt2046.h
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace xpt2046 {
|
||||||
|
|
||||||
|
class XPT2046OnStateTrigger : public Trigger<int, int, bool> {
|
||||||
|
public:
|
||||||
|
void process(int x, int y, bool touched);
|
||||||
|
};
|
||||||
|
|
||||||
|
class XPT2046Button : public binary_sensor::BinarySensor {
|
||||||
|
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(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,
|
||||||
|
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->x_dim_ = x;
|
||||||
|
this->y_dim_ = 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; }
|
||||||
|
/// 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(GPIOPin *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 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);
|
||||||
|
|
||||||
|
int16_t threshold_;
|
||||||
|
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 swap_x_y_;
|
||||||
|
|
||||||
|
uint32_t report_millis_;
|
||||||
|
unsigned long last_pos_ms_{0};
|
||||||
|
|
||||||
|
GPIOPin *irq_pin_{nullptr};
|
||||||
|
bool last_irq_{true};
|
||||||
|
|
||||||
|
XPT2046OnStateTrigger *on_state_trigger_{new XPT2046OnStateTrigger()};
|
||||||
|
std::vector<XPT2046Button *> buttons_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xpt2046
|
||||||
|
} // namespace esphome
|
|
@ -100,6 +100,15 @@ binary_sensor:
|
||||||
on_state:
|
on_state:
|
||||||
then:
|
then:
|
||||||
- lambda: 'ESP_LOGI("ar1:", "%d", x);'
|
- lambda: 'ESP_LOGI("ar1:", "%d", x);'
|
||||||
|
- platform: xpt2046
|
||||||
|
xpt2046_id: touchscreen
|
||||||
|
id: touch_key0
|
||||||
|
x_min: 80
|
||||||
|
x_max: 160
|
||||||
|
y_min: 106
|
||||||
|
y_max: 212
|
||||||
|
on_state:
|
||||||
|
- lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));'
|
||||||
|
|
||||||
climate:
|
climate:
|
||||||
- platform: tuya
|
- platform: tuya
|
||||||
|
@ -180,3 +189,28 @@ external_components:
|
||||||
components: ["bh1750"]
|
components: ["bh1750"]
|
||||||
- source: ../esphome/components
|
- source: ../esphome/components
|
||||||
components: ["sntp"]
|
components: ["sntp"]
|
||||||
|
xpt2046:
|
||||||
|
id: 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:
|
||||||
|
- 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(touchscreen).x,
|
||||||
|
id(touchscreen).y,
|
||||||
|
(int) id(touchscreen).touched,
|
||||||
|
id(touchscreen).x_raw,
|
||||||
|
id(touchscreen).y_raw,
|
||||||
|
id(touchscreen).z_raw
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in a new issue