Compare commits

...

32 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
13 changed files with 885 additions and 260 deletions

View File

@@ -65,6 +65,18 @@ jobs:
yaml-file: ".test/esphome_ard_core.yaml" yaml-file: ".test/esphome_ard_core.yaml"
version: latest version: latest
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 Arduino Core - dev
uses: esphome/build-action@main
with:
yaml-file: ".test/esphome_ard_core.yaml"
version: dev
build_ard_basic: build_ard_basic:
name: "📦 Arduino Basic (latest)" name: "📦 Arduino Basic (latest)"
needs: build_ard_core needs: build_ard_core
@@ -79,8 +91,8 @@ jobs:
build_ard_basic_dev: build_ard_basic_dev:
name: "📦 Arduino Basic (dev)" name: "📦 Arduino Basic (dev)"
needs: build_ard_core needs: build_ard_core_dev
if: success() && !github.event.inputs.skip_dev if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -141,7 +153,7 @@ jobs:
build_ard_ble_proxy_dev: build_ard_ble_proxy_dev:
name: "⚡ Arduino BLE Proxy (dev)" name: "⚡ Arduino BLE Proxy (dev)"
needs: build_ard_basic_dev needs: build_ard_basic_dev
if: success() && !github.event.inputs.skip_dev if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -164,6 +176,19 @@ jobs:
yaml-file: ".test/esphome_idf_core.yaml" yaml-file: ".test/esphome_idf_core.yaml"
version: latest 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: build_idf_basic:
name: "📦 IDF Basic (latest)" name: "📦 IDF Basic (latest)"
needs: build_idf_core needs: build_idf_core
@@ -178,8 +203,8 @@ jobs:
build_idf_basic_dev: build_idf_basic_dev:
name: "📦 IDF Basic (dev)" name: "📦 IDF Basic (dev)"
needs: build_idf_core needs: build_idf_core_dev
if: success() && !github.event.inputs.skip_dev if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -240,7 +265,7 @@ jobs:
build_idf_ble_proxy_dev: build_idf_ble_proxy_dev:
name: "⚡ IDF BLE Proxy (dev)" name: "⚡ IDF BLE Proxy (dev)"
needs: build_idf_basic_dev needs: build_idf_basic_dev
if: success() && !github.event.inputs.skip_dev if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
@@ -255,6 +280,7 @@ jobs:
name: "📊 Arduino Framework" name: "📊 Arduino Framework"
needs: needs:
- build_ard_core - build_ard_core
- build_ard_core_dev
- build_ard_basic - build_ard_basic
- build_ard_basic_dev - build_ard_basic_dev
- build_ard_hw_relays - build_ard_hw_relays
@@ -281,9 +307,10 @@ jobs:
# Count dev builds (if not skipped) # Count dev builds (if not skipped)
if [ "${{ github.event.inputs.skip_dev }}" != "true" ]; then 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_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
TOTAL=$((TOTAL + 2)) TOTAL=$((TOTAL + 3))
fi fi
echo "🤖 Arduino Framework: $SUCCESS/$TOTAL builds passed" echo "🤖 Arduino Framework: $SUCCESS/$TOTAL builds passed"
@@ -299,6 +326,7 @@ jobs:
name: "📊 ESP-IDF Framework" name: "📊 ESP-IDF Framework"
needs: needs:
- build_idf_core - build_idf_core
- build_idf_core_dev
- build_idf_basic - build_idf_basic
- build_idf_basic_dev - build_idf_basic_dev
- build_idf_hw_relays - build_idf_hw_relays
@@ -325,9 +353,10 @@ jobs:
# Count dev builds (if not skipped) # Count dev builds (if not skipped)
if [ "${{ github.event.inputs.skip_dev }}" != "true" ]; then 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_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
TOTAL=$((TOTAL + 2)) TOTAL=$((TOTAL + 3))
fi fi
echo "🔧 ESP-IDF Framework: $SUCCESS/$TOTAL builds passed" echo "🔧 ESP-IDF Framework: $SUCCESS/$TOTAL builds passed"
@@ -390,23 +419,27 @@ jobs:
summary_dev: summary_dev:
name: "📊 ESPHome (Dev)" name: "📊 ESPHome (Dev)"
needs: needs:
- build_ard_core_dev
- build_ard_basic_dev - build_ard_basic_dev
- build_ard_ble_proxy_dev - build_ard_ble_proxy_dev
- build_idf_core_dev
- build_idf_basic_dev - build_idf_basic_dev
- build_idf_ble_proxy_dev - build_idf_ble_proxy_dev
if: always() && !github.event.inputs.skip_dev if: ${{ always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Dev ESPHome Results - name: Dev ESPHome Results
run: | run: |
SUCCESS=0 SUCCESS=0
TOTAL=4 TOTAL=6
# Arduino Dev builds # 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_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
# ESP-IDF Dev builds # 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_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
[ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))

View File

@@ -1,23 +0,0 @@
####################################################################################################
##### TX Ultimate Easy for ESPHome #####
##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy #####
####################################################################################################
##### Purpose: ESPHome Core - Add-on - BLE Proxy (REMOVED) #####
####################################################################################################
##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo #####
####################################################################################################
##### REMOVAL NOTICE: #####
##### This package has been removed. Please update your configuration. #####
##### Add BLE proxy functionality directly in your YAML configuration instead: #####
##### bluetooth_proxy: #####
####################################################################################################
---
# Trigger the error at compile time
script:
- id: force_compilation_error
then:
- lambda: |-
#error "The package TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml has been removed." \
"Please remove this package from your configuration and add" \
"'bluetooth_proxy:' directly to your YAML instead."
...

View File

@@ -27,7 +27,7 @@ substitutions:
TX_MODEL_3_GANG_TEXT: "3 Gang" TX_MODEL_3_GANG_TEXT: "3 Gang"
TX_MODEL_4_GANG_TEXT: "4 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 TAG_CORE_COMMON: core.common
@@ -48,11 +48,18 @@ binary_sensor:
device_class: problem device_class: problem
on_state: on_state:
then: then:
- lambda: |- - script.execute: dump_config_caller
if (x)
ESP_LOGW("${TAG_CORE_COMMON}", "Pending restart: YES"); - id: bs_boot_completed
else name: Boot Completed
ESP_LOGD("${TAG_CORE_COMMON}", "Pending restart: No"); 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 - script.execute: dump_config_caller
button: button:
@@ -71,6 +78,7 @@ esphome:
name: ${name} name: ${name}
friendly_name: ${friendly_name} friendly_name: ${friendly_name}
comment: TX Ultimate Easy comment: TX Ultimate Easy
min_version: 2025.8.0
project: project:
name: "edwardtfn.tx_ultimate_easy" name: "edwardtfn.tx_ultimate_easy"
version: ${version} version: ${version}
@@ -79,6 +87,12 @@ esphome:
- -D TX_ULTIMATE_EASY_CORE_COMMON - -D TX_ULTIMATE_EASY_CORE_COMMON
on_boot: 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 - priority: 750
then: then:
- script.execute: restore_from_nvs - script.execute: restore_from_nvs
@@ -98,23 +112,23 @@ esphome:
external_components: external_components:
- source: - source:
type: git type: git
url: https://github.com/edwardtfn/TX-Ultimate-Easy url: https://git.andrejka.at/oneil1987/TX-Ultimate-Easy
ref: v${version} ref: v${version}
refresh: 1h refresh: 1h
components: components:
- tx_ultimate_easy - tx_ultimate_easy
globals: globals:
- id: is_us_model
type: bool
restore_value: true
initial_value: 'false'
- id: gang_count - id: gang_count
type: uint8_t type: uint8_t
restore_value: true restore_value: true
initial_value: '0' initial_value: '0'
- id: is_us_model
type: bool
restore_value: true
initial_value: 'false'
logger: logger:
level: DEBUG level: DEBUG
@@ -142,9 +156,16 @@ script:
- id: boot_done - id: boot_done
mode: restart mode: restart
then: then:
- script.wait: restore_from_nvs
- script.wait: boot_initialize
- script.wait: boot_sequence
- script.execute: - script.execute:
id: api_send_ha_event_boot id: api_send_ha_event_boot
type: done 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 - id: boot_initialize
mode: restart mode: restart
@@ -207,6 +228,12 @@ script:
ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Gangs (detected): %" PRIu8 "-Gang%s", ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Gangs (detected): %" PRIu8 "-Gang%s",
id(gang_count), id(gang_count) > 1 ? "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 // System state
if (bs_pending_restart->state) if (bs_pending_restart->state)
ESP_LOGW("${TAG_CORE_COMMON}", "Pending restart: YES"); ESP_LOGW("${TAG_CORE_COMMON}", "Pending restart: YES");
@@ -256,7 +283,9 @@ script:
index: !lambda if (id(is_us_model)) return 1; else return 0; index: !lambda if (id(is_us_model)) return 1; else return 0;
- select.set_index: - select.set_index:
id: sl_tx_model_gang id: sl_tx_model_gang
index: !lambda return (id(gang_count)-1); 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: select:
- id: sl_tx_model_format - id: sl_tx_model_format

View File

@@ -25,9 +25,9 @@ substitutions:
LIGHT_TRANSITION_TURN_OFF: '0' # Transition time in msec LIGHT_TRANSITION_TURN_OFF: '0' # Transition time in msec
LIGHT_BRIGHTNESS_TURN_ON: '0' # Brightness (1 to 100%, 0 to not set it) LIGHT_BRIGHTNESS_TURN_ON: '0' # Brightness (1 to 100%, 0 to not set it)
LIGHT_FULL_RESTORE_MODE: RESTORE_DEFAULT_OFF LIGHT_FULL_RESTORE_MODE: ALWAYS_OFF
LIGHT_SIDES_RESTORE_MODE: RESTORE_DEFAULT_OFF LIGHT_SIDES_RESTORE_MODE: ALWAYS_OFF
LIGHT_INDIVIDUAL_RESTORE_MODE: RESTORE_DEFAULT_OFF LIGHT_INDIVIDUAL_RESTORE_MODE: ALWAYS_OFF
LIGHT_ATTRS_DEFAULT: '0x64FFFFFF' # brightness=100, RGB=255,255,255 LIGHT_ATTRS_DEFAULT: '0x64FFFFFF' # brightness=100, RGB=255,255,255
LIGHT_DUAL_ATTRS_DEFAULT: '0x64FFFFFF64FFFFFF' # Both lights: brightness=100, RGB=255,255,255 LIGHT_DUAL_ATTRS_DEFAULT: '0x64FFFFFF64FFFFFF' # Both lights: brightness=100, RGB=255,255,255
@@ -39,6 +39,41 @@ esphome:
build_flags: build_flags:
- -D TX_ULTIMATE_EASY_CORE_HW_LEDS - -D TX_ULTIMATE_EASY_CORE_HW_LEDS
globals:
# 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: light:
# These lights are controlling the LEDs on the device # These lights are controlling the LEDs on the device
- id: light_full - id: light_full
@@ -102,12 +137,26 @@ light:
name: "Flicker" name: "Flicker"
update_interval: 16ms update_interval: 16ms
intensity: 5% intensity: 5%
- addressable_nachtlicht:
name: "Nachtlicht"
intensity: 25%
- random: - random:
name: "Random" name: "Random"
segments: segments:
- id: light_full - id: light_full
from: 0 from: 0
to: 27 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 - &light_partition_with_effects_us
id: light_us id: light_us
@@ -160,6 +209,17 @@ light:
- id: light_full - id: light_full
from: 0 from: 0
to: 31 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_no_effects_disabled - &light_partition_no_effects_disabled
id: light_eu_bottom id: light_eu_bottom
@@ -174,6 +234,13 @@ light:
from: 6 from: 6
to: 12 to: 12
reversed: true 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 - id: light_eu_left
name: Light - Left EU name: Light - Left EU
<<: *light_partition_no_effects_disabled <<: *light_partition_no_effects_disabled
@@ -182,6 +249,12 @@ light:
from: 13 from: 13
to: 19 to: 19
reversed: true 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 - id: light_eu_right
name: Light - Right EU name: Light - Right EU
<<: *light_partition_no_effects_disabled <<: *light_partition_no_effects_disabled
@@ -194,6 +267,12 @@ light:
from: 0 from: 0
to: 5 to: 5
reversed: false 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 - id: light_eu_top
name: Light - Top EU name: Light - Top EU
<<: *light_partition_no_effects_disabled <<: *light_partition_no_effects_disabled
@@ -202,6 +281,12 @@ light:
from: 20 from: 20
to: 26 to: 26
reversed: false 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 - id: light_us_bottom
name: Light - Bottom US name: Light - Bottom US
<<: *light_partition_no_effects_disabled <<: *light_partition_no_effects_disabled
@@ -210,6 +295,12 @@ light:
from: 9 from: 9
to: 14 to: 14
reversed: true 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 - id: light_us_left
name: Light - Left US name: Light - Left US
<<: *light_partition_no_effects_disabled <<: *light_partition_no_effects_disabled
@@ -218,6 +309,12 @@ light:
from: 15 from: 15
to: 24 to: 24
reversed: true 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 - id: light_us_right
name: Light - Right US name: Light - Right US
<<: *light_partition_no_effects_disabled <<: *light_partition_no_effects_disabled
@@ -230,6 +327,12 @@ light:
from: 0 from: 0
to: 8 to: 8
reversed: false 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 - id: light_us_top
name: Light - Top US name: Light - Top US
<<: *light_partition_no_effects_disabled <<: *light_partition_no_effects_disabled
@@ -238,6 +341,12 @@ light:
from: 25 from: 25
to: 30 to: 30
reversed: false 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 - &light_partition_individual_led
id: light_00 id: light_00
@@ -494,27 +603,6 @@ script:
- id: !extend dump_config - id: !extend dump_config
then: then:
- lambda: |- - lambda: |-
std::string gang_count_plural_suffix = id(gang_count) > 1 ? "s" : "";
// Relay's LEDs modes
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", "Relay%s LEDs mode%s:",
gang_count_plural_suffix.c_str(),
gang_count_plural_suffix.c_str());
ESP_LOGCONFIG("${TAG_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("${TAG_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("${TAG_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("${TAG_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 // Transition times
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", "Transition times:"); ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", "Transition times:");
ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Default: ${default_transition_length}"); ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Default: ${default_transition_length}");
@@ -552,6 +640,14 @@ script:
attr_global_ptr: uint32_t* attr_global_ptr: uint32_t*
then: then:
- lambda: |- - 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;
}
tx_ultimate_easy::LightAttributes attrs = tx_ultimate_easy::unpack_light_attributes(*attr_global_ptr); tx_ultimate_easy::LightAttributes attrs = tx_ultimate_easy::unpack_light_attributes(*attr_global_ptr);
auto call = light_ptr->make_call(); auto call = light_ptr->make_call();
call.set_brightness(attrs.brightness / 100.0f); call.set_brightness(attrs.brightness / 100.0f);
@@ -568,6 +664,18 @@ script:
group_index: uint8_t # 1 or 2 group_index: uint8_t # 1 or 2
then: then:
- lambda: |- - 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 attrs_pair = tx_ultimate_easy::unpack_dual_light_attributes(*attr_global_ptr); 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; tx_ultimate_easy::LightAttributes attrs = (group_index == 1) ? attrs_pair.first : attrs_pair.second;
@@ -575,6 +683,8 @@ script:
call.set_brightness(attrs.brightness / 100.0f); call.set_brightness(attrs.brightness / 100.0f);
call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f); call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f);
call.perform(); 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 - id: light_attributes_save
mode: parallel mode: parallel
@@ -583,6 +693,14 @@ script:
attr_global_ptr: uint32_t* attr_global_ptr: uint32_t*
then: then:
- lambda: |- - 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; auto current = light_ptr->current_values;
tx_ultimate_easy::LightAttributes attrs = { tx_ultimate_easy::LightAttributes attrs = {
.brightness = uint8_t(current.get_brightness() * 100.0f), .brightness = uint8_t(current.get_brightness() * 100.0f),
@@ -596,13 +714,25 @@ script:
attrs.brightness, attrs.red, attrs.green, attrs.blue); attrs.brightness, attrs.red, attrs.green, attrs.blue);
- id: light_attributes_save_dual - id: light_attributes_save_dual
mode: parallel mode: queued # To prevent two instances trying to change the same global
parameters: parameters:
light_ptr: light::LightState* light_ptr: light::LightState*
attr_global_ptr: uint64_t* attr_global_ptr: uint64_t*
group_index: uint8_t # 1 or 2 (which group within the dual pack) group_index: uint8_t # 1 or 2 (which group within the dual pack)
then: then:
- lambda: |- - 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; auto current = light_ptr->current_values;
tx_ultimate_easy::LightAttributes new_attrs = { tx_ultimate_easy::LightAttributes new_attrs = {
.brightness = uint8_t(current.get_brightness() * 100.0f), .brightness = uint8_t(current.get_brightness() * 100.0f),
@@ -621,58 +751,98 @@ script:
*attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(existing.first, new_attrs); *attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(existing.first, new_attrs);
} }
select: - id: light_effect_save
# EU relay light mode template mode: queued
- &relay_light_mode_eu parameters:
id: sl_relay_1_light_mode_eu light_ptr: light::LightState*
name: Relay 1 light mode EU effect_global_ptr: uint32_t*
platform: template then:
options: - lambda: |-
- "${LIGHT_MODE_DISABLED_TEXT}" if (!light_ptr || !effect_global_ptr) return;
- "${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
# US relay light mode template uint32_t effect_index = light_ptr->get_current_effect_index();
- &relay_light_mode_us // Store effect index (16 bits) + reserved space for future use
id: sl_relay_1_light_mode_us *effect_global_ptr = effect_index & 0xFFFF;
name: Relay 1 light mode US
platform: template ESP_LOGD("${TAG_CORE_HW_LEDS}", "Saved effect index: %d", effect_index);
options:
- "${LIGHT_MODE_DISABLED_TEXT}" - id: light_effect_restore
- "${LIGHT_MODE_LEFT_TEXT}" mode: parallel
- "${LIGHT_MODE_RIGHT_TEXT}" parameters:
- "${LIGHT_MODE_BOTH_TEXT}" light_ptr: light::LightState*
initial_option: "${LIGHT_MODE_RIGHT_TEXT}" effect_global_ptr: uint32_t*
optimistic: true then:
restore_value: true - lambda: |-
internal: true if (!light_ptr || !effect_global_ptr) return;
entity_category: config
disabled_by_default: false uint32_t effect_index = *effect_global_ptr & 0xFFFF;
icon: mdi:dots-square
- id: sl_relay_2_light_mode_us if (effect_index == 0) return; // No effect
name: Relay 2 light mode US if (effect_index > light_ptr->get_effect_count()) {
<<: *relay_light_mode_us ESP_LOGW("${TAG_CORE_HW_LEDS}", "Invalid effect index: %d (max: %d)",
- id: sl_relay_3_light_mode_us effect_index, light_ptr->get_effect_count());
name: Relay 3 light mode US return;
<<: *relay_light_mode_us }
- id: sl_relay_4_light_mode_us
name: Relay 4 light mode US auto call = light_ptr->make_call();
<<: *relay_light_mode_us 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

@@ -16,7 +16,12 @@ substitutions:
RELAY_MODE_TEXT_LIGHT: "Light" RELAY_MODE_TEXT_LIGHT: "Light"
RELAY_MODE_TEXT_NOT_USED: "Not in use" RELAY_MODE_TEXT_NOT_USED: "Not in use"
LIGHT_RELAYS_RESTORE_MODE: ALWAYS_OFF # You shouldn't change the restore mode as there is a custom restoring engine based on globals.
# The reason of a custom engine is to address an issue where NVS space was exceeded causing saving/restoring issues
RELAYS_LIGHT_RESTORE_MODE: ALWAYS_OFF
RELAYS_LIGHT_PARTITION_RESTORE_MODE: ALWAYS_OFF
RELAYS_EXPOSE_LEDS_TO_HA_RESTORE_MODE: ALWAYS_OFF
RELAYS_SWITCH_RESTORE_MODE: ALWAYS_OFF
TAG_STD_HW_RELAYS: std.hw.relays TAG_STD_HW_RELAYS: std.hw.relays
@@ -26,6 +31,30 @@ esphome:
- -D TX_ULTIMATE_EASY_STANDARD_HW_RELAYS - -D TX_ULTIMATE_EASY_STANDARD_HW_RELAYS
globals: globals:
- &global_attr_dual_light
id: attr_light_rl_1_1_both # 1-gang: relay 1, both groups
type: uint64_t
restore_value: true
initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}'
- id: attr_light_rl_1_2_both # 2-gang: relay 1, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_2_2_both # 2-gang: relay 2, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_1_3_both # 3-gang: relay 1, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_2_3_both # 3-gang: relay 2, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_3_3_both # 3-gang: relay 3, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_1_4_both # 4-gang: relay 1, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_2_4_both # 4-gang: relay 2, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_3_4_both # 4-gang: relay 3, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_4_4_both # 4-gang: relay 4, both groups
<<: *global_attr_dual_light
- id: boot_initialization_relays - id: boot_initialization_relays
type: bool type: bool
restore_value: false restore_value: false
@@ -54,12 +83,20 @@ globals:
type: std::vector<light::LightState*> type: std::vector<light::LightState*>
restore_value: false restore_value: false
# Packed relay light modes - stores current model's 4 light modes
- id: relay_light_modes_packed
type: uint16_t
restore_value: true
initial_value: '0x1111' # All 4 relays set to option 1 (bottom/left)
# Bit layout: [relay4(4)][relay3(4)][relay2(4)][relay1(4)]
# 16-bit value with 4 bits per relay: 0=Disabled, 1=Bottom/Left, 2=Top/Right, 3=Both, 4-15=Reserved
# Packed relay modes - replaces 4 separate selects # Packed relay modes - replaces 4 separate selects
- id: relay_modes_packed - id: relay_modes_packed
type: uint16_t type: uint16_t
restore_value: true restore_value: true
initial_value: '0' # Binary: 0000000000000000 = all relay modes set to option 0 (default action - switch) initial_value: '0' # Binary: 0000000000000000 = all relay modes set to option 0 (default action - switch)
# Bit layout: [unused][relay4(4)][relay3(4)][relay2(4)][relay1(4)] # Bit layout: [relay4(4)][relay3(4)][relay2(4)][relay1(4)]
# 16-bit value with 4 bits per relay: 0=Switch, 1=Light, 2=Not in use, 3-15=Reserved # 16-bit value with 4 bits per relay: 0=Switch, 1=Light, 2=Not in use, 3-15=Reserved
# Packed switch states - replaces 5 separate switches # Packed switch states - replaces 5 separate switches
@@ -67,31 +104,7 @@ globals:
type: uint8_t type: uint8_t
restore_value: true restore_value: true
initial_value: '0' # All switches default to OFF initial_value: '0' # All switches default to OFF
# Bit layout: [unused(3)][sw_relay4][sw_relay3][sw_relay2][sw_relay1][sw_expose_leds] # Bit layout: [sw_relay4][sw_relay3][sw_relay2][sw_relay1][sw_expose_leds]
- &global_attr_dual_light
id: attr_light_rl_1_1_both # 1-gang: relay 1, both groups
type: uint64_t
restore_value: true
initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}'
- id: attr_light_rl_1_2_both # 2-gang: relay 1, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_2_2_both # 2-gang: relay 2, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_1_3_both # 3-gang: relay 1, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_2_3_both # 3-gang: relay 2, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_3_3_both # 3-gang: relay 3, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_1_4_both # 4-gang: relay 1, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_2_4_both # 4-gang: relay 2, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_3_4_both # 4-gang: relay 3, both groups
<<: *global_attr_dual_light
- id: attr_light_rl_4_4_both # 4-gang: relay 4, both groups
<<: *global_attr_dual_light
light: light:
# These lights are used for the physical relays to be shown as lights # These lights are used for the physical relays to be shown as lights
@@ -100,7 +113,7 @@ light:
output: output_relay_1 output: output_relay_1
platform: binary platform: binary
internal: true internal: true
restore_mode: ${LIGHT_RELAYS_RESTORE_MODE} restore_mode: ${RELAYS_LIGHT_RESTORE_MODE}
on_turn_on: on_turn_on:
then: then:
- if: - if:
@@ -120,6 +133,7 @@ light:
output: output_relay_2 output: output_relay_2
platform: binary platform: binary
internal: true internal: true
restore_mode: ${RELAYS_LIGHT_RESTORE_MODE}
on_turn_on: on_turn_on:
then: then:
- if: - if:
@@ -139,6 +153,7 @@ light:
output: output_relay_3 output: output_relay_3
platform: binary platform: binary
internal: true internal: true
restore_mode: ${RELAYS_LIGHT_RESTORE_MODE}
on_turn_on: on_turn_on:
then: then:
- if: - if:
@@ -158,6 +173,7 @@ light:
output: output_relay_4 output: output_relay_4
platform: binary platform: binary
internal: true internal: true
restore_mode: ${RELAYS_LIGHT_RESTORE_MODE}
on_turn_on: on_turn_on:
then: then:
- if: - if:
@@ -181,7 +197,7 @@ light:
internal: true internal: true
disabled_by_default: false disabled_by_default: false
default_transition_length: ${default_transition_length} default_transition_length: ${default_transition_length}
restore_mode: ${LIGHT_RELAYS_RESTORE_MODE} restore_mode: ${RELAYS_LIGHT_PARTITION_RESTORE_MODE}
segments: segments:
- id: light_full - id: light_full
from: 9 from: 9
@@ -897,8 +913,8 @@ script:
// Set indicator (either switch or light) visible on Home Assistant // Set indicator (either switch or light) visible on Home Assistant
auto relay_mode_index = sl_relay_1_mode->active_index(); auto relay_mode_index = sl_relay_1_mode->active_index();
if (relay_mode_index.has_value()) { if (relay_mode_index.has_value()) {
light_output_1->set_internal(false or relay_mode_index.value() != 1); light_output_1->set_internal(relay_mode_index.value() != 1);
sw_relay_1->set_internal(false or relay_mode_index.value() != 0); sw_relay_1->set_internal(relay_mode_index.value() != 0);
} }
relay_mode_index = sl_relay_2_mode->active_index(); relay_mode_index = sl_relay_2_mode->active_index();
if (relay_mode_index.has_value()) { if (relay_mode_index.has_value()) {
@@ -1028,6 +1044,25 @@ script:
ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Relay modes: %s", YESNO(id(boot_initialization_relays_relay_modes))); ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Relay modes: %s", YESNO(id(boot_initialization_relays_relay_modes)));
ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Status LEDs: %s", YESNO(id(boot_initialization_relays_relay_leds))); ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Status LEDs: %s", YESNO(id(boot_initialization_relays_relay_leds)));
// Relay's LEDs modes
ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", "Relay%s LEDs mode%s:", gang_count_plural_suffix.c_str(),
gang_count_plural_suffix.c_str());
ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " 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("${TAG_STD_HW_RELAYS}", " 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("${TAG_STD_HW_RELAYS}", " 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("${TAG_STD_HW_RELAYS}", " 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());
- id: !extend dump_config_list_packages - id: !extend dump_config_list_packages
then: then:
- script.wait: dump_config - script.wait: dump_config
@@ -1087,11 +1122,13 @@ script:
break; break;
case 2: case 2:
if (light_index >= id(gb_lights_2).size()) { if (light_index >= id(gb_lights_2).size()) {
ESP_LOGE("${TAG_STD_HW_RELAYS}", "Invalid set with %s light for Relay %" PRIu8, light_mode_group_2.c_str(), light_index+1); ESP_LOGE("${TAG_STD_HW_RELAYS}",
"Invalid set with %s light for Relay %" PRIu8, light_mode_group_2.c_str(), light_index+1);
return; return;
} }
if (state and !id(gb_lights_2)[light_index]->current_values.is_on()) { if (state and !id(gb_lights_2)[light_index]->current_values.is_on()) {
ESP_LOGI("${TAG_STD_HW_RELAYS}", "Turn-on %s light for Relay %" PRIu8, light_mode_group_2.c_str(), light_index+1); ESP_LOGI("${TAG_STD_HW_RELAYS}",
"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(); auto call = id(gb_lights_2)[light_index]->turn_on();
if (LIGHT_TRANSITION_TURN_ON > 0) if (LIGHT_TRANSITION_TURN_ON > 0)
call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms
@@ -1099,13 +1136,17 @@ script:
call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f); call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f);
call.perform(); call.perform();
} else if (!state and id(gb_lights_2)[light_index]->current_values.is_on()) { } else if (!state and id(gb_lights_2)[light_index]->current_values.is_on()) {
ESP_LOGI("${TAG_STD_HW_RELAYS}", "Turn-off %s light for Relay %" PRIu8, light_mode_group_2.c_str(), light_index+1); ESP_LOGI("${TAG_STD_HW_RELAYS}",
"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(); auto call = id(gb_lights_2)[light_index]->turn_off();
if (LIGHT_TRANSITION_TURN_OFF > 0) if (LIGHT_TRANSITION_TURN_OFF > 0)
call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms
call.perform(); call.perform();
} }
break; break;
default:
ESP_LOGE("${TAG_STD_HW_RELAYS}", "Invalid light_group=%" PRIu8 " (expected 1 or 2)", light_group);
break;
} }
- id: !extend restore_from_nvs - id: !extend restore_from_nvs
@@ -1124,42 +1165,50 @@ script:
id: sl_relay_4_mode id: sl_relay_4_mode
index: !lambda return std::min((uint8_t)((id(relay_modes_packed) >> 12) & 0x0F), (uint8_t)2); index: !lambda return std::min((uint8_t)((id(relay_modes_packed) >> 12) & 0x0F), (uint8_t)2);
# Light mode restoration - restore to active model's selects only
# Restore US light modes
- select.set_index:
id: sl_relay_1_light_mode_us
index: !lambda return std::min((uint8_t)(id(relay_light_modes_packed) & 0x0F), (uint8_t)3);
- select.set_index:
id: sl_relay_2_light_mode_us
index: !lambda return std::min((uint8_t)((id(relay_light_modes_packed) >> 4) & 0x0F), (uint8_t)3);
- select.set_index:
id: sl_relay_3_light_mode_us
index: !lambda return std::min((uint8_t)((id(relay_light_modes_packed) >> 8) & 0x0F), (uint8_t)3);
- select.set_index:
id: sl_relay_4_light_mode_us
index: !lambda return std::min((uint8_t)((id(relay_light_modes_packed) >> 12) & 0x0F), (uint8_t)3);
# Restore EU light modes
- select.set_index:
id: sl_relay_1_light_mode_eu
index: !lambda return std::min((uint8_t)(id(relay_light_modes_packed) & 0x0F), (uint8_t)3);
- select.set_index:
id: sl_relay_2_light_mode_eu
index: !lambda return std::min((uint8_t)((id(relay_light_modes_packed) >> 4) & 0x0F), (uint8_t)3);
- select.set_index:
id: sl_relay_3_light_mode_eu
index: !lambda return std::min((uint8_t)((id(relay_light_modes_packed) >> 8) & 0x0F), (uint8_t)3);
- select.set_index:
id: sl_relay_4_light_mode_eu
index: !lambda return std::min((uint8_t)((id(relay_light_modes_packed) >> 12) & 0x0F), (uint8_t)3);
# Switch states # Switch states
- if: - switch.control:
condition: id: sw_expose_relays_leds_to_ha
lambda: return (id(relay_switches_packed) & 0x01) == 0; state: !lambda return (id(relay_switches_packed) & 0x01);
then: - switch.control:
- switch.turn_off: sw_expose_relays_leds_to_ha id: sw_relay_1
else: state: !lambda return (id(relay_switches_packed) & 0x02);
- switch.turn_on: sw_expose_relays_leds_to_ha - switch.control:
- if: id: sw_relay_2
condition: state: !lambda return (id(relay_switches_packed) & 0x04);
lambda: return (id(relay_switches_packed) & 0x02) == 0; - switch.control:
then: id: sw_relay_3
- switch.turn_off: sw_relay_1 state: !lambda return (id(relay_switches_packed) & 0x08);
else: - switch.control:
- switch.turn_on: sw_relay_1 id: sw_relay_4
- if: state: !lambda return (id(relay_switches_packed) & 0x10);
condition:
lambda: return (id(relay_switches_packed) & 0x04) == 0;
then:
- switch.turn_off: sw_relay_2
else:
- switch.turn_on: sw_relay_2
- if:
condition:
lambda: return (id(relay_switches_packed) & 0x08) == 0;
then:
- switch.turn_off: sw_relay_3
else:
- switch.turn_on: sw_relay_3
- if:
condition:
lambda: return (id(relay_switches_packed) & 0x10) == 0;
then:
- switch.turn_off: sw_relay_4
else:
- switch.turn_on: sw_relay_4
# update lights based on new relay statuses (catch up, as this should be done when each relay is updated) # update lights based on new relay statuses (catch up, as this should be done when each relay is updated)
- script.execute: show_relay_status - script.execute: show_relay_status
@@ -1294,6 +1343,116 @@ script:
} }
select: 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: false
internal: true
entity_category: config
disabled_by_default: false
icon: mdi:dots-square
on_value:
- lambda: |-
// Update packed value (existing code)
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 0)) |
((id(sl_relay_1_light_mode_eu).active_index().value_or(1) & 0x0F) << 0);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = ""; // Static variable local to THIS lambda only
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 1 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 0, false); // Group 1, relay index 0
id(light_set_state)->execute(2, 0, false); // Group 2, relay index 0
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 1 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_1).state;
if (current_mode == "${LIGHT_MODE_BOTTOM_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 0, relay_state);
}
if (current_mode == "${LIGHT_MODE_TOP_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 0, relay_state);
}
}
previous_mode = current_mode;
# 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: false
internal: true
entity_category: config
disabled_by_default: false
icon: mdi:dots-square
on_value:
- lambda: |-
// Update packed value
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 0)) |
((id(sl_relay_1_light_mode_us).active_index().value_or(1) & 0x0F) << 0);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = "";
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 1 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 0, false); // Group 1, relay index 0
id(light_set_state)->execute(2, 0, false); // Group 2, relay index 0
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 1 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_1).state;
if (current_mode == "${LIGHT_MODE_LEFT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 0, relay_state);
}
if (current_mode == "${LIGHT_MODE_RIGHT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 0, relay_state);
}
}
previous_mode = current_mode;
- id: sl_relay_1_mode - id: sl_relay_1_mode
name: Relay 1 display mode name: Relay 1 display mode
platform: template platform: template
@@ -1303,7 +1462,7 @@ select:
- "${RELAY_MODE_TEXT_NOT_USED}" - "${RELAY_MODE_TEXT_NOT_USED}"
initial_option: "${RELAY_MODE_TEXT_SWITCH}" initial_option: "${RELAY_MODE_TEXT_SWITCH}"
optimistic: true optimistic: true
restore_value: true restore_value: false
internal: true internal: true
entity_category: config entity_category: config
disabled_by_default: false disabled_by_default: false
@@ -1315,7 +1474,90 @@ select:
state: true state: true
- lambda: |- - lambda: |-
id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 0)) | id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 0)) |
((id(sl_relay_1_mode).active_index().value_or(1) & 0x0F) << 0); ((id(sl_relay_1_mode).active_index().value_or(0) & 0x0F) << 0);
- id: sl_relay_2_light_mode_eu
name: Relay 2 light mode EU
<<: *relay_light_mode_eu
on_value:
- lambda: |-
// Update packed value
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 4)) |
((id(sl_relay_2_light_mode_eu).active_index().value_or(1) & 0x0F) << 4);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = "";
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 2 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 1, false); // Group 1, relay index 1
id(light_set_state)->execute(2, 1, false); // Group 2, relay index 1
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 2 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_2).state;
if (current_mode == "${LIGHT_MODE_BOTTOM_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 1, relay_state);
}
if (current_mode == "${LIGHT_MODE_TOP_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 1, relay_state);
}
}
previous_mode = current_mode;
- id: sl_relay_2_light_mode_us
name: Relay 2 light mode US
<<: *relay_light_mode_us
on_value:
- lambda: |-
// Update packed value
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 4)) |
((id(sl_relay_2_light_mode_us).active_index().value_or(1) & 0x0F) << 4);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = "";
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 2 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 1, false); // Group 1, relay index 1
id(light_set_state)->execute(2, 1, false); // Group 2, relay index 1
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 2 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_2).state;
if (current_mode == "${LIGHT_MODE_LEFT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 1, relay_state);
}
if (current_mode == "${LIGHT_MODE_RIGHT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 1, relay_state);
}
}
previous_mode = current_mode;
- id: sl_relay_2_mode - id: sl_relay_2_mode
name: Relay 2 display mode name: Relay 2 display mode
platform: template platform: template
@@ -1325,7 +1567,7 @@ select:
- "${RELAY_MODE_TEXT_NOT_USED}" - "${RELAY_MODE_TEXT_NOT_USED}"
initial_option: "${RELAY_MODE_TEXT_SWITCH}" initial_option: "${RELAY_MODE_TEXT_SWITCH}"
optimistic: true optimistic: true
restore_value: true restore_value: false
internal: true internal: true
entity_category: config entity_category: config
disabled_by_default: false disabled_by_default: false
@@ -1337,7 +1579,90 @@ select:
state: true state: true
- lambda: |- - lambda: |-
id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 4)) | id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 4)) |
((id(sl_relay_2_mode).active_index().value_or(1) & 0x0F) << 4); ((id(sl_relay_2_mode).active_index().value_or(0) & 0x0F) << 4);
- id: sl_relay_3_light_mode_eu
name: Relay 3 light mode EU
<<: *relay_light_mode_eu
on_value:
- lambda: |-
// Update packed value
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 8)) |
((id(sl_relay_3_light_mode_eu).active_index().value_or(1) & 0x0F) << 8);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = "";
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 3 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 2, false); // Group 1, relay index 2
id(light_set_state)->execute(2, 2, false); // Group 2, relay index 2
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 3 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_3).state;
if (current_mode == "${LIGHT_MODE_BOTTOM_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 2, relay_state);
}
if (current_mode == "${LIGHT_MODE_TOP_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 2, relay_state);
}
}
previous_mode = current_mode;
- id: sl_relay_3_light_mode_us
name: Relay 3 light mode US
<<: *relay_light_mode_us
on_value:
- lambda: |-
// Update packed value
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 8)) |
((id(sl_relay_3_light_mode_us).active_index().value_or(1) & 0x0F) << 8);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = "";
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 3 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 2, false); // Group 1, relay index 2
id(light_set_state)->execute(2, 2, false); // Group 2, relay index 2
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}",
"Relay 3 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_3).state;
if (current_mode == "${LIGHT_MODE_LEFT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 2, relay_state);
}
if (current_mode == "${LIGHT_MODE_RIGHT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 2, relay_state);
}
}
previous_mode = current_mode;
- id: sl_relay_3_mode - id: sl_relay_3_mode
name: Relay 3 display mode name: Relay 3 display mode
platform: template platform: template
@@ -1347,7 +1672,7 @@ select:
- "${RELAY_MODE_TEXT_NOT_USED}" - "${RELAY_MODE_TEXT_NOT_USED}"
initial_option: "${RELAY_MODE_TEXT_SWITCH}" initial_option: "${RELAY_MODE_TEXT_SWITCH}"
optimistic: true optimistic: true
restore_value: true restore_value: false
internal: true internal: true
entity_category: config entity_category: config
disabled_by_default: false disabled_by_default: false
@@ -1359,7 +1684,86 @@ select:
state: true state: true
- lambda: |- - lambda: |-
id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 8)) | id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 8)) |
((id(sl_relay_3_mode).active_index().value_or(1) & 0x0F) << 8); ((id(sl_relay_3_mode).active_index().value_or(0) & 0x0F) << 8);
- id: sl_relay_4_light_mode_eu
name: Relay 4 light mode EU
<<: *relay_light_mode_eu
on_value:
- lambda: |-
// Update packed value
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 12)) |
((id(sl_relay_4_light_mode_eu).active_index().value_or(1) & 0x0F) << 12);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = "";
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}", "Relay 4 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 3, false); // Group 1, relay index 3
id(light_set_state)->execute(2, 3, false); // Group 2, relay index 3
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}", "Relay 4 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_4).state;
if (current_mode == "${LIGHT_MODE_BOTTOM_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 3, relay_state);
}
if (current_mode == "${LIGHT_MODE_TOP_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 3, relay_state);
}
}
previous_mode = current_mode;
- id: sl_relay_4_light_mode_us
name: Relay 4 light mode US
<<: *relay_light_mode_us
on_value:
- lambda: |-
// Update packed value
id(relay_light_modes_packed) = (id(relay_light_modes_packed) & ~(0x0F << 12)) |
((id(sl_relay_4_light_mode_us).active_index().value_or(1) & 0x0F) << 12);
// Handle mode transitions (only after boot is complete)
if (!id(bs_boot_completed)->state) return; // Skip during boot
static std::string previous_mode = "";
std::string current_mode = x;
if (previous_mode.empty()) {
previous_mode = current_mode; // Initialize on first run after boot
return;
}
if (previous_mode != "${LIGHT_MODE_DISABLED_TEXT}" && current_mode == "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning TO Disabled - turn off lights
ESP_LOGI("${TAG_STD_HW_RELAYS}", "Relay 4 light mode changed to ${LIGHT_MODE_DISABLED_TEXT} - turning off lights");
id(light_set_state)->execute(1, 3, false); // Group 1, relay index 3
id(light_set_state)->execute(2, 3, false); // Group 2, relay index 3
} else if (previous_mode == "${LIGHT_MODE_DISABLED_TEXT}" && current_mode != "${LIGHT_MODE_DISABLED_TEXT}") {
// Transitioning FROM Disabled - update lights based on relay state
ESP_LOGI("${TAG_STD_HW_RELAYS}", "Relay 4 light mode changed from ${LIGHT_MODE_DISABLED_TEXT} - updating lights");
bool relay_state = id(sw_relay_4).state;
if (current_mode == "${LIGHT_MODE_LEFT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(1, 3, relay_state);
}
if (current_mode == "${LIGHT_MODE_RIGHT_TEXT}" || current_mode == "${LIGHT_MODE_BOTH_TEXT}") {
id(light_set_state)->execute(2, 3, relay_state);
}
}
previous_mode = current_mode;
- id: sl_relay_4_mode - id: sl_relay_4_mode
name: Relay 4 display mode name: Relay 4 display mode
platform: template platform: template
@@ -1369,7 +1773,7 @@ select:
- "${RELAY_MODE_TEXT_NOT_USED}" - "${RELAY_MODE_TEXT_NOT_USED}"
initial_option: "${RELAY_MODE_TEXT_SWITCH}" initial_option: "${RELAY_MODE_TEXT_SWITCH}"
optimistic: true optimistic: true
restore_value: true restore_value: false
internal: true internal: true
entity_category: config entity_category: config
disabled_by_default: false disabled_by_default: false
@@ -1381,133 +1785,128 @@ select:
state: true state: true
- lambda: |- - lambda: |-
id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 12)) | id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 12)) |
((id(sl_relay_4_mode).active_index().value_or(1) & 0x0F) << 12); ((id(sl_relay_4_mode).active_index().value_or(0) & 0x0F) << 12);
switch: switch:
- id: sw_expose_relays_leds_to_ha - id: sw_expose_relays_leds_to_ha
name: Expose Relay's LEDs to Home Assistant name: Expose Relay's LEDs to Home Assistant
platform: template platform: template
restore_mode: ALWAYS_OFF restore_mode: ${RELAYS_EXPOSE_LEDS_TO_HA_RESTORE_MODE}
optimistic: true optimistic: true
internal: false internal: false
entity_category: config entity_category: config
on_turn_on: on_state:
then: then:
- binary_sensor.template.publish: - binary_sensor.template.publish:
id: bs_pending_restart id: bs_pending_restart
state: true state: true
- lambda: |- - if:
id(relay_switches_packed) |= 0x01; // Set bit 0 condition:
on_turn_off: - lambda: return x; // Turned on?
then: then:
- binary_sensor.template.publish: - lambda: id(relay_switches_packed) |= 0x01; // Set bit 0
id: bs_pending_restart else:
state: true - lambda: id(relay_switches_packed) &= ~0x01; // Clear bit 0
- lambda: |-
id(relay_switches_packed) &= ~0x01; // Clear bit 0
- id: sw_relay_1 - id: sw_relay_1
name: Relay 1 name: Relay 1
output: output_relay_1 output: output_relay_1
platform: output platform: output
restore_mode: ALWAYS_OFF restore_mode: ${RELAYS_SWITCH_RESTORE_MODE}
internal: true internal: true
on_turn_on: on_state:
then: then:
- script.execute: show_relay_status - script.execute: show_relay_status
on_turn_on:
then:
- if: - if:
condition: condition:
- light.is_off: light_output_1 - light.is_off: light_output_1
then: then:
light.turn_on: light_output_1 light.turn_on: light_output_1
- lambda: |- - lambda: id(relay_switches_packed) |= 0x02; // Set bit 1
id(relay_switches_packed) |= 0x02; // Set bit 1
on_turn_off: on_turn_off:
then: then:
- script.execute: show_relay_status
- if: - if:
condition: condition:
- light.is_on: light_output_1 - light.is_on: light_output_1
then: then:
- light.turn_off: light_output_1 - light.turn_off: light_output_1
- lambda: |- - lambda: id(relay_switches_packed) &= ~0x02; // Clear bit 1
id(relay_switches_packed) &= ~0x02; // Clear bit 1
- id: sw_relay_2 - id: sw_relay_2
name: Relay 2 name: Relay 2
output: output_relay_2 output: output_relay_2
platform: output platform: output
restore_mode: ALWAYS_OFF restore_mode: ${RELAYS_SWITCH_RESTORE_MODE}
internal: true internal: true
on_turn_on: on_state:
then: then:
- script.execute: show_relay_status - script.execute: show_relay_status
on_turn_on:
then:
- if: - if:
condition: condition:
- light.is_off: light_output_2 - light.is_off: light_output_2
then: then:
light.turn_on: light_output_2 light.turn_on: light_output_2
- lambda: |- - lambda: id(relay_switches_packed) |= 0x04; // Set bit 2
id(relay_switches_packed) |= 0x04; // Set bit 2
on_turn_off: on_turn_off:
then: then:
- script.execute: show_relay_status
- if: - if:
condition: condition:
- light.is_on: light_output_2 - light.is_on: light_output_2
then: then:
- light.turn_off: light_output_2 - light.turn_off: light_output_2
- lambda: |- - lambda: id(relay_switches_packed) &= ~0x04; // Clear bit 2
id(relay_switches_packed) &= ~0x04; // Clear bit 2
- id: sw_relay_3 - id: sw_relay_3
name: Relay 3 name: Relay 3
output: output_relay_3 output: output_relay_3
platform: output platform: output
restore_mode: ALWAYS_OFF restore_mode: ${RELAYS_SWITCH_RESTORE_MODE}
internal: true internal: true
on_turn_on: on_state:
then: then:
- script.execute: show_relay_status - script.execute: show_relay_status
on_turn_on:
then:
- if: - if:
condition: condition:
- light.is_off: light_output_3 - light.is_off: light_output_3
then: then:
- light.turn_on: light_output_3 - light.turn_on: light_output_3
- lambda: |- - lambda: id(relay_switches_packed) |= 0x08; // Set bit 3
id(relay_switches_packed) |= 0x08; // Set bit 3
on_turn_off: on_turn_off:
then: then:
- script.execute: show_relay_status
- if: - if:
condition: condition:
- light.is_on: light_output_3 - light.is_on: light_output_3
then: then:
- light.turn_off: light_output_3 - light.turn_off: light_output_3
- lambda: |- - lambda: id(relay_switches_packed) &= ~0x08; // Clear bit 3
id(relay_switches_packed) &= ~0x08; // Clear bit 3
- id: sw_relay_4 - id: sw_relay_4
name: Relay 4 name: Relay 4
output: output_relay_4 output: output_relay_4
platform: output platform: output
restore_mode: ALWAYS_OFF restore_mode: ${RELAYS_SWITCH_RESTORE_MODE}
internal: true internal: true
on_turn_on: on_state:
then: then:
- script.execute: show_relay_status - script.execute: show_relay_status
on_turn_on:
then:
- if: - if:
condition: condition:
- light.is_off: light_output_4 - light.is_off: light_output_4
then: then:
- light.turn_on: light_output_4 - light.turn_on: light_output_4
- lambda: |- - lambda: id(relay_switches_packed) |= 0x10; // Set bit 4
id(relay_switches_packed) |= 0x10; // Set bit 4
on_turn_off: on_turn_off:
then: then:
- script.execute: show_relay_status
- if: - if:
condition: condition:
- light.is_on: light_output_4 - light.is_on: light_output_4
then: then:
- light.turn_off: light_output_4 - light.turn_off: light_output_4
- lambda: |- - lambda: id(relay_switches_packed) &= ~0x10; // Clear bit 4
id(relay_switches_packed) &= ~0x10; // Clear bit 4
... ...

View File

@@ -151,20 +151,17 @@ switch:
pin: GPIO21 pin: GPIO21
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
internal: true internal: true
on_turn_on: on_state:
then: then:
- binary_sensor.template.publish: - binary_sensor.template.publish:
id: bs_vibrating id: bs_vibrating
state: ON # yamllint disable-line rule:truthy state: !lambda return x;
on_turn_on:
then:
- delay: ${vibration_max_duration} - delay: ${vibration_max_duration}
- if: - if:
condition: condition:
switch.is_on: sw_vibration_motor switch.is_on: sw_vibration_motor
then: then:
- switch.turn_off: sw_vibration_motor - 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

@@ -27,6 +27,9 @@
[buymeacoffee-shield]: https://img.shields.io/static/v1?label=Buy%20me%20an%20ice%20cream&message=❄&color=blue [buymeacoffee-shield]: https://img.shields.io/static/v1?label=Buy%20me%20an%20ice%20cream&message=❄&color=blue
[buymeacoffee]: https://www.buymeacoffee.com/edwardfirmo [buymeacoffee]: https://www.buymeacoffee.com/edwardfirmo
> [!WARNING]
> ESPHome builder v2025.8.0+ is required
## Framework Migration to ESP-IDF ## Framework Migration to ESP-IDF
> [!IMPORTANT] > [!IMPORTANT]

View File

@@ -1,5 +1,7 @@
// tx_ultimate_easy_light.cpp // tx_ultimate_easy_light.cpp
#ifdef TX_ULTIMATE_EASY_CORE_HW_LEDS
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "tx_ultimate_easy_light.h" #include "tx_ultimate_easy_light.h"
#include <cinttypes> #include <cinttypes>
@@ -8,6 +10,9 @@
namespace esphome { namespace esphome {
namespace tx_ultimate_easy { namespace tx_ultimate_easy {
// Log tag
static const char *TAG = "tx_ultimate_easy.light";
// Constant definitions // Constant definitions
const LightAttributes LIGHT_ATTRS_DEFAULT = {100, 255, 255, 255}; const LightAttributes LIGHT_ATTRS_DEFAULT = {100, 255, 255, 255};
const uint32_t LIGHT_ATTRS_DEFAULT_PACKED = 0x64FFFFFF; const uint32_t LIGHT_ATTRS_DEFAULT_PACKED = 0x64FFFFFF;
@@ -15,7 +20,7 @@ namespace esphome {
// Function implementations // Function implementations
uint32_t pack_light_attributes(const LightAttributes& attr) { uint32_t pack_light_attributes(const LightAttributes& attr) {
return (uint32_t(attr.brightness) << 24) | return (uint32_t(std::min(attr.brightness, uint8_t(100))) << 24) |
(uint32_t(attr.red) << 16) | (uint32_t(attr.red) << 16) |
(uint32_t(attr.green) << 8) | (uint32_t(attr.green) << 8) |
(uint32_t(attr.blue)); (uint32_t(attr.blue));
@@ -23,7 +28,7 @@ namespace esphome {
LightAttributes unpack_light_attributes(uint32_t packed) { LightAttributes unpack_light_attributes(uint32_t packed) {
return { return {
.brightness = uint8_t((packed >> 24) & 0xFF), .brightness = std::min(uint8_t((packed >> 24) & 0xFF), uint8_t(100)),
.red = uint8_t((packed >> 16) & 0xFF), .red = uint8_t((packed >> 16) & 0xFF),
.green = uint8_t((packed >> 8) & 0xFF), .green = uint8_t((packed >> 8) & 0xFF),
.blue = uint8_t(packed & 0xFF) .blue = uint8_t(packed & 0xFF)
@@ -49,3 +54,5 @@ namespace esphome {
} // namespace tx_ultimate_easy } // namespace tx_ultimate_easy
} // namespace esphome } // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_LEDS

View File

@@ -2,6 +2,8 @@
#pragma once #pragma once
#ifdef TX_ULTIMATE_EASY_CORE_HW_LEDS
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
@@ -13,9 +15,6 @@
namespace esphome { namespace esphome {
namespace tx_ultimate_easy { namespace tx_ultimate_easy {
// Log tag
static const char *TAG = "tx_ultimate_easy.light";
/** /**
* @brief Light attributes structure for storing color and brightness data. * @brief Light attributes structure for storing color and brightness data.
* *
@@ -113,3 +112,5 @@ namespace esphome {
} // namespace tx_ultimate_easy } // namespace tx_ultimate_easy
} // namespace esphome } // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_LEDS

View File

@@ -1,5 +1,7 @@
// tx_ultimate_easy_touch.cpp // tx_ultimate_easy_touch.cpp
#ifdef TX_ULTIMATE_EASY_CORE_HW_TOUCH
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "tx_ultimate_easy_touch.h" #include "tx_ultimate_easy_touch.h"
#include <cinttypes> #include <cinttypes>
@@ -8,6 +10,9 @@
namespace esphome { namespace esphome {
namespace tx_ultimate_easy { namespace tx_ultimate_easy {
// Log tag
static const char *TAG = "tx_ultimate_easy.touch";
void TxUltimateEasy::setup() { void TxUltimateEasy::setup() {
ESP_LOGI(TAG, "TX Ultimate Easy is initialized"); ESP_LOGI(TAG, "TX Ultimate Easy is initialized");
} }
@@ -69,8 +74,9 @@ namespace esphome {
if (this->gang_count_ == 1) if (this->gang_count_ == 1)
return 1; return 1;
// Calculate button number Change to round up instead of truncate (integer division) // Calculate button width (rounds up to ensure full coverage)
const uint8_t width = (TOUCH_MAX_POSITION + gang_count_) / this->gang_count_; // Width of each button region 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 if (width < 1) // Invalid width - and prevents division by zero
return 0; return 0;
const uint8_t button = std::min( const uint8_t button = std::min(
@@ -225,3 +231,5 @@ namespace esphome {
} // namespace tx_ultimate_easy } // namespace tx_ultimate_easy
} // namespace esphome } // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_TOUCH

View File

@@ -2,6 +2,8 @@
#pragma once #pragma once
#ifdef TX_ULTIMATE_EASY_CORE_HW_TOUCH
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/uart/uart.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_2 = 0x01;
constexpr int VALID_DATA_BYTE_3 = 0x02; constexpr int VALID_DATA_BYTE_3 = 0x02;
// Log tag
static const char *TAG = "tx_ultimate_easy.touch";
struct TouchPoint { struct TouchPoint {
uint8_t button = 0; uint8_t button = 0;
int8_t x = -1; int8_t x = -1;
@@ -84,3 +83,5 @@ namespace esphome {
} // namespace tx_ultimate_easy } // namespace tx_ultimate_easy
} // namespace esphome } // namespace esphome
#endif // TX_ULTIMATE_EASY_CORE_HW_TOUCH

View File

@@ -1 +1 @@
2025.8.2 2025.10.1

View File

@@ -1 +1 @@
version: 2025.8.2dev version: 2025.10.1