From 6910f30d4ca89f8afe74386bb3fc658ea3ba8036 Mon Sep 17 00:00:00 2001 From: Xandrossian Date: Mon, 21 Sep 2026 14:03:05 -0700 Subject: [PATCH] Add touch_probe and touch_reset actions to the Elecrow for a diagnosis over the API (0.2.97, firmware 0.2.83) --- .../smart_display/elecrow_touch_reset.h | 60 +++++++++++++++---- docs/ELECROW.md | 8 ++- packages/boards/elecrow-crowpanel7.yaml | 18 ++++++ packages/core.yaml | 2 +- screen_manager/CHANGELOG.md | 7 +++ screen_manager/app/core.py | 2 +- screen_manager/config.yaml | 2 +- tests/test_elecrow.py | 11 +++- 8 files changed, 90 insertions(+), 20 deletions(-) diff --git a/components/smart_display/elecrow_touch_reset.h b/components/smart_display/elecrow_touch_reset.h index 054c838..37585bb 100644 --- a/components/smart_display/elecrow_touch_reset.h +++ b/components/smart_display/elecrow_touch_reset.h @@ -7,6 +7,10 @@ // (setup priority 700, between the bus's 1000 and the driver's 600), so the driver finds it at 0x5D. A V2.0 board // has no expander: the writes fail, the log says so, and the GT911 boots by its pull-up as before. // +// probe() and reset() are the touch_probe and touch_reset actions of the board file (firmware 0.2.83): the expander's +// registers and whether a GT911 answers at 0x5D or 0x14 (its product id, "911"), and the same sequence with values of +// one's own, for a diagnosis over the API without a new build. +// // Only the Elecrow's board file builds this (`smart_display: elecrow_touch_reset:`, which sets the flag), so the // header adds nothing to a CYD, which has no I2C at all. #if defined(ESP_SCREENS_ELECROW_TOUCH_RESET) && ESP_SCREENS_ELECROW_TOUCH_RESET @@ -19,22 +23,54 @@ namespace elecrow { class TouchReset : public esphome::Component, public esphome::i2c::I2CDevice { public: float get_setup_priority() const override { return 700.0f; } - void setup() override { - auto write = [this](uint8_t reg, uint8_t value) { - const uint8_t data[2] = {reg, value}; - return this->write(data, sizeof(data)) == esphome::i2c::ERROR_OK; - }; - bool ok = write(0x03, 0x00); // configuration: every pin an output - ok = write(0x01, 0x00) && ok; // output: reset low, interrupt low (address 0x5D), the rest low - esphome::delay(20); - ok = write(0x01, 0x01) && ok; // reset released, the interrupt line still low - esphome::delay(100); - ok = write(0x03, 0x02) && ok; // the interrupt line an input again; the GT911 drives it from here + void setup() override { this->reset(0x00, 0x00, 0x01, 20, 100, 0x02); } + + // Elecrow's sequence with its values: `config` the expander's configuration register during the reset (0 = every + // pin an output), `low` its output register while the reset is held, `high` after the release, `final_config` + // afterwards; then the probe. + void reset(int config, int low, int high, int hold_ms, int wait_ms, int final_config) { + bool ok = this->write_reg_(0x03, config); + ok = this->write_reg_(0x01, low) && ok; + esphome::delay(hold_ms); + ok = this->write_reg_(0x01, high) && ok; + esphome::delay(wait_ms); + ok = this->write_reg_(0x03, final_config) && ok; if (ok) { - ESP_LOGI("touch", "GT911 reset through the PCA9557 at 0x%02X", this->address_); + ESP_LOGI("touch", "GT911 reset through the PCA9557 at 0x%02X: config 0x%02X, out 0x%02X -> 0x%02X, %d + %d ms, config 0x%02X", + this->address_, config, low, high, hold_ms, wait_ms, final_config); } else { ESP_LOGW("touch", "No PCA9557 at 0x%02X: the GT911 was not reset (a V2.0 board boots it by its pull-up)", this->address_); } + this->probe(); + } + + // The expander's four registers and the GT911's answer at both addresses. + void probe() { + uint8_t regs[4] = {0, 0, 0, 0}; + bool ok = true; + for (uint8_t reg = 0; reg < 4; ++reg) { + ok = this->write(®, 1) == esphome::i2c::ERROR_OK && this->read(®s[reg], 1) == esphome::i2c::ERROR_OK && ok; + } + ESP_LOGI("touch", "PCA9557 at 0x%02X: %s input 0x%02X output 0x%02X polarity 0x%02X config 0x%02X", this->address_, + ok ? "answers," : "no answer;", regs[0], regs[1], regs[2], regs[3]); + for (uint8_t address : {0x5D, 0x14}) { + esphome::i2c::I2CDevice chip; + chip.set_i2c_bus(this->bus_); + chip.set_i2c_address(address); + const uint8_t product_id[2] = {0x81, 0x40}; + uint8_t id[4] = {0, 0, 0, 0}; + const bool written = chip.write(product_id, sizeof(product_id)) == esphome::i2c::ERROR_OK; + const bool read = written && chip.read(id, sizeof(id)) == esphome::i2c::ERROR_OK; + ESP_LOGI("touch", "GT911 at 0x%02X: %s%s product id %02X %02X %02X %02X (\"%c%c%c\")", address, + written ? "write ok, " : "no write ack, ", read ? "read ok," : "no read,", id[0], id[1], id[2], id[3], + id[0] >= 32 && id[0] < 127 ? id[0] : '.', id[1] >= 32 && id[1] < 127 ? id[1] : '.', id[2] >= 32 && id[2] < 127 ? id[2] : '.'); + } + } + + protected: + bool write_reg_(uint8_t reg, int value) { + const uint8_t data[2] = {reg, static_cast(value)}; + return this->write(data, sizeof(data)) == esphome::i2c::ERROR_OK; } }; } // namespace elecrow diff --git a/docs/ELECROW.md b/docs/ELECROW.md index 33743ef..cec5af2 100644 --- a/docs/ELECROW.md +++ b/docs/ELECROW.md @@ -50,8 +50,12 @@ through the PCA9557`. A V2.0 board without the expander boots its GT911 by the p log says so), and the driver tries 0x14 by itself when 0x5D does not answer, so no address override is ever needed. The bus is scanned at boot. For a diagnosis, give the screen `logger: level: DEBUG` in its Override YAML (Screen -settings → Override YAML) and read its log: the scan lists 0x18 (the expander) and 0x5D or 0x14 (the GT911), and the -driver logs `Switches ADDR: 0x5D`; a screen without a working bus lists nothing. +settings → Override YAML) and read its log: the scan lists 0x18 (the expander; the GT911 is still in reset at that +moment), and the driver logs `Switches ADDR: 0x5D`; a screen without a working bus lists nothing. Two actions of the +screen (firmware 0.2.83; Developer tools → Actions, or over the API) work at any time: `touch_probe` logs the PCA9557's +four registers and whether a GT911 answers at 0x5D or 0x14 (its product id, "911"); `touch_reset` runs Elecrow's +sequence with values of one's own (`config`, `low`, `high`, `hold_ms`, `wait_ms`, `final_config`; Elecrow's are +0, 0, 1, 20, 100, 2) and probes again. **GPIO 19 and 20, the touch I2C, are the ESP32-S3's USB D- and D+.** The ROM leaves the USB PHY on those pads and ESP-IDF's I2C driver cannot take them back: the bus reads as `I2C software timeout` and the touch never answers diff --git a/packages/boards/elecrow-crowpanel7.yaml b/packages/boards/elecrow-crowpanel7.yaml index 3bd9f96..a1f2833 100644 --- a/packages/boards/elecrow-crowpanel7.yaml +++ b/packages/boards/elecrow-crowpanel7.yaml @@ -385,6 +385,7 @@ smart_display: partitions: 4mb_wide_slots # The GT911 out of reset through the V3.0's PCA9557 before the driver runs (elecrow_touch_reset.h, firmware 0.2.82). elecrow_touch_reset: + id: touch_reset i2c_id: touch_bus address: 0x18 @@ -410,6 +411,23 @@ api: - action: ui_snapshot then: - script.execute: capture_ui_snapshot + # The touch bus at a glance (firmware 0.2.83): the PCA9557's registers and whether a GT911 answers at 0x5D or 0x14. + - action: touch_probe + then: + - lambda: 'id(touch_reset).probe();' + # Elecrow's reset sequence with values of one's own, then the probe: the expander's configuration register during + # the reset (0 = every pin an output), its output register while the reset is held and after the release, the two + # waits in ms, and the configuration afterwards. Elecrow's own values: 0, 0, 1, 20, 100, 2. + - action: touch_reset + variables: + config: int + low: int + high: int + hold_ms: int + wait_ms: int + final_config: int + then: + - lambda: 'id(touch_reset).reset(config, low, high, hold_ms, wait_ms, final_config);' # A camera full screen from Home Assistant (firmware 0.2.57+), as a tap on its tile opens it; an empty entity closes # it. Only a camera on the screen's tiles or in a recent alert gets an image from ESP Screens. - action: preview_camera diff --git a/packages/core.yaml b/packages/core.yaml index c0021ce..3b898c0 100644 --- a/packages/core.yaml +++ b/packages/core.yaml @@ -14,7 +14,7 @@ # fonts/materialdesignicons-webfont.ttf (Apache 2.0, github.com/Templarian/MaterialDesign-Webfont). ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.82" + SCREEN_FIRMWARE_VERSION: "0.2.83" DASHBOARD_INBOX_INTERNAL: "false" CALIBRATION_ON_BOOT: "false" # a bench build: start on the touch test page ROOM_NAME: "" # top left until ESP Screens sends the screen name diff --git a/screen_manager/CHANGELOG.md b/screen_manager/CHANGELOG.md index 8826612..1edbe07 100644 --- a/screen_manager/CHANGELOG.md +++ b/screen_manager/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.2.97 (firmware 0.2.83) + +Two diagnostics for the Elecrow's touch bus, callable from Home Assistant. + +- **`touch_probe` and `touch_reset` on an Elecrow.** Two actions of the screen (Developer tools → Actions, `esphome._touch_probe`): the probe logs the PCA9557's four registers and whether a GT911 answers at 0x5D or 0x14 (its product id); the reset runs Elecrow's sequence with values of one's own (the expander's configuration during the reset, its outputs while the reset is held and after, the two waits, the configuration afterwards) and probes again. For a unit whose touch stays silent, without a new build for every try. +- Needs firmware 0.2.83 on an Elecrow: press **Update** on the screen. Elecrow firmware: 1,867,584 bytes, 91.9 % of its update slot. + ## 0.2.96 (firmware 0.2.82) The Elecrow's touch chip reset the way Elecrow does it. diff --git a/screen_manager/app/core.py b/screen_manager/app/core.py index 97a3522..d9badf1 100644 --- a/screen_manager/app/core.py +++ b/screen_manager/app/core.py @@ -54,7 +54,7 @@ REFS = {'cyd': 'main', 'guition': 'main', 'elecrow': 'main'} # is the small one. The Elecrow CrowPanel 7.0" (app 0.2.93) is the Guition's kind at 800x480, without the Rotation. LARGE_BOARDS = ('guition', 'elecrow') # Firmware shipped with this app release; screens below it get an update offer. -FIRMWARE_VERSION = '0.2.82' +FIRMWARE_VERSION = '0.2.83' # The Auto standby switch a screen offers Home Assistant automations. AUTO_STANDBY_MIN_FIRMWARE = '0.2.41' # The settings page the screen opens itself, and the screen.settings tile that opens it. diff --git a/screen_manager/config.yaml b/screen_manager/config.yaml index 915eea6..24b5ac7 100644 --- a/screen_manager/config.yaml +++ b/screen_manager/config.yaml @@ -1,5 +1,5 @@ name: ESP Screen Manager -version: "0.2.96" +version: "0.2.97" slug: esp_screen_manager description: Choose tiles for your CYD and Guition touchscreen, without reflashing. url: https://gittea.trippedonmy.name/Xandrossian/homeassistant_espscreen diff --git a/tests/test_elecrow.py b/tests/test_elecrow.py index d35b1a4..24dd995 100644 --- a/tests/test_elecrow.py +++ b/tests/test_elecrow.py @@ -58,14 +58,19 @@ class BoardFile(unittest.TestCase): # The USB pins are the touch I2C: the header that frees them. self.assertIn('', TEXT) # The V3.0's PCA9557 brings the GT911 out of reset before the driver runs (firmware 0.2.82). - self.assertIn(' elecrow_touch_reset:\n i2c_id: touch_bus\n address: 0x18', TEXT) + self.assertIn(' elecrow_touch_reset:\n id: touch_reset\n i2c_id: touch_bus\n address: 0x18', TEXT) code = '\n'.join(line for line in TEXT.split('\n') if not line.lstrip().startswith('#')) self.assertNotIn('reset_pin', code) self.assertNotIn('interrupt_pin', code) header = (ROOT / 'components/smart_display/elecrow_touch_reset.h').read_text() - for step in ('write(0x03, 0x00)', 'write(0x01, 0x00)', 'delay(20)', 'write(0x01, 0x01)', 'delay(100)', 'write(0x03, 0x02)'): - self.assertIn(step, header) + # Elecrow's own values at setup, and the sequence in that order. + self.assertIn('this->reset(0x00, 0x00, 0x01, 20, 100, 0x02)', header) + order = [header.index(step) for step in ("write_reg_(0x03, config)", "write_reg_(0x01, low)", "delay(hold_ms)", + "write_reg_(0x01, high)", "delay(wait_ms)", "write_reg_(0x03, final_config)")] + self.assertEqual(order, sorted(order)) self.assertIn('return 700.0f', header) + for action in ('touch_probe', 'touch_reset'): + self.assertIn(f'- action: {action}', TEXT) self.assertIn('assertion_level: SILENT', TEXT) def test_the_flash_fits_by_three_measures(self):