Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TT21100 touchscreen component #4793

Merged
merged 12 commits into from
Jul 12, 2023
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -294,6 +294,7 @@ esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz
esphome/components/tsl2591/* @wjcarpenter
esphome/components/tt21100/* @kroimon
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/number/* @frankiboy1
Expand Down
Expand Up @@ -3,6 +3,11 @@
namespace esphome {
namespace touchscreen {

void TouchscreenBinarySensor::setup() {
this->parent_->register_listener(this);
this->publish_initial_state(false);
}

void TouchscreenBinarySensor::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_);

Expand Down
Expand Up @@ -14,7 +14,7 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor,
public TouchListener,
public Parented<Touchscreen> {
public:
void setup() override { this->parent_->register_listener(this); }
void setup() override;

/// 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) {
Expand Down
5 changes: 5 additions & 0 deletions esphome/components/tt21100/__init__.py
@@ -0,0 +1,5 @@
import esphome.codegen as cg

CODEOWNERS = ["@kroimon"]

tt21100_ns = cg.esphome_ns.namespace("tt21100")
31 changes: 31 additions & 0 deletions esphome/components/tt21100/binary_sensor/__init__.py
@@ -0,0 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_INDEX

from .. import tt21100_ns
from ..touchscreen import TT21100Touchscreen, TT21100ButtonListener

CONF_TT21100_ID = "tt21100_id"

jesserockz marked this conversation as resolved.
Show resolved Hide resolved
TT21100Button = tt21100_ns.class_(
"TT21100Button",
binary_sensor.BinarySensor,
cg.Component,
TT21100ButtonListener,
cg.Parented.template(TT21100Touchscreen),
)

CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(TT21100Button).extend(
{
cv.GenerateID(CONF_TT21100_ID): cv.use_id(TT21100Touchscreen),
cv.Required(CONF_INDEX): cv.int_range(min=0, max=3),
}
)


async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_TT21100_ID])
cg.add(var.set_index(config[CONF_INDEX]))
27 changes: 27 additions & 0 deletions esphome/components/tt21100/binary_sensor/tt21100_button.cpp
@@ -0,0 +1,27 @@
#include "tt21100_button.h"
#include "esphome/core/log.h"

namespace esphome {
namespace tt21100 {

static const char *const TAG = "tt21100.binary_sensor";

void TT21100Button::setup() {
this->parent_->register_button_listener(this);
this->publish_initial_state(false);
}

void TT21100Button::dump_config() {
LOG_BINARY_SENSOR("", "TT21100 Button", this);
ESP_LOGCONFIG(TAG, " Index: %u", this->index_);
}

void TT21100Button::update_button(uint8_t index, uint16_t state) {
if (index != this->index_)
return;

this->publish_state(state > 0);
}

} // namespace tt21100
} // namespace esphome
28 changes: 28 additions & 0 deletions esphome/components/tt21100/binary_sensor/tt21100_button.h
@@ -0,0 +1,28 @@
#pragma once

#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/tt21100/touchscreen/tt21100.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"

namespace esphome {
namespace tt21100 {

class TT21100Button : public binary_sensor::BinarySensor,
public Component,
public TT21100ButtonListener,
public Parented<TT21100Touchscreen> {
public:
void setup() override;
void dump_config() override;

void set_index(uint8_t index) { this->index_ = index; }

void update_button(uint8_t index, uint16_t state) override;

protected:
uint8_t index_;
};

} // namespace tt21100
} // namespace esphome
44 changes: 44 additions & 0 deletions esphome/components/tt21100/touchscreen/__init__.py
@@ -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

from .. import tt21100_ns

DEPENDENCIES = ["i2c"]

TT21100Touchscreen = tt21100_ns.class_(
"TT21100Touchscreen",
touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice,
)
TT21100ButtonListener = tt21100_ns.class_("TT21100ButtonListener")

CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(TT21100Touchscreen),
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)
)


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)

interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin))

if CONF_RESET_PIN in config:
rts_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(rts_pin))
175 changes: 175 additions & 0 deletions esphome/components/tt21100/touchscreen/tt21100.cpp
@@ -0,0 +1,175 @@
#include "tt21100.h"
#include "esphome/core/log.h"

namespace esphome {
namespace tt21100 {

static const char *const TAG = "tt21100";

static const uint8_t MAX_BUTTONS = 4;
static const uint8_t MAX_TOUCH_POINTS = 5;
static const uint8_t MAX_DATA_LEN = (7 + MAX_TOUCH_POINTS * 10); // 7 Header + (Points * 10 data bytes)

struct TT21100ButtonReport {
uint16_t length; // Always 14 (0x000E)
uint8_t report_id; // Always 0x03
uint16_t timestamp; // Number in units of 100 us
uint8_t btn_value; // Only use bit 0..3
uint16_t btn_signal[MAX_BUTTONS];
} __attribute__((packed));

struct TT21100TouchRecord {
uint8_t : 5;
uint8_t touch_type : 3;
uint8_t tip : 1;
uint8_t event_id : 2;
uint8_t touch_id : 5;
uint16_t x;
uint16_t y;
uint8_t pressure;
uint16_t major_axis_length;
uint8_t orientation;
} __attribute__((packed));

struct TT21100TouchReport {
uint16_t length;
uint8_t report_id;
uint16_t timestamp;
uint8_t : 2;
uint8_t large_object : 1;
uint8_t record_num : 5;
uint8_t report_counter : 2;
uint8_t : 3;
uint8_t noise_effect : 3;
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() {
ESP_LOGCONFIG(TAG, "Setting up TT21100 Touchscreen...");

// 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);

// Perform reset if necessary
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_();
}

// Update display dimensions if they were updated during display setup
this->display_width_ = this->display_->get_width_internal();
this->display_height_ = this->display_->get_height_internal();
this->rotation_ = static_cast<TouchRotation>(this->display_->get_rotation());

// Trigger initial read to activate the interrupt
this->store_.touch = true;
}

void TT21100Touchscreen::loop() {
if (!this->store_.touch)
return;
this->store_.touch = false;

// Read report length
uint16_t data_len;
this->read((uint8_t *) &data_len, sizeof(data_len));

// Read report data
uint8_t data[MAX_DATA_LEN];
if (data_len > 0 && data_len < sizeof(data)) {
this->read(data, data_len);

if (data_len == 14) {
// Button event
auto *report = (TT21100ButtonReport *) data;

ESP_LOGV(TAG, "Button report: Len=%d, ID=%d, Time=%5u, Value=[%u], Signal=[%04X][%04X][%04X][%04X]",
report->length, report->report_id, report->timestamp, report->btn_value, report->btn_signal[0],
report->btn_signal[1], report->btn_signal[2], report->btn_signal[3]);

for (uint8_t i = 0; i < 4; i++) {
for (auto *listener : this->button_listeners_)
listener->update_button(i, report->btn_signal[i]);
}

} else if (data_len >= 7) {
// Touch point event
auto *report = (TT21100TouchReport *) data;

ESP_LOGV(TAG,
"Touch report: Len=%d, ID=%d, Time=%5u, LargeObject=%u, RecordNum=%u, RecordCounter=%u, NoiseEffect=%u",
report->length, report->report_id, report->timestamp, report->large_object, report->record_num,
report->report_counter, report->noise_effect);

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];

ESP_LOGV(TAG,
"Touch %d: Type=%u, Tip=%u, EventId=%u, TouchId=%u, X=%u, Y=%u, Pressure=%u, MajorAxisLen=%u, "
"Orientation=%u",
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); });
}
}
}
}

void TT21100Touchscreen::reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
delay(10);
}
}

void TT21100Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "TT21100 Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
}

} // namespace tt21100
} // namespace esphome
49 changes: 49 additions & 0 deletions esphome/components/tt21100/touchscreen/tt21100.h
@@ -0,0 +1,49 @@
#pragma once

#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"

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 {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;

void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }

void register_button_listener(TT21100ButtonListener *listener) { this->button_listeners_.push_back(listener); }

protected:
void reset_();

TT21100TouchscreenStore store_;

InternalGPIOPin *interrupt_pin_;
GPIOPin *reset_pin_{nullptr};

std::vector<TT21100ButtonListener *> button_listeners_;
};

} // namespace tt21100
} // namespace esphome