From 4d7895a2e9235f74546889e184369d96691ffb12 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Fri, 2 May 2025 18:29:07 +0200 Subject: [PATCH 1/3] Remove enforcing psram mode to octal Solves #105 --- ESPHome/TX-Ultimate-Easy-ESPHome.yaml | 19 + .../TX-Ultimate-Easy-ESPHome_core_common.yaml | 320 ++++++ .../TX-Ultimate-Easy-ESPHome_standard.yaml | 20 + ...timate-Easy-ESPHome_standard_hw_audio.yaml | 36 + ...imate-Easy-ESPHome_standard_hw_relays.yaml | 970 ++++++++++++++++++ ...mate-Easy-ESPHome_standard_hw_speaker.yaml | 182 ++++ ...te-Easy-ESPHome_standard_hw_vibration.yaml | 168 +++ ...te-Easy-ESPHome_standard_media_player.yaml | 64 ++ TX-Ultimate-Easy-ESPHome_advanced.yaml | 38 + 9 files changed, 1817 insertions(+) create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome.yaml create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml create mode 100644 ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml create mode 100644 TX-Ultimate-Easy-ESPHome_advanced.yaml diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome.yaml new file mode 100644 index 0000000..061c27c --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome.yaml @@ -0,0 +1,19 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Purpose: ESPHome Basic (includes core plus non-essential, but recommended packages) ##### +#################################################################################################### +##### 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: + # yamllint disable rule:colons + core: !include TX-Ultimate-Easy-ESPHome_core.yaml + standard: !include TX-Ultimate-Easy-ESPHome_standard.yaml + # yamllint enable rule:colons +... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml new file mode 100644 index 0000000..dc41971 --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml @@ -0,0 +1,320 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Purpose: ESPHome Core - Common ##### +#################################################################################################### +##### 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. ##### +#################################################################################################### +--- +substitutions: + name: tx-ultimate-easy + friendly_name: TX Ultimate Easy + + <<: !include ../versioning/VERSION_YAML + + TX_MODEL_FORMAT_EU_TEXT: "EU (Square, T5-xC-86)" + TX_MODEL_FORMAT_US_TEXT: "US (Rectangle, T5-xC-120)" + + TX_MODEL_1_GANG_TEXT: "1 Gang" + TX_MODEL_2_GANG_TEXT: "2 Gang" + 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 + + +api: + id: api_server + on_client_connected: + then: + - script.execute: dump_config_caller + - script.execute: publish_device_info + +binary_sensor: + - id: bs_pending_restart + name: Pending Restart Status + internal: false + disabled_by_default: false + platform: template + entity_category: diagnostic + 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 + +button: + - id: bt_restart + name: Restart + internal: false + platform: restart + +esp32: + board: esp32dev + flash_size: 8MB + +esphome: + name: ${name} + friendly_name: ${friendly_name} + comment: TX Ultimate Easy + project: + name: "edwardtfn.tx_ultimate_easy" + version: ${version} + platformio_options: + build_flags: + - -D TX_ULTIMATE_EASY_CORE + + on_boot: + - priority: 700 + then: + - script.execute: boot_initialize + + - priority: 600 # This is where most sensors are set up. + then: + - script.execute: boot_sequence + + - priority: -100 # At this priority, pretty much everything should already be initialized. + then: + - script.execute: boot_done + +globals: + - id: is_us_model + type: bool + restore_value: true + initial_value: 'false' + + - id: gang_count + type: uint8_t + restore_value: true + initial_value: '0' + + - id: gang_count_plural_suffix + type: std::string + restore_value: true + max_restore_data_length: 3 + +logger: + level: DEBUG + +ota: + platform: esphome + +psram: + +script: + - id: api_send_ha_event_boot + mode: queued + parameters: + type: string + then: + - homeassistant.event: + event: esphome.tx_ultimate_easy + data: + device_name: !lambda return tx_device_name->state.c_str(); + firmware: ${version} + domain: boot + type: !lambda return type.c_str(); + + - id: boot_done + mode: restart + then: + - script.execute: + id: api_send_ha_event_boot + type: done + + - id: boot_initialize + mode: restart + then: + # Extended by: + # - HW Buttons + # - HW Relays + # - HW Touch + - script.execute: + id: api_send_ha_event_boot + type: start + - wait_until: + condition: + - lambda: return sl_tx_model_format->active_index().has_value(); + - lambda: return sl_tx_model_gang->active_index().has_value(); + - lambda: |- + 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)); + } + id(gang_count_plural_suffix) = id(gang_count) > 1 ? "s" : ""; + + - id: boot_sequence + mode: restart + then: + - script.execute: publish_device_info + - binary_sensor.template.publish: + id: bs_pending_restart + state: false + + - id: dump_config + mode: restart + then: + # 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()); + + dump_config_versions->execute(); + + // Framework detection + #ifdef USE_ARDUINO + ESP_LOGCONFIG("core", "Framework: Arduino"); + #elif defined(USE_ESP_IDF) + ESP_LOGCONFIG("core", "Framework: ESP-IDF"); + #else + ESP_LOGW("core", "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()); + + // System state + if (bs_pending_restart->state) + ESP_LOGW("core", "Pending restart: YES"); + else + ESP_LOGCONFIG("core", "Pending restart: No"); + + - id: dump_config_caller + mode: restart + then: + - delay: ${DUMP_CONFIG_CALLER_DELAY} + - script.execute: dump_config + - script.wait: dump_config + - script.execute: dump_config_list_packages + + - id: dump_config_list_packages + mode: restart + then: + - script.wait: dump_config + - lambda: |- + ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, "Installed packages:"); + + // Identify itself + ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Core"); + + - id: dump_config_versions + mode: restart + then: + - lambda: |- + // Version information + ESP_LOGCONFIG("core", "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()); + + - id: publish_device_info + mode: restart + then: + - lambda: |- + tx_fw_version->publish_state("${version}"); + tx_device_name->publish_state(App.get_name().c_str()); + +select: + - id: sl_tx_model_format + name: Model (Format) + platform: template + options: + - "${TX_MODEL_FORMAT_EU_TEXT}" + - "${TX_MODEL_FORMAT_US_TEXT}" + initial_option: "${TX_MODEL_FORMAT_EU_TEXT}" + optimistic: true + restore_value: true + internal: false + entity_category: config + disabled_by_default: false + icon: mdi:tablet-cellphone + on_value: + then: + - binary_sensor.template.publish: + id: bs_pending_restart + 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) + platform: template + options: + - "${TX_MODEL_1_GANG_TEXT}" + - "${TX_MODEL_2_GANG_TEXT}" + - "${TX_MODEL_3_GANG_TEXT}" + - "${TX_MODEL_4_GANG_TEXT}" + initial_option: "${TX_MODEL_1_GANG_TEXT}" + optimistic: true + restore_value: true + internal: false + entity_category: config + disabled_by_default: false + icon: mdi:dip-switch + on_value: + then: + - binary_sensor.template.publish: + id: bs_pending_restart + state: true + - lambda: |- + id(gang_count) = static_cast(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: + - id: tx_fw_version + name: TX Ultimate Easy Firmware Version + platform: template + entity_category: diagnostic + icon: mdi:tag-text-outline + internal: false + update_interval: never + lambda: |- + return {"${version}"}; + + - id: tx_device_name + name: Device Name + platform: template + icon: mdi:identifier + entity_category: diagnostic + internal: false + disabled_by_default: false + update_interval: never + lambda: |- + return {"${name}"}; + filters: + - lambda: |- + const std::string raw_name = x; + std::string result; + bool last_was_underscore = false; + for (const char& c : raw_name) { + if (isalnum(c)) { + result += tolower(c); // Add alphanumeric characters as lowercase + last_was_underscore = false; + } else if (!last_was_underscore) { // Replace non-alphanumeric with '_' but avoid consecutive '_' + result += '_'; + last_was_underscore = true; + } + } + return result; + +wifi: + id: wifi_component +... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml new file mode 100644 index 0000000..17cbe16 --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard.yaml @@ -0,0 +1,20 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Purpose: ESPHome Standard (non-essential, but recommended packages) ##### +#################################################################################################### +##### 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: + # yamllint disable rule:colons + standard_hw_relays: !include TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml + standard_hw_vibration: !include TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml + standard_media_player: !include TX-Ultimate-Easy-ESPHome_standard_media_player.yaml + # yamllint enable rule:colons +... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml new file mode 100644 index 0000000..3dcabb1 --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml @@ -0,0 +1,36 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Purpose: ESPHome - Standard - I2S Audio ##### +#################################################################################################### +##### 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. ##### +#################################################################################################### +--- +esphome: + platformio_options: + build_flags: + - -D TX_ULTIMATE_EASY_STANDARD_HW_AUDIO + +i2s_audio: + id: if_i2s_audio + i2s_bclk_pin: GPIO2 + i2s_lrclk_pin: GPIO4 + +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, " - Standard - Hardware - Audio"); +... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml new file mode 100644 index 0000000..bb738ad --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml @@ -0,0 +1,970 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Purpose: ESPHome - Standard - Hardware - Relays ##### +#################################################################################################### +##### 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. ##### +#################################################################################################### +--- +substitutions: + RELAY_MODE_TEXT_SWITCH: "Switch" + RELAY_MODE_TEXT_LIGHT: "Light" + RELAY_MODE_TEXT_NOT_USED: "Not in use" + + RELAY_RESTORE_MODE: RESTORE_DEFAULT_OFF + LIGHT_RELAYS_RESTORE_MODE: RESTORE_DEFAULT_OFF + +esphome: + platformio_options: + build_flags: + - -D TX_ULTIMATE_EASY_STANDARD_HW_RELAYS + +globals: + - id: boot_initialization_relays + type: bool + restore_value: false + initial_value: 'false' + - id: boot_initialization_relays_group_assignments + type: bool + restore_value: false + initial_value: 'false' + - id: boot_initialization_relays_light_modes + type: bool + restore_value: false + initial_value: 'false' + - id: boot_initialization_relays_relay_leds + type: bool + restore_value: false + initial_value: 'false' + - id: boot_initialization_relays_relay_modes + type: bool + restore_value: false + initial_value: 'false' + +light: + # These lights are used for the physical relays to be shown as lights + - id: light_output_1 + name: Light output 1 + output: output_relay_1 + platform: binary + internal: true + restore_mode: ${RELAY_RESTORE_MODE} + on_turn_on: + then: + - if: + condition: + - switch.is_off: sw_relay_1 + then: + - switch.turn_on: sw_relay_1 + on_turn_off: + then: + - if: + condition: + - switch.is_on: sw_relay_1 + then: + - switch.turn_off: sw_relay_1 + - id: light_output_2 + name: Light output 2 + output: output_relay_2 + platform: binary + internal: true + on_turn_on: + then: + - if: + condition: + - switch.is_off: sw_relay_2 + then: + - switch.turn_on: sw_relay_2 + on_turn_off: + then: + - if: + condition: + - switch.is_on: sw_relay_2 + then: + - switch.turn_off: sw_relay_2 + - id: light_output_3 + name: Light output 3 + output: output_relay_3 + platform: binary + internal: true + on_turn_on: + then: + - if: + condition: + - switch.is_off: sw_relay_3 + then: + - switch.turn_on: sw_relay_3 + on_turn_off: + then: + - if: + condition: + - switch.is_on: sw_relay_3 + then: + - switch.turn_off: sw_relay_3 + - id: light_output_4 + name: Light output 4 + output: output_relay_4 + platform: binary + internal: true + on_turn_on: + then: + - if: + condition: + - switch.is_off: sw_relay_4 + then: + - switch.turn_on: sw_relay_4 + on_turn_off: + then: + - if: + condition: + - switch.is_on: sw_relay_4 + then: + - switch.turn_off: sw_relay_4 + + # These lights are used to indicate relay's statuses + - &light_partition_relays + id: light_eu_rl_1_1_bottom + name: Light - Relay 1 of 1 (bottom) + platform: partition + internal: true + disabled_by_default: false + default_transition_length: ${default_transition_length} + restore_mode: ${LIGHT_RELAYS_RESTORE_MODE} + segments: + - id: light_full + from: 9 + to: 9 + - id: light_eu_rl_1_1_top + name: Light - Relay 1 of 1 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 23 + to: 23 + - id: light_eu_rl_1_2_bottom + name: Light - Relay 1 of 2 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 11 + to: 11 + - id: light_eu_rl_1_2_top + name: Light - Relay 1 of 2 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 21 + to: 21 + - id: light_eu_rl_2_2_bottom + name: Light - Relay 2 of 2 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 7 + to: 7 + - id: light_eu_rl_2_2_top + name: Light - Relay 2 of 2 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 25 + to: 25 + - id: light_eu_rl_1_3_bottom + name: Light - Relay 1 of 3 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 12 + to: 12 + - id: light_eu_rl_1_3_top + name: Light - Relay 1 of 3 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 20 + to: 20 + - id: light_eu_rl_2_3_bottom + name: Light - Relay 2 of 3 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 9 + to: 9 + - id: light_eu_rl_2_3_top + name: Light - Relay 2 of 3 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 23 + to: 23 + - id: light_eu_rl_3_3_bottom + name: Light - Relay 3 of 3 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 6 + to: 6 + - id: light_eu_rl_3_3_top + name: Light - Relay 3 of 3 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 26 + to: 26 + - id: light_eu_rl_1_4_bottom + name: Light - Relay 1 of 4 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 12 + to: 12 + - id: light_eu_rl_1_4_top + name: Light - Relay 1 of 4 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 20 + to: 20 + - id: light_eu_rl_2_4_bottom + name: Light - Relay 2 of 4 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 10 + to: 10 + - id: light_eu_rl_2_4_top + name: Light - Relay 2 of 4 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 22 + to: 22 + - id: light_eu_rl_3_4_bottom + name: Light - Relay 3 of 4 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 8 + to: 8 + - id: light_eu_rl_3_4_top + name: Light - Relay 3 of 4 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 24 + to: 24 + - id: light_eu_rl_4_4_bottom + name: Light - Relay 4 of 4 (bottom) + <<: *light_partition_relays + segments: + - id: light_full + from: 6 + to: 6 + - id: light_eu_rl_4_4_top + name: Light - Relay 4 of 4 (top) + <<: *light_partition_relays + segments: + - id: light_full + from: 26 + to: 26 + - id: light_us_rl_1_1_left + name: Light - Relay 1 of 1 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 15 + to: 24 + - id: light_us_rl_1_1_right + name: Light - Relay 1 of 1 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 31 + to: 31 + - id: light_full + from: 0 + to: 8 + - id: light_us_rl_1_2_left + name: Light - Relay 1 of 2 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 20 + to: 24 + - id: light_us_rl_1_2_right + name: Light - Relay 1 of 2 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 31 + to: 31 + - id: light_full + from: 0 + to: 3 + - id: light_us_rl_2_2_left + name: Light - Relay 2 of 2 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 15 + to: 19 + - id: light_us_rl_2_2_right + name: Light - Relay 2 of 2 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 4 + to: 8 + - id: light_us_rl_1_3_left + name: Light - Relay 1 of 3 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 22 + to: 23 + - id: light_us_rl_1_3_right + name: Light - Relay 1 of 3 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 0 + to: 1 + - id: light_us_rl_2_3_left + name: Light - Relay 2 of 3 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 19 + to: 20 + - id: light_us_rl_2_3_right + name: Light - Relay 2 of 3 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 3 + to: 4 + - id: light_us_rl_3_3_left + name: Light - Relay 3 of 3 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 16 + to: 17 + - id: light_us_rl_3_3_right + name: Light - Relay 3 of 3 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 6 + to: 7 + - id: light_us_rl_1_4_left + name: Light - Relay 1 of 4 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 23 + to: 24 + - id: light_us_rl_1_4_right + name: Light - Relay 1 of 4 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 31 + to: 31 + - id: light_full + from: 0 + to: 0 + - id: light_us_rl_2_4_left + name: Light - Relay 2 of 4 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 21 + to: 22 + - id: light_us_rl_2_4_right + name: Light - Relay 2 of 4 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 1 + to: 2 + - id: light_us_rl_3_4_left + name: Light - Relay 3 of 4 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 17 + to: 18 + - id: light_us_rl_3_4_right + name: Light - Relay 3 of 4 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 5 + to: 6 + - id: light_us_rl_4_4_left + name: Light - Relay 4 of 4 (left) + <<: *light_partition_relays + segments: + - id: light_full + from: 15 + to: 16 + - id: light_us_rl_4_4_right + name: Light - Relay 4 of 4 (right) + <<: *light_partition_relays + segments: + - id: light_full + from: 7 + to: 8 + +output: + - id: output_relay_1 + platform: gpio + pin: GPIO18 + - id: output_relay_2 + platform: gpio + pin: GPIO17 + - id: output_relay_3 + platform: gpio + pin: GPIO27 + - id: output_relay_4 + platform: gpio + pin: GPIO23 + +script: + - id: !extend boot_initialize + then: + - script.execute: boot_initialize_relays + + - id: boot_initialize_relays + mode: restart + then: + - script.execute: boot_initialize_relays_group_assignments + - script.execute: boot_initialize_relays_light_modes + - script.execute: boot_initialize_relays_relay_leds + - script.execute: boot_initialize_relays_relay_modes + - script.wait: boot_initialize_relays_group_assignments + - script.wait: boot_initialize_relays_light_modes + - script.wait: boot_initialize_relays_relay_leds + - script.wait: boot_initialize_relays_relay_modes + - wait_until: + condition: + - lambda: return id(boot_initialization_relays_group_assignments); + - lambda: return id(boot_initialization_relays_light_modes); + - lambda: return id(boot_initialization_relays_relay_leds); + - lambda: return id(boot_initialization_relays_relay_modes); + - globals.set: + id: boot_initialization_relays + value: 'true' + + - id: boot_initialize_relays_group_assignments + mode: restart + then: + - lambda: |- + // LED Group Assignment: + // - For US model: gb_lights_1 = left side LEDs, gb_lights_2 = right side LEDs + // - For EU model: gb_lights_1 = bottom LEDs, gb_lights_2 = top LEDs + // Gang configurations determine which LED segments are active + switch (id(gang_count)) { + case 1: // 1 Gang + if (id(is_us_model)) { + id(gb_lights_1) = { light_us_rl_1_1_left }; + id(gb_lights_2) = { light_us_rl_1_1_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_1_bottom }; + id(gb_lights_2) = { light_eu_rl_1_1_top }; + } + break; + case 2: // 2 Gang + if (id(is_us_model)) { + id(gb_lights_1) = { light_us_rl_1_2_left, light_us_rl_2_2_left }; + id(gb_lights_2) = { light_us_rl_1_2_right, light_us_rl_2_2_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_2_bottom, light_eu_rl_2_2_bottom }; + id(gb_lights_2) = { light_eu_rl_1_2_top, light_eu_rl_2_2_top }; + } + break; + case 3: // 3 Gang + if (id(is_us_model)) { + id(gb_lights_1) = { light_us_rl_1_3_left, light_us_rl_2_3_left, light_us_rl_3_3_left }; + id(gb_lights_2) = { light_us_rl_1_3_right, light_us_rl_2_3_right, light_us_rl_3_3_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_3_bottom, light_eu_rl_2_3_bottom, light_eu_rl_3_3_bottom }; + id(gb_lights_2) = { light_eu_rl_1_3_top, light_eu_rl_2_3_top, light_eu_rl_3_3_top }; + } + break; + case 4: // 4 Gang + if (id(is_us_model)) { + id(gb_lights_1) = { light_us_rl_1_4_left, light_us_rl_2_4_left, + light_us_rl_3_4_left, light_us_rl_4_4_left }; + id(gb_lights_2) = { light_us_rl_1_4_right, light_us_rl_2_4_right, + light_us_rl_3_4_right, light_us_rl_4_4_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_4_bottom, light_eu_rl_2_4_bottom, + light_eu_rl_3_4_bottom, light_eu_rl_4_4_bottom }; + id(gb_lights_2) = { light_eu_rl_1_4_top, light_eu_rl_2_4_top, + light_eu_rl_3_4_top, light_eu_rl_4_4_top }; + } + break; + default: + ESP_LOGE("standard_hw_relays", "Invalid gang count: %" PRIu8, id(gang_count)); + break; + } + id(boot_initialization_relays_group_assignments) = true; + + - id: boot_initialize_relays_light_modes + mode: restart + then: + - lambda: |- + // EU light modes + sl_relay_1_light_mode_eu->set_internal(id(is_us_model) or id(gang_count) < 1); + sl_relay_2_light_mode_eu->set_internal(id(is_us_model) or id(gang_count) < 2); + sl_relay_3_light_mode_eu->set_internal(id(is_us_model) or id(gang_count) < 3); + sl_relay_4_light_mode_eu->set_internal(id(is_us_model) or id(gang_count) < 4); + + // US light modes + sl_relay_1_light_mode_us->set_internal(!id(is_us_model) or id(gang_count) < 1); + sl_relay_2_light_mode_us->set_internal(!id(is_us_model) or id(gang_count) < 2); + sl_relay_3_light_mode_us->set_internal(!id(is_us_model) or id(gang_count) < 3); + sl_relay_4_light_mode_us->set_internal(!id(is_us_model) or id(gang_count) < 4); + + // Set this section of initialization as completed + id(boot_initialization_relays_light_modes) = true; + + - id: boot_initialize_relays_relay_leds + mode: restart + then: + - lambda: |- + const bool set_internal = not sw_expose_relays_leds_to_ha->state; + + // EU model LEDs + // EU model - 1 Gang + light_eu_rl_1_1_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 1); + light_eu_rl_1_1_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 1); + // EU model - 2 Gang + light_eu_rl_1_2_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 2); + light_eu_rl_1_2_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 2); + light_eu_rl_2_2_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 2); + light_eu_rl_2_2_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 2); + // EU model - 3 Gang + light_eu_rl_1_3_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 3); + light_eu_rl_1_3_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 3); + light_eu_rl_2_3_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 3); + light_eu_rl_2_3_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 3); + light_eu_rl_3_3_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 3); + light_eu_rl_3_3_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 3); + // EU model - 4 Gang + light_eu_rl_1_4_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + light_eu_rl_1_4_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + light_eu_rl_2_4_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + light_eu_rl_2_4_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + light_eu_rl_3_4_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + light_eu_rl_3_4_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + light_eu_rl_4_4_bottom->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + light_eu_rl_4_4_top->set_internal(set_internal or id(is_us_model) or id(gang_count) != 4); + + // US model LEDs + // US model - 1 Gang + light_us_rl_1_1_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 1); + light_us_rl_1_1_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 1); + // US model - 2 Gang + light_us_rl_1_2_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 2); + light_us_rl_1_2_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 2); + light_us_rl_2_2_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 2); + light_us_rl_2_2_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 2); + // US model - 3 Gang + light_us_rl_1_3_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 3); + light_us_rl_1_3_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 3); + light_us_rl_2_3_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 3); + light_us_rl_2_3_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 3); + light_us_rl_3_3_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 3); + light_us_rl_3_3_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 3); + // US model - 4 Gang + light_us_rl_1_4_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + light_us_rl_1_4_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + light_us_rl_2_4_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + light_us_rl_2_4_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + light_us_rl_3_4_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + light_us_rl_3_4_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + light_us_rl_4_4_left->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + light_us_rl_4_4_right->set_internal(set_internal or !id(is_us_model) or id(gang_count) != 4); + + // Set this section of initialization as completed + id(boot_initialization_relays_relay_leds) = true; + + - id: boot_initialize_relays_relay_modes + mode: restart + then: + - lambda: |- + // Set selector internal based on the model (num of relays) + sl_relay_1_mode->set_internal(id(gang_count) < 1); + sl_relay_2_mode->set_internal(id(gang_count) < 2); + sl_relay_3_mode->set_internal(id(gang_count) < 3); + sl_relay_4_mode->set_internal(id(gang_count) < 4); + + // Set indicator (either switch or light) visible on Home Assistant + auto relay_mode_index = sl_relay_1_mode->active_index(); + if (relay_mode_index.has_value()) { + light_output_1->set_internal(false or relay_mode_index.value() != 1); + sw_relay_1->set_internal(false or relay_mode_index.value() != 0); + } + relay_mode_index = sl_relay_2_mode->active_index(); + if (relay_mode_index.has_value()) { + light_output_2->set_internal(id(gang_count) < 2 or relay_mode_index.value() != 1); + sw_relay_2->set_internal(id(gang_count) < 2 or relay_mode_index.value() != 0); + } + relay_mode_index = sl_relay_3_mode->active_index(); + if (relay_mode_index.has_value()) { + light_output_3->set_internal(id(gang_count) < 3 or relay_mode_index.value() != 1); + sw_relay_3->set_internal(id(gang_count) < 3 or relay_mode_index.value() != 0); + } + relay_mode_index = sl_relay_4_mode->active_index(); + if (relay_mode_index.has_value()) { + light_output_4->set_internal(id(gang_count) < 4 or relay_mode_index.value() != 1); + sw_relay_4->set_internal(id(gang_count) < 4 or relay_mode_index.value() != 0); + } + + // Set this section of initialization as completed + id(boot_initialization_relays_relay_modes) = true; + + - id: !extend button_click_event + then: + - lambda: |- + // Handle only single clicks + if (click_count != 1) + return; + + // Ignore if other touch events are active + if (bs_multi_touch->state || + bs_swipe_left->state || + bs_swipe_down->state || + bs_swipe_right->state) { + return; + } + + // Toggle relay if corresponding button action is enabled + const bool api_disconnected = !global_api_server->is_connected() or + !wifi_component->is_connected(); + std::string select_state; + bool toggle_relay = false; + if (api_disconnected) { + ESP_LOGW("standard_hw_relays", "Toggle relays:"); + ESP_LOGW("standard_hw_relays", " API state: DISCONNECTED"); + ESP_LOGW("standard_hw_relays", " Button: %" PRIu8, button); + } else { + ESP_LOGVV("standard_hw_relays", "Toggle relays:"); + ESP_LOGVV("standard_hw_relays", " API state: Connected"); + ESP_LOGVV("standard_hw_relays", " Button: %" PRIu8, button); + } + switch (button) { + case ${BUTTON_1_ID}: + select_state = sl_button_1_action->state; + toggle_relay = (select_state == "${BUTTON_1_ACTION_TEXT}" or + (api_disconnected and select_state == "${BUTTON_1_ACTION_FAILSAFE_TEXT}")); + if (toggle_relay) + sw_relay_1->toggle(); + break; + case ${BUTTON_2_ID}: + select_state = sl_button_2_action->state; + toggle_relay = (select_state == "${BUTTON_2_ACTION_TEXT}" or + (api_disconnected and select_state == "${BUTTON_2_ACTION_FAILSAFE_TEXT}")); + if (toggle_relay) + sw_relay_2->toggle(); + break; + case ${BUTTON_3_ID}: + select_state = sl_button_3_action->state; + toggle_relay = (select_state == "${BUTTON_3_ACTION_TEXT}" or + (api_disconnected and select_state == "${BUTTON_3_ACTION_FAILSAFE_TEXT}")); + if (toggle_relay) + sw_relay_3->toggle(); + break; + case ${BUTTON_4_ID}: + select_state = sl_button_4_action->state; + toggle_relay = (select_state == "${BUTTON_4_ACTION_TEXT}" or + (api_disconnected and select_state == "${BUTTON_4_ACTION_FAILSAFE_TEXT}")); + if (toggle_relay) + sw_relay_4->toggle(); + break; + } + if (toggle_relay) { + ESP_LOGI("standard_hw_relays", "Toggle relay %" PRIu8 ": (%s)", button, select_state.c_str()); + } else if (not select_state.empty()) { + ESP_LOGV("standard_hw_relays", "Ignoring toggle for relay %" PRIu8 ": action is '%s'", + button, select_state.c_str()); + } else { + ESP_LOGE("standard_hw_relays", "No button action selected for button %" PRIu8, button); + } + + - id: !extend dump_config + then: + - lambda: |- + // Relay's modes + ESP_LOGCONFIG("standard_hw_relays", "Relay%s mode%s:", + id(gang_count_plural_suffix).c_str(), + id(gang_count_plural_suffix).c_str()); + ESP_LOGCONFIG("standard_hw_relays", " Relay 1: %s", sl_relay_1_mode->has_state() + ? sl_relay_1_mode->state.c_str() + : "Unknown"); + if (id(gang_count) >= 2) + ESP_LOGCONFIG("standard_hw_relays", " Relay 2: %s", sl_relay_2_mode->has_state() + ? sl_relay_2_mode->state.c_str() + : "Unknown"); + if (id(gang_count) >= 3) + ESP_LOGCONFIG("standard_hw_relays", " Relay 3: %s", sl_relay_3_mode->has_state() + ? sl_relay_3_mode->state.c_str() + : "Unknown"); + if (id(gang_count) >= 4) + ESP_LOGCONFIG("standard_hw_relays", " Relay 4: %s", sl_relay_4_mode->has_state() + ? sl_relay_4_mode->state.c_str() + : "Unknown"); + + // Relay's states + ESP_LOGCONFIG("standard_hw_relays", "Relay%s state%s:", + id(gang_count_plural_suffix).c_str(), + id(gang_count_plural_suffix).c_str()); + ESP_LOGCONFIG("standard_hw_relays", " Relay 1: %s", sw_relay_1->state ? "ON" : "OFF"); + if (id(gang_count) >= 2) + ESP_LOGCONFIG("standard_hw_relays", " Relay 2: %s", sw_relay_2->state ? "ON" : "OFF"); + if (id(gang_count) >= 3) + ESP_LOGCONFIG("standard_hw_relays", " Relay 3: %s", sw_relay_3->state ? "ON" : "OFF"); + if (id(gang_count) >= 4) + ESP_LOGCONFIG("standard_hw_relays", " Relay 4: %s", sw_relay_4->state ? "ON" : "OFF"); + ESP_LOGCONFIG("standard_hw_relays", "Expose Relay's LEDs to Home Assistant: %s", + YESNO(sw_expose_relays_leds_to_ha->state)); + + // Boot initialization statuses + ESP_LOGCONFIG("standard_hw_relays", "Boot initialization flags:"); + ESP_LOGCONFIG("standard_hw_relays", " Overall: %s", YESNO(id(boot_initialization_relays))); + ESP_LOGCONFIG("standard_hw_relays", " Groups: %s", + YESNO(id(boot_initialization_relays_group_assignments))); + ESP_LOGCONFIG("standard_hw_relays", " Light modes: %s", YESNO(id(boot_initialization_relays_light_modes))); + ESP_LOGCONFIG("standard_hw_relays", " Relay modes: %s", YESNO(id(boot_initialization_relays_relay_modes))); + ESP_LOGCONFIG("standard_hw_relays", " Status LEDs: %s", YESNO(id(boot_initialization_relays_relay_leds))); + + // Substitutions + ESP_LOGCONFIG("standard_hw_relays", "Restore modes:"); + ESP_LOGCONFIG("standard_hw_relays", " Relays: ${RELAY_RESTORE_MODE}"); + ESP_LOGCONFIG("standard_hw_relays", " Status LEDs: ${LIGHT_RELAYS_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." + #endif + + // Identify itself + ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Standard - Hardware - Relays"); + + - id: show_relay_status + mode: restart + then: + - script.wait: boot_initialize_relays + - lambda: |- + if (not id(boot_initialization_relays)) { + ESP_LOGW("standard_hw_relays", "Relays not initialized yet"); + if (not boot_initialize_relays->is_running()) + boot_initialize_relays->execute(); + return; + } + for (uint8_t i = 0; i < id(gang_count); i++) { + bool relay_state = false; + std::string light_mode; + 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}"; + + // Determine relay state and light mode + switch (i) { + case 0: + relay_state = id(sw_relay_1).state; + light_mode = id(is_us_model) + ? id(sl_relay_1_light_mode_us)->state + : id(sl_relay_1_light_mode_eu)->state; + break; + case 1: + relay_state = id(sw_relay_2).state; + light_mode = id(is_us_model) + ? id(sl_relay_2_light_mode_us)->state + : id(sl_relay_2_light_mode_eu)->state; + break; + case 2: + relay_state = id(sw_relay_3).state; + light_mode = id(is_us_model) + ? id(sl_relay_3_light_mode_us)->state + : id(sl_relay_3_light_mode_eu)->state; + break; + case 3: + relay_state = id(sw_relay_4).state; + light_mode = id(is_us_model) + ? id(sl_relay_4_light_mode_us)->state + : id(sl_relay_4_light_mode_eu)->state; + break; + } + + ESP_LOGV("standard_hw_relays", "Relay %" PRIu8 " is %s", i + 1, relay_state ? "ON" : "OFF"); + ESP_LOGV("standard_hw_relays", "Light Mode for Relay %" PRIu8 ": %s", i + 1, light_mode.c_str()); + + // Use light_set_state for light updates + if (relay_state) { + if (light_mode == light_mode_group_1 || light_mode == "${LIGHT_MODE_BOTH_TEXT}") { + light_set_state->execute(1, i, true); // Left/bottom light + } + if (light_mode == light_mode_group_2 || light_mode == "${LIGHT_MODE_BOTH_TEXT}") { + light_set_state->execute(2, i, true); // Right/top light + } + } else { + light_set_state->execute(1, i, false); // Turn off left/bottom light + light_set_state->execute(2, i, false); // Turn off right/top light + } + } + +select: + - &relay_select_mode_base + id: sl_relay_1_mode + name: Relay 1 display mode + platform: template + options: + - "${RELAY_MODE_TEXT_SWITCH}" + - "${RELAY_MODE_TEXT_LIGHT}" + - "${RELAY_MODE_TEXT_NOT_USED}" + initial_option: "${RELAY_MODE_TEXT_SWITCH}" + optimistic: true + restore_value: true + internal: true + entity_category: config + disabled_by_default: false + icon: mdi:dip-switch + on_value: + then: + - binary_sensor.template.publish: + id: bs_pending_restart + state: true + - id: sl_relay_2_mode + name: Relay 2 display mode + <<: *relay_select_mode_base + - id: sl_relay_3_mode + name: Relay 3 display mode + <<: *relay_select_mode_base + - id: sl_relay_4_mode + name: Relay 4 display mode + <<: *relay_select_mode_base + +switch: + - id: sw_expose_relays_leds_to_ha + name: Expose Relay's LEDs to Home Assistant + platform: template + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + internal: false + entity_category: config + on_turn_on: + then: + - binary_sensor.template.publish: + id: bs_pending_restart + state: true + on_turn_off: + then: + - binary_sensor.template.publish: + id: bs_pending_restart + state: true + + - id: sw_relay_1 + name: Relay 1 + output: output_relay_1 + platform: output + restore_mode: ${RELAY_RESTORE_MODE} + internal: true + on_turn_on: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_off: light_output_1 + then: + light.turn_on: light_output_1 + on_turn_off: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_on: light_output_1 + then: + - light.turn_off: light_output_1 + - id: sw_relay_2 + name: Relay 2 + output: output_relay_2 + platform: output + restore_mode: ${RELAY_RESTORE_MODE} + internal: true + on_turn_on: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_off: light_output_2 + then: + light.turn_on: light_output_2 + on_turn_off: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_on: light_output_2 + then: + - light.turn_off: light_output_2 + - id: sw_relay_3 + name: Relay 3 + output: output_relay_3 + platform: output + restore_mode: ${RELAY_RESTORE_MODE} + internal: true + on_turn_on: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_off: light_output_3 + then: + - light.turn_on: light_output_3 + on_turn_off: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_on: light_output_3 + then: + - light.turn_off: light_output_3 + - id: sw_relay_4 + name: Relay 4 + output: output_relay_4 + platform: output + restore_mode: ${RELAY_RESTORE_MODE} + internal: true + on_turn_on: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_off: light_output_4 + then: + - light.turn_on: light_output_4 + on_turn_off: + then: + - script.execute: show_relay_status + - if: + condition: + - light.is_on: light_output_4 + then: + - light.turn_off: light_output_4 +... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml new file mode 100644 index 0000000..e9d5c3b --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml @@ -0,0 +1,182 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Purpose: ESPHome - Standard - Hardware - Speaker ##### +#################################################################################################### +##### 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. ##### +#################################################################################################### +--- +substitutions: + # RTTTL format: name:d=duration,o=octave,b=tempo:notes + # 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 + +packages: + standard_hw_audio: !include TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml + +api: + actions: + - action: rtttl_play + variables: + # The RTTTL string for the melody to be played. + # It should follow the RTTTL format, including the melody's name, + # default settings, and a sequence of notes. + tone: string + then: + - lambda: |- + rtttl_speaker->stop(); + if (speaker_volume->state > 0) { + ESP_LOGI("standard_speaker", "Play tone: '%s'", tone.c_str()); + rtttl_speaker->play(tone.c_str()); + } + +esphome: + platformio_options: + build_flags: + - -D TX_ULTIMATE_EASY_STANDARD_SPEAKER + +number: + - id: speaker_volume + name: Sound - Volume + platform: template + max_value: 100 + min_value: 0 + step: 1 + initial_value: 100 + unit_of_measurement: "%" + internal: false + disabled_by_default: true + entity_category: config + device_class: sound_pressure + restore_value: true + optimistic: true + set_action: + then: + - speaker.volume_set: + id: speaker_embedded + volume: !lambda return x/100.0f; + - lambda: |- + static const std::string tone_volume_change = "${tone_volume_change}"; + // Play sound if tone_volume_change is not "none" + if (x > 0 and !tone_volume_change.empty() and tone_volume_change != "none") + rtttl_speaker->play(tone_volume_change); + +rtttl: + - id: rtttl_speaker + speaker: speaker_embedded + +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, " - Standard - Hardware - Speaker"); + + - id: speaker_play_click + then: + - if: + condition: + not: + - speaker.is_playing: speaker_embedded + then: + - speaker.play: + id: speaker_embedded + data: [ + 0x01, 0x02, 0x04, 0x06, 0x07, 0x08, 0x08, 0x06, 0x01, 0xFA, 0xF4, + 0xEF, 0xEE, 0xF1, 0xF7, 0xFF, 0x07, 0x0E, 0x11, 0x11, 0x10, 0x0C, + 0x06, 0xFF, 0xF8, 0xF1, 0xEC, 0xEA, 0xEC, 0xF2, 0xF9, 0xFF, 0x05, + 0x0A, 0x10, 0x14, 0x17, 0x17, 0x14, 0x0B, 0xFD, 0xED, 0xE0, 0xD9, + 0xDB, 0xE6, 0xF7, 0x0A, 0x19, 0x20, 0x1E, 0x14, 0x05, 0xF4, 0xE5, + 0xDF, 0xE4, 0xF4, 0x0C, 0x24, 0x36, 0x3D, 0x35, 0x20, 0xFF, 0xDD, + 0xC0, 0xAE, 0xA9, 0xB0, 0xC4, 0xE0, 0x03, 0x26, 0x48, 0x62, 0x70, + 0x6D, 0x59, 0x35, 0x08, 0xD8, 0xAC, 0x8D, 0x80, 0x85, 0x9C, 0xC1, + 0xF0, 0x21, 0x4E, 0x6F, 0x7F, 0x7A, 0x63, 0x3D, 0x0F, 0xE0, 0xB7, + 0x9B, 0x90, 0x96, 0xAC, 0xCE, 0xF6, 0x1D, 0x3C, 0x50, 0x55, 0x4D, + 0x3A, 0x20, 0x04, 0xEC, 0xD8, 0xCC, 0xC9, 0xCD, 0xD8, 0xE8, 0xF9, + 0x0A, 0x17, 0x1F, 0x20, 0x1C, 0x12, 0x07, 0xFC, 0xF2, 0xEC, 0xEB, + 0xED, 0xF3, 0xFA, 0x02, 0x0A, 0x10, 0x12, 0x12, 0x0E, 0x07, 0xFE, + 0xF5, 0xED, 0xEA, 0xEB, 0xEF, 0xF7, 0x00, 0x0A, 0x10, 0x13, 0x12, + 0x0F, 0x09, 0x02, 0xFB, 0xF5, 0xF3, 0xF2, 0xF3, 0xF6, 0xFB, 0xFF, + 0x03, 0x05, 0x07, 0x08, 0x07, 0x05, 0x03, 0x01, 0xFF, 0xFE, 0xFC, + 0xFB, 0xFB, 0xFC, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x02, + 0x01] + + - wait_until: + condition: + not: + - speaker.is_playing: speaker_embedded + - speaker.stop: speaker_embedded + + - id: !extend touch_on_multi_touch_release + then: + - script.execute: touch_on_multi_touch_release_speaker + + - id: touch_on_multi_touch_release_speaker + mode: restart + then: + - lambda: |- + if (sl_touch_speaker_feedback->state != "Disabled") + speaker_play_click->execute(); + + - id: !extend touch_on_press + then: + - script.execute: touch_on_press_speaker + + - id: touch_on_press_speaker + mode: restart + then: + - lambda: |- + if (sl_touch_speaker_feedback->state == "On press" or sl_touch_speaker_feedback->state == "Always") + speaker_play_click->execute(); + + - id: !extend touch_on_release + then: + - script.execute: touch_on_release_speaker + + - id: touch_on_release_speaker + mode: restart + then: + - lambda: |- + if (sl_touch_speaker_feedback->state == "On release" or sl_touch_speaker_feedback->state == "Always") + speaker_play_click->execute(); + +select: + - id: sl_touch_speaker_feedback + name: Touch - Speaker feedback + platform: template + options: + - "Disabled" + - "On press" + - "On release" + - "Always" + initial_option: "Disabled" + optimistic: true + restore_value: true + internal: false + entity_category: config + disabled_by_default: false + icon: mdi:vibrate + +speaker: + - id: speaker_embedded + platform: i2s_audio + dac_type: external + i2s_dout_pin: GPIO15 + i2s_audio_id: if_i2s_audio + i2s_comm_fmt: stand_msb + channel: mono +... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml new file mode 100644 index 0000000..bf7205c --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml @@ -0,0 +1,168 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Purpose: ESPHome - Standard - Hardware - Vibration system ##### +#################################################################################################### +##### 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. ##### +#################################################################################################### +--- +substitutions: + vibration_max_duration: 2s + +binary_sensor: + - id: bs_vibrating + name: Vibrating + icon: mdi:vibrate + internal: false + platform: template + lambda: return sw_vibration_motor->state; + +button: + - id: bt_vibrate + name: Vibrate + icon: mdi:vibrate + internal: false + platform: template + on_press: + then: + - script.execute: vibrate + +esphome: + platformio_options: + build_flags: + - -D TX_ULTIMATE_EASY_STANDARD_HW_VIBRATION + +number: + - id: nr_vibrating_duration + name: Vibrate duration + icon: mdi:vibrate + unit_of_measurement: ms + internal: false + entity_category: config + platform: template + min_value: 1 + max_value: 500 + step: 1 + initial_value: 15 + optimistic: true + restore_value: true + +ota: + on_state_change: + then: + - switch.turn_off: sw_vibration_motor + +script: + - id: !extend dump_config + then: + - lambda: |- + // Configuration + ESP_LOGCONFIG("standard_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}"); + + // State + if (bs_vibrating->state) + ESP_LOGW("standard_hw_vibration", "Vibrating now: YES"); + else + ESP_LOGCONFIG("standard_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." + #endif + + // Identify itself + ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Standard - Hardware - Vibration"); + + - id: !extend touch_on_multi_touch_release + then: + - script.execute: touch_on_multi_touch_release_vibration + + - id: touch_on_multi_touch_release_vibration + mode: restart + then: + - lambda: |- + if (sl_touch_vibration_feedback->state != "Disabled") + vibrate->execute(); + + - id: !extend touch_on_press + then: + - script.execute: touch_on_press_vibration + + - id: touch_on_press_vibration + mode: restart + then: + - lambda: |- + if (sl_touch_vibration_feedback->state == "On press" or sl_touch_vibration_feedback->state == "Always") + vibrate->execute(); + + - id: !extend touch_on_release + then: + - script.execute: touch_on_release_vibration + + - id: touch_on_release_vibration + mode: restart + then: + - lambda: |- + if (sl_touch_vibration_feedback->state == "On release" or sl_touch_vibration_feedback->state == "Always") + vibrate->execute(); + + - id: vibrate + mode: restart + then: + - switch.turn_on: sw_vibration_motor + - delay: !lambda return nr_vibrating_duration->state; + - switch.turn_off: sw_vibration_motor + +select: + - id: sl_touch_vibration_feedback + name: Touch - Vibration feedback + platform: template + options: + - "Disabled" + - "On press" + - "On release" + - "Always" + initial_option: "Disabled" + optimistic: true + restore_value: true + internal: false + entity_category: config + disabled_by_default: false + icon: mdi:vibrate + +switch: + - id: sw_vibration_motor + name: Vibration motor + platform: gpio + pin: GPIO21 + restore_mode: ALWAYS_OFF + internal: true + on_turn_on: + then: + - binary_sensor.template.publish: + id: bs_vibrating + state: ON # yamllint disable-line rule:truthy + - 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 +... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml new file mode 100644 index 0000000..32a8eb5 --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml @@ -0,0 +1,64 @@ +#################################################################################################### +##### 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 + +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 +... diff --git a/TX-Ultimate-Easy-ESPHome_advanced.yaml b/TX-Ultimate-Easy-ESPHome_advanced.yaml new file mode 100644 index 0000000..9ffd141 --- /dev/null +++ b/TX-Ultimate-Easy-ESPHome_advanced.yaml @@ -0,0 +1,38 @@ +#################################################################################################### +##### TX Ultimate Easy for ESPHome ##### +##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### +#################################################################################################### +##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo ##### +#################################################################################################### +##### WARNING: This is an advanced configuration template. Use with caution as improper ##### +##### configuration may lead to reduced functionality or system instability. ##### +#################################################################################################### +--- +substitutions: + name: tx-ultimate-easy-adv + friendly_name: TX Ultimate Easy Advanced + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +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 + refresh: 5min + files: + # Core (essential) packages - Do not disable these unless you know what you're doing + - ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml # Basic shared settings + - ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml # Button logic + - ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml # LED configuration + - ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml # Touch panel support + + # Optional functionality - Can be enabled/disabled as needed + - ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml # Relay control + - ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml # Haptic feedback + + # 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 +... From b1a978b3f311159ba6945c06d987e12b95777731 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 14 May 2025 20:57:58 +0200 Subject: [PATCH 2/3] Remove `rmt_channel` Not supported with IDF 5 --- .test/common_idf.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.test/common_idf.yaml b/.test/common_idf.yaml index fb9297c..9351dd6 100644 --- a/.test/common_idf.yaml +++ b/.test/common_idf.yaml @@ -10,7 +10,6 @@ light: - id: !extend light_full platform: esp32_rmt_led_strip rgb_order: GRB - rmt_channel: 1 chipset: ws2812 max_refresh_rate: 33ms use_psram: false From 5eb42082dbb15c25477d1ef1962d3c9c8eb020af Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 14 May 2025 21:07:07 +0200 Subject: [PATCH 3/3] Remove `rmt_channel` Not supported with IDF 5 --- ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml index 292ed0a..0e6dbb3 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_addon_ble_proxy.yaml @@ -31,7 +31,7 @@ light: - id: !extend light_full platform: esp32_rmt_led_strip rgb_order: GRB - rmt_channel: 1 + # rmt_channel: 1 # Not supported with IDF 5 chipset: ws2812 max_refresh_rate: 33ms use_psram: false