Merge branch 'relay-on-light-mode' of https://github.com/edwardtfn/TX-Ultimate-Easy into relay-on-light-mode

This commit is contained in:
Edward Firmo
2024-12-23 00:53:37 +01:00
17 changed files with 345 additions and 210 deletions

View File

@@ -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
...

View File

@@ -30,8 +30,6 @@ jobs:
build_basic: build_basic:
name: Basic name: Basic
needs:
- code_scan
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -42,7 +40,6 @@ jobs:
build_bluetooth_proxy_4: build_bluetooth_proxy_4:
name: Bluetooth Proxy (IDF) name: Bluetooth Proxy (IDF)
needs: build_basic
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -53,7 +50,6 @@ jobs:
build_bluetooth_proxy_53: build_bluetooth_proxy_53:
name: Bluetooth Proxy (IDF v5.3) name: Bluetooth Proxy (IDF v5.3)
needs: build_basic
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main

View File

@@ -47,8 +47,6 @@ jobs:
build_basic: build_basic:
name: Basic name: Basic
needs:
- code_scan
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -60,7 +58,6 @@ jobs:
build_bluetooth_proxy_4: build_bluetooth_proxy_4:
name: Bluetooth Proxy (IDF) name: Bluetooth Proxy (IDF)
needs: build_basic
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -72,7 +69,6 @@ jobs:
build_bluetooth_proxy_53: build_bluetooth_proxy_53:
name: Bluetooth Proxy (IDF v5.3) name: Bluetooth Proxy (IDF v5.3)
needs: build_basic
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main

View File

@@ -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 on: # yamllint disable-line rule:truthy
push: push:
@@ -9,34 +14,93 @@ on: # yamllint disable-line rule:truthy
paths-ignore: paths-ignore:
- '**/VERSION' - '**/VERSION'
- '**/VERSION_YAML' - '**/VERSION_YAML'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
update_stable: update_stable:
description: "Update stable tag?" description: "Update stable tag?"
required: true required: true
default: "false" default: false
type: boolean
update_latest:
description: "Update latest tag?"
required: true
default: false
type: boolean
jobs: jobs:
versioning: version-and-tag:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: "actions/checkout@v4" - name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Git - name: Set up Git
run: | run: |
git config user.name "GitHub Actions" git config user.name "GitHub Actions"
git config user.email "actions@github.com" git config user.email "actions@github.com"
- name: Bump Version - name: Bump version
run: | run: |
chmod +x "./versioning/bump_version.sh" chmod +x ./versioning/bump_version.sh
"./versioning/bump_version.sh" ./versioning/bump_version.sh
- name: Push Changes and Tags - name: Push Changes and Tags
env: env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
git push "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" main git push origin main
git push "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" --tags 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
... ...

View File

@@ -1,6 +1,6 @@
--- ---
packages: packages:
basic_package: !include ../TX-Ultimate-Easy-ESPHome.yaml # Basic package basic_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml # Basic package
esp32: esp32:
framework: framework:

View File

@@ -1,6 +1,6 @@
--- ---
packages: 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 addon_bluetooth_proxy: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml
esp32: esp32:

View File

@@ -1,5 +1,5 @@
--- ---
packages: 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 addon_bluetooth_proxy: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml
... ...

View File

@@ -74,7 +74,7 @@ improv_serial:
id: serial_improv id: serial_improv
logger: logger:
level: INFO level: DEBUG
ota: ota:
platform: esphome platform: esphome

View File

@@ -20,111 +20,53 @@ substitutions:
BUTTON_3_ACTION_TEXT: "Relay 3 (toggle)" BUTTON_3_ACTION_TEXT: "Relay 3 (toggle)"
BUTTON_4_ACTION_TEXT: "Relay 4 (toggle)" BUTTON_4_ACTION_TEXT: "Relay 4 (toggle)"
binary_sensor: BUTTON_CLICK_MIN_LENGTH: '50' # The minimum duration the click should last, in msec
- id: bs_button_1 BUTTON_CLICK_MAX_LENGTH: '350' # The maximum duration the click should last, in msec
name: Button 1 BUTTON_MULTI_CLICK_DELAY: '250' # The time to wait for another click, in msec
icon: mdi:gesture-tap-box BUTTON_PRESS_TIMEOUT: '10000' # Ignore if button is pressed for longer than this time, in msec
internal: false BUTTON_LONG_PRESS_DELAY: '800' # The time to wait to consider a long press, in msec
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
- id: bs_button_2 binary_sensor:
- &binary_sensor_button_base
id: bs_button_2
name: Button 2 name: Button 2
icon: mdi:gesture-tap-box icon: mdi:gesture-tap-box
internal: true
platform: template platform: template
on_click: internal: true
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
- id: bs_button_3 - id: bs_button_3
name: Button 3 name: Button 3
icon: mdi:gesture-tap-box <<: *binary_sensor_button_base
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
- id: bs_button_4 - id: bs_button_4
name: Button 4 name: Button 4
icon: mdi:gesture-tap-box <<: *binary_sensor_button_base
internal: true
platform: template - id: bs_button_1
on_click: name: Button 1
then: internal: false
- script.execute: <<: *binary_sensor_button_base
id: button_action
component: bs_button_4 globals:
event: click - id: button_press_button
on_double_click: type: uint8_t
then: restore_value: false
- script.execute: initial_value: '0'
id: button_action
component: bs_button_4 - id: button_press_position
event: double_click type: uint8_t
on_multi_click: restore_value: false
- timing: *long_click-timing initial_value: '0'
invalid_cooldown: ${invalid_cooldown}
then: - id: button_press_start_time
- script.execute: type: uint32_t
id: button_action restore_value: false
component: bs_button_4 initial_value: '0'
event: long_click
- id: click_counter
type: uint8_t
restore_value: false
initial_value: '0'
script: script:
- id: !extend boot_initialize - id: !extend boot_initialize
@@ -162,9 +104,31 @@ script:
parameters: parameters:
component: string component: string
event: string event: string
then: # There's nothing here so far then:
# Extended by: # Extended by:
# - core_api # - 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 - id: buttons_release
mode: restart mode: restart
@@ -187,42 +151,78 @@ script:
then: then:
- script.execute: - script.execute:
id: touch_on_press_buttons id: touch_on_press_buttons
touch_x: !lambda return touch_x; touch_position: !lambda return touch_position;
- id: touch_on_press_buttons - id: touch_on_press_buttons
mode: restart mode: restart
parameters: parameters:
touch_x: uint8_t touch_position: uint8_t
then: then:
- lambda: |- - 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(); auto model_index = sl_tx_model_gang->active_index();
if (model_index.has_value()) { if (model_index.has_value()) {
uint8_t model_idx = model_index.value() + 1; const uint8_t model_idx = model_index.value() + 1; // Increment for 1-based indexing
switch (model_idx) { if (model_idx == 1) {
case 1: // 1 Gang 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); bs_button_1->publish_state(true);
break; break;
case 2: // 2 Gang case 2:
if (touch_x <= 5) bs_button_1->publish_state(true); bs_button_2->publish_state(true);
else bs_button_2->publish_state(true);
break; break;
case 3: // 3 Gang case 3:
if (touch_x <= 3) bs_button_1->publish_state(true); bs_button_3->publish_state(true);
else if (touch_x <= 7) bs_button_2->publish_state(true);
else bs_button_3->publish_state(true);
break; break;
case 4: // 4 Gang case 4:
if (touch_x <= 2) bs_button_1->publish_state(true); bs_button_4->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; 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 - id: !extend touch_on_release
then: 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 - id: !extend touch_swipe_left
then: then:

View File

@@ -11,6 +11,9 @@
##### - For normal system use, modifications to this file are NOT required. ##### ##### - 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: binary_sensor:
- id: bs_multi_touch - id: bs_multi_touch
name: Multi-touch name: Multi-touch
@@ -61,7 +64,7 @@ external_components:
- source: - source:
type: git type: git
url: https://github.com/edwardtfn/TX-Ultimate-Easy url: https://github.com/edwardtfn/TX-Ultimate-Easy
ref: ${version} ref: v${version}
refresh: 1h refresh: 1h
components: components:
- tx_ultimate_easy - tx_ultimate_easy
@@ -124,7 +127,7 @@ script:
- id: touch_on_press - id: touch_on_press
mode: restart mode: restart
parameters: parameters:
touch_x: uint8_t touch_position: uint8_t
then: then:
# Extended by: # Extended by:
# - HW Buttons # - HW Buttons
@@ -203,16 +206,28 @@ tx_ultimate_easy:
id: tx_ultimate id: tx_ultimate
uart: uart_touch uart: uart_touch
on_long_touch_release:
- lambda: |-
const uint8_t touch_position = static_cast<uint8_t>(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: on_multi_touch_release:
- lambda: ESP_LOGI("tx_ultimate_easy", "Multi-touch released"); - lambda: ESP_LOGI("tx_ultimate_easy", "Multi-touch released");
- script.execute: touch_on_multi_touch_release - script.execute: touch_on_multi_touch_release
on_press: on_press:
- lambda: |- - lambda: |-
ESP_LOGI("tx_ultimate_easy", "Pressed at position %" PRIu8, static_cast<uint8_t>(touch.x)); const uint8_t touch_position = static_cast<uint8_t>(touch.x);
- script.execute: if (touch_position > ${TOUCH_POSITION_MAX_VALUE}) { // Check for valid range
id: touch_on_press ESP_LOGE("tx_ultimate_easy", "Invalid touch position: %" PRIu8, touch_position);
touch_x: !lambda return static_cast<uint8_t>(touch.x); } else {
ESP_LOGI("tx_ultimate_easy", "Pressed at position %" PRIu8, touch_position);
touch_on_press->execute(touch_position);
}
on_release: on_release:
- lambda: ESP_LOGI("tx_ultimate_easy", "Released"); - lambda: ESP_LOGI("tx_ultimate_easy", "Released");
@@ -233,7 +248,7 @@ tx_ultimate_easy:
ESP_LOGD("tx_ultimate_easy", " Position: %i", touch.x); ESP_LOGD("tx_ultimate_easy", " Position: %i", touch.x);
uart: uart:
id: uart_touch - id: uart_touch
tx_pin: GPIO19 tx_pin: GPIO19
rx_pin: GPIO22 rx_pin: GPIO22
baud_rate: 115200 baud_rate: 115200

View File

@@ -1,19 +1,20 @@
# TX Ultimate Easy # TX Ultimate Easy
<!-- markdownlint-disable MD033 --> [![Version][version-shield]](https://github.com/edwardtfn/TX-Ultimate-Easy/tags)
<a href="https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main" target="_blank">![GitHub Activity][commits-shield]</a> [![GitHub Activity][commits-shield]](https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main)
<a href="LICENSE" target="_blank">![License][license-shield]</a> [![License][license-shield]](LICENSE)
<a href="https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main" target="_blank">![GitHub Last Commit][last-commit-shield]</a> [![GitHub Last Commit][last-commit-shield]](https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main)
<a href="https://esphome.io/" target="_blank">![ESPHome][esphome-shield]</a> [![ESPHome][esphome-shield]](https://esphome.io/)
<a href="https://discord.gg/Db6WJWzWuf" target="_blank">![Discord][discord-shield]</a> [![Discord][discord-shield]](https://discord.gg/Db6WJWzWuf)
<a href="https://www.buymeacoffee.com/edwardfirmo" target="_blank">![Buy me an ice-cream][buymeacoffee-shield]</a> [![Buy me an ice-cream][buymeacoffee-shield]](https://www.buymeacoffee.com/edwardfirmo)
<!-- markdownlint-enable MD033 -->
<!-- markdownlint-disable MD013 --> <!-- markdownlint-disable MD013 -->
| &nbsp;![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. | | &nbsp;![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. |
| --- | :-- | | --- | :-- |
<!-- markdownlint-enable MD013 --> <!-- markdownlint-enable MD013 -->
[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-shield]: https://img.shields.io/github/commit-activity/y/edwardtfn/TX-Ultimate-Easy
[commits]: https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main [commits]: https://github.com/edwardtfn/TX-Ultimate-Easy/commits/main
[license-shield]: https://img.shields.io/github/license/edwardtfn/TX-Ultimate-Easy [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 - Use device triggers and states in your Home Assistant automations and scripts
- Configure device behavior through Home Assistant's service calls - 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 ## 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 4. Submit a pull request targeting the `main` branch
Please ensure your code follows our standards: Please ensure your code follows our standards:
- Passes all lint checks (YAML, C++, Markdown) - Passes all lint checks (YAML, C++, Markdown)
- Includes appropriate documentation - Includes appropriate documentation
- Follows existing code style - 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 - **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 - **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) [![Buy Me a Coffee](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://www.buymeacoffee.com/edwardfirmo)

View File

@@ -16,7 +16,7 @@ wifi:
packages: packages:
remote_package: remote_package:
url: https://github.com/edwardtfn/TX-Ultimate-Easy 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 refresh: 30s
files: files:
- ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml - ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml

View File

@@ -13,6 +13,7 @@ DEPENDENCIES = ['uart']
CONF_TX_ULTIMATE_EASY = "tx_ultimate_easy" CONF_TX_ULTIMATE_EASY = "tx_ultimate_easy"
CONF_UART = "uart" CONF_UART = "uart"
CONF_GANG_COUNT = "gang_count"
CONF_ON_TOUCH_EVENT = "on_touch_event" CONF_ON_TOUCH_EVENT = "on_touch_event"
CONF_ON_PRESS = "on_press" CONF_ON_PRESS = "on_press"
@@ -33,6 +34,7 @@ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(TxUltimateTouch), cv.GenerateID(): cv.declare_id(TxUltimateTouch),
cv.Required(CONF_UART): cv.use_id(uart), 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_TOUCH_EVENT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_PRESS): 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]) uart_component = await cg.get_variable(config[CONF_UART])
cg.add(var.set_uart_component(uart_component)) 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: if CONF_ON_TOUCH_EVENT in config:
await automation.build_automation( await automation.build_automation(
var.get_trigger_touch_event(), var.get_trigger_touch_event(),

View File

@@ -48,6 +48,35 @@ namespace esphome {
void TxUltimateEasy::dump_config() { void TxUltimateEasy::dump_config() {
ESP_LOGCONFIG(TAG, "TX Ultimate Easy"); 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<uint8_t>((position / width) + 1), // Convert position to button index
this->gang_count_ // Clamp to max gang count
);
return button;
} }
void TxUltimateEasy::send_touch_(TouchPoint tp) { void TxUltimateEasy::send_touch_(TouchPoint tp) {
@@ -103,6 +132,7 @@ namespace esphome {
state == TOUCH_STATE_SWIPE_LEFT || state == TOUCH_STATE_SWIPE_LEFT ||
state == TOUCH_STATE_SWIPE_RIGHT || state == TOUCH_STATE_SWIPE_RIGHT ||
state == TOUCH_STATE_MULTI_TOUCH) && 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); (uart_received_bytes[6] >= 0 || state == TOUCH_STATE_MULTI_TOUCH);
} }
@@ -133,6 +163,8 @@ namespace esphome {
TouchPoint TxUltimateEasy::get_touch_point(const std::array<int, UART_RECEIVED_BYTES_SIZE> &uart_received_bytes) { TouchPoint TxUltimateEasy::get_touch_point(const std::array<int, UART_RECEIVED_BYTES_SIZE> &uart_received_bytes) {
TouchPoint tp; TouchPoint tp;
tp.x = this->get_touch_position_x(uart_received_bytes); tp.x = this->get_touch_position_x(uart_received_bytes);
if (tp.x >= 0)
tp.button = this->get_button_from_position(static_cast<uint8_t>(tp.x));
tp.state = this->get_touch_state(uart_received_bytes); tp.state = this->get_touch_state(uart_received_bytes);
switch (tp.state) { switch (tp.state) {
case TOUCH_STATE_RELEASE: case TOUCH_STATE_RELEASE:

View File

@@ -13,6 +13,9 @@
namespace esphome { namespace esphome {
namespace tx_ultimate_easy { namespace tx_ultimate_easy {
// Touch Max Position
constexpr uint8_t TOUCH_MAX_POSITION = 10;
// Touch State Constants // Touch State Constants
constexpr uint8_t TOUCH_STATE_RELEASE = 0x01; constexpr uint8_t TOUCH_STATE_RELEASE = 0x01;
constexpr uint8_t TOUCH_STATE_PRESS = 0x02; constexpr uint8_t TOUCH_STATE_PRESS = 0x02;
@@ -32,6 +35,7 @@ namespace esphome {
static const char *TAG = "tx_ultimate_easy"; static const char *TAG = "tx_ultimate_easy";
struct TouchPoint { struct TouchPoint {
uint8_t button = 0;
int8_t x = -1; int8_t x = -1;
int8_t state = -1; int8_t state = -1;
std::string state_str = "Unknown"; std::string state_str = "Unknown";
@@ -53,6 +57,10 @@ namespace esphome {
void loop() override; void loop() override;
void dump_config() 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: protected:
void send_touch_(TouchPoint tp); void send_touch_(TouchPoint tp);
void handle_touch(const std::array<int, UART_RECEIVED_BYTES_SIZE> &bytes); void handle_touch(const std::array<int, UART_RECEIVED_BYTES_SIZE> &bytes);
@@ -70,6 +78,8 @@ namespace esphome {
Trigger<TouchPoint> trigger_multi_touch_release_; Trigger<TouchPoint> trigger_multi_touch_release_;
Trigger<TouchPoint> trigger_long_touch_release_; Trigger<TouchPoint> trigger_long_touch_release_;
uint8_t gang_count_ = 1;
}; // class TxUltimateEasy }; // class TxUltimateEasy
} // namespace tx_ultimate_easy } // namespace tx_ultimate_easy

View File

@@ -1 +1 @@
2024.12.3 2024.12.7

View File

@@ -1 +1 @@
version: 2024.12.3 version: 2024.12.7