Compare commits

...

122 Commits

Author SHA1 Message Date
76cecb268a ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml gelöscht
Some checks failed
Validate YAML (secondary files) / Validate YAML (push) Has been cancelled
Version and Tags / version-and-tag (push) Has been cancelled
Validate ESPHome / Code scan (YAML) (push) Has been cancelled
Validate ESPHome / 🏗️ Arduino Core (latest) (push) Has been cancelled
Validate ESPHome / 🏗️ Arduino Core (dev) (push) Has been cancelled
Validate ESPHome / 📦 Arduino Basic (latest) (push) Has been cancelled
Validate ESPHome / 📦 Arduino Basic (dev) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Relays (latest) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Speaker (latest) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Vibration (latest) (push) Has been cancelled
Validate ESPHome / ⚡ Arduino BLE Proxy (latest) (push) Has been cancelled
Validate ESPHome / ⚡ Arduino BLE Proxy (dev) (push) Has been cancelled
Validate ESPHome / 🏗️ IDF Core (latest) (push) Has been cancelled
Validate ESPHome / 🏗️ IDF Core (dev) (push) Has been cancelled
Validate ESPHome / 📦 IDF Basic (latest) (push) Has been cancelled
Validate ESPHome / 📦 IDF Basic (dev) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Relays (latest) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Speaker (latest) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Vibration (latest) (push) Has been cancelled
Validate ESPHome / ⚡ IDF BLE Proxy (latest) (push) Has been cancelled
Validate ESPHome / ⚡ IDF BLE Proxy (dev) (push) Has been cancelled
Validate ESPHome / 📊 Arduino Framework (push) Has been cancelled
Validate ESPHome / 📊 ESP-IDF Framework (push) Has been cancelled
Validate ESPHome / 📊 ESPHome (Latest) (push) Has been cancelled
Validate ESPHome / 📊 ESPHome (Dev) (push) Has been cancelled
2025-10-16 19:15:05 +00:00
69f3985d59 ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml aktualisiert
Some checks failed
Validate ESPHome / Code scan (YAML) (push) Has been cancelled
Validate ESPHome / 🏗️ Arduino Core (latest) (push) Has been cancelled
Validate ESPHome / 🏗️ Arduino Core (dev) (push) Has been cancelled
Validate ESPHome / 📦 Arduino Basic (latest) (push) Has been cancelled
Validate ESPHome / 📦 Arduino Basic (dev) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Relays (latest) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Speaker (latest) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Vibration (latest) (push) Has been cancelled
Validate YAML (secondary files) / Validate YAML (push) Has been cancelled
Version and Tags / version-and-tag (push) Has been cancelled
Validate ESPHome / ⚡ Arduino BLE Proxy (latest) (push) Has been cancelled
Validate ESPHome / ⚡ Arduino BLE Proxy (dev) (push) Has been cancelled
Validate ESPHome / 🏗️ IDF Core (latest) (push) Has been cancelled
Validate ESPHome / 🏗️ IDF Core (dev) (push) Has been cancelled
Validate ESPHome / 📦 IDF Basic (latest) (push) Has been cancelled
Validate ESPHome / 📦 IDF Basic (dev) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Relays (latest) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Speaker (latest) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Vibration (latest) (push) Has been cancelled
Validate ESPHome / ⚡ IDF BLE Proxy (latest) (push) Has been cancelled
Validate ESPHome / ⚡ IDF BLE Proxy (dev) (push) Has been cancelled
Validate ESPHome / 📊 Arduino Framework (push) Has been cancelled
Validate ESPHome / 📊 ESP-IDF Framework (push) Has been cancelled
Validate ESPHome / 📊 ESPHome (Latest) (push) Has been cancelled
Validate ESPHome / 📊 ESPHome (Dev) (push) Has been cancelled
2025-10-16 19:14:48 +00:00
1a73150368 ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml aktualisiert
Some checks failed
Validate ESPHome / Code scan (YAML) (push) Has been cancelled
Validate ESPHome / 🏗️ Arduino Core (latest) (push) Has been cancelled
Validate ESPHome / 🏗️ Arduino Core (dev) (push) Has been cancelled
Validate ESPHome / 📦 Arduino Basic (latest) (push) Has been cancelled
Validate ESPHome / 📦 Arduino Basic (dev) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Relays (latest) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Speaker (latest) (push) Has been cancelled
Validate ESPHome / 🔌 Arduino HW Vibration (latest) (push) Has been cancelled
Validate ESPHome / ⚡ Arduino BLE Proxy (latest) (push) Has been cancelled
Validate ESPHome / ⚡ Arduino BLE Proxy (dev) (push) Has been cancelled
Validate ESPHome / 🏗️ IDF Core (latest) (push) Has been cancelled
Validate ESPHome / 🏗️ IDF Core (dev) (push) Has been cancelled
Validate ESPHome / 📦 IDF Basic (latest) (push) Has been cancelled
Validate ESPHome / 📦 IDF Basic (dev) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Relays (latest) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Speaker (latest) (push) Has been cancelled
Validate ESPHome / 🔌 IDF HW Vibration (latest) (push) Has been cancelled
Validate ESPHome / ⚡ IDF BLE Proxy (latest) (push) Has been cancelled
Validate ESPHome / ⚡ IDF BLE Proxy (dev) (push) Has been cancelled
Validate ESPHome / 📊 Arduino Framework (push) Has been cancelled
Validate ESPHome / 📊 ESP-IDF Framework (push) Has been cancelled
Validate ESPHome / 📊 ESPHome (Latest) (push) Has been cancelled
Validate ESPHome / 📊 ESPHome (Dev) (push) Has been cancelled
Validate YAML (secondary files) / Validate YAML (push) Has been cancelled
Version and Tags / version-and-tag (push) Has been cancelled
2025-10-16 18:12:20 +00:00
GitHub Actions
cd39fee788 Bump version to 2025.10.1 [skip-versioning] 2025-10-03 06:07:29 +00:00
Edward Firmo
d9f361b404 Merge pull request #120 from edwardtfn/nvs-optimization
Resolve configuration loss by optimizing storage usage
2025-10-03 08:07:18 +02:00
Edward Firmo
6ffa73b321 Update .github/workflows/validate_esphome.yml
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-10-03 07:40:20 +02:00
Edward Firmo
e2b4fa4186 Do not turn light on when restoring state 2025-09-30 09:44:06 +02:00
Edward Firmo
63c98fcc68 Remove reference to PR #10195 2025-09-18 19:48:32 +02:00
Edward Firmo
8943dc7ea9 Req v2025.8.0 2025-09-18 06:20:14 +02:00
Edward Firmo
15c7ddc4b4 Fix dev job gating for IDF core (dev) 2025-08-12 00:08:53 +02:00
Edward Firmo
22a99017fa Update .github/workflows/validate_esphome.yml
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-12 00:04:21 +02:00
Edward Firmo
67d1c2cdce Call to restore_from_nvs 2025-08-12 00:00:33 +02:00
Edward Firmo
65d08194aa nvs optimization for hw leds 2025-08-11 23:55:32 +02:00
Edward Firmo
0690ae14d8 Test core under dev 2025-08-10 23:42:22 +02:00
Edward Firmo
0b430a7ce0 Lint 2025-08-10 23:36:08 +02:00
Edward Firmo
03e0004e8c Update lights when mode changes 2025-08-10 23:29:25 +02:00
Edward Firmo
6d2de0d32b Guard against invalid gang_count when restoring index 2025-08-10 23:12:15 +02:00
Edward Firmo
8af692e82d Introduces bs_boot_completed to know the boot status
To know when the boot was completed
2025-08-10 22:59:29 +02:00
Edward Firmo
fc135875c5 Use new switch.on_state 2025-08-09 14:46:14 +02:00
Edward Firmo
aae3eaa204 Validate group_index and add symmetric logging in restore_dual 2025-08-08 14:54:56 +02:00
Edward Firmo
fe745c0132 Update TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml 2025-08-08 14:52:40 +02:00
Edward Firmo
5757714b31 Guard against invalid group_index 2025-08-08 14:05:12 +02:00
Edward Firmo
6520b8050e Fixes "Dereference without null-check may crash on bad caller" 2025-08-08 14:02:40 +02:00
Edward Firmo
a10edb3d06 Fix "Hex literal exceeds 64-bit capacity" 2025-08-08 14:00:43 +02:00
Edward Firmo
1df58e162f Set min_version to ESPHome 2025.8.0 2025-08-08 07:37:52 +02:00
Edward Firmo
d905b69ad2 Requires ESPHome v2025.8.0 2025-08-06 23:49:27 +02:00
Edward Firmo
462b131cb6 Fixes "Wrong selector referenced – US light-mode writes EU value" 2025-08-06 23:08:00 +02:00
Edward Firmo
cf8590b460 Use switch.control from pr#10105 (esphome) 2025-08-06 23:00:41 +02:00
Edward Firmo
eca54c01ed Move "Relay n light mode" to standard_hw_relays 2025-08-06 19:47:27 +02:00
Edward Firmo
0d0de08e19 Ensure brightness is limited to 100
As suggested by coderabbitai.
2025-08-06 19:21:32 +02:00
Edward Firmo
3557c847b2 Remove TAG from .h 2025-08-06 19:12:43 +02:00
Edward Firmo
dbbbc4040d Move tag to .cpp 2025-08-06 19:06:59 +02:00
Edward Firmo
1bb444e008 Fix component tag 2025-08-06 16:45:57 +02:00
Edward Firmo
00d70efc3b Remove duplicated files 2025-08-06 16:44:31 +02:00
Edward Firmo
3c53b21fbe Lint 2025-08-06 16:34:52 +02:00
Edward Firmo
0fb4b5fe09 Fix typo 2025-08-06 16:32:13 +02:00
Edward Firmo
4634b650d9 Optimize nvs even more with dual light attributes saving 2025-08-06 16:30:40 +02:00
Edward Firmo
6a1a5d5ae2 Extend nvs optimization to relay's lights 2025-08-06 14:27:35 +02:00
Edward Firmo
271aa74f99 Do not update lights when the action is disabled
Solves #109
2025-08-06 13:43:36 +02:00
Edward Firmo
ce54db0f6d Set version for test 2025-08-05 21:36:48 +02:00
Edward Firmo
93120a8b51 Fix log reference 2025-08-05 21:27:02 +02:00
Edward Firmo
91f24db3b1 Clarify touch support
Solves #98
2025-08-05 21:15:35 +02:00
Edward Firmo
fa2e8f3b62 Update dependencies 2025-08-05 20:01:32 +02:00
Edward Firmo
c9a85504f1 Initial nvs optimization for relay lights 2025-08-05 19:46:12 +02:00
Edward Firmo
33bbad6adb Lint 2025-08-05 18:07:39 +02:00
Edward Firmo
bda597f748 nvs optimization - relays (except lights) 2025-08-05 17:51:18 +02:00
Edward Firmo
1552aaec6c Lint 2025-08-05 11:39:41 +02:00
Edward Firmo
250b22562d Simplifies tags
It should be limited to 15ish chars
2025-08-05 11:36:09 +02:00
Edward Firmo
243a18bdf2 Fix 4-bit mask 2025-08-05 11:08:16 +02:00
Edward Firmo
07de172188 Claude's changes for CodeRabbit suggestion 2025-08-05 10:57:08 +02:00
Edward Firmo
c1f1b5a7e7 Standardize log tag - hw leds 2025-08-05 09:01:36 +02:00
Edward Firmo
1d8b4fee1a Standardize log tag 2025-08-05 08:54:45 +02:00
Edward Firmo
6d16d7d645 restore_from_nvs for core_hw_buttons 2025-08-05 08:11:50 +02:00
Edward Firmo
42ae160217 Introduce restore_from_nvs on core.common 2025-08-04 23:50:06 +02:00
Edward Firmo
ecfc64d2f3 add PR information to tag descriptions in versioning workflow
Enhance the versioning workflow to include PR title and description in tag messages.

Changes:
- Add PR information extraction step using GitHub API
- Format tag messages with version, PR title, and description
- Apply structured messages to stable, latest, and version tags
- Handle fallback cases for manual dispatches and API errors
- Use temporary file approach for reliable multiline message handling

Tag descriptions now follow the format:
`# v{version} - {PR title}`
This improves release traceability by providing clear context
for what changes are included in each tagged version.
2025-08-04 21:29:29 +02:00
GitHub Actions
db83050d7b Bump version to 2025.8.2 [skip-versioning] 2025-08-02 22:10:08 +00:00
Edward Firmo
d7dcc05506 Merge pull request #119 from edwardtfn/stable_tag_update
Return last position on swipe
2025-08-03 00:09:59 +02:00
Edward Firmo
b6dc915469 use main also for advanced 2025-08-02 23:55:52 +02:00
Edward Firmo
dcc68f7077 Revert defaults 2025-08-02 23:53:13 +02:00
Edward Firmo
23d28a32a3 Improved readability 2025-08-02 23:50:42 +02:00
Edward Firmo
ba8f19060f Declare variable used in the loop 2025-08-02 23:40:01 +02:00
Edward Firmo
c36af8a220 Return last position on swipe
As per #110
2025-08-02 23:26:49 +02:00
Edward Firmo
6fad9d7074 Update stable tag automatically
To make it easier to release.
I will point users to `main` instead.
2025-08-02 22:58:11 +02:00
GitHub Actions
f79ed28364 Bump version to 2025.8.1 [skip-versioning] 2025-08-01 08:22:09 +00:00
Edward Firmo
11807ce892 Merge pull request #112 from edwardtfn/idf-as-default
Set ESP-IDF as default framework
2025-08-01 10:22:00 +02:00
Edward Firmo
526b257ad6 Use github.event.inputs.skip_dev 2025-08-01 09:28:42 +02:00
Edward Firmo
0a95a7f8d0 Fix typo on std::string 2025-08-01 09:10:58 +02:00
Edward Firmo
6687c5c6a5 Fix other references to gang_count_plural_suffix 2025-08-01 09:01:37 +02:00
Edward Firmo
ad92799035 Remove gang_count_plural_suffix and set audio channel to mono 2025-08-01 08:51:33 +02:00
Edward Firmo
8e4bbda412 Fix totals 2025-07-31 13:29:34 +02:00
Edward Firmo
8eaa60b5c0 Update totals 2025-07-31 13:14:11 +02:00
Edward Firmo
334932c6c8 Completely remove media player specific tests 2025-07-31 11:24:33 +02:00
Edward Firmo
0480f2ddf3 Media player is already part of standard package 2025-07-31 11:22:29 +02:00
Edward Firmo
720cf3ab36 Ensure tests call the right media player code 2025-07-31 11:18:40 +02:00
Edward Firmo
9edd43079b Use globals for persistence 2025-07-31 10:58:22 +02:00
Edward Firmo
a67f3dfd4a Removed initial value for volume control 2025-07-31 10:34:03 +02:00
Edward Firmo
796eb7620c Remember Media Player volume 2025-07-31 10:26:37 +02:00
Edward Firmo
e53334ac18 Clarify support to bluetooth_proxy 2025-07-31 07:21:48 +02:00
GitHub Actions
7d2080cd8d Bump version to 2025.7.3 [skip-versioning] 2025-07-31 03:20:55 +00:00
Edward Firmo
efd44fab02 Merge pull request #115 from MorghusDragon/correctly-looping-rainbow-effect
Fix rainbow effect loop by setting correct width per model.
2025-07-31 05:20:44 +02:00
Edward Firmo
8422168837 Remove effects from smaller segments 2025-07-30 19:34:56 +02:00
Edward Firmo
0b7f83ebca Add anchor per model 2025-07-30 19:15:39 +02:00
Edward Firmo
5aecb343f6 Return full audio settings 2025-07-30 19:00:54 +02:00
Edward Firmo
a4340da836 Lint 2025-07-30 07:31:17 +02:00
Edward Firmo
912607d1ec Refine speaker settings for esp-idf
Solves #95
2025-07-30 07:28:05 +02:00
Edward Firmo
ba041a9d50 Set audio channel to mono 2025-07-29 21:19:21 +02:00
Edward Firmo
b99a6b8c54 Fail-fast - Test basic before hardware 2025-07-29 16:06:11 +02:00
Edward Firmo
29b292bf45 Fix dependencies 2025-07-29 16:00:40 +02:00
Edward Firmo
4c0a83851e Test basic on dev 2025-07-29 15:54:34 +02:00
Edward Firmo
9663f295d5 Lint 2025-07-29 15:44:22 +02:00
Edward Firmo
4707506c74 Fix grouping 2025-07-29 15:40:49 +02:00
Edward Firmo
5b77f44ae2 Lint 2025-07-29 15:33:54 +02:00
Edward Firmo
efd4eaa6bb Detailed summary 2025-07-29 15:26:04 +02:00
Edward Firmo
bc0b775df2 A bit more reorg 2025-07-29 15:21:48 +02:00
Edward Firmo
ddfd4b3220 Add environment names 2025-07-29 15:12:35 +02:00
Edward Firmo
54ac959abb Org test back on platform-centric 2025-07-29 15:07:06 +02:00
Edward Firmo
9fdfd52ad4 Resolve conflict with PR#116 2025-07-29 14:58:48 +02:00
Edward Firmo
e326f03502 Merge branch 'main' into idf-as-default 2025-07-29 14:55:53 +02:00
Edward Firmo
bd86371453 Lint 2025-07-29 14:48:06 +02:00
Edward Firmo
54c9119a63 Test basic first 2025-07-29 14:45:52 +02:00
Morghus
1190802ccd Extract base light effect configuration so it can be customized per model. 2025-07-29 20:51:32 +12:00
Edward Firmo
2cc8199593 No limits on the core tests 2025-07-29 10:46:41 +02:00
Edward Firmo
669a78a427 Reorg tests 2025-07-29 10:39:06 +02:00
Edward Firmo
78d820d91e Remove BLE proxy addon 2025-07-29 10:35:11 +02:00
Edward Firmo
a8277e5b88 Fail-fast tests 2025-07-29 10:33:56 +02:00
Edward Firmo
458273206d Test idf first 2025-07-29 10:26:03 +02:00
Edward Firmo
982c64c1c1 Use latest (main) for all actions 2025-07-29 10:22:11 +02:00
Edward Firmo
66d1b27c0d Run tests on every push 2025-07-29 10:15:41 +02:00
Edward Firmo
f9da912777 Test Media player under IDF and unify ESPHome tests 2025-07-29 10:13:18 +02:00
Edward Firmo
6d663323e5 Simplify tests in a matrix
To make it easier to maintain
2025-07-29 10:06:46 +02:00
Edward Firmo
122cfff8db Fix compiler key name 2025-07-29 09:50:19 +02:00
Edward Firmo
70a054616c Remove old code 2025-07-29 09:39:49 +02:00
Edward Firmo
5957e277b7 Remove mapping on !lambda calls 2025-07-29 09:31:35 +02:00
Edward Firmo
7e070617ae Rebuild homeassistant.event calls
To support ESPHome 2025.8
2025-07-29 09:28:57 +02:00
Edward Firmo
50a11f61ff Removed allow_other_uses for pin 26 2025-07-29 09:20:05 +02:00
Edward Firmo
97b193d7c3 Add notice about migration to ESP-IDF 2025-07-29 09:15:49 +02:00
Edward Firmo
644ba8e711 Add test against ESPHome Dev 2025-07-29 09:05:29 +02:00
Edward Firmo
36b28a2475 Make media player dependent on speaker 2025-07-29 09:03:45 +02:00
Edward Firmo
1bd51f304d Deprecate BLE proxy addon package
This kind of solves #9

Replace TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml with compilation
error to force migration to direct bluetooth_proxy configuration.

BREAKING CHANGE: Configurations including this package will fail to
compile. Users must remove the package reference and add bluetooth_proxy
directly to their YAML.

Before:
  packages:
    addon_bluetooth_proxy: !include TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml

After:
  bluetooth_proxy:
2025-07-29 09:00:38 +02:00
Edward Firmo
680042a428 Replaces i2s_audio.media_player by i2s_audio.speaker.media_player 2025-07-29 08:44:46 +02:00
Edward Firmo
0a3ceb97f5 No special settings for IDF 2025-07-14 13:54:07 +02:00
Edward Firmo
daaffdb6ca Set ESP-IDF as default framework 2025-07-14 13:51:21 +02:00
28 changed files with 2527 additions and 953 deletions

View File

@@ -1,12 +1,36 @@
---
name: Validate ESPHome (Latest)
name: Validate ESPHome
permissions:
contents: read
pull-requests: read
on: # yamllint disable-line rule:truthy
push:
paths:
- "**/*.yml"
- "**/*.yaml"
- "**/*.h"
- "**/*.cpp"
- "**/*.c"
- "**/*.py"
pull_request:
paths:
- "**/*.yml"
- "**/*.yaml"
- "**/*.h"
- "**/*.cpp"
- "**/*.c"
- "**/*.py"
workflow_dispatch:
inputs:
skip_dev:
description: 'Skip dev testing (test only on latest)'
required: false
default: false
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -15,178 +39,416 @@ concurrency:
jobs:
code_scan:
name: Code scan (YAML)
runs-on: "ubuntu-latest"
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@main
- name: Setup Python
uses: actions/setup-python@main
with:
python-version: '3.8'
python-version: '3.11'
- name: Install Yamllint
run: pip install yamllint
- name: Validate YAML files
run: find . -name "*.yaml" -exec yamllint -c ./.rules/yamllint.yml {} +
# Arduino Framework - Foundation (latest)
build_ard_core:
name: Core (Arduino)
name: "🏗️ Arduino Core (latest)"
needs: code_scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Core (Arduino)
- name: Build Arduino Core - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_core.yaml"
version: latest
build_idf_core:
name: Core (IDF)
build_ard_core_dev:
name: "🏗️ Arduino Core (dev)"
needs: code_scan
if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Core (IDF)
- name: Build Arduino Core - dev
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_core.yaml"
version: latest
yaml-file: ".test/esphome_ard_core.yaml"
version: dev
build_ard_basic:
name: Basic (Arduino)
name: "📦 Arduino Basic (latest)"
needs: build_ard_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Basic (Arduino)
- name: Build Arduino Basic - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_basic.yaml"
version: latest
build_idf_basic:
name: Basic (IDF)
needs: build_idf_core
build_ard_basic_dev:
name: "📦 Arduino Basic (dev)"
needs: build_ard_core_dev
if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Basic (IDF)
- name: Build Arduino Basic - dev
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_basic.yaml"
version: latest
yaml-file: ".test/esphome_ard_basic.yaml"
version: dev
build_ard_hw_relays:
name: HW Relays (Arduino)
needs: build_ard_core
name: "🔌 Arduino HW Relays (latest)"
needs: build_ard_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Relays (Arduino)
- name: Build Arduino HW Relays - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_hw_relays.yaml"
version: latest
build_idf_hw_relays:
name: HW Relays (IDF)
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Relays (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_relays.yaml"
version: latest
build_ard_hw_speaker:
name: HW Speaker (Arduino)
needs: build_ard_core
name: "🔌 Arduino HW Speaker (latest)"
needs: build_ard_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Speaker (Arduino)
- name: Build Arduino HW Speaker - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_hw_speaker.yaml"
version: latest
build_idf_hw_speaker:
name: HW Speaker (IDF)
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Speaker (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_speaker.yaml"
version: latest
build_ard_hw_vibration:
name: HW Vibration (Arduino)
needs: build_ard_core
name: "🔌 Arduino HW Vibration (latest)"
needs: build_ard_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Vibration (Arduino)
- name: Build Arduino HW Vibration - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_hw_vibration.yaml"
version: latest
build_idf_hw_vibration:
name: HW Vibration (IDF)
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Vibration (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_vibration.yaml"
version: latest
build_ard_ble_proxy:
name: Bluetooth Proxy (Ard)
build_ard_ble_proxy_latest:
name: "⚡ Arduino BLE Proxy (latest)"
needs: build_ard_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build BLE Proxy (Ard)
- name: Build Arduino BLE Proxy - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_ble_proxy.yaml"
version: latest
build_idf_ble_proxy:
name: Bluetooth Proxy (IDF)
build_ard_ble_proxy_dev:
name: "⚡ Arduino BLE Proxy (dev)"
needs: build_ard_basic_dev
if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Arduino BLE Proxy - dev
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_ble_proxy.yaml"
version: dev
# IDF Framework - Foundation (latest)
build_idf_core:
name: "🏗️ IDF Core (latest)"
needs: code_scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build IDF Core - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_core.yaml"
version: latest
build_idf_core_dev:
name: "🏗️ IDF Core (dev)"
needs: code_scan
if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build IDF Core - dev
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_core.yaml"
version: dev
build_idf_basic:
name: "📦 IDF Basic (latest)"
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build IDF Basic - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_basic.yaml"
version: latest
build_idf_basic_dev:
name: "📦 IDF Basic (dev)"
needs: build_idf_core_dev
if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build IDF Basic - dev
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_basic.yaml"
version: dev
build_idf_hw_relays:
name: "🔌 IDF HW Relays (latest)"
needs: build_idf_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build BLE Proxy (IDF)
- name: Build IDF HW Relays - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_relays.yaml"
version: latest
build_idf_hw_speaker:
name: "🔌 IDF HW Speaker (latest)"
needs: build_idf_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build IDF HW Speaker - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_speaker.yaml"
version: latest
build_idf_hw_vibration:
name: "🔌 IDF HW Vibration (latest)"
needs: build_idf_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build IDF HW Vibration - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_vibration.yaml"
version: latest
build_idf_ble_proxy_latest:
name: "⚡ IDF BLE Proxy (latest)"
needs: build_idf_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build IDF BLE Proxy - latest
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_ble_proxy.yaml"
version: latest
build_ard_media_player:
name: Media Player (Ard)
needs:
- build_ard_basic
- build_ard_hw_relays
- build_ard_hw_vibration
build_idf_ble_proxy_dev:
name: "⚡ IDF BLE Proxy (dev)"
needs: build_idf_basic_dev
if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Media Player (Ard)
- name: Build IDF BLE Proxy - dev
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_media_player.yaml"
version: latest
yaml-file: ".test/esphome_idf_ble_proxy.yaml"
version: dev
# Summary Jobs - Grouped by framework and environment
summary_arduino:
name: "📊 Arduino Framework"
needs:
- build_ard_core
- build_ard_core_dev
- build_ard_basic
- build_ard_basic_dev
- build_ard_hw_relays
- build_ard_hw_speaker
- build_ard_hw_vibration
- build_ard_ble_proxy_latest
- build_ard_ble_proxy_dev
if: always()
runs-on: ubuntu-latest
steps:
- name: Arduino Framework Results
run: |
SUCCESS=0
TOTAL=0
# Count latest builds (always run)
[ "${{ needs.build_ard_core.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_basic.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_hw_relays.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_hw_speaker.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_hw_vibration.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_ble_proxy_latest.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
TOTAL=$((TOTAL + 6))
# Count dev builds (if not skipped)
if [ "${{ github.event.inputs.skip_dev }}" != "true" ]; then
[ "${{ needs.build_ard_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
TOTAL=$((TOTAL + 3))
fi
echo "🤖 Arduino Framework: $SUCCESS/$TOTAL builds passed"
if [ "$SUCCESS" -eq "$TOTAL" ]; then
echo "✅ All Arduino builds successful!"
else
echo "❌ Some Arduino builds failed"
exit 1
fi
summary_idf:
name: "📊 ESP-IDF Framework"
needs:
- build_idf_core
- build_idf_core_dev
- build_idf_basic
- build_idf_basic_dev
- build_idf_hw_relays
- build_idf_hw_speaker
- build_idf_hw_vibration
- build_idf_ble_proxy_latest
- build_idf_ble_proxy_dev
if: always()
runs-on: ubuntu-latest
steps:
- name: ESP-IDF Framework Results
run: |
SUCCESS=0
TOTAL=0
# Count latest builds (always run)
[ "${{ needs.build_idf_core.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_basic.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_hw_relays.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_hw_speaker.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_hw_vibration.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_ble_proxy_latest.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
TOTAL=$((TOTAL + 6))
# Count dev builds (if not skipped)
if [ "${{ github.event.inputs.skip_dev }}" != "true" ]; then
[ "${{ needs.build_idf_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
TOTAL=$((TOTAL + 3))
fi
echo "🔧 ESP-IDF Framework: $SUCCESS/$TOTAL builds passed"
if [ "$SUCCESS" -eq "$TOTAL" ]; then
echo "✅ All ESP-IDF builds successful!"
else
echo "❌ Some ESP-IDF builds failed"
exit 1
fi
summary_latest:
name: "📊 ESPHome (Latest)"
needs:
- build_ard_core
- build_ard_basic
- build_ard_hw_relays
- build_ard_hw_speaker
- build_ard_hw_vibration
- build_ard_ble_proxy_latest
- build_idf_core
- build_idf_basic
- build_idf_hw_relays
- build_idf_hw_speaker
- build_idf_hw_vibration
- build_idf_ble_proxy_latest
if: always()
runs-on: ubuntu-latest
steps:
- name: Latest ESPHome Results
run: |
SUCCESS=0
TOTAL=12
# Arduino Latest builds
[ "${{ needs.build_ard_core.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_basic.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_hw_relays.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_hw_speaker.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_hw_vibration.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_ble_proxy_latest.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
# ESP-IDF Latest builds
[ "${{ needs.build_idf_core.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_basic.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_hw_relays.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_hw_speaker.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_hw_vibration.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_ble_proxy_latest.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
echo "📋 ESPHome (Latest): $SUCCESS/$TOTAL builds passed"
if [ "$SUCCESS" -eq "$TOTAL" ]; then
echo "✅ All builds on latest ESPHome successful!"
else
echo "❌ Some builds on latest ESPHome failed"
exit 1
fi
summary_dev:
name: "📊 ESPHome (Dev)"
needs:
- build_ard_core_dev
- build_ard_basic_dev
- build_ard_ble_proxy_dev
- build_idf_core_dev
- build_idf_basic_dev
- build_idf_ble_proxy_dev
if: ${{ always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest
steps:
- name: Dev ESPHome Results
run: |
SUCCESS=0
TOTAL=6
# Arduino Dev builds
[ "${{ needs.build_ard_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
# ESP-IDF Dev builds
[ "${{ needs.build_idf_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
echo "🚀 ESPHome (Dev): $SUCCESS/$TOTAL builds passed"
if [ "$SUCCESS" -eq "$TOTAL" ]; then
echo "✅ All builds on dev ESPHome successful!"
else
echo "❌ Some builds on dev ESPHome failed"
exit 1
fi
...

View File

@@ -1,210 +0,0 @@
---
name: Validate ESPHome (Beta)
permissions:
contents: read
pull-requests: read
on: # yamllint disable-line rule:truthy
push:
paths:
- "**/*.yml"
- "**/*.yaml"
- "**/*.h"
- "**/*.cpp"
- "**/*.c"
- "**/*.py"
pull_request:
paths:
- "**/*.yml"
- "**/*.yaml"
- "**/*.h"
- "**/*.cpp"
- "**/*.c"
- "**/*.py"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
code_scan:
name: Code scan (YAML)
runs-on: "ubuntu-latest"
steps:
- name: Checkout Code
uses: actions/checkout@main
- name: Setup Python
uses: actions/setup-python@main
with:
python-version: '3.8'
- name: Install Yamllint
run: pip install yamllint
- name: Validate YAML files
run: find . -name "*.yaml" -exec yamllint -c ./.rules/yamllint.yml {} +
build_ard_core:
name: Core (Arduino)
needs: code_scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Core (Arduino)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_core.yaml"
version: beta
build_idf_core:
name: Core (IDF)
needs: code_scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Core (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_core.yaml"
version: beta
build_ard_basic:
name: Basic (Arduino)
needs: build_ard_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Basic (Arduino)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_basic.yaml"
version: beta
build_idf_basic:
name: Basic (IDF)
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Basic (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_basic.yaml"
version: beta
build_ard_hw_relays:
name: HW Relays (Arduino)
needs: build_ard_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Relays (Arduino)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_hw_relays.yaml"
version: beta
build_idf_hw_relays:
name: HW Relays (IDF)
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Relays (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_relays.yaml"
version: beta
build_ard_hw_speaker:
name: HW Speaker (Arduino)
needs: build_ard_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Speaker (Arduino)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_hw_speaker.yaml"
version: beta
build_idf_hw_speaker:
name: HW Speaker (IDF)
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Speaker (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_speaker.yaml"
version: beta
build_ard_hw_vibration:
name: HW Vibration (Arduino)
needs: build_ard_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Vibration (Arduino)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_hw_vibration.yaml"
version: beta
build_idf_hw_vibration:
name: HW Vibration (IDF)
needs: build_idf_core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build HW Vibration (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_hw_vibration.yaml"
version: beta
build_ard_ble_proxy:
name: Bluetooth Proxy (Ard)
needs: build_ard_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build BLE Proxy (Ard)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_ble_proxy.yaml"
version: beta
build_idf_ble_proxy:
name: Bluetooth Proxy (IDF)
needs: build_idf_basic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build BLE Proxy (IDF)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_idf_ble_proxy.yaml"
version: beta
build_ard_media_player:
name: Media Player (Ard)
needs:
- build_ard_basic
- build_ard_hw_relays
- build_ard_hw_vibration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Build Media Player (Ard)
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_media_player.yaml"
version: beta
...

View File

@@ -41,11 +41,84 @@ jobs:
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
- name: Get PR information
id: pr_info
if: github.event_name == 'push'
uses: actions/github-script@v7
with:
script: |
try {
// Use GitHub's reliable API to find PRs associated with this commit
const { data: associatedPRs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha
});
if (associatedPRs.length > 0) {
// Get the most recent merged PR
const pr = associatedPRs.find(pr => pr.state === 'closed' && pr.merged_at) || associatedPRs[0];
return {
title: pr.title,
body: pr.body || 'No description provided',
number: pr.number,
found: true
};
} else {
// Fallback for direct pushes or when no PR is found
const commit = await github.rest.repos.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
});
return {
title: 'Direct push to main',
body: commit.data.commit.message,
number: null,
found: false
};
}
} catch (error) {
console.log('Could not get PR info:', error.message);
return {
title: 'Version update',
body: 'Automated version bump',
number: null,
found: false
};
}
- name: Bump version
run: |
chmod +x ./versioning/bump_version.sh
./versioning/bump_version.sh
- name: Create tag message
id: tag_message
run: |
NEW_VERSION=$(cat ./versioning/VERSION)
if [ "${{ github.event_name }}" == "push" ] && [ "${{ steps.pr_info.outcome }}" == "success" ]; then
# Use PR information directly in heredoc to avoid quoting issues
cat > tag_message.txt << EOF
# v${NEW_VERSION} - ${{ fromJson(steps.pr_info.outputs.result).title }}
${{ fromJson(steps.pr_info.outputs.result).body }}
EOF
else
# Manual dispatch or PR info unavailable - simpler message
cat > tag_message.txt << EOF
# v${NEW_VERSION} - Manual tag update
Tag updated via manual workflow dispatch.
EOF
fi
# Store the message for use in subsequent steps
echo "TAG_MESSAGE_FILE=tag_message.txt" >> $GITHUB_ENV
- name: Push Changes and Tags
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -55,9 +128,10 @@ jobs:
- name: Update stable tag
if: |
success() &&
github.event_name == 'workflow_dispatch' &&
inputs.update_stable
success() && (
github.event_name == 'workflow_dispatch' && inputs.update_stable ||
github.event_name == 'push'
)
run: |
# Verify version bump was successful
if [ ! -f "./versioning/VERSION" ]; then
@@ -71,10 +145,10 @@ jobs:
echo "Backing up current stable tag ($OLD_STABLE)"
fi
# Update tag
# Update tag with detailed message
NEW_VERSION=$(cat ./versioning/VERSION)
echo "Updating stable tag to $NEW_VERSION"
git tag -fa stable -m "Update stable tag"
git tag -fa stable -F "$TAG_MESSAGE_FILE"
git push origin stable --force
- name: Update latest tag
@@ -96,9 +170,15 @@ jobs:
echo "Backing up current latest tag ($OLD_LATEST)"
fi
# Update tag
# Update tag with detailed message
NEW_VERSION=$(cat ./versioning/VERSION)
echo "Updating latest tag to $NEW_VERSION"
git tag -fa latest -m "Update latest tag"
git tag -fa latest -F "$TAG_MESSAGE_FILE"
git push origin latest --force
- name: Cleanup
if: always()
run: |
# Clean up temporary files
[ -f tag_message.txt ] && rm tag_message.txt || true
...

View File

@@ -5,14 +5,4 @@ packages:
esp32:
framework:
type: esp-idf
light:
- id: !extend light_full
platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: ws2812
max_refresh_rate: 33ms
use_psram: false
type: !remove
variant: !remove
...

View File

@@ -1,10 +0,0 @@
---
packages:
common_idf: !include common_idf.yaml
esp32:
framework:
type: esp-idf
version: 5.3.1
platform_version: 6.9.0
...

View File

@@ -3,5 +3,6 @@ packages:
common_arduino: !include common_ard.yaml
core_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml
standard_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml
addon_bluetooth_proxy: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml
bluetooth_proxy:
...

View File

@@ -1,8 +0,0 @@
---
packages:
common_arduino: !include common_ard.yaml
core_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml
standard_hw_relays_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml
standard_hw_vibration_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml
addon_media_player: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_addon_media_player.yaml
...

View File

@@ -3,9 +3,4 @@ packages:
common_idf: !include common_idf.yaml
core_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml
standard_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml
media_player: !remove
switch:
- id: !remove sw_speaker_amplifier
...

View File

@@ -3,5 +3,6 @@ packages:
common: !include common.yaml
core_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml
standard_package: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml
addon_bluetooth_proxy: !include ../ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml
bluetooth_proxy:
...

View File

@@ -1,58 +0,0 @@
####################################################################################################
##### TX Ultimate Easy for ESPHome #####
##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy #####
####################################################################################################
##### Purpose: ESPHome Core - Add-on - BLE Proxy #####
####################################################################################################
##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo #####
####################################################################################################
##### NOTE: #####
##### - Make changes ONLY if absolutely necessary and you have the required knowledge. #####
##### - For normal system use, modifications to this file are NOT required. #####
####################################################################################################
---
packages:
standard_hw_speaker: !include TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml
bluetooth_proxy:
esp32:
framework:
type: esp-idf
esphome:
platformio_options:
build_flags:
- -D TX_ULTIMATE_EASY_ADDON_BLE_PROXY
i2s_audio: !remove
light:
- id: !extend light_full
platform: esp32_rmt_led_strip
rgb_order: GRB
# rmt_channel: 1 # Not supported with IDF 5
chipset: ws2812
max_refresh_rate: 33ms
use_psram: false
type: !remove
variant: !remove
media_player: !remove
script:
- id: !extend dump_config_list_packages
then:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#endif
// Identify itself
ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Add-on - BLE Proxy");
switch:
- id: !remove sw_speaker_amplifier
...

View File

@@ -1,68 +0,0 @@
####################################################################################################
##### TX Ultimate Easy for ESPHome #####
##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy #####
####################################################################################################
##### Purpose: ESPHome - Add-on - Media player #####
####################################################################################################
##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo #####
####################################################################################################
##### NOTE: #####
##### - Make changes ONLY if absolutely necessary and you have the required knowledge. #####
##### - For normal system use, modifications to this file are NOT required. #####
####################################################################################################
---
packages:
standard_hw_audio: !include TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml
esp32:
framework:
type: arduino
esphome:
platformio_options:
build_flags:
- -D TX_ULTIMATE_EASY_ADDON_MEDIA_PLAYER
media_player:
- id: mp_media_player
name: Media Player
internal: false
platform: i2s_audio
i2s_dout_pin: GPIO15
i2s_audio_id: if_i2s_audio
i2s_comm_fmt: msb
dac_type: external
mode: mono
mute_pin:
number: GPIO26
inverted: true
allow_other_uses: true
script:
- id: !extend dump_config_list_packages
then:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#endif
#if !defined(TX_ULTIMATE_EASY_STANDARD_HW_AUDIO)
#error "The package TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml is required."
#endif
// Identify itself
ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Add-on - Media Player");
switch:
- id: sw_speaker_amplifier
name: Speaker - Amplifier
platform: gpio
pin:
number: GPIO26
inverted: true
allow_other_uses: true
restore_mode: RESTORE_DEFAULT_ON
internal: true
entity_category: config
...

View File

@@ -27,8 +27,9 @@ substitutions:
TX_MODEL_3_GANG_TEXT: "3 Gang"
TX_MODEL_4_GANG_TEXT: "4 Gang"
DUMP_CONFIG_CALLER_DELAY: 10s # Delay to dump config after requested
DUMP_CONFIG_CALLER_DELAY: 5s # Delay to dump config after requested
TAG_CORE_COMMON: core.common
api:
id: api_server
@@ -47,11 +48,18 @@ binary_sensor:
device_class: problem
on_state:
then:
- lambda: |-
if (x)
ESP_LOGW("core", "Pending restart: YES");
else
ESP_LOGCONFIG("core", "Pending restart: No");
- script.execute: dump_config_caller
- id: bs_boot_completed
name: Boot Completed
internal: false
disabled_by_default: false
platform: template
entity_category: diagnostic
device_class: connectivity
icon: mdi:rocket-launch
on_state:
then:
- script.execute: dump_config_caller
button:
@@ -63,19 +71,32 @@ button:
esp32:
board: esp32dev
flash_size: 8MB
framework:
type: esp-idf
esphome:
name: ${name}
friendly_name: ${friendly_name}
comment: TX Ultimate Easy
min_version: 2025.8.0
project:
name: "edwardtfn.tx_ultimate_easy"
version: ${version}
platformio_options:
build_flags:
- -D TX_ULTIMATE_EASY_CORE
- -D TX_ULTIMATE_EASY_CORE_COMMON
on_boot:
- priority: 1000 # Very early in boot process
then:
- binary_sensor.template.publish:
id: bs_boot_completed
state: OFF # yamllint disable-line rule:truthy
- priority: 750
then:
- script.execute: restore_from_nvs
- priority: 700
then:
- script.execute: boot_initialize
@@ -88,21 +109,25 @@ esphome:
then:
- script.execute: boot_done
globals:
- id: is_us_model
type: bool
restore_value: true
initial_value: 'false'
external_components:
- source:
type: git
url: https://git.andrejka.at/oneil1987/TX-Ultimate-Easy
ref: v${version}
refresh: 1h
components:
- tx_ultimate_easy
globals:
- id: gang_count
type: uint8_t
restore_value: true
initial_value: '0'
- id: gang_count_plural_suffix
type: std::string
- id: is_us_model
type: bool
restore_value: true
max_restore_data_length: 3
initial_value: 'false'
logger:
level: DEBUG
@@ -131,9 +156,16 @@ script:
- id: boot_done
mode: restart
then:
- script.wait: restore_from_nvs
- script.wait: boot_initialize
- script.wait: boot_sequence
- script.execute:
id: api_send_ha_event_boot
type: done
- script.wait: api_send_ha_event_boot
- binary_sensor.template.publish:
id: bs_boot_completed
state: ON # yamllint disable-line rule:truthy
- id: boot_initialize
mode: restart
@@ -153,9 +185,8 @@ script:
id(is_us_model) = (sl_tx_model_format->state == "${TX_MODEL_FORMAT_US_TEXT}");
id(gang_count) = sl_tx_model_gang->active_index().value() + 1;
if (id(gang_count) < 1 || id(gang_count) > 4) {
ESP_LOGE("core_hw_leds", "Invalid number of gangs: %" PRIu8, id(gang_count));
ESP_LOGE("${TAG_CORE_COMMON}", "Invalid number of gangs: %" PRIu8, id(gang_count));
}
id(gang_count_plural_suffix) = id(gang_count) > 1 ? "s" : "";
- id: boot_sequence
mode: restart
@@ -171,33 +202,43 @@ script:
# Extended by all modules
- lambda: |-
// Device identification
ESP_LOGCONFIG("core", "Device friendly name: ${friendly_name}");
ESP_LOGCONFIG("core", "Device name: ${name}");
ESP_LOGCONFIG("core", "Device name (HA): %s", tx_device_name->state.c_str());
ESP_LOGCONFIG("core", "Device hostname: %s", App.get_name().c_str());
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device friendly name: ${friendly_name}");
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device name: ${name}");
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device name (HA): %s", tx_device_name->state.c_str());
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device hostname: %s", App.get_name().c_str());
dump_config_versions->execute();
// Framework detection
#ifdef USE_ARDUINO
ESP_LOGCONFIG("core", "Framework: Arduino");
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Framework: Arduino");
#elif defined(USE_ESP_IDF)
ESP_LOGCONFIG("core", "Framework: ESP-IDF");
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Framework: ESP-IDF");
#else
ESP_LOGW("core", "Framework: UNKNOWN");
ESP_LOGW("${TAG_CORE_COMMON}", "Framework: UNKNOWN");
#endif
// Model configuration
ESP_LOGCONFIG("core", "Model format (selected): %s", sl_tx_model_format->state.c_str());
ESP_LOGCONFIG("core", "Model format (detected): %s", id(is_us_model) ? "US" : "EU");
ESP_LOGCONFIG("core", "Gangs (selected): %s", sl_tx_model_gang->state.c_str());
ESP_LOGCONFIG("core", "Gangs (detected): %" PRIu8 "-Gang%s", id(gang_count), id(gang_count_plural_suffix).c_str());
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Model format (selected): %s",
sl_tx_model_format->state.c_str());
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Model format (detected): %s",
id(is_us_model) ? "US" : "EU");
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Gangs (selected): %s",
sl_tx_model_gang->state.c_str());
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Gangs (detected): %" PRIu8 "-Gang%s",
id(gang_count), id(gang_count) > 1 ? "s" : "");
// Boot completion status
if (bs_boot_completed->state)
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Boot completed: Yes");
else
ESP_LOGW("${TAG_CORE_COMMON}", "Boot completed: NO");
// System state
if (bs_pending_restart->state)
ESP_LOGW("core", "Pending restart: YES");
ESP_LOGW("${TAG_CORE_COMMON}", "Pending restart: YES");
else
ESP_LOGCONFIG("core", "Pending restart: No");
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Pending restart: No");
- id: dump_config_caller
mode: restart
@@ -222,10 +263,10 @@ script:
then:
- lambda: |-
// Version information
ESP_LOGCONFIG("core", "TX Ultimate firmware version: ${version}");
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "TX Ultimate firmware version: ${version}");
// ESPHome builder information
ESP_LOGCONFIG("core", "ESPHome builder: " ESPHOME_VERSION);
ESP_LOGCONFIG("core", "ESPHome build timestamp: %s", App.get_compilation_time().c_str());
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "ESPHome builder: " ESPHOME_VERSION);
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "ESPHome build timestamp: %s", App.get_compilation_time().c_str());
- id: publish_device_info
mode: restart
@@ -234,6 +275,18 @@ script:
tx_fw_version->publish_state("${version}");
tx_device_name->publish_state(App.get_name().c_str());
- id: restore_from_nvs
mode: single
then:
- select.set_index:
id: sl_tx_model_format
index: !lambda if (id(is_us_model)) return 1; else return 0;
- select.set_index:
id: sl_tx_model_gang
index: !lambda |-
if (id(gang_count) < 1 || id(gang_count) > 4) return 0; // default to 1-Gang
return static_cast<uint8_t>(id(gang_count)) - 1;
select:
- id: sl_tx_model_format
name: Model (Format)
@@ -243,7 +296,7 @@ select:
- "${TX_MODEL_FORMAT_US_TEXT}"
initial_option: "${TX_MODEL_FORMAT_EU_TEXT}"
optimistic: true
restore_value: true
restore_value: false
internal: false
entity_category: config
disabled_by_default: false
@@ -255,7 +308,6 @@ select:
state: true
- lambda: |-
id(is_us_model) = (x == "${TX_MODEL_FORMAT_US_TEXT}");
ESP_LOGI("core", "New model selected: %s", id(is_us_model) ? "US" : "EU");
- id: sl_tx_model_gang
name: Model (Gang)
@@ -267,7 +319,7 @@ select:
- "${TX_MODEL_4_GANG_TEXT}"
initial_option: "${TX_MODEL_1_GANG_TEXT}"
optimistic: true
restore_value: true
restore_value: false
internal: false
entity_category: config
disabled_by_default: false
@@ -279,7 +331,6 @@ select:
state: true
- lambda: |-
id(gang_count) = static_cast<uint8_t>(i) + 1;
ESP_LOGI("core", "New model selected: %" PRIu8 "-%s", id(gang_count), id(gang_count) > 1 ? "Gangs" : "Gang");
tx_ultimate->set_gang_count(id(gang_count));
text_sensor:
@@ -319,6 +370,9 @@ text_sensor:
}
return result;
tx_ultimate_easy:
id: tx_ultimate
wifi:
id: wifi_component
...

View File

@@ -36,6 +36,8 @@ substitutions:
BUTTON_3_ID: '3'
BUTTON_4_ID: '4'
TAG_CORE_HW_BUTTONS: core.hw.buttons
binary_sensor:
- &binary_sensor_button_base
id: bs_button_2
@@ -63,6 +65,14 @@ esphome:
- -D TX_ULTIMATE_EASY_CORE_HW_BUTTONS
globals:
# Packed button actions - replaces 4 separate selects
- id: button_actions_packed
type: uint16_t
restore_value: true
initial_value: '4369' # Binary: 0001000100010001 = all buttons set to option 1 (default action)
# Bit layout: [button4(4)][button3(4)][button2(4)][button1(4)]
# Each button uses 4 bits: 0=None, 1=Toggle, 2=Failsafe, 3-15=Reserved for future options
- id: button_press_button
type: uint8_t
restore_value: false
@@ -125,7 +135,7 @@ script:
{"count", std::to_string(count)},
{"position", std::to_string(id(button_press_position))}
});
ESP_LOGI("core_hw_buttons", "Button %" PRIu8 " action: '%s'", button, action.c_str());
ESP_LOGI("${TAG_CORE_HW_BUTTONS}", "Button %" PRIu8 " action: '%s'", button, action.c_str());
if (action == "click") {
switch (button) {
case ${BUTTON_1_ID}:
@@ -209,38 +219,50 @@ script:
- id: !extend dump_config
then:
- lambda: |-
std::string gang_count_plural_suffix = id(gang_count) > 1 ? "s" : "";
// Button's actions
ESP_LOGCONFIG("core_hw_buttons", "Button%s action%s:",
id(gang_count_plural_suffix).c_str(),
id(gang_count_plural_suffix).c_str());
ESP_LOGCONFIG("core_hw_buttons", " Relay 1: %s", sl_button_1_action->has_state()
? sl_button_1_action->state.c_str()
: "Unknown");
ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", "Button%s action%s:",
gang_count_plural_suffix.c_str(),
gang_count_plural_suffix.c_str());
ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 1: %s", sl_button_1_action->has_state() ?
sl_button_1_action->state.c_str() : "Unknown");
if (id(gang_count) >= 2)
ESP_LOGCONFIG("core_hw_buttons", " Relay 2: %s", sl_button_2_action->has_state()
? sl_button_2_action->state.c_str()
: "Unknown");
ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 2: %s", sl_button_2_action->has_state() ?
sl_button_2_action->state.c_str() : "Unknown");
if (id(gang_count) >= 3)
ESP_LOGCONFIG("core_hw_buttons", " Relay 3: %s", sl_button_3_action->has_state()
? sl_button_3_action->state.c_str()
: "Unknown");
ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 3: %s", sl_button_3_action->has_state() ?
sl_button_3_action->state.c_str() : "Unknown");
if (id(gang_count) >= 4)
ESP_LOGCONFIG("core_hw_buttons", " Relay 4: %s", sl_button_4_action->has_state()
? sl_button_4_action->state.c_str()
: "Unknown");
ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 4: %s", sl_button_4_action->has_state() ?
sl_button_4_action->state.c_str() : "Unknown");
- id: !extend dump_config_list_packages
then:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#if !defined(TX_ULTIMATE_EASY_CORE_COMMON)
#error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required."
#endif
// Identify itself
ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Core - Hardware - Buttons");
- id: !extend restore_from_nvs
then:
- select.set_index:
id: sl_button_1_action
index: !lambda return std::min((uint8_t)(id(button_actions_packed) & 0x0F), (uint8_t)2);
- select.set_index:
id: sl_button_2_action
index: !lambda return std::min((uint8_t)((id(button_actions_packed) >> 4) & 0x0F), (uint8_t)2);
- select.set_index:
id: sl_button_3_action
index: !lambda return std::min((uint8_t)((id(button_actions_packed) >> 8) & 0x0F), (uint8_t)2);
- select.set_index:
id: sl_button_4_action
index: !lambda return std::min((uint8_t)((id(button_actions_packed) >> 12) & 0x0F), (uint8_t)2);
- id: !extend touch_on_multi_touch_release
then:
- script.execute: buttons_release
@@ -293,19 +315,20 @@ script:
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);
ESP_LOGI("${TAG_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)");
ESP_LOGW("${TAG_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(id(button_press_button), "long_press", 1);
} 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");
ESP_LOGW("${TAG_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");
ESP_LOGW("${TAG_CORE_HW_BUTTONS}", "Press event timestamp not recorded yet");
}
id(button_press_start_time) = 0;
@@ -328,11 +351,16 @@ select:
- "${BUTTON_1_ACTION_FAILSAFE_TEXT}"
initial_option: "${BUTTON_1_ACTION_TEXT}"
optimistic: true
restore_value: true
restore_value: false
internal: true
entity_category: config
disabled_by_default: false
icon: mdi:dip-switch
on_value:
then:
- lambda: |-
id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 0)) |
((id(sl_button_1_action).active_index().value_or(1) & 0x0F) << 0);
- id: sl_button_2_action
name: Button 2 action
@@ -343,6 +371,11 @@ select:
- "${BUTTON_2_ACTION_FAILSAFE_TEXT}"
initial_option: "${BUTTON_2_ACTION_TEXT}"
<<: *button_select_action_base
on_value:
then:
- lambda: |-
id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 4)) |
((id(sl_button_2_action).active_index().value_or(1) & 0x0F) << 4);
- id: sl_button_3_action
name: Button 3 action
@@ -353,6 +386,11 @@ select:
- "${BUTTON_3_ACTION_FAILSAFE_TEXT}"
initial_option: "${BUTTON_3_ACTION_TEXT}"
<<: *button_select_action_base
on_value:
then:
- lambda: |-
id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 8)) |
((id(sl_button_3_action).active_index().value_or(1) & 0x0F) << 8);
- id: sl_button_4_action
name: Button 4 action
@@ -363,4 +401,9 @@ select:
- "${BUTTON_4_ACTION_FAILSAFE_TEXT}"
initial_option: "${BUTTON_4_ACTION_TEXT}"
<<: *button_select_action_base
on_value:
then:
- lambda: |-
id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 12)) |
((id(sl_button_4_action).active_index().value_or(1) & 0x0F) << 12);
...

View File

@@ -25,9 +25,14 @@ substitutions:
LIGHT_TRANSITION_TURN_OFF: '0' # Transition time in msec
LIGHT_BRIGHTNESS_TURN_ON: '0' # Brightness (1 to 100%, 0 to not set it)
LIGHT_FULL_RESTORE_MODE: RESTORE_DEFAULT_OFF
LIGHT_SIDES_RESTORE_MODE: RESTORE_DEFAULT_OFF
LIGHT_INDIVIDUAL_RESTORE_MODE: RESTORE_DEFAULT_OFF
LIGHT_FULL_RESTORE_MODE: ALWAYS_OFF
LIGHT_SIDES_RESTORE_MODE: ALWAYS_OFF
LIGHT_INDIVIDUAL_RESTORE_MODE: ALWAYS_OFF
LIGHT_ATTRS_DEFAULT: '0x64FFFFFF' # brightness=100, RGB=255,255,255
LIGHT_DUAL_ATTRS_DEFAULT: '0x64FFFFFF64FFFFFF' # Both lights: brightness=100, RGB=255,255,255
TAG_CORE_HW_LEDS: core.hw.leds
esphome:
platformio_options:
@@ -35,12 +40,39 @@ esphome:
- -D TX_ULTIMATE_EASY_CORE_HW_LEDS
globals:
- id: gb_lights_1
type: std::vector<light::LightState*>
restore_value: false
- id: gb_lights_2
type: std::vector<light::LightState*>
restore_value: false
# light_full with effect support
- id: led_full_attrs
type: uint32_t
restore_value: true
initial_value: '${LIGHT_ATTRS_DEFAULT}'
# Basic attributes: brightness + RGB
# For light_full effects
- id: led_full_effect_packed
type: uint32_t
restore_value: true
initial_value: '0x00000000'
# Stores: effect_index(16 bits) + effect_speed(8 bits) + reserved(8 bits)
# 0 = no effect/solid color, 1-10 = effect indices
- id: led_main_packed_attrs
type: uint64_t
restore_value: true
initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}'
# light_eu(group1) + light_us(group2) - the main effect lights
- id: led_main_packed_effect
type: uint32_t
restore_value: true
initial_value: '0x00000000'
# Shared between light_eu and light_us
# Main user-facing LED lights only
- id: led_sides_packed_attrs
type: uint64_t
restore_value: true
initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}'
# EU: bottom(group1) + top(group2) OR US: left(group1) + right(group2)
light:
# These lights are controlling the LEDs on the device
@@ -50,12 +82,17 @@ light:
num_leds: 32
pin: GPIO13
restore_mode: ${LIGHT_FULL_RESTORE_MODE}
platform: neopixelbus
type: GRB
variant: WS2811
platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: ws2812
max_refresh_rate: 33ms
use_psram: false
# platform: neopixelbus
# type: GRB
# variant: WS2811
# Those lights are available based on the model selections
- &light_partition_with_effects
- &light_partition_with_effects_eu
id: light_eu
name: Light EU
platform: partition
@@ -65,8 +102,76 @@ light:
effects:
- addressable_rainbow:
name: "Rainbow"
width: 28
- addressable_rainbow:
name: "Rainbow - Fast"
width: 28
speed: 50
- pulse:
name: "Pulse"
transition_length: 1s
update_interval: 1s
- pulse:
name: "Pulse - Slow"
transition_length: 2s
update_interval: 2s
- addressable_scan:
name: "Scan"
move_interval: 100ms
scan_width: 3
- addressable_twinkle:
name: "Twinkle"
twinkle_probability: 5%
progress_interval: 4ms
- addressable_random_twinkle:
name: "Random Twinkle"
twinkle_probability: 20%
progress_interval: 32ms
- addressable_fireworks:
name: "Fireworks"
update_interval: 32ms
spark_probability: 10%
use_random_color: false
fade_out_rate: 120
- addressable_flicker:
name: "Flicker"
update_interval: 16ms
intensity: 5%
- addressable_nachtlicht:
name: "Nachtlicht"
intensity: 25%
- random:
name: "Random"
segments:
- id: light_full
from: 0
to: 27
on_state:
then:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_eu);
attr_global_ptr: !lambda return &id(led_main_packed_attrs);
group_index: 1
- script.execute:
id: light_effect_save
light_ptr: !lambda return id(light_eu);
effect_global_ptr: !lambda return &id(led_main_packed_effect);
- &light_partition_with_effects_us
id: light_us
name: Light US
platform: partition
default_transition_length: ${default_transition_length}
internal: true
restore_mode: ${LIGHT_SIDES_RESTORE_MODE}
effects:
- addressable_rainbow:
name: "Rainbow"
width: 32
- addressable_rainbow:
name: "Rainbow - Fast"
width: 32
speed: 50
- pulse:
name: "Pulse"
@@ -100,40 +205,59 @@ light:
intensity: 5%
- random:
name: "Random"
segments:
- id: light_full
from: 0
to: 27
- id: light_us
name: Light US
<<: *light_partition_with_effects
segments:
- id: light_full
from: 0
to: 31
on_state:
then:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_us);
attr_global_ptr: !lambda return &id(led_main_packed_attrs);
group_index: 2
- script.execute:
id: light_effect_save
light_ptr: !lambda return id(light_us);
effect_global_ptr: !lambda return &id(led_main_packed_effect);
- &light_partition_with_effects_disabled
- &light_partition_no_effects_disabled
id: light_eu_bottom
name: Light - Bottom EU
platform: partition
disabled_by_default: true
<<: *light_partition_with_effects
default_transition_length: ${default_transition_length}
restore_mode: ${LIGHT_SIDES_RESTORE_MODE}
internal: true
segments:
- id: light_full
from: 6
to: 12
reversed: true
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_eu_bottom);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 1
- id: light_eu_left
name: Light - Left EU
<<: *light_partition_with_effects_disabled
<<: *light_partition_no_effects_disabled
segments:
- id: light_full
from: 13
to: 19
reversed: true
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_eu_left);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 1
- id: light_eu_right
name: Light - Right EU
<<: *light_partition_with_effects_disabled
<<: *light_partition_no_effects_disabled
segments:
- id: light_full
from: 27
@@ -143,33 +267,57 @@ light:
from: 0
to: 5
reversed: false
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_eu_right);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 2
- id: light_eu_top
name: Light - Top EU
<<: *light_partition_with_effects_disabled
<<: *light_partition_no_effects_disabled
segments:
- id: light_full
from: 20
to: 26
reversed: false
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_eu_top);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 2
- id: light_us_bottom
name: Light - Bottom US
<<: *light_partition_with_effects_disabled
<<: *light_partition_no_effects_disabled
segments:
- id: light_full
from: 9
to: 14
reversed: true
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_us_bottom);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 1
- id: light_us_left
name: Light - Left US
<<: *light_partition_with_effects_disabled
<<: *light_partition_no_effects_disabled
segments:
- id: light_full
from: 15
to: 24
reversed: true
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_us_left);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 1
- id: light_us_right
name: Light - Right US
<<: *light_partition_with_effects_disabled
<<: *light_partition_no_effects_disabled
segments:
- id: light_full
from: 31
@@ -179,14 +327,26 @@ light:
from: 0
to: 8
reversed: false
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_us_right);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 2
- id: light_us_top
name: Light - Top US
<<: *light_partition_with_effects_disabled
<<: *light_partition_no_effects_disabled
segments:
- id: light_full
from: 25
to: 30
reversed: false
on_state:
- script.execute:
id: light_attributes_save_dual
light_ptr: !lambda return id(light_us_top);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 2
- &light_partition_individual_led
id: light_00
@@ -443,178 +603,246 @@ script:
- id: !extend dump_config
then:
- lambda: |-
// Relay's LEDs modes
ESP_LOGCONFIG("core_hw_leds", "Relay%s LEDs mode%s:",
id(gang_count_plural_suffix).c_str(),
id(gang_count_plural_suffix).c_str());
ESP_LOGCONFIG("core_hw_leds", " Relay 1: %s", id(is_us_model)
? sl_relay_1_light_mode_us->state.c_str()
: sl_relay_1_light_mode_eu->state.c_str());
if (id(gang_count) >= 2)
ESP_LOGCONFIG("core_hw_leds", " Relay 2: %s", id(is_us_model)
? sl_relay_2_light_mode_us->state.c_str()
: sl_relay_2_light_mode_eu->state.c_str());
if (id(gang_count) >= 3)
ESP_LOGCONFIG("core_hw_leds", " Relay 3: %s", id(is_us_model)
? sl_relay_3_light_mode_us->state.c_str()
: sl_relay_3_light_mode_eu->state.c_str());
if (id(gang_count) >= 4)
ESP_LOGCONFIG("core_hw_leds", " Relay 4: %s", id(is_us_model)
? sl_relay_4_light_mode_us->state.c_str()
: sl_relay_4_light_mode_eu->state.c_str());
// Transition times
ESP_LOGCONFIG("core_hw_leds", "Transition times:");
ESP_LOGCONFIG("core_hw_leds", " Default: ${default_transition_length}");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", "Transition times:");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Default: ${default_transition_length}");
if (${LIGHT_TRANSITION_TURN_ON} > 0)
ESP_LOGCONFIG("core_hw_leds", " Turning on: ${LIGHT_TRANSITION_TURN_ON}ms");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning on: ${LIGHT_TRANSITION_TURN_ON}ms");
else
ESP_LOGCONFIG("core_hw_leds", " Turning on: Disabled");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning on: Disabled");
if (${LIGHT_TRANSITION_TURN_OFF} > 0)
ESP_LOGCONFIG("core_hw_leds", " Turning off: ${LIGHT_TRANSITION_TURN_OFF}ms");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning off: ${LIGHT_TRANSITION_TURN_OFF}ms");
else
ESP_LOGCONFIG("core_hw_leds", " Turning off: Disabled");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning off: Disabled");
// Restore modes
ESP_LOGCONFIG("core_hw_leds", "LED restore modes:");
ESP_LOGCONFIG("core_hw_leds", " Light - All: ${LIGHT_FULL_RESTORE_MODE}");
ESP_LOGCONFIG("core_hw_leds", " Sides: ${LIGHT_SIDES_RESTORE_MODE}");
ESP_LOGCONFIG("core_hw_leds", " Individual: ${LIGHT_INDIVIDUAL_RESTORE_MODE}");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", "LED restore modes:");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Light - All: ${LIGHT_FULL_RESTORE_MODE}");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Sides: ${LIGHT_SIDES_RESTORE_MODE}");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Individual: ${LIGHT_INDIVIDUAL_RESTORE_MODE}");
- id: !extend dump_config_list_packages
then:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#if !defined(TX_ULTIMATE_EASY_CORE_COMMON)
#error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required."
#endif
// Identify itself
ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Core - Hardware - LEDs");
- id: light_set_state
- id: light_attributes_restore
mode: parallel
parameters:
light_group: uint8_t
light_index: uint8_t
state: bool
light_ptr: light::LightState*
attr_global_ptr: uint32_t*
then:
- lambda: |-
static const uint32_t LIGHT_TRANSITION_TURN_ON = ${LIGHT_TRANSITION_TURN_ON};
static const uint32_t LIGHT_TRANSITION_TURN_OFF = ${LIGHT_TRANSITION_TURN_OFF};
static const uint8_t LIGHT_BRIGHTNESS_TURN_ON = ${LIGHT_BRIGHTNESS_TURN_ON};
const std::string light_mode_group_1 = id(is_us_model)
? "${LIGHT_MODE_LEFT_TEXT}"
: "${LIGHT_MODE_BOTTOM_TEXT}";
const std::string light_mode_group_2 = id(is_us_model)
? "${LIGHT_MODE_RIGHT_TEXT}"
: "${LIGHT_MODE_TOP_TEXT}";
switch (light_group) {
case 1:
if (light_index >= id(gb_lights_1).size()) {
ESP_LOGE("core_hw_leds", "Invalid set with %s light for Relay %" PRIu8,
light_mode_group_1.c_str(), light_index+1);
if (!light_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer");
return;
}
if (state and !id(gb_lights_1)[light_index]->current_values.is_on()) {
ESP_LOGI("core_hw_leds", "Turn-on %s light for Relay %" PRIu8,
light_mode_group_1.c_str(), light_index+1);
auto call = id(gb_lights_1)[light_index]->turn_on();
if (LIGHT_TRANSITION_TURN_ON > 0)
call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms
if (LIGHT_BRIGHTNESS_TURN_ON > 0 and LIGHT_BRIGHTNESS_TURN_ON <= 100)
call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f);
call.perform();
} else if (!state and id(gb_lights_1)[light_index]->current_values.is_on()) {
ESP_LOGI("core_hw_leds", "Turn-off %s light for Relay %" PRIu8,
light_mode_group_1.c_str(), light_index+1);
auto call = id(gb_lights_1)[light_index]->turn_off();
if (LIGHT_TRANSITION_TURN_OFF > 0)
call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms
call.perform();
}
break;
case 2:
if (light_index >= id(gb_lights_2).size()) {
ESP_LOGE("core_hw_leds", "Invalid set with %s light for Relay %" PRIu8,
light_mode_group_2.c_str(), light_index+1);
if (!attr_global_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer");
return;
}
if (state and !id(gb_lights_2)[light_index]->current_values.is_on()) {
ESP_LOGI("core_hw_leds", "Turn-on %s light for Relay %" PRIu8,
light_mode_group_2.c_str(), light_index+1);
auto call = id(gb_lights_2)[light_index]->turn_on();
if (LIGHT_TRANSITION_TURN_ON > 0)
call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms
if (LIGHT_BRIGHTNESS_TURN_ON > 0 and LIGHT_BRIGHTNESS_TURN_ON <= 100)
call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f);
call.perform();
} else if (!state and id(gb_lights_2)[light_index]->current_values.is_on()) {
ESP_LOGI("core_hw_leds", "Turn-off %s light for Relay %" PRIu8,
light_mode_group_2.c_str(), light_index+1);
auto call = id(gb_lights_2)[light_index]->turn_off();
if (LIGHT_TRANSITION_TURN_OFF > 0)
call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms
tx_ultimate_easy::LightAttributes attrs = tx_ultimate_easy::unpack_light_attributes(*attr_global_ptr);
auto call = light_ptr->make_call();
call.set_brightness(attrs.brightness / 100.0f);
call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f);
call.perform();
ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored light attributes: brightness=%d%%, RGB=(%d,%d,%d)",
attrs.brightness, attrs.red, attrs.green, attrs.blue);
- id: light_attributes_restore_dual
mode: parallel
parameters:
light_ptr: light::LightState*
attr_global_ptr: uint64_t*
group_index: uint8_t # 1 or 2
then:
- lambda: |-
if (!light_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer");
return;
}
break;
if (!attr_global_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer");
return;
}
if (group_index != 1 && group_index != 2) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid group_index=%d (expected 1 or 2)", group_index);
return;
}
auto attrs_pair = tx_ultimate_easy::unpack_dual_light_attributes(*attr_global_ptr);
tx_ultimate_easy::LightAttributes attrs = (group_index == 1) ? attrs_pair.first : attrs_pair.second;
auto call = light_ptr->make_call();
call.set_brightness(attrs.brightness / 100.0f);
call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f);
call.perform();
ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored (group=%d) attributes: brightness=%d%%, RGB=(%d,%d,%d)",
group_index, attrs.brightness, attrs.red, attrs.green, attrs.blue);
- id: light_attributes_save
mode: parallel
parameters:
light_ptr: light::LightState*
attr_global_ptr: uint32_t*
then:
- lambda: |-
if (!light_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer");
return;
}
if (!attr_global_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer");
return;
}
auto current = light_ptr->current_values;
tx_ultimate_easy::LightAttributes attrs = {
.brightness = uint8_t(current.get_brightness() * 100.0f),
.red = uint8_t(current.get_red() * 255.0f),
.green = uint8_t(current.get_green() * 255.0f),
.blue = uint8_t(current.get_blue() * 255.0f)
};
*attr_global_ptr = tx_ultimate_easy::pack_light_attributes(attrs);
ESP_LOGD("${TAG_CORE_HW_LEDS}", "Saved light attributes: brightness=%d%%, RGB=(%d,%d,%d)",
attrs.brightness, attrs.red, attrs.green, attrs.blue);
- id: light_attributes_save_dual
mode: queued # To prevent two instances trying to change the same global
parameters:
light_ptr: light::LightState*
attr_global_ptr: uint64_t*
group_index: uint8_t # 1 or 2 (which group within the dual pack)
then:
- lambda: |-
if (!light_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer");
return;
}
if (!attr_global_ptr) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer");
return;
}
if (group_index != 1 && group_index != 2) {
ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid group_index=%d (expected 1 or 2)", group_index);
return;
}
auto current = light_ptr->current_values;
tx_ultimate_easy::LightAttributes new_attrs = {
.brightness = uint8_t(current.get_brightness() * 100.0f),
.red = uint8_t(current.get_red() * 255.0f),
.green = uint8_t(current.get_green() * 255.0f),
.blue = uint8_t(current.get_blue() * 255.0f)
};
// Unpack existing dual attributes
auto existing = tx_ultimate_easy::unpack_dual_light_attributes(*attr_global_ptr);
// Update the appropriate group
if (group_index == 1) {
*attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(new_attrs, existing.second);
} else {
*attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(existing.first, new_attrs);
}
select:
# EU relay light mode template
- &relay_light_mode_eu
id: sl_relay_1_light_mode_eu
name: Relay 1 light mode EU
platform: template
options:
- "${LIGHT_MODE_DISABLED_TEXT}"
- "${LIGHT_MODE_BOTTOM_TEXT}"
- "${LIGHT_MODE_TOP_TEXT}"
- "${LIGHT_MODE_BOTH_TEXT}"
initial_option: "${LIGHT_MODE_BOTTOM_TEXT}"
optimistic: true
restore_value: true
internal: true
entity_category: config
disabled_by_default: false
icon: mdi:dots-square
- id: sl_relay_2_light_mode_eu
name: Relay 2 light mode EU
<<: *relay_light_mode_eu
- id: sl_relay_3_light_mode_eu
name: Relay 3 light mode EU
<<: *relay_light_mode_eu
- id: sl_relay_4_light_mode_eu
name: Relay 4 light mode EU
<<: *relay_light_mode_eu
- id: light_effect_save
mode: queued
parameters:
light_ptr: light::LightState*
effect_global_ptr: uint32_t*
then:
- lambda: |-
if (!light_ptr || !effect_global_ptr) return;
# US relay light mode template
- &relay_light_mode_us
id: sl_relay_1_light_mode_us
name: Relay 1 light mode US
platform: template
options:
- "${LIGHT_MODE_DISABLED_TEXT}"
- "${LIGHT_MODE_LEFT_TEXT}"
- "${LIGHT_MODE_RIGHT_TEXT}"
- "${LIGHT_MODE_BOTH_TEXT}"
initial_option: "${LIGHT_MODE_RIGHT_TEXT}"
optimistic: true
restore_value: true
internal: true
entity_category: config
disabled_by_default: false
icon: mdi:dots-square
- id: sl_relay_2_light_mode_us
name: Relay 2 light mode US
<<: *relay_light_mode_us
- id: sl_relay_3_light_mode_us
name: Relay 3 light mode US
<<: *relay_light_mode_us
- id: sl_relay_4_light_mode_us
name: Relay 4 light mode US
<<: *relay_light_mode_us
uint32_t effect_index = light_ptr->get_current_effect_index();
// Store effect index (16 bits) + reserved space for future use
*effect_global_ptr = effect_index & 0xFFFF;
ESP_LOGD("${TAG_CORE_HW_LEDS}", "Saved effect index: %d", effect_index);
- id: light_effect_restore
mode: parallel
parameters:
light_ptr: light::LightState*
effect_global_ptr: uint32_t*
then:
- lambda: |-
if (!light_ptr || !effect_global_ptr) return;
uint32_t effect_index = *effect_global_ptr & 0xFFFF;
if (effect_index == 0) return; // No effect
if (effect_index > light_ptr->get_effect_count()) {
ESP_LOGW("${TAG_CORE_HW_LEDS}", "Invalid effect index: %d (max: %d)",
effect_index, light_ptr->get_effect_count());
return;
}
auto call = light_ptr->make_call();
call.set_effect(effect_index);
call.perform();
ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored effect: %s (index=%d)",
light_ptr->get_effect_name_by_index(effect_index).c_str(), effect_index);
- id: !extend restore_from_nvs
then:
# LED restoration
- if:
condition:
lambda: return id(is_us_model);
then:
# US model restoration
- script.execute:
id: light_attributes_restore_dual
light_ptr: !lambda return id(light_us);
attr_global_ptr: !lambda return &id(led_main_packed_attrs);
group_index: 2
- script.execute:
id: light_effect_restore
light_ptr: !lambda return id(light_us);
effect_global_ptr: !lambda return &id(led_main_packed_effect);
- script.execute:
id: light_attributes_restore_dual
light_ptr: !lambda return id(light_us_left);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 1
- script.execute:
id: light_attributes_restore_dual
light_ptr: !lambda return id(light_us_right);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 2
else:
# EU model restoration
- script.execute:
id: light_attributes_restore_dual
light_ptr: !lambda return id(light_eu);
attr_global_ptr: !lambda return &id(led_main_packed_attrs);
group_index: 1
- script.execute:
id: light_effect_restore
light_ptr: !lambda return id(light_eu);
effect_global_ptr: !lambda return &id(led_main_packed_effect);
- script.execute:
id: light_attributes_restore_dual
light_ptr: !lambda return id(light_eu_bottom);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 1
- script.execute:
id: light_attributes_restore_dual
light_ptr: !lambda return id(light_eu_top);
attr_global_ptr: !lambda return &id(led_sides_packed_attrs);
group_index: 2
# Light full restoration
- script.execute:
id: light_attributes_restore
light_ptr: !lambda return id(light_full);
attr_global_ptr: !lambda return &id(led_full_attrs);
...

View File

@@ -1,29 +0,0 @@
####################################################################################################
##### TX Ultimate Easy for ESPHome #####
##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy #####
####################################################################################################
##### Purpose: ESPHome Core - Hardware - Media player #####
####################################################################################################
##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo #####
####################################################################################################
##### NOTE: #####
##### - Make changes ONLY if absolutely necessary and you have the required knowledge. #####
##### - For normal system use, modifications to this file are NOT required. #####
####################################################################################################
---
i2s_audio:
id: if_i2s_audio
i2s_bclk_pin: GPIO2
i2s_lrclk_pin: GPIO4
media_player:
- id: mp_media_player
name: Media Player
internal: false
platform: i2s_audio
i2s_dout_pin: GPIO15
i2s_audio_id: if_i2s_audio
i2s_comm_fmt: msb # Experimental - It was 'lsb' until v2024.12.12
dac_type: external
mode: mono
...

View File

@@ -14,6 +14,8 @@
substitutions:
TOUCH_POSITION_MAX_VALUE: '10' # Maximum touch position value returned by the touch pad via uart
TAG_CORE_HW_TOUCH: core.hw.touch
binary_sensor:
- &bs_button_event_template
id: bs_button_event_template
@@ -203,15 +205,6 @@ esphome:
build_flags:
- -D TX_ULTIMATE_EASY_CORE_HW_TOUCH
external_components:
- source:
type: git
url: https://github.com/edwardtfn/TX-Ultimate-Easy
ref: v${version}
refresh: 1h
components:
- tx_ultimate_easy
number:
- id: nr_touch_duration
name: Touch event duration
@@ -293,18 +286,18 @@ script:
- lambda: |-
// Touch panel's state
if (sw_touch_panel_power->state)
ESP_LOGCONFIG("core_hw_touch", "Touch panel - Power: On");
ESP_LOGCONFIG("${TAG_CORE_HW_TOUCH}", "Touch panel - Power: On");
else
ESP_LOGW("core_hw_touch", "Touch panel - Power: OFF");
ESP_LOGCONFIG("core_hw_touch", "Touch event duration: %.0fms", nr_touch_duration->state);
ESP_LOGW("${TAG_CORE_HW_TOUCH}", "Touch panel - Power: OFF");
ESP_LOGCONFIG("${TAG_CORE_HW_TOUCH}", "Touch event duration: %.0fms", nr_touch_duration->state);
- id: !extend dump_config_list_packages
then:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#if !defined(TX_ULTIMATE_EASY_CORE_COMMON)
#error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required."
#endif
// Identify itself
@@ -373,32 +366,30 @@ switch:
entity_category: config
tx_ultimate_easy:
id: tx_ultimate
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("core_hw_touch", "Invalid long-touch position: %" PRIu8, touch_position);
ESP_LOGE("${TAG_CORE_HW_TOUCH}", "Invalid long-touch position: %" PRIu8, touch_position);
} else {
ESP_LOGI("core_hw_touch", "Long-touch released at position %" PRIu8, touch_position);
ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Long-touch released at position %" PRIu8, touch_position);
}
on_multi_touch_release:
then:
- homeassistant.event:
event: ${EVENT_NAME}
data:
device_name: !lambda return id(tx_device_name).state.c_str();
firmware: ${version}
domain: touch
type: multi_touch
action: release
position: !lambda return std::to_string(touch.x);
- lambda: |-
// Send event to Home Assistant
esphome::api::CustomAPIDevice ha_event;
ha_event.fire_homeassistant_event("${EVENT_NAME}", {
{"device_name", id(tx_device_name).state.c_str()},
{"firmware", "${version}"},
{"domain", "touch"},
{"type", "multi_touch"},
{"action", "release"},
{"position", std::to_string(touch.x)}
});
ESP_LOGI("core_hw_touch", "Multi-touch released");
ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Multi-touch released");
touch_on_multi_touch_release->execute();
on_press:
@@ -406,58 +397,73 @@ tx_ultimate_easy:
- lambda: |-
const uint8_t position = static_cast<uint8_t>(touch.x);
if (position > ${TOUCH_POSITION_MAX_VALUE}) { // Check for valid range
ESP_LOGE("core_hw_touch", "Invalid touch position: %" PRIu8, position);
ESP_LOGE("${TAG_CORE_HW_TOUCH}", "Invalid touch position: %" PRIu8, position);
} else {
ESP_LOGI("core_hw_touch", "Pressed at position %" PRIu8, position);
ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Pressed at position %" PRIu8, position);
touch_on_press->execute(static_cast<uint8_t>(touch.button), position);
}
on_release:
then:
- lambda: ESP_LOGI("core_hw_touch", "Released");
- lambda: |-
ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Released");
- script.execute: touch_on_release
on_swipe_left:
then:
- homeassistant.event:
event: ${EVENT_NAME}
data:
device_name: !lambda return id(tx_device_name).state.c_str();
firmware: ${version}
domain: touch
type: swipe
action: !lambda |-
if (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}")
return "left";
else
return "down";
swipe-direction: !lambda |-
if (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}")
return "left";
else
return "down";
position: !lambda return std::to_string(touch.x);
- lambda: |-
std::string swipe_direction = (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}") ? "left" : "down";
// Send event to Home Assistant
esphome::api::CustomAPIDevice ha_event;
ha_event.fire_homeassistant_event("${EVENT_NAME}", {
{"device_name", id(tx_device_name).state.c_str()},
{"firmware", "${version}"},
{"domain", "touch"},
{"type", "swipe"},
{"action", swipe_direction.c_str()},
{"swipe-direction", swipe_direction.c_str()},
{"position", std::to_string(touch.x)}
});
ESP_LOGI("core_hw_touch", "Swipe %s", swipe_direction.c_str());
ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Swipe %s",
(sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}") ? "left" : "down");
touch_swipe_left->execute();
on_swipe_right:
then:
- homeassistant.event:
event: ${EVENT_NAME}
data:
device_name: !lambda return id(tx_device_name).state.c_str();
firmware: ${version}
domain: touch
type: swipe
action: !lambda |-
if (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}")
return "right";
else
return "up";
swipe-direction: !lambda |-
if (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}")
return "right";
else
return "up";
position: !lambda return std::to_string(touch.x);
- lambda: |-
std::string swipe_direction = (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}") ? "right" : "up";
// Send event to Home Assistant
esphome::api::CustomAPIDevice ha_event;
ha_event.fire_homeassistant_event("${EVENT_NAME}", {
{"device_name", id(tx_device_name).state.c_str()},
{"firmware", "${version}"},
{"domain", "touch"},
{"type", "swipe"},
{"action", swipe_direction.c_str()},
{"swipe-direction", swipe_direction.c_str()},
{"position", std::to_string(touch.x)}
});
ESP_LOGI("core_hw_touch", "Swipe %s", swipe_direction.c_str());
ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Swipe %s",
(sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}") ? "right" : "up");
touch_swipe_right->execute();
on_touch_event:
- lambda: |-
ESP_LOGD("core_hw_touch", "Touch event:");
ESP_LOGD("core_hw_touch", " State: %i (%s)", touch.state, touch.state_str.c_str());
ESP_LOGD("core_hw_touch", " Position: %i", touch.x);
ESP_LOGD("${TAG_CORE_HW_TOUCH}", "Touch event:");
ESP_LOGD("${TAG_CORE_HW_TOUCH}", " State: %i (%s)", touch.state, touch.state_str.c_str());
ESP_LOGD("${TAG_CORE_HW_TOUCH}", " Position: %i", touch.x);
uart:
- id: uart_touch

View File

@@ -11,13 +11,18 @@
##### - For normal system use, modifications to this file are NOT required. #####
####################################################################################################
---
substitutions:
SWITCH_AUDIO_AMPLIFIER_RESTORE_MODE: RESTORE_DEFAULT_ON
TAG_STD_HW_AUDIO: std.hw.audio
esphome:
platformio_options:
build_flags:
- -D TX_ULTIMATE_EASY_STANDARD_HW_AUDIO
i2s_audio:
id: if_i2s_audio
- id: if_i2s_audio
i2s_bclk_pin: GPIO2
i2s_lrclk_pin: GPIO4
@@ -27,10 +32,21 @@ script:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#if !defined(TX_ULTIMATE_EASY_CORE_COMMON)
#error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required."
#endif
// Identify itself
ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Standard - Hardware - Audio");
switch:
- id: sw_audio_amplifier
name: Audio Amplifier
platform: gpio
pin:
number: GPIO26
inverted: false
restore_mode: ${SWITCH_AUDIO_AMPLIFIER_RESTORE_MODE}
internal: true
entity_category: config
...

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,8 @@ substitutions:
# d=32 (32nd note), o=5 (octave 5), b=100 (tempo 100)
tone_volume_change: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b" # Use "none" to disable sound
TAG_STD_HW_SPEAKER: std.hw.speaker
packages:
standard_hw_audio: !include TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml
@@ -31,18 +33,18 @@ api:
- lambda: |-
rtttl_speaker->stop();
if (speaker_volume->state > 0) {
ESP_LOGI("standard_speaker", "Play tone: '%s'", tone.c_str());
ESP_LOGI("${TAG_STD_HW_SPEAKER}", "Play tone: '%s'", tone.c_str());
rtttl_speaker->play(tone.c_str());
}
esphome:
platformio_options:
build_flags:
- -D TX_ULTIMATE_EASY_STANDARD_SPEAKER
- -D TX_ULTIMATE_EASY_STANDARD_HW_SPEAKER
number:
- id: speaker_volume
name: Sound - Volume
name: Speaker Volume
platform: template
max_value: 100
min_value: 0
@@ -76,8 +78,8 @@ script:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#if !defined(TX_ULTIMATE_EASY_CORE_COMMON)
#error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required."
#endif
#if !defined(TX_ULTIMATE_EASY_STANDARD_HW_AUDIO)
#error "The package TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml is required."
@@ -179,4 +181,8 @@ speaker:
i2s_audio_id: if_i2s_audio
i2s_comm_fmt: stand_msb
channel: mono
timeout: never
bits_per_sample: 16bit
sample_rate: 48000
buffer_duration: 500ms
...

View File

@@ -14,6 +14,8 @@
substitutions:
vibration_max_duration: 2s
TAG_STD_HW_VIBRATION: std.hw.vibration
binary_sensor:
- id: bs_vibrating
name: Vibrating
@@ -62,24 +64,24 @@ script:
then:
- lambda: |-
// Configuration
ESP_LOGCONFIG("standard_hw_vibration", "Touch - Vibration feedback: %s",
ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Touch - Vibration feedback: %s",
sl_touch_vibration_feedback->state.c_str());
ESP_LOGCONFIG("standard_hw_vibration", "Vibrate duration: %.0fms", nr_vibrating_duration->state);
ESP_LOGCONFIG("standard_hw_vibration", "Vibrate max duration: ${vibration_max_duration}");
ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Vibrate duration: %.0fms", nr_vibrating_duration->state);
ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Vibrate max duration: ${vibration_max_duration}");
// State
if (bs_vibrating->state)
ESP_LOGW("standard_hw_vibration", "Vibrating now: YES");
ESP_LOGW("${TAG_STD_HW_VIBRATION}", "Vibrating now: YES");
else
ESP_LOGCONFIG("standard_hw_vibration", "Vibrating now: No");
ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Vibrating now: No");
- id: !extend dump_config_list_packages
then:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#if !defined(TX_ULTIMATE_EASY_CORE_COMMON)
#error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required."
#endif
// Identify itself
@@ -149,20 +151,17 @@ switch:
pin: GPIO21
restore_mode: ALWAYS_OFF
internal: true
on_turn_on:
on_state:
then:
- binary_sensor.template.publish:
id: bs_vibrating
state: ON # yamllint disable-line rule:truthy
state: !lambda return x;
on_turn_on:
then:
- delay: ${vibration_max_duration}
- if:
condition:
switch.is_on: sw_vibration_motor
then:
- switch.turn_off: sw_vibration_motor
on_turn_off:
then:
- binary_sensor.template.publish:
id: bs_vibrating
state: OFF # yamllint disable-line rule:truthy
...

View File

@@ -11,54 +11,108 @@
##### - For normal system use, modifications to this file are NOT required. #####
####################################################################################################
---
substitutions:
TAG_STD_MEDIA_PLAYER: "std.media-player"
packages:
standard_hw_audio: !include TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml
standard_hw_speaker: !include TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml
esphome:
platformio_options:
build_flags:
- -D TX_ULTIMATE_EASY_ADDON_MEDIA_PLAYER
- -D TX_ULTIMATE_EASY_STANDARD_MEDIA_PLAYER
globals:
- id: last_media_player_volume
type: uint8_t
restore_value: false
initial_value: '100'
- id: persistent_media_player_volume
type: uint8_t
restore_value: true
initial_value: '100'
media_player:
- id: mp_media_player
name: Media Player
internal: false
platform: i2s_audio
i2s_dout_pin: GPIO15
i2s_audio_id: if_i2s_audio
i2s_comm_fmt: msb
dac_type: external
mode: mono
mute_pin:
number: GPIO26
inverted: true
allow_other_uses: true
platform: speaker
codec_support_enabled: true
buffer_size: 250000
task_stack_in_psram: false
announcement_pipeline:
speaker: announcement_spk_resampling_input
format: WAV
num_channels: 1
sample_rate: 48000
media_pipeline:
speaker: media_spk_resampling_input
format: FLAC
num_channels: 1
sample_rate: 48000
on_state:
then:
- lambda: |-
// Check if volume has changed and update number entity
id(persistent_media_player_volume) = static_cast<uint8_t>(mp_media_player->volume * 100.0f);
if (id(persistent_media_player_volume) != id(last_media_player_volume)) {
id(last_media_player_volume) = id(persistent_media_player_volume);
ESP_LOGD("${TAG_STD_MEDIA_PLAYER}", "Media player volume changed to %" PRIu8 "%%, updating number entity",
id(persistent_media_player_volume));
}
script:
- id: !extend boot_sequence
then:
- script.execute: sync_initial_volume
- id: !extend dump_config
then:
- lambda: |-
// Volume sync configuration
ESP_LOGCONFIG("${TAG_STD_MEDIA_PLAYER}", "Media Player Volume Sync:");
ESP_LOGCONFIG("${TAG_STD_MEDIA_PLAYER}", " Persistent volume: %" PRIu8 "%",
id(persistent_media_player_volume));
ESP_LOGCONFIG("${TAG_STD_MEDIA_PLAYER}", " Current volume: %.1f%%", mp_media_player->volume * 100.0f);
- id: !extend dump_config_list_packages
then:
- script.wait: dump_config
- lambda: |-
// Check for requirements
#if !defined(TX_ULTIMATE_EASY_CORE)
#error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required."
#endif
#if !defined(TX_ULTIMATE_EASY_STANDARD_HW_AUDIO)
#error "The package TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml is required."
#endif
#if !defined(TX_ULTIMATE_EASY_CORE_COMMON)
#error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required."
#endif // TX_ULTIMATE_EASY_CORE
#if !defined(TX_ULTIMATE_EASY_STANDARD_HW_SPEAKER)
#error "The package TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml is required."
#endif // TX_ULTIMATE_EASY_STANDARD_HW_SPEAKER
// Identify itself
ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Add-on - Media Player");
ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Standard - Media Player");
switch:
- id: sw_speaker_amplifier
name: Speaker - Amplifier
platform: gpio
pin:
number: GPIO26
inverted: true
allow_other_uses: true
restore_mode: RESTORE_DEFAULT_ON
internal: true
entity_category: config
- id: sync_initial_volume
mode: restart
then:
- delay: 2s # Wait for media player to initialize
- media_player.volume_set: # Sync initial volume from persisted number entity to media player
id: mp_media_player
volume: !lambda return id(persistent_media_player_volume) / 100.0f;
- lambda: |-
id(last_media_player_volume) = id(persistent_media_player_volume);
ESP_LOGI("${TAG_STD_MEDIA_PLAYER}", "Initial volume sync: %.1f%% (restored from persistence)",
id(persistent_media_player_volume));
speaker:
- platform: mixer
id: mixer_speaker_embedded
output_speaker: speaker_embedded
source_speakers:
- id: announcement_speaker_embedded
- id: media_speaker_embedded
- platform: resampler
id: announcement_spk_resampling_input
output_speaker: announcement_speaker_embedded
- platform: resampler
id: media_spk_resampling_input
output_speaker: media_speaker_embedded
...

View File

@@ -27,6 +27,48 @@
[buymeacoffee-shield]: https://img.shields.io/static/v1?label=Buy%20me%20an%20ice%20cream&message=❄&color=blue
[buymeacoffee]: https://www.buymeacoffee.com/edwardfirmo
> [!WARNING]
> ESPHome builder v2025.8.0+ is required
## Framework Migration to ESP-IDF
> [!IMPORTANT]
> **ESP-IDF Framework Migration**
>
> Starting with version **2025.8.0**, TX Ultimate Easy will use **ESP-IDF** as the default framework, following ESPHome's direction towards ESP-IDF for better performance, stability, and feature support.
>
> **What this means for you:**
> - **New installations**: Will automatically use ESP-IDF (no action required)
> - **Existing Arduino users**: Can continue using Arduino but with limited support
> - **Migration recommended**: For better performance and full feature support
>
> **Arduino Framework Support:**
> - Still functional but **no longer actively tested**
> - **Limited support** will be provided for Arduino-specific issues
> - May miss out on new features optimized for ESP-IDF
### Migration Guidelines
#### For New Users
No action required - ESP-IDF will be used automatically.
#### For Existing Arduino Users
**Option 1: Migrate to ESP-IDF (Recommended)**
1. **Best practice**: Flash via serial/USB to ensure proper partition setup
2. **Alternative**: If serial flashing isn't possible, flash OTA twice consecutively to improve success rate
**Option 2: Continue with Arduino**
Add this to your YAML configuration to force Arduino framework:
```yaml
esp32:
framework:
type: arduino
```
> [!WARNING]
> **Migration via OTA:** When switching frameworks via OTA, the partition table may not update correctly. Flash twice in succession to ensure both firmware partitions contain the latest firmware and prevent boot failures.
## Integration with Home Assistant
TX Ultimate Easy exposes your device's components (sensors, touch panel, relays, etc.) to Home Assistant,
@@ -70,9 +112,10 @@ All device behaviors can be customized through Home Assistant automations withou
- **Flexible Light Control**: Customize LED behavior and effects
- **Touch Panel Support**: Full support for touch gestures and multi-touch capabilities
- **Advanced Automations**: Create complex automations using Home Assistant's powerful automation engine
- **BLE Proxy Support**: Optional Bluetooth Low Energy proxy functionality
- **Bluetooth Proxy Support**: Compatible with ESPHome's bluetooth_proxy component for BLE device integration
- **Audio Feedback**: Built-in speaker support for audible feedback
- **Haptic Feedback**: Vibration motor support for tactile feedback
- **ESP-IDF Framework**: Enhanced performance and stability with ESP-IDF support
## Hardware Support
@@ -121,12 +164,11 @@ Follow these steps to get your TX Ultimate device up and running with ESPHome.
packages:
remote_package:
url: https://github.com/edwardtfn/TX-Ultimate-Easy
ref: stable # Or you can specify a version, like `ref: v2024.12.6` or `ref: latest` to the latest non-stable
ref: main # Or you can specify a version, like `ref: v2024.12.6` or `ref: latest`
refresh: 5min
files:
- ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml # Core (essential) packages
- ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml # Non-essential, but recommended packages
# - ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml # Adds BLE proxy support
```
You can also use a specific version tag for better control over updates:
```yaml
@@ -151,6 +193,27 @@ Follow these steps to get your TX Ultimate device up and running with ESPHome.
> ap: # Access point configuration
> ```
### Optional Features
#### Bluetooth Proxy
TX Ultimate Easy is fully compatible with ESPHome's `bluetooth_proxy` component.
To enable Bluetooth proxy functionality, add the following to your device configuration:
```yaml
bluetooth_proxy:
# Optional: Configure specific settings
# active: true
```
**Requirements for Bluetooth Proxy:**
- ESP-IDF framework (default framework, not recommended with Arduino framework)
- Sufficient memory (TX Ultimate Easy components are compatible,
but adding additional custom components may require memory optimization)
> [!NOTE]
> Bluetooth proxy functionality is provided by ESPHome's native component.
> TX Ultimate Easy ensures compatibility but does not include it by default to maintain optimal memory usage.
### Advanced Settings
For more granular control over components,
you can use our [advanced configuration template](TX-Ultimate-Easy-ESPHome_advanced.yaml).
@@ -165,7 +228,7 @@ Here's an example of the advanced configuration:
packages:
remote_package:
url: https://github.com/edwardtfn/TX-Ultimate-Easy
ref: stable # Or you can specify a version, like `ref: v2024.12.6` or `ref: latest` to the latest non-stable
ref: main # Or you can specify a version, like `ref: v2024.12.6` or `ref: latest`
refresh: 5min
files:
# Core (essential) packages
@@ -181,6 +244,9 @@ packages:
# Audio options (use none or choose only one - using both will fail)
- ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml # Media player (Recommended for most users)
# - ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml # Basic speaker
# Optional: Add Bluetooth proxy support
bluetooth_proxy:
```
> [!NOTE]
@@ -285,7 +351,7 @@ If the device isn't discovered automatically:
- Button actions
3. Optional: Configure advanced features
- LED behaviors
- Touch sensitivity
- Touch duration
- Haptic feedback
- Audio feedback
4. Test your configuration:
@@ -305,17 +371,18 @@ After installation, you can:
2. Customize touch behaviors
3. Set up LED patterns and effects
4. Create automations
5. Enable optional features like BLE proxy
5. Enable optional features like Bluetooth proxy
## Configuration Options
TX Ultimate Easy offers extensive configuration options:
- Touch panel sensitivity and gestures
- Touch panel gestures
- LED colors, patterns, and behaviors
- Relay modes and functions
- Audio and haptic feedback settings
- Network and connectivity options
- Optional Bluetooth proxy functionality
## Contributing

View File

@@ -0,0 +1,58 @@
// tx_ultimate_easy_light.cpp
#ifdef TX_ULTIMATE_EASY_CORE_HW_LEDS
#include "esphome/core/log.h"
#include "tx_ultimate_easy_light.h"
#include <cinttypes>
#include <string>
namespace esphome {
namespace tx_ultimate_easy {
// Log tag
static const char *TAG = "tx_ultimate_easy.light";
// Constant definitions
const LightAttributes LIGHT_ATTRS_DEFAULT = {100, 255, 255, 255};
const uint32_t LIGHT_ATTRS_DEFAULT_PACKED = 0x64FFFFFF;
const uint64_t DEFAULT_DUAL_LIGHT_ATTRS_PACKED = 0x64FFFFFF64FFFFFF;
// Function implementations
uint32_t pack_light_attributes(const LightAttributes& attr) {
return (uint32_t(std::min(attr.brightness, uint8_t(100))) << 24) |
(uint32_t(attr.red) << 16) |
(uint32_t(attr.green) << 8) |
(uint32_t(attr.blue));
}
LightAttributes unpack_light_attributes(uint32_t packed) {
return {
.brightness = std::min(uint8_t((packed >> 24) & 0xFF), uint8_t(100)),
.red = uint8_t((packed >> 16) & 0xFF),
.green = uint8_t((packed >> 8) & 0xFF),
.blue = uint8_t(packed & 0xFF)
};
}
uint64_t pack_dual_light_attributes(const LightAttributes& group1_attrs, const LightAttributes& group2_attrs) {
uint32_t group1_packed = pack_light_attributes(group1_attrs);
uint32_t group2_packed = pack_light_attributes(group2_attrs);
return (uint64_t(group2_packed) << 32) | uint64_t(group1_packed);
}
std::pair<LightAttributes, LightAttributes> unpack_dual_light_attributes(uint64_t packed) {
uint32_t group1_packed = uint32_t(packed & 0xFFFFFFFF); // Lower 32 bits
uint32_t group2_packed = uint32_t((packed >> 32) & 0xFFFFFFFF); // Upper 32 bits
return std::make_pair(
unpack_light_attributes(group1_packed),
unpack_light_attributes(group2_packed)
);
}
} // namespace tx_ultimate_easy
} // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_LEDS

View File

@@ -0,0 +1,116 @@
// tx_ultimate_easy_light.h
#pragma once
#ifdef TX_ULTIMATE_EASY_CORE_HW_LEDS
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/uart/uart_component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/light/addressable_light.h"
#include <array>
namespace esphome {
namespace tx_ultimate_easy {
/**
* @brief Light attributes structure for storing color and brightness data.
*
* This structure represents the visual attributes of a light without the state (on/off).
* It's designed to be packed efficiently into a 32-bit integer for NVS storage.
* RGB components use the full 8-bit range (0-255) while brightness uses percentage (0-100).
*/
struct LightAttributes {
uint8_t brightness; ///< Light brightness percentage (0-100, where 100 = full brightness)
uint8_t red; ///< Red color component (0-255)
uint8_t green; ///< Green color component (0-255)
uint8_t blue; ///< Blue color component (0-255)
};
/**
* @brief Packs LightAttributes structure into a 32-bit integer for storage.
*
* Bit layout: [brightness(8)][red(8)][green(8)][blue(8)]
* This allows efficient storage in ESPHome globals with restore_value support.
* Note: Brightness values > 100 will be clamped to 100 during packing.
*
* @param attr The LightAttributes structure to pack
* @return uint32_t Packed representation suitable for NVS storage
*
* @example
* LightAttributes attrs = {75, 255, 128, 64};
* uint32_t packed = pack_light_attributes(attrs); // Returns 0x4BFF8040
*/
uint32_t pack_light_attributes(const LightAttributes& attr);
/**
* @brief Unpacks a 32-bit integer back into LightAttributes structure.
*
* Reverses the packing operation to extract individual color and brightness values.
* Brightness values > 100 in the packed data will be clamped to 100.
*
* @param packed The packed 32-bit representation
* @return LightAttributes The unpacked structure with individual components
*
* @example
* uint32_t packed = 0x4BFF8040;
* LightAttributes attrs = unpack_light_attributes(packed);
* // attrs.brightness = 75, attrs.red = 255, attrs.green = 128, attrs.blue = 64
*/
LightAttributes unpack_light_attributes(uint32_t packed);
/**
* @brief Default light attributes for initialization.
*
* Provides sensible defaults for new lights:
* - Brightness: 100 (full brightness)
* - Red: 255 (full intensity)
* - Green: 255 (full intensity)
* - Blue: 255 (full intensity)
* Result: Pure white color at full brightness
*/
extern const LightAttributes LIGHT_ATTRS_DEFAULT;
/**
* @brief Pre-packed version of DEFAULT_LIGHT_ATTRS for convenience.
*
* This constant contains LIGHT_ATTRS_DEFAULT already packed into uint32_t format,
* suitable for direct use in ESPHome YAML initial_value fields.
*
* Value: 0x64FFFFFF (brightness=100, RGB=255,255,255)
*/
extern const uint32_t LIGHT_ATTRS_DEFAULT_PACKED;
/**
* @brief Packs two LightAttributes into a single uint64_t for dual light storage.
*
* Bit layout: [group2_light(32)][group1_light(32)]
* Each light uses 32 bits: [brightness(8)][red(8)][green(8)][blue(8)]
*
* @param group1_attrs First light attributes (stored in lower 32 bits)
* @param group2_attrs Second light attributes (stored in upper 32 bits)
* @return uint64_t Packed representation suitable for NVS storage
*/
uint64_t pack_dual_light_attributes(const LightAttributes& group1_attrs, const LightAttributes& group2_attrs);
/**
* @brief Unpacks uint64_t back into two LightAttributes structures.
*
* @param packed The packed 64-bit representation
* @return std::pair<LightAttributes, LightAttributes> Pair of (group1, group2) attributes
*/
std::pair<LightAttributes, LightAttributes> unpack_dual_light_attributes(uint64_t packed);
/**
* @brief Pre-packed version of dual default attributes for convenience.
*
* Value: 0x64FFFFFF64FFFFFF (both lights: brightness=100, RGB=255,255,255)
*/
extern const uint64_t DEFAULT_DUAL_LIGHT_ATTRS_PACKED;
} // namespace tx_ultimate_easy
} // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_LEDS

View File

@@ -1,13 +1,18 @@
// tx_ultimate_easy.cpp
// tx_ultimate_easy_touch.cpp
#ifdef TX_ULTIMATE_EASY_CORE_HW_TOUCH
#include "esphome/core/log.h"
#include "tx_ultimate_easy.h"
#include "tx_ultimate_easy_touch.h"
#include <cinttypes>
#include <string>
namespace esphome {
namespace tx_ultimate_easy {
// Log tag
static const char *TAG = "tx_ultimate_easy.touch";
void TxUltimateEasy::setup() {
ESP_LOGI(TAG, "TX Ultimate Easy is initialized");
}
@@ -69,9 +74,10 @@ namespace esphome {
if (this->gang_count_ == 1)
return 1;
//Calculate button number Change to round up insted of truncate (integer division)
const uint8_t width = (TOUCH_MAX_POSITION + gang_count_) / this->gang_count_; // Width of each button region
if (width < 1) // Invalid width - and prevents division by zero removed "width > gang_count_" issue with 2 gang
// Calculate button width (rounds up to ensure full coverage)
const uint8_t width =
(TOUCH_MAX_POSITION + this->gang_count_) / this->gang_count_; // Width of each button region
if (width < 1) // 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
@@ -141,8 +147,36 @@ namespace esphome {
switch (uart_received_bytes[4]) {
case TOUCH_STATE_RELEASE:
case TOUCH_STATE_MULTI_TOUCH:
return uart_received_bytes[5];
case TOUCH_STATE_SWIPE_LEFT:
case TOUCH_STATE_SWIPE_RIGHT:
// uart_received_bytes[5] indicates swipe direction:
// 12 = find last touch position (scan right to left)
// 13 = find first touch position (scan left to right)
// Bytes 6-7 contain a 10-bit bitmap of touch positions:
// Byte 7: bits 0-7 represent positions 1-8
// Byte 6: bits 0-1 represent positions 9-10
switch (uart_received_bytes[5]) {
case 12:
for (uint8_t i = 10; i > 0; i--) {
if (i > 8
? uart_received_bytes[6] & (1 << (i - 9))
: uart_received_bytes[7] & (1 << (i - 1))) {
return i;
}
}
break;
case 13:
for (uint8_t i = 1; i <= 10; i++) {
if (i > 8
? uart_received_bytes[6] & (1 << (i - 9))
: uart_received_bytes[7] & (1 << (i - 1))) {
return i;
}
}
break;
}
return uart_received_bytes[5];
default:
@@ -152,8 +186,10 @@ namespace esphome {
int TxUltimateEasy::get_touch_state(const std::array<int, UART_RECEIVED_BYTES_SIZE> &uart_received_bytes) {
int state = uart_received_bytes[4];
if (state == TOUCH_STATE_PRESS && uart_received_bytes[5] != 0) state = TOUCH_STATE_RELEASE;
if (state == TOUCH_STATE_RELEASE && uart_received_bytes[5] == TOUCH_STATE_MULTI_TOUCH) state = TOUCH_STATE_MULTI_TOUCH;
if (state == TOUCH_STATE_PRESS && uart_received_bytes[5] != 0)
state = TOUCH_STATE_RELEASE;
if (state == TOUCH_STATE_RELEASE && uart_received_bytes[5] == TOUCH_STATE_MULTI_TOUCH)
state = TOUCH_STATE_MULTI_TOUCH;
if (state == TOUCH_STATE_SWIPE) {
state = (uart_received_bytes[5] == TOUCH_STATE_SWIPE_RIGHT) ? TOUCH_STATE_SWIPE_RIGHT :
(uart_received_bytes[5] == TOUCH_STATE_SWIPE_LEFT) ? TOUCH_STATE_SWIPE_LEFT : state;
@@ -195,3 +231,5 @@ namespace esphome {
} // namespace tx_ultimate_easy
} // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_TOUCH

View File

@@ -1,7 +1,9 @@
// tx_ultimate_easy.h
// tx_ultimate_easy_touch.h
#pragma once
#ifdef TX_ULTIMATE_EASY_CORE_HW_TOUCH
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
@@ -31,9 +33,6 @@ namespace esphome {
constexpr int VALID_DATA_BYTE_2 = 0x01;
constexpr int VALID_DATA_BYTE_3 = 0x02;
// Log tag
static const char *TAG = "tx_ultimate_easy";
struct TouchPoint {
uint8_t button = 0;
int8_t x = -1;
@@ -84,3 +83,5 @@ namespace esphome {
} // namespace tx_ultimate_easy
} // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_TOUCH

View File

@@ -1 +1 @@
2025.7.2
2025.10.1

View File

@@ -1 +1 @@
version: 2025.7.2
version: 2025.10.1