From 1e95711b1d22ad64712f22e9b6c926b239ecfd0e Mon Sep 17 00:00:00 2001 From: Simon Helson Date: Sun, 13 Jul 2025 14:47:19 +1200 Subject: [PATCH] initial commit --- .gitignore | 239 ++++++++++++++++++--- README.md | 9 +- components/cst9217/__init__.py | 0 components/cst9217/cst9217_touchscreen.cpp | 124 +++++++++++ components/cst9217/cst9217_touchscreen.h | 49 +++++ components/cst9217/touchscreen.py | 34 +++ examples/waveshare-1.75-amoled.yaml | 29 +++ 7 files changed, 447 insertions(+), 37 deletions(-) create mode 100644 components/cst9217/__init__.py create mode 100644 components/cst9217/cst9217_touchscreen.cpp create mode 100644 components/cst9217/cst9217_touchscreen.h create mode 100644 components/cst9217/touchscreen.py create mode 100644 examples/waveshare-1.75-amoled.yaml diff --git a/.gitignore b/.gitignore index d4fb281..abd8cb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,210 @@ -# Prerequisites -*.d +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Linker files -*.ilk - -# Debugger Files -*.pdb - -# Compiled Dynamic libraries +# C extensions *.so -*.dylib -*.dll -# Fortran module files -*.mod -*.smod +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST -# Compiled Static libraries -*.lai -*.la -*.a -*.lib +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec -# Executables -*.exe -*.out -*.app +# Installer logs +pip-log.txt +pip-delete-this-directory.txt -# debug information files -*.dwo +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml diff --git a/README.md b/README.md index de977dc..050c095 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ -# esphome-cst9217 -Esphome external_component to enable the cst9217 touchscreen +# Esphome cst9217 touchscreen component + +This will let you use your cst9217 touchscreen in esphome. + +It needs the `esp-idf` framework option in esphome. + +Example yaml is in the examples dir. diff --git a/components/cst9217/__init__.py b/components/cst9217/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/components/cst9217/cst9217_touchscreen.cpp b/components/cst9217/cst9217_touchscreen.cpp new file mode 100644 index 0000000..d7bbe40 --- /dev/null +++ b/components/cst9217/cst9217_touchscreen.cpp @@ -0,0 +1,124 @@ +#include "cst9217_touchscreen.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace cst9217 { + +void CST9217Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up CST9217 Touchscreen..."); + // Register operations taken from + // https://github.com/waveshareteam/Waveshare-ESP32-components/blob/master/display/touch/esp_lcd_touch_cst9217/esp_lcd_touch_cst9217.c + // and adapted for ESPHome. + + // holder for data returned + uint8_t data[4] = {0}; + + // Reset the device before setup. This checks if the reset pin is set. + this->reset_device_(); + + // Enter command mode + uint8_t cmd_mode[2] = { 0xD1, 0x01 }; + this->write_register16(ESP_LCD_TOUCH_CST9217_CMD_MODE_REG, cmd_mode, sizeof(cmd_mode)); // Set to cmd mode + vTaskDelay(pdMS_TO_TICKS(10)); + + // Read the checkcodes + this->read_register16(ESP_LCD_TOUCH_CST9217_CHECKCODE_REG, data, sizeof(data)); + ESP_LOGV(TAG, "Checkcode: 0x%02X%02X%02X%02X", + data[0], data[1], data[2], data[3]); + + // Get the touchscreen resolution + this->read_register16(ESP_LCD_TOUCH_CST9217_RESOLUTION_REG, data, sizeof(data)); + this->touch_res_x_ = (data[1] << 8) | data[0]; + this->touch_res_y_ = (data[3] << 8) | data[2]; + this->x_raw_min_ = 0; + this->y_raw_min_ = 0; + this->x_raw_max_ = this->touch_res_x_; + this->y_raw_max_ = this->touch_res_y_; + ESP_LOGV(TAG, "Resolution X: %d, Y: %d", this->touch_res_x_, this->touch_res_y_); + + // Get the chip ID and project ID and verify chip ID + this->read_register16(ESP_LCD_TOUCH_CST9217_PROJECT_ID_REG, data, sizeof(data)); + this->chip_id_ = (data[3] << 8) | data[2]; + this->touch_project_id_ = (data[1] << 8) | data[0]; + + if (this->chip_id_ != CST9217_CHIP_ID) { + this->mark_failed(); + this->status_set_error(str_sprintf("CST9217 Chip ID mismatch, expected 0x%04X, got 0x%04X", CST9217_CHIP_ID, this->chip_id_).c_str()); + return; + } + ESP_LOGV(TAG, "Chip Type: 0x%04X, ProjectID: 0x%04X", this->chip_id_, this->touch_project_id_); + + // Attach an interrupt pin if provided + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + ESP_LOGV(TAG, "Attached Interrupt Pin: %d", this->interrupt_pin_); + } + + // At this point we're all good. + ESP_LOGV(TAG, "CST9217 Touchscreen initialized successfully"); +} + +void CST9217Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "CST9217 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Resolution X: %d, Y: %d", this->touch_res_x_, this->touch_res_y_); + ESP_LOGCONFIG(TAG, " Chip Type: 0x%04X, ProjectID: 0x%04X", this->chip_id_, this->touch_project_id_); + ESP_LOGCONFIG(TAG, " CST9217 Touchscreen config dump complete"); +} + +void CST9217Touchscreen::update_touches() { + // ESP_LOGI(TAG, "CST9217 Touchscreen: running update_touches"); + uint8_t data[CST9217_DATA_LENGTH] = {0}; + i2c::ErrorCode ret; + + if(!this->read_register16(ESP_LCD_TOUCH_CST9217_DATA_REG, data, sizeof(data)) == i2c::ErrorCode::NO_ERROR) { + this->status_set_warning("CST9217 Touchscreen: Failed to read touch data"); + return; + } + + ESP_LOGV(TAG, "Touch data dump: %02X %02X %02X %02X %02X %02X %02X %02X", + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + + if (data[6] != CST9217_ACK_VALUE) { + this->status_set_warning(str_sprintf("Invalid ACK: 0x%02X vs 0x%02X", data[6], CST9217_ACK_VALUE).c_str()); + return; + } + + this->status_clear_warning(); + + uint8_t points = data[5] & 0x7F; + points = (points > CST9217_MAX_TOUCH_POINTS) ? CST9217_MAX_TOUCH_POINTS : points; + for (int i = 0; i < points; i++) { + uint8_t *p = &data[i * 5 + (i ? 2 : 0)]; + uint8_t status = p[0] & 0x0F; + + if (status == 0x06) { + int16_t x = ((p[1] << 4) | (p[3] >> 4)); + int16_t y = ((p[2] << 4) | (p[3] & 0x0F)); + this->add_raw_touch_position_(i, x, y); + ESP_LOGV(TAG, "Point %d: X=%d, Y=%d", + i, x, y); + } + } +} + +void CST9217Touchscreen::reset_device_() { + if (this->reset_pin_ != nullptr) { + ESP_LOGD(TAG, "Resetting CST9217 Touchscreen..."); + this->reset_pin_->digital_write(false); + vTaskDelay(pdMS_TO_TICKS(10)); + this->reset_pin_->digital_write(true); + vTaskDelay(pdMS_TO_TICKS(50)); + ESP_LOGD(TAG, "CST9217 Touchscreen reset complete"); + } else { + ESP_LOGD(TAG, "No reset pin configured, skipping reset"); + } +} + +} // namespace cst9217 +} // namespace esphome + diff --git a/components/cst9217/cst9217_touchscreen.h b/components/cst9217/cst9217_touchscreen.h new file mode 100644 index 0000000..7bc86d2 --- /dev/null +++ b/components/cst9217/cst9217_touchscreen.h @@ -0,0 +1,49 @@ +// cst9217.h +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" + +/* CST9217 registers */ +#define ESP_LCD_TOUCH_CST9217_DATA_REG 0xD000 +#define ESP_LCD_TOUCH_CST9217_PROJECT_ID_REG 0xD204 +#define ESP_LCD_TOUCH_CST9217_CMD_MODE_REG 0xD101 +#define ESP_LCD_TOUCH_CST9217_CHECKCODE_REG 0xD1FC +#define ESP_LCD_TOUCH_CST9217_RESOLUTION_REG 0xD1F8 + +/* CST9217 parameters */ +#define CST9217_CHIP_ID 0x9217 +#define CST9217_ACK_VALUE 0xAB +#define CST9217_MAX_TOUCH_POINTS 1 +#define CST9217_DATA_LENGTH (CST9217_MAX_TOUCH_POINTS * 5 + 5) + +namespace esphome { +namespace cst9217 { + +static const char *const TAG = "cst9217.touchscreen"; + + +class CST9217Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + void update_touches() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } + + protected: + void CST9217Touchscreen::reset_device_(); + // Internal buffer to read touch data + uint8_t touch_data_[CST9217_DATA_LENGTH]; // Sufficient for 2 touch points (8 bytes each) + header + InternalGPIOPin *interrupt_pin_{}; + GPIOPin *reset_pin_{}; + uint16_t chip_id_; + uint16_t touch_res_x_; + uint16_t touch_res_y_; + uint16_t touch_project_id_; +}; + +} // namespace cst9217 +} // namespace esphome + diff --git a/components/cst9217/touchscreen.py b/components/cst9217/touchscreen.py new file mode 100644 index 0000000..a981dba --- /dev/null +++ b/components/cst9217/touchscreen.py @@ -0,0 +1,34 @@ +# Touchscreen component for CST9217 +from esphome import pins +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN + +DEPENDENCIES = ["i2c"] + +# Define the namespace for your component +cst9217_ns = cg.esphome_ns.namespace("cst9217") +CST9217Touchscreen = cst9217_ns.class_( + "CST9217Touchscreen", + touchscreen.Touchscreen, + i2c.I2CDevice, +) + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CST9217Touchscreen), + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + } +).extend(i2c.i2c_device_schema(0x5A)) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) + + if interrupt_pin := config.get(CONF_INTERRUPT_PIN): + cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) + if reset_pin := config.get(CONF_RESET_PIN): + cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin))) \ No newline at end of file diff --git a/examples/waveshare-1.75-amoled.yaml b/examples/waveshare-1.75-amoled.yaml new file mode 100644 index 0000000..346bd01 --- /dev/null +++ b/examples/waveshare-1.75-amoled.yaml @@ -0,0 +1,29 @@ +# Examle of using this component with the Waveshare 1.75" round amoled display + +external_components: + - source: + type: git + url: https://github.com/shelson/esphome-cst9217 + +i2c: + sda: GPIO15 + scl: GPIO14 + scan: true + +touchscreen: + - platform: cst9217 + id: my_touchscreen + interrupt_pin: GPIO11 + reset_pin: + number: GPIO39 + allow_other_uses: True + transform: + mirror_x: True + mirror_y: True + on_update: + - lambda: |- + for (auto touch: touches) { + if (touch.state <= 2) { + ESP_LOGI("Touch points:", "id=%d x=%d, y=%d", touch.id, touch.x, touch.y); + } + }