diff --git a/.github/workflows/release_tag.yml b/.github/workflows/release_tag.yml deleted file mode 100644 index 6dce8f0..0000000 --- a/.github/workflows/release_tag.yml +++ /dev/null @@ -1,40 +0,0 @@ -# This GitHub Actions workflow updates the "stable" and "latest" Git tags -# to point to the tag associated with a newly published release. It performs -# the following steps: -# 1. Checks out the repository code with full history. -# 2. Configures Git with a generic user for the action. -# 3. Moves and forcibly pushes the "stable" and "latest" tags to align with -# the tag of the newly published release. -# This ensures that "stable" and "latest" always point to the most recent release. ---- -name: Update Tags - -# yamllint disable-line rule:truthy -on: - release: - types: [published] - -jobs: - update-tags: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@main - with: - fetch-depth: '0' - - - name: Set up Git - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - - name: Move and push stable tag - run: | - git tag -f stable ${{ github.event.release.tag_name }} - git push -f origin stable - - - name: Move and push latest tag - run: | - git tag -f latest ${{ github.event.release.tag_name }} - git push -f origin latest -... diff --git a/.github/workflows/validate_esphome.yml b/.github/workflows/validate_esphome.yml index 9aa4fb7..9ff3b92 100644 --- a/.github/workflows/validate_esphome.yml +++ b/.github/workflows/validate_esphome.yml @@ -30,8 +30,6 @@ jobs: build_basic: name: Basic - needs: - - code_scan runs-on: ubuntu-latest steps: - uses: actions/checkout@main @@ -42,7 +40,6 @@ jobs: build_bluetooth_proxy_4: name: Bluetooth Proxy (IDF) - needs: build_basic runs-on: ubuntu-latest steps: - uses: actions/checkout@main @@ -53,7 +50,6 @@ jobs: build_bluetooth_proxy_53: name: Bluetooth Proxy (IDF v5.3) - needs: build_basic runs-on: ubuntu-latest steps: - uses: actions/checkout@main diff --git a/.github/workflows/validate_esphome_beta.yml b/.github/workflows/validate_esphome_beta.yml index 5d16d51..3f01fd1 100644 --- a/.github/workflows/validate_esphome_beta.yml +++ b/.github/workflows/validate_esphome_beta.yml @@ -47,8 +47,6 @@ jobs: build_basic: name: Basic - needs: - - code_scan runs-on: ubuntu-latest steps: - uses: actions/checkout@main @@ -60,7 +58,6 @@ jobs: build_bluetooth_proxy_4: name: Bluetooth Proxy (IDF) - needs: build_basic runs-on: ubuntu-latest steps: - uses: actions/checkout@main @@ -72,7 +69,6 @@ jobs: build_bluetooth_proxy_53: name: Bluetooth Proxy (IDF v5.3) - needs: build_basic runs-on: ubuntu-latest steps: - uses: actions/checkout@main diff --git a/.github/workflows/versioning.yml b/.github/workflows/versioning.yml index 2e49b1a..31a94d5 100644 --- a/.github/workflows/versioning.yml +++ b/.github/workflows/versioning.yml @@ -1,6 +1,11 @@ -# Workflow for managing versioning and tagging +# This GitHub Actions workflow handles version bumping and tag updates. +# It can be triggered either by: +# 1. Pushing to main branch - automatically bumps version and updates tags +# 2. Manual dispatch - allows independent control over stable and latest tags + --- -name: Version Bump and Tag + +name: Version and Tags on: # yamllint disable-line rule:truthy push: @@ -9,34 +14,93 @@ on: # yamllint disable-line rule:truthy paths-ignore: - '**/VERSION' - '**/VERSION_YAML' + workflow_dispatch: inputs: update_stable: description: "Update stable tag?" required: true - default: "false" + default: false + type: boolean + update_latest: + description: "Update latest tag?" + required: true + default: false + type: boolean jobs: - versioning: + version-and-tag: runs-on: ubuntu-latest - steps: - - uses: "actions/checkout@v4" + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Git run: | git config user.name "GitHub Actions" git config user.email "actions@github.com" - - name: Bump Version + - name: Bump version run: | - chmod +x "./versioning/bump_version.sh" - "./versioning/bump_version.sh" + chmod +x ./versioning/bump_version.sh + ./versioning/bump_version.sh - name: Push Changes and Tags env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - git push "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" main - git push "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" --tags + git push origin main + git push origin --tags --force + + - name: Update stable tag + if: | + success() && ( + github.event_name == 'workflow_dispatch' && inputs.update_stable || + github.event_name == 'push' + ) + run: | + # Verify version bump was successful + if [ ! -f "./versioning/VERSION" ]; then + echo "Error: VERSION file not found. Version bump may have failed." + exit 1 + fi + + # Backup existing tag + if git rev-parse --verify stable >/dev/null 2>&1; then + OLD_STABLE=$(git rev-parse stable) + echo "Backing up current stable tag ($OLD_STABLE)" + fi + + # Update tag + NEW_VERSION=$(cat ./versioning/VERSION) + echo "Updating stable tag to $NEW_VERSION" + git tag -fa stable -m "Update stable tag" + git push origin stable --force + + - name: Update latest tag + if: | + success() && ( + github.event_name == 'workflow_dispatch' && inputs.update_latest || + github.event_name == 'push' + ) + run: | + # Verify version bump was successful + if [ ! -f "./versioning/VERSION" ]; then + echo "Error: VERSION file not found. Version bump may have failed." + exit 1 + fi + + # Backup existing tag + if git rev-parse --verify latest >/dev/null 2>&1; then + OLD_LATEST=$(git rev-parse latest) + echo "Backing up current latest tag ($OLD_LATEST)" + fi + + # Update tag + NEW_VERSION=$(cat ./versioning/VERSION) + echo "Updating latest tag to $NEW_VERSION" + git tag -fa latest -m "Update latest tag" + git push origin latest --force ... diff --git a/.test/esphome_ard_basic.yaml b/.test/esphome_ard_basic.yaml index dbda8d8..ed32a8f 100644 --- a/.test/esphome_ard_basic.yaml +++ b/.test/esphome_ard_basic.yaml @@ -1,6 +1,6 @@ --- packages: - basic_package: !include ../TX-Ultimate-Easy-ESPHome.yaml # Basic package + basic_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml # Basic package esp32: framework: diff --git a/.test/esphome_idf53_bluetooth_proxy.yaml b/.test/esphome_idf53_bluetooth_proxy.yaml index 1e8643f..a8d3868 100644 --- a/.test/esphome_idf53_bluetooth_proxy.yaml +++ b/.test/esphome_idf53_bluetooth_proxy.yaml @@ -1,6 +1,6 @@ --- packages: - basic_package: !include ../TX-Ultimate-Easy-ESPHome.yaml # Core package + core_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml addon_bluetooth_proxy: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml esp32: diff --git a/.test/esphome_idf_bluetooth_proxy.yaml b/.test/esphome_idf_bluetooth_proxy.yaml index a2f9a0b..3e434a5 100644 --- a/.test/esphome_idf_bluetooth_proxy.yaml +++ b/.test/esphome_idf_bluetooth_proxy.yaml @@ -1,5 +1,5 @@ --- packages: - basic_package: !include ../TX-Ultimate-Easy-ESPHome.yaml # Core package + core_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml addon_bluetooth_proxy: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml ... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml index 12ecd7d..5911a02 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml @@ -74,7 +74,7 @@ improv_serial: id: serial_improv logger: - level: INFO + level: DEBUG ota: platform: esphome diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml index 2e8a8a0..20eb5a7 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml @@ -20,111 +20,53 @@ substitutions: BUTTON_3_ACTION_TEXT: "Relay 3 (toggle)" BUTTON_4_ACTION_TEXT: "Relay 4 (toggle)" -binary_sensor: - - id: bs_button_1 - name: Button 1 - icon: mdi:gesture-tap-box - internal: false - platform: template - on_click: - then: - - script.execute: - id: button_action - component: bs_button_1 - event: click - on_double_click: - then: - - script.execute: - id: button_action - component: bs_button_1 - event: double_click - on_multi_click: - - timing: &long_click-timing - - ON for at least 0.8s - invalid_cooldown: ${invalid_cooldown} - then: - - script.execute: - id: button_action - component: bs_button_1 - event: long_click + BUTTON_CLICK_MIN_LENGTH: '50' # The minimum duration the click should last, in msec + BUTTON_CLICK_MAX_LENGTH: '350' # The maximum duration the click should last, in msec + BUTTON_MULTI_CLICK_DELAY: '250' # The time to wait for another click, in msec + BUTTON_PRESS_TIMEOUT: '10000' # Ignore if button is pressed for longer than this time, in msec + BUTTON_LONG_PRESS_DELAY: '800' # The time to wait to consider a long press, in msec - - id: bs_button_2 +binary_sensor: + - &binary_sensor_button_base + id: bs_button_2 name: Button 2 icon: mdi:gesture-tap-box - internal: true platform: template - on_click: - then: - - script.execute: - id: button_action - component: bs_button_2 - event: click - on_double_click: - then: - - script.execute: - id: button_action - component: bs_button_2 - event: double_click - on_multi_click: - - timing: *long_click-timing - invalid_cooldown: ${invalid_cooldown} - then: - - script.execute: - id: button_action - component: bs_button_2 - event: long_click + internal: true - id: bs_button_3 name: Button 3 - icon: mdi:gesture-tap-box - internal: true - platform: template - on_click: - then: - - script.execute: - id: button_action - component: bs_button_3 - event: click - on_double_click: - then: - - script.execute: - id: button_action - component: bs_button_3 - event: double_click - on_multi_click: - - timing: *long_click-timing - invalid_cooldown: ${invalid_cooldown} - then: - - script.execute: - id: button_action - component: bs_button_3 - event: long_click + <<: *binary_sensor_button_base - id: bs_button_4 name: Button 4 - icon: mdi:gesture-tap-box - internal: true - platform: template - on_click: - then: - - script.execute: - id: button_action - component: bs_button_4 - event: click - on_double_click: - then: - - script.execute: - id: button_action - component: bs_button_4 - event: double_click - on_multi_click: - - timing: *long_click-timing - invalid_cooldown: ${invalid_cooldown} - then: - - script.execute: - id: button_action - component: bs_button_4 - event: long_click + <<: *binary_sensor_button_base + + - id: bs_button_1 + name: Button 1 + internal: false + <<: *binary_sensor_button_base + +globals: + - id: button_press_button + type: uint8_t + restore_value: false + initial_value: '0' + + - id: button_press_position + type: uint8_t + restore_value: false + initial_value: '0' + + - id: button_press_start_time + type: uint32_t + restore_value: false + initial_value: '0' + + - id: click_counter + type: uint8_t + restore_value: false + initial_value: '0' script: - id: !extend boot_initialize @@ -162,9 +104,31 @@ script: parameters: component: string event: string - then: # There's nothing here so far - # Extended by: - # - core_api + then: + # Extended by: + # - core_api + - lambda: |- + ESP_LOGI("core_hw_buttons", "Button '%s' action: '%s'", component.c_str(), event.c_str()); + id(button_press_button) = 0; + id(click_counter) = 0; + id(button_press_start_time) = 0; + buttons_release->execute(); + + - id: button_click_event + mode: restart + parameters: + button_id: uint8_t + click_count: uint8_t + then: + - delay: + milliseconds: ${BUTTON_MULTI_CLICK_DELAY} + - lambda: |- + const std::string button_name = "bs_button_" + std::to_string(button_id); + std::string event_name; + if (click_count == 1) event_name = "click"; + else if (click_count == 2) event_name = "double_click"; + else event_name = std::to_string(click_count) + "_click"; + button_action->execute(button_name.c_str(), event_name.c_str()); - id: buttons_release mode: restart @@ -187,42 +151,78 @@ script: then: - script.execute: id: touch_on_press_buttons - touch_x: !lambda return touch_x; + touch_position: !lambda return touch_position; - id: touch_on_press_buttons mode: restart parameters: - touch_x: uint8_t + touch_position: uint8_t then: - lambda: |- + id(button_press_start_time) = millis(); + id(button_press_position) = touch_position; + uint8_t button = 0; auto model_index = sl_tx_model_gang->active_index(); if (model_index.has_value()) { - uint8_t model_idx = model_index.value() + 1; - switch (model_idx) { - case 1: // 1 Gang - bs_button_1->publish_state(true); - break; - case 2: // 2 Gang - if (touch_x <= 5) bs_button_1->publish_state(true); - else bs_button_2->publish_state(true); - break; - case 3: // 3 Gang - if (touch_x <= 3) bs_button_1->publish_state(true); - else if (touch_x <= 7) bs_button_2->publish_state(true); - else bs_button_3->publish_state(true); - break; - case 4: // 4 Gang - if (touch_x <= 2) bs_button_1->publish_state(true); - else if (touch_x <= 5) bs_button_2->publish_state(true); - else if (touch_x <= 8) bs_button_3->publish_state(true); - else bs_button_4->publish_state(true); - break; - } + const uint8_t model_idx = model_index.value() + 1; // Increment for 1-based indexing + if (model_idx == 1) { + button = 1; // Single button, always 1 + } else { + const uint8_t width = ${TOUCH_POSITION_MAX_VALUE} / model_idx; // Width of each button region + ESP_LOGV("core_hw_buttons", "Button regions: width=%" PRIu8 ", touch_position=%" PRIu8, + width, touch_position); + button = (touch_position / width) + 1; // Determine button region + if (button > model_idx) + button = model_idx; // Clamp to max button count + } + } + // Update binary sensor + switch (button) { + case 1: + bs_button_1->publish_state(true); + break; + case 2: + bs_button_2->publish_state(true); + break; + case 3: + bs_button_3->publish_state(true); + break; + case 4: + bs_button_4->publish_state(true); + break; + } + // Update counters + if (id(button_press_button) == button) { + id(click_counter)++; + } else { + id(click_counter) = 1; + id(button_press_button) = button; } - id: !extend touch_on_release then: - - script.execute: buttons_release + - lambda: |- + uint32_t current_time = millis(); + buttons_release->execute(); + if (id(button_press_start_time) > 0 and + id(button_press_start_time) < current_time) { + uint32_t press_duration = current_time - id(button_press_start_time); + // Handle overflow (optional, since it's unlikely to happen here) + ESP_LOGI("core_hw_buttons", "Button press duration: %" PRIu32 " ms", press_duration); + if (press_duration < ${BUTTON_CLICK_MIN_LENGTH}) { + ESP_LOGW("core_hw_buttons", "Ignoring button press (too short)"); + } else if (press_duration >= ${BUTTON_CLICK_MIN_LENGTH} and + press_duration <= ${BUTTON_CLICK_MAX_LENGTH}) { // Short/normal click + button_click_event->execute(id(button_press_button), id(click_counter)); + } else if (press_duration >= ${BUTTON_LONG_PRESS_DELAY} and press_duration <= ${BUTTON_PRESS_TIMEOUT}) { + button_action->execute(("bs_button_" + std::to_string(id(button_press_button))).c_str(), "long_click"); + } else if (press_duration > ${BUTTON_PRESS_TIMEOUT}) { // Timeout or invalid + ESP_LOGW("core_hw_buttons", "Button press cancelled or timed out after ${BUTTON_PRESS_TIMEOUT} ms"); + } + } else { + ESP_LOGW("core_hw_buttons", "Press event timestamp not recorded yet"); + } + id(button_press_start_time) = 0; - id: !extend touch_swipe_left then: diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml index 08c6490..0a47ab3 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml @@ -11,6 +11,9 @@ ##### - For normal system use, modifications to this file are NOT required. ##### #################################################################################################### --- +substitutions: + TOUCH_POSITION_MAX_VALUE: '10' # Maximum touch position value returned by the touch pad via uart + binary_sensor: - id: bs_multi_touch name: Multi-touch @@ -61,7 +64,7 @@ external_components: - source: type: git url: https://github.com/edwardtfn/TX-Ultimate-Easy - ref: ${version} + ref: v${version} refresh: 1h components: - tx_ultimate_easy @@ -124,7 +127,7 @@ script: - id: touch_on_press mode: restart parameters: - touch_x: uint8_t + touch_position: uint8_t then: # Extended by: # - HW Buttons @@ -203,16 +206,28 @@ tx_ultimate_easy: id: tx_ultimate uart: uart_touch + on_long_touch_release: + - lambda: |- + const uint8_t touch_position = static_cast(touch.x); + if (touch_position > ${TOUCH_POSITION_MAX_VALUE}) { // Check for valid range + ESP_LOGE("tx_ultimate_easy", "Invalid long-touch position: %" PRIu8, touch_position); + } else { + ESP_LOGI("tx_ultimate_easy", "Long-touch released at position %" PRIu8, touch_position); + } + on_multi_touch_release: - lambda: ESP_LOGI("tx_ultimate_easy", "Multi-touch released"); - script.execute: touch_on_multi_touch_release on_press: - lambda: |- - ESP_LOGI("tx_ultimate_easy", "Pressed at position %" PRIu8, static_cast(touch.x)); - - script.execute: - id: touch_on_press - touch_x: !lambda return static_cast(touch.x); + const uint8_t touch_position = static_cast(touch.x); + if (touch_position > ${TOUCH_POSITION_MAX_VALUE}) { // Check for valid range + ESP_LOGE("tx_ultimate_easy", "Invalid touch position: %" PRIu8, touch_position); + } else { + ESP_LOGI("tx_ultimate_easy", "Pressed at position %" PRIu8, touch_position); + touch_on_press->execute(touch_position); + } on_release: - lambda: ESP_LOGI("tx_ultimate_easy", "Released"); @@ -233,8 +248,8 @@ tx_ultimate_easy: ESP_LOGD("tx_ultimate_easy", " Position: %i", touch.x); uart: - id: uart_touch - tx_pin: GPIO19 - rx_pin: GPIO22 - baud_rate: 115200 + - id: uart_touch + tx_pin: GPIO19 + rx_pin: GPIO22 + baud_rate: 115200 ... diff --git a/README.md b/README.md index 80a5575..79b2db7 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ # TX Ultimate Easy - -![GitHub Activity][commits-shield] -![License][license-shield] -![GitHub Last Commit][last-commit-shield] -![ESPHome][esphome-shield] -![Discord][discord-shield] -![Buy me an ice-cream][buymeacoffee-shield] - +[![Version][version-shield]](https://github.com/edwardtfn/TX-Ultimate-Easy/tags) +[![GitHub Activity][commits-shield]](https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main) +[![License][license-shield]](LICENSE) +[![GitHub Last Commit][last-commit-shield]](https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main) +[![ESPHome][esphome-shield]](https://esphome.io/) +[![Discord][discord-shield]](https://discord.gg/Db6WJWzWuf) +[![Buy me an ice-cream][buymeacoffee-shield]](https://www.buymeacoffee.com/edwardfirmo) |  ![TX Ultimate Easy Logo](Assets/Logo.webp) | TX Ultimate Easy provides custom ESPHome firmware for Sonoff TX Ultimate devices. Our project focuses on user-friendly configuration through the Home Assistant UI, eliminating the need for manual YAML editing. Whether you're new to home automation or an experienced user, TX Ultimate Easy makes it simple to manage your device. | | --- | :-- | +[version-shield]: https://img.shields.io/github/v/tag/edwardtfn/TX-Ultimate-Easy?label=version +[version]: https://github.com/edwardtfn/TX-Ultimate-Easy/tags [commits-shield]: https://img.shields.io/github/commit-activity/y/edwardtfn/TX-Ultimate-Easy [commits]: https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main [license-shield]: https://img.shields.io/github/license/edwardtfn/TX-Ultimate-Easy @@ -35,7 +36,60 @@ TX Ultimate Easy exposes your device's components (sensors, touch panel, relays, - Use device triggers and states in your Home Assistant automations and scripts - Configure device behavior through Home Assistant's service calls -All automation capabilities are handled through Home Assistant's native automation system - this project focuses on providing reliable device integration rather than implementing its own automation tools. +All automation capabilities are handled through Home Assistant's native automation system - this project focuses on providing reliable +device integration rather than implementing its own automation tools. + +### Event-Based Automation + +TX Ultimate Easy uses Home Assistant's native Events system for reliable automation triggers. +While sensors show the current state (e.g., button pressed/not pressed), events capture-specific actions like clicks, swipes, and long presses. + +To view available events: + +1. Go to Developer Tools in Home Assistant +2. Select the "Events" tab +3. Enter `esphome.tx_ultimate_easy` in the "Event to subscribe to" field +4. Click "Start listening" +5. Interact with your device to see events in real-time + +Example event trigger in automation (YAML): + +```yaml +triggers: + - platform: event + event_type: esphome.tx_ultimate_easy + event_data: + device_name: your_device_name # Replace with your specific device name + component: bs_button_1 # Button identifier (e.g., bs_button_1, bs_button_2, bs_button_3 or bs_button_4) + event: click +actions: + - action: light.toggle + target: + entity_id: light.living_room +``` + +**Common event types**: +- `click`: Single press and release +- `double_click`: Two quick presses +- `long_press`: Press and hold +- `swipe_left`: Left swipe gesture +- `swipe_right`: Right swipe gesture + +You can also create event-based automations through the Home Assistant UI by selecting "Event" as the trigger type and filtering by your device. + +### Device Configuration +#### Relay Modes +- **Light Mode**: Exposes the relay as a light entity with brightness controls (if supported) +- **Switch Mode**: Exposes the relay as a simple on/off switch entity + +#### Button Actions +- **None**: Allows using button events for custom automations + Example: Trigger scenes or complex automations through Home Assistant +- **Relay Toggle**: Direct control of the associated relay + Example: Toggle relay state with each press, independent of Home Assistant + +#### Automation +All device behaviors can be customized through Home Assistant automations without relying on local device triggers. ## Key Features @@ -103,6 +157,7 @@ We welcome contributions from the community! Here's how you can help: 4. Submit a pull request targeting the `main` branch Please ensure your code follows our standards: + - Passes all lint checks (YAML, C++, Markdown) - Includes appropriate documentation - Follows existing code style @@ -115,7 +170,9 @@ Need help? Here are your options: - **Community Chat**: Join our [Discord Server](https://discord.gg/Db6WJWzWuf) for discussions and community interaction - **Support the Project**: Consider supporting through Buy Me a Coffee -Note: For proper tracking and resolution, all bug reports and feature requests must be submitted through GitHub Issues, not Discord. The Issues page can be found at: [Issues · edwardtfn/TX-Ultimate-Easy](https://github.com/edwardtfn/TX-Ultimate-Easy/issues) +Note: For proper tracking and resolution: +- All bug reports and feature requests must be submitted through GitHub Issues, not Discord +- Submit issues here: [Issues · edwardtfn/TX-Ultimate-Easy](https://github.com/edwardtfn/TX-Ultimate-Easy/issues) [![Buy Me a Coffee](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://www.buymeacoffee.com/edwardfirmo) diff --git a/TX-Ultimate-Easy-ESPHome.yaml b/TX-Ultimate-Easy-ESPHome.yaml index 3f181d8..f3b95c3 100644 --- a/TX-Ultimate-Easy-ESPHome.yaml +++ b/TX-Ultimate-Easy-ESPHome.yaml @@ -16,7 +16,7 @@ wifi: packages: remote_package: url: https://github.com/edwardtfn/TX-Ultimate-Easy - ref: latest # Or you can specify a version, like `ref: v2024.12.6` + ref: main refresh: 30s files: - ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml diff --git a/components/tx_ultimate_easy/__init__.py b/components/tx_ultimate_easy/__init__.py index 3a598ad..7120ead 100644 --- a/components/tx_ultimate_easy/__init__.py +++ b/components/tx_ultimate_easy/__init__.py @@ -13,6 +13,7 @@ DEPENDENCIES = ['uart'] CONF_TX_ULTIMATE_EASY = "tx_ultimate_easy" CONF_UART = "uart" +CONF_GANG_COUNT = "gang_count" CONF_ON_TOUCH_EVENT = "on_touch_event" CONF_ON_PRESS = "on_press" @@ -33,6 +34,7 @@ CONFIG_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(TxUltimateTouch), cv.Required(CONF_UART): cv.use_id(uart), + cv.Optional(CONF_GANG_COUNT, default=1): cv.int_range(min=1, max=4), cv.Optional(CONF_ON_TOUCH_EVENT): automation.validate_automation(single=True), cv.Optional(CONF_ON_PRESS): automation.validate_automation(single=True), @@ -49,6 +51,9 @@ async def register_tx_ultimate_easy(var, config): uart_component = await cg.get_variable(config[CONF_UART]) cg.add(var.set_uart_component(uart_component)) + if CONF_GANG_COUNT in config: + cg.add(var.set_gang_count(config[CONF_GANG_COUNT])) + if CONF_ON_TOUCH_EVENT in config: await automation.build_automation( var.get_trigger_touch_event(), diff --git a/components/tx_ultimate_easy/tx_ultimate_easy.cpp b/components/tx_ultimate_easy/tx_ultimate_easy.cpp index 84150db..640f3db 100644 --- a/components/tx_ultimate_easy/tx_ultimate_easy.cpp +++ b/components/tx_ultimate_easy/tx_ultimate_easy.cpp @@ -48,6 +48,35 @@ namespace esphome { void TxUltimateEasy::dump_config() { ESP_LOGCONFIG(TAG, "TX Ultimate Easy"); + ESP_LOGCONFIG(TAG, " Gang count: %" PRIu8, this->gang_count_); + } + + bool TxUltimateEasy::set_gang_count(const uint8_t gang_count) { + // Hardware supports maximum of 4 touch-sensitive buttons + if (gang_count < 1 or gang_count > 4) + return false; + this->gang_count_ = gang_count; + return true; + } + + uint8_t TxUltimateEasy::get_button_from_position(const uint8_t position) { + // Validate position bounds + if (position > TOUCH_MAX_POSITION) + return 0; + + // Special case for single gang (only one button exists) + if (this->gang_count_ == 1) + return 1; + + // Calculate button number + const uint8_t width = (TOUCH_MAX_POSITION + 1) / this->gang_count_; // Width of each button region + if (width < 1 or width > this->gang_count_) // Invalid width - and prevents division by zero + return 0; + const uint8_t button = std::min( + static_cast((position / width) + 1), // Convert position to button index + this->gang_count_ // Clamp to max gang count + ); + return button; } void TxUltimateEasy::send_touch_(TouchPoint tp) { @@ -103,7 +132,8 @@ namespace esphome { state == TOUCH_STATE_SWIPE_LEFT || state == TOUCH_STATE_SWIPE_RIGHT || state == TOUCH_STATE_MULTI_TOUCH) && - (uart_received_bytes[6] >= 0 || state == TOUCH_STATE_MULTI_TOUCH); + // Multi-touch events may have x < 0, all other events require valid x position + (uart_received_bytes[6] >= 0 || state == TOUCH_STATE_MULTI_TOUCH); } int TxUltimateEasy::get_touch_position_x(const std::array &uart_received_bytes) { @@ -133,6 +163,8 @@ namespace esphome { TouchPoint TxUltimateEasy::get_touch_point(const std::array &uart_received_bytes) { TouchPoint tp; tp.x = this->get_touch_position_x(uart_received_bytes); + if (tp.x >= 0) + tp.button = this->get_button_from_position(static_cast(tp.x)); tp.state = this->get_touch_state(uart_received_bytes); switch (tp.state) { case TOUCH_STATE_RELEASE: diff --git a/components/tx_ultimate_easy/tx_ultimate_easy.h b/components/tx_ultimate_easy/tx_ultimate_easy.h index 5d7b3a0..cfb9e8b 100644 --- a/components/tx_ultimate_easy/tx_ultimate_easy.h +++ b/components/tx_ultimate_easy/tx_ultimate_easy.h @@ -13,6 +13,9 @@ namespace esphome { namespace tx_ultimate_easy { + // Touch Max Position + constexpr uint8_t TOUCH_MAX_POSITION = 10; + // Touch State Constants constexpr uint8_t TOUCH_STATE_RELEASE = 0x01; constexpr uint8_t TOUCH_STATE_PRESS = 0x02; @@ -32,6 +35,7 @@ namespace esphome { static const char *TAG = "tx_ultimate_easy"; struct TouchPoint { + uint8_t button = 0; int8_t x = -1; int8_t state = -1; std::string state_str = "Unknown"; @@ -53,6 +57,10 @@ namespace esphome { void loop() override; void dump_config() override; + uint8_t get_gang_count() { return this->gang_count_; } + bool set_gang_count(const uint8_t gang_count); + uint8_t get_button_from_position(const uint8_t position); + protected: void send_touch_(TouchPoint tp); void handle_touch(const std::array &bytes); @@ -70,6 +78,8 @@ namespace esphome { Trigger trigger_multi_touch_release_; Trigger trigger_long_touch_release_; + uint8_t gang_count_ = 1; + }; // class TxUltimateEasy } // namespace tx_ultimate_easy diff --git a/versioning/VERSION b/versioning/VERSION index 13d8c70..e47b131 100644 --- a/versioning/VERSION +++ b/versioning/VERSION @@ -1 +1 @@ -2024.12.3 +2024.12.7 diff --git a/versioning/VERSION_YAML b/versioning/VERSION_YAML index 823f360..72211d6 100644 --- a/versioning/VERSION_YAML +++ b/versioning/VERSION_YAML @@ -1 +1 @@ -version: 2024.12.3 +version: 2024.12.7