Files
pyxis/lib/tdeck_ui/Hardware/TDeck/Touch.cpp
T
torlando-agent[bot]andClaude Opus 4.8 70d4aa6be9 feat: graft pyxis onto upstream microReticulum 0.4.1
Repins microReticulum + microLXMF onto the upstream-0.4.1 graft and adapts
pyxis to the new src/microReticulum/ layout and 0.4.x APIs. The far-diverged
0.3.0 fork's Resource/Transport/Identity work is subsumed by upstream's
reimplementation; only the still-needed fixes ride on the pinned branches
(PKCS7/HMAC/X25519 crypto -- proven byte-identical to python RNS 1.3.1 --
Packet link-proof callback, Identity short-sig guard, and the bz2 layer +
decompress-on-receive in Resource::assemble()).

Consumer-side changes:
- platformio.ini: pin microReticulum @2f21fee (pyxis-fixes-on-0.4.1) and
  microLXMF @33760d0 (chore/microreticulum-0.4.1-layout); bump microStore
  ceea8f5 -> c5fb69d (0.4.x requires the new BasicFileStore::init API);
  -std=gnu++11 -> gnu++17 (upstream requires C++17).
- Namespace all microReticulum includes (angle + quote) to <microReticulum/...>
  for the relocated layout; shim-local Utilities/Stream.h|Print.h preserved.
- Interface::send_outgoing now returns bool: update TCP/BLE/SX1262/Auto
  overrides with correct success/failure returns.
- SDArchiveFileSystem::init(bool reformatOnFail=true) to match new microStore.
- Static Transport::get_path_table() -> path_table(); instance getter unchanged.
- Remove duplicate shim Cryptography/BZ2 (microReticulum provides it now; keep
  lib/libbz2 as the ESP32 bzlib provider).
- patch_littlefs_paths.py: normalize microStore's LittleFS adapter paths to a
  leading "/" -- ESP32 Arduino LittleFS rejects "./"-prefixed paths, which
  silently broke the path store (no peer paths learned, all messaging blocked).

Validated on T-Deck Plus: builds (RAM 27.5% / Flash 77.7%), boots stable
(no WDT/panic), and a full on-device LXMF e2e (DIRECT + OPPORTUNISTIC +
bz2-compressed-Resource receive) passes 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-19 15:49:44 -04:00

324 lines
8.1 KiB
C++

// Copyright (c) 2024 microReticulum contributors
// SPDX-License-Identifier: MIT
#include "Touch.h"
#ifdef ARDUINO
#include <microReticulum/Log.h>
using namespace RNS;
namespace Hardware {
namespace TDeck {
TwoWire* Touch::_wire = nullptr;
bool Touch::_initialized = false;
uint8_t Touch::_i2c_addr = I2C::TOUCH_ADDR_1;
Touch::TouchPoint Touch::_points[Tch::MAX_TOUCH_POINTS];
uint8_t Touch::_touch_count = 0;
uint32_t Touch::_last_poll_time = 0;
bool Touch::init(TwoWire& wire) {
if (_initialized) {
return true;
}
INFO("Initializing T-Deck touch controller");
// Initialize hardware first
if (!init_hardware_only(wire)) {
return false;
}
// Register LVGL input device
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = lvgl_read_cb;
lv_indev_t* indev = lv_indev_drv_register(&indev_drv);
if (!indev) {
ERROR("Failed to register touch controller with LVGL");
return false;
}
INFO("Touch controller initialized successfully");
return true;
}
bool Touch::init_hardware_only(TwoWire& wire) {
if (_initialized) {
return true;
}
INFO("Initializing touch hardware");
_wire = &wire;
// Detect I2C address (0x5D or 0x14)
if (!detect_i2c_address()) {
ERROR(" Failed to detect GT911 I2C address");
return false;
}
INFO((" GT911 detected at address 0x" + String(_i2c_addr, HEX)).c_str());
// Verify product ID
if (!verify_product_id()) {
WARNING(" Could not verify GT911 product ID (may not be critical)");
}
// Clear initial touch state
for (uint8_t i = 0; i < Tch::MAX_TOUCH_POINTS; i++) {
_points[i].valid = false;
}
_touch_count = 0;
_initialized = true;
INFO(" Touch hardware ready");
return true;
}
uint8_t Touch::poll() {
if (!_initialized) {
return 0;
}
uint32_t now = millis();
if (now - _last_poll_time < Tch::POLL_INTERVAL_MS) {
return _touch_count; // Don't poll too frequently
}
_last_poll_time = now;
// Read status register
uint8_t status = 0;
if (!read_register(Tch::REG_STATUS, &status)) {
return 0;
}
// Check if touch data is ready
bool buffer_status = (status & 0x80) != 0;
uint8_t touch_count = status & 0x0F;
if (!buffer_status || touch_count == 0) {
// No touch or data not ready
_touch_count = 0;
for (uint8_t i = 0; i < Tch::MAX_TOUCH_POINTS; i++) {
_points[i].valid = false;
}
// Clear status register
write_register(Tch::REG_STATUS, 0x00);
return 0;
}
// Limit to max supported points
if (touch_count > Tch::MAX_TOUCH_POINTS) {
touch_count = Tch::MAX_TOUCH_POINTS;
}
// Read touch point data
_touch_count = 0;
for (uint8_t i = 0; i < touch_count; i++) {
uint8_t point_data[8];
uint16_t point_reg = Tch::REG_POINT_1 + (i * 8);
if (read_registers(point_reg, point_data, 8)) {
uint8_t track_id = point_data[0];
uint16_t x = point_data[1] | (point_data[2] << 8);
uint16_t y = point_data[3] | (point_data[4] << 8);
uint16_t size = point_data[5] | (point_data[6] << 8);
// Validate coordinates
if (x < Tch::RAW_WIDTH && y < Tch::RAW_HEIGHT) {
_points[i].x = x;
_points[i].y = y;
_points[i].size = size;
_points[i].track_id = track_id;
_points[i].valid = true;
_touch_count++;
} else {
_points[i].valid = false;
}
} else {
_points[i].valid = false;
}
}
// Clear remaining points
for (uint8_t i = touch_count; i < Tch::MAX_TOUCH_POINTS; i++) {
_points[i].valid = false;
}
// Clear status register
write_register(Tch::REG_STATUS, 0x00);
return _touch_count;
}
bool Touch::get_point(uint8_t index, TouchPoint& point) {
if (index >= Tch::MAX_TOUCH_POINTS) {
return false;
}
if (!_points[index].valid) {
return false;
}
point = _points[index];
return true;
}
bool Touch::is_touched() {
return _touch_count > 0;
}
uint8_t Touch::get_touch_count() {
return _touch_count;
}
String Touch::get_product_id() {
uint8_t product_id[4];
if (!read_registers(Tch::REG_PRODUCT_ID, product_id, 4)) {
return "";
}
char id_str[5];
id_str[0] = (char)product_id[0];
id_str[1] = (char)product_id[1];
id_str[2] = (char)product_id[2];
id_str[3] = (char)product_id[3];
id_str[4] = '\0';
return String(id_str);
}
void Touch::lvgl_read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) {
// Poll for new touch data
poll();
// Get first touch point
TouchPoint point;
if (get_point(0, point)) {
data->state = LV_INDEV_STATE_PRESSED;
// Transform touch coordinates for landscape display rotation
// Display uses MADCTL with MX|MV for landscape (rotation=1)
// GT911 reports in native portrait orientation:
// - raw X: 0-239 (portrait width, maps to screen Y after rotation)
// - raw Y: 0-319 (portrait height, maps to screen X after rotation)
// Transform: swap X/Y and invert the new Y axis
data->point.x = point.y;
data->point.y = Disp::HEIGHT - 1 - point.x; // Use display height (240), not raw height
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
// Continue reading is set automatically by LVGL based on state
}
bool Touch::write_register(uint16_t reg, uint8_t value) {
if (!_wire) {
return false;
}
_wire->beginTransmission(_i2c_addr);
_wire->write((uint8_t)(reg >> 8)); // Register high byte
_wire->write((uint8_t)(reg & 0xFF)); // Register low byte
_wire->write(value);
uint8_t result = _wire->endTransmission();
return (result == 0);
}
bool Touch::read_register(uint16_t reg, uint8_t* value) {
if (!_wire) {
return false;
}
// Write register address
_wire->beginTransmission(_i2c_addr);
_wire->write((uint8_t)(reg >> 8)); // Register high byte
_wire->write((uint8_t)(reg & 0xFF)); // Register low byte
uint8_t result = _wire->endTransmission(false); // Send repeated start
if (result != 0) {
return false;
}
// Read register value
if (_wire->requestFrom(_i2c_addr, (uint8_t)1) != 1) {
return false;
}
*value = _wire->read();
return true;
}
bool Touch::read_registers(uint16_t reg, uint8_t* buffer, size_t len) {
if (!_wire) {
return false;
}
// Write register address
_wire->beginTransmission(_i2c_addr);
_wire->write((uint8_t)(reg >> 8)); // Register high byte
_wire->write((uint8_t)(reg & 0xFF)); // Register low byte
uint8_t result = _wire->endTransmission(false); // Send repeated start
if (result != 0) {
return false;
}
// Read register values
if (_wire->requestFrom(_i2c_addr, (uint8_t)len) != len) {
return false;
}
for (size_t i = 0; i < len; i++) {
buffer[i] = _wire->read();
}
return true;
}
bool Touch::detect_i2c_address() {
// Try primary address first (0x5D)
_i2c_addr = I2C::TOUCH_ADDR_1;
_wire->beginTransmission(_i2c_addr);
uint8_t result = _wire->endTransmission();
if (result == 0) {
return true;
}
// Try alternative address (0x14)
_i2c_addr = I2C::TOUCH_ADDR_2;
_wire->beginTransmission(_i2c_addr);
result = _wire->endTransmission();
return (result == 0);
}
bool Touch::verify_product_id() {
String product_id = get_product_id();
if (product_id.length() == 0) {
return false;
}
// GT911 should return "911" as product ID
if (product_id.indexOf("911") >= 0) {
INFO((" Touch product ID: " + product_id).c_str());
return true;
}
WARNING((" Unexpected touch product ID: " + product_id).c_str());
return false;
}
} // namespace TDeck
} // namespace Hardware
#endif // ARDUINO