diff --git a/.github/workflows/validate_esphome.yml b/.github/workflows/validate_esphome.yml index a4f2363..0288479 100644 --- a/.github/workflows/validate_esphome.yml +++ b/.github/workflows/validate_esphome.yml @@ -65,6 +65,18 @@ jobs: yaml-file: ".test/esphome_ard_core.yaml" 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: name: "📦 Arduino Basic (latest)" needs: build_ard_core @@ -79,8 +91,8 @@ jobs: build_ard_basic_dev: name: "📦 Arduino Basic (dev)" - needs: build_ard_core - if: success() && !github.event.inputs.skip_dev + needs: build_ard_core_dev + if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@main @@ -141,7 +153,7 @@ jobs: build_ard_ble_proxy_dev: name: "⚡ Arduino BLE Proxy (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 steps: - uses: actions/checkout@main @@ -164,6 +176,19 @@ jobs: yaml-file: ".test/esphome_idf_core.yaml" version: latest + build_idf_core_dev: + name: "🏗️ IDF Core (dev)" + needs: code_scan + if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - name: Build IDF Core - dev + uses: esphome/build-action@main + with: + yaml-file: ".test/esphome_idf_core.yaml" + version: dev + build_idf_basic: name: "📦 IDF Basic (latest)" needs: build_idf_core @@ -178,8 +203,8 @@ jobs: build_idf_basic_dev: name: "📦 IDF Basic (dev)" - needs: build_idf_core - if: success() && !github.event.inputs.skip_dev + needs: build_idf_core_dev + if: ${{ success() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@main @@ -240,7 +265,7 @@ jobs: build_idf_ble_proxy_dev: name: "⚡ IDF BLE Proxy (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 steps: - uses: actions/checkout@main @@ -255,6 +280,7 @@ jobs: name: "📊 Arduino Framework" needs: - build_ard_core + - build_ard_core_dev - build_ard_basic - build_ard_basic_dev - build_ard_hw_relays @@ -281,9 +307,10 @@ jobs: # Count dev builds (if not skipped) if [ "${{ github.event.inputs.skip_dev }}" != "true" ]; then + [ "${{ needs.build_ard_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_ard_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) - TOTAL=$((TOTAL + 2)) + TOTAL=$((TOTAL + 3)) fi echo "🤖 Arduino Framework: $SUCCESS/$TOTAL builds passed" @@ -299,6 +326,7 @@ jobs: name: "📊 ESP-IDF Framework" needs: - build_idf_core + - build_idf_core_dev - build_idf_basic - build_idf_basic_dev - build_idf_hw_relays @@ -325,9 +353,10 @@ jobs: # Count dev builds (if not skipped) if [ "${{ github.event.inputs.skip_dev }}" != "true" ]; then + [ "${{ needs.build_idf_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_idf_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) - TOTAL=$((TOTAL + 2)) + TOTAL=$((TOTAL + 3)) fi echo "🔧 ESP-IDF Framework: $SUCCESS/$TOTAL builds passed" @@ -390,23 +419,27 @@ jobs: summary_dev: name: "📊 ESPHome (Dev)" needs: + - build_ard_core_dev - build_ard_basic_dev - build_ard_ble_proxy_dev + - build_idf_core_dev - build_idf_basic_dev - build_idf_ble_proxy_dev - if: always() && !github.event.inputs.skip_dev + if: ${{ always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_dev != 'true') }} runs-on: ubuntu-latest steps: - name: Dev ESPHome Results run: | SUCCESS=0 - TOTAL=4 + TOTAL=6 # Arduino Dev builds + [ "${{ needs.build_ard_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_ard_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_ard_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) # ESP-IDF Dev builds + [ "${{ needs.build_idf_core_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_idf_basic_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) [ "${{ needs.build_idf_ble_proxy_dev.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1)) diff --git a/.github/workflows/versioning.yml b/.github/workflows/versioning.yml index dc50d74..5bb683c 100644 --- a/.github/workflows/versioning.yml +++ b/.github/workflows/versioning.yml @@ -41,11 +41,84 @@ jobs: git config user.name "GitHub Actions" git config user.email "actions@github.com" + - name: Get PR information + id: pr_info + if: github.event_name == 'push' + uses: actions/github-script@v7 + with: + script: | + try { + // Use GitHub's reliable API to find PRs associated with this commit + const { data: associatedPRs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: context.sha + }); + + if (associatedPRs.length > 0) { + // Get the most recent merged PR + const pr = associatedPRs.find(pr => pr.state === 'closed' && pr.merged_at) || associatedPRs[0]; + + return { + title: pr.title, + body: pr.body || 'No description provided', + number: pr.number, + found: true + }; + } else { + // Fallback for direct pushes or when no PR is found + const commit = await github.rest.repos.getCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: context.sha + }); + + return { + title: 'Direct push to main', + body: commit.data.commit.message, + number: null, + found: false + }; + } + } catch (error) { + console.log('Could not get PR info:', error.message); + return { + title: 'Version update', + body: 'Automated version bump', + number: null, + found: false + }; + } + - name: Bump version run: | chmod +x ./versioning/bump_version.sh ./versioning/bump_version.sh + - name: Create tag message + id: tag_message + run: | + NEW_VERSION=$(cat ./versioning/VERSION) + + if [ "${{ github.event_name }}" == "push" ] && [ "${{ steps.pr_info.outcome }}" == "success" ]; then + # Use PR information directly in heredoc to avoid quoting issues + cat > tag_message.txt << EOF + # v${NEW_VERSION} - ${{ fromJson(steps.pr_info.outputs.result).title }} + + ${{ fromJson(steps.pr_info.outputs.result).body }} + EOF + else + # Manual dispatch or PR info unavailable - simpler message + cat > tag_message.txt << EOF + # v${NEW_VERSION} - Manual tag update + + Tag updated via manual workflow dispatch. + EOF + fi + + # Store the message for use in subsequent steps + echo "TAG_MESSAGE_FILE=tag_message.txt" >> $GITHUB_ENV + - name: Push Changes and Tags env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -72,10 +145,10 @@ jobs: echo "Backing up current stable tag ($OLD_STABLE)" fi - # Update tag + # Update tag with detailed message NEW_VERSION=$(cat ./versioning/VERSION) echo "Updating stable tag to $NEW_VERSION" - git tag -fa stable -m "Update stable tag to $NEW_VERSION" + git tag -fa stable -F "$TAG_MESSAGE_FILE" git push origin stable --force - name: Update latest tag @@ -97,9 +170,15 @@ jobs: echo "Backing up current latest tag ($OLD_LATEST)" fi - # Update tag + # Update tag with detailed message NEW_VERSION=$(cat ./versioning/VERSION) echo "Updating latest tag to $NEW_VERSION" - git tag -fa latest -m "Update latest tag to $NEW_VERSION" + git tag -fa latest -F "$TAG_MESSAGE_FILE" git push origin latest --force + + - name: Cleanup + if: always() + run: | + # Clean up temporary files + [ -f tag_message.txt ] && rm tag_message.txt || true ... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml index a254e28..1e7531f 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core_common.yaml @@ -27,7 +27,9 @@ substitutions: TX_MODEL_3_GANG_TEXT: "3 Gang" TX_MODEL_4_GANG_TEXT: "4 Gang" - DUMP_CONFIG_CALLER_DELAY: 10s # Delay to dump config after requested + DUMP_CONFIG_CALLER_DELAY: 5s # Delay to dump config after requested + + TAG_CORE_COMMON: core.common api: id: api_server @@ -46,11 +48,18 @@ binary_sensor: device_class: problem on_state: then: - - lambda: |- - if (x) - ESP_LOGW("core", "Pending restart: YES"); - else - ESP_LOGCONFIG("core", "Pending restart: No"); + - script.execute: dump_config_caller + + - id: bs_boot_completed + name: Boot Completed + internal: false + disabled_by_default: false + platform: template + entity_category: diagnostic + device_class: connectivity + icon: mdi:rocket-launch + on_state: + then: - script.execute: dump_config_caller button: @@ -69,14 +78,25 @@ esphome: name: ${name} friendly_name: ${friendly_name} comment: TX Ultimate Easy + min_version: 2025.8.0 project: name: "edwardtfn.tx_ultimate_easy" version: ${version} platformio_options: build_flags: - - -D TX_ULTIMATE_EASY_CORE + - -D TX_ULTIMATE_EASY_CORE_COMMON on_boot: + - priority: 1000 # Very early in boot process + then: + - binary_sensor.template.publish: + id: bs_boot_completed + state: OFF # yamllint disable-line rule:truthy + + - priority: 750 + then: + - script.execute: restore_from_nvs + - priority: 700 then: - script.execute: boot_initialize @@ -89,17 +109,26 @@ esphome: then: - script.execute: boot_done -globals: - - id: is_us_model - type: bool - restore_value: true - initial_value: 'false' +external_components: + - source: + type: git + url: https://github.com/edwardtfn/TX-Ultimate-Easy + ref: v${version} + refresh: 1h + components: + - tx_ultimate_easy +globals: - id: gang_count type: uint8_t restore_value: true initial_value: '0' + - id: is_us_model + type: bool + restore_value: true + initial_value: 'false' + logger: level: DEBUG @@ -127,9 +156,16 @@ script: - id: boot_done mode: restart then: + - script.wait: restore_from_nvs + - script.wait: boot_initialize + - script.wait: boot_sequence - script.execute: id: api_send_ha_event_boot type: done + - script.wait: api_send_ha_event_boot + - binary_sensor.template.publish: + id: bs_boot_completed + state: ON # yamllint disable-line rule:truthy - id: boot_initialize mode: restart @@ -149,7 +185,7 @@ script: id(is_us_model) = (sl_tx_model_format->state == "${TX_MODEL_FORMAT_US_TEXT}"); id(gang_count) = sl_tx_model_gang->active_index().value() + 1; if (id(gang_count) < 1 || id(gang_count) > 4) { - ESP_LOGE("core_hw_leds", "Invalid number of gangs: %" PRIu8, id(gang_count)); + ESP_LOGE("${TAG_CORE_COMMON}", "Invalid number of gangs: %" PRIu8, id(gang_count)); } - id: boot_sequence @@ -166,33 +202,43 @@ script: # Extended by all modules - lambda: |- // Device identification - ESP_LOGCONFIG("core", "Device friendly name: ${friendly_name}"); - ESP_LOGCONFIG("core", "Device name: ${name}"); - ESP_LOGCONFIG("core", "Device name (HA): %s", tx_device_name->state.c_str()); - ESP_LOGCONFIG("core", "Device hostname: %s", App.get_name().c_str()); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device friendly name: ${friendly_name}"); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device name: ${name}"); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device name (HA): %s", tx_device_name->state.c_str()); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Device hostname: %s", App.get_name().c_str()); dump_config_versions->execute(); // Framework detection #ifdef USE_ARDUINO - ESP_LOGCONFIG("core", "Framework: Arduino"); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Framework: Arduino"); #elif defined(USE_ESP_IDF) - ESP_LOGCONFIG("core", "Framework: ESP-IDF"); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Framework: ESP-IDF"); #else - ESP_LOGW("core", "Framework: UNKNOWN"); + ESP_LOGW("${TAG_CORE_COMMON}", "Framework: UNKNOWN"); #endif // Model configuration - ESP_LOGCONFIG("core", "Model format (selected): %s", sl_tx_model_format->state.c_str()); - ESP_LOGCONFIG("core", "Model format (detected): %s", id(is_us_model) ? "US" : "EU"); - ESP_LOGCONFIG("core", "Gangs (selected): %s", sl_tx_model_gang->state.c_str()); - ESP_LOGCONFIG("core", "Gangs (detected): %" PRIu8 "-Gang%s", id(gang_count), id(gang_count) > 1 ? "s" : ""); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Model format (selected): %s", + sl_tx_model_format->state.c_str()); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Model format (detected): %s", + id(is_us_model) ? "US" : "EU"); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Gangs (selected): %s", + sl_tx_model_gang->state.c_str()); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Gangs (detected): %" PRIu8 "-Gang%s", + id(gang_count), id(gang_count) > 1 ? "s" : ""); + + // Boot completion status + if (bs_boot_completed->state) + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Boot completed: Yes"); + else + ESP_LOGW("${TAG_CORE_COMMON}", "Boot completed: NO"); // System state if (bs_pending_restart->state) - ESP_LOGW("core", "Pending restart: YES"); + ESP_LOGW("${TAG_CORE_COMMON}", "Pending restart: YES"); else - ESP_LOGCONFIG("core", "Pending restart: No"); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "Pending restart: No"); - id: dump_config_caller mode: restart @@ -217,10 +263,10 @@ script: then: - lambda: |- // Version information - ESP_LOGCONFIG("core", "TX Ultimate firmware version: ${version}"); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "TX Ultimate firmware version: ${version}"); // ESPHome builder information - ESP_LOGCONFIG("core", "ESPHome builder: " ESPHOME_VERSION); - ESP_LOGCONFIG("core", "ESPHome build timestamp: %s", App.get_compilation_time().c_str()); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "ESPHome builder: " ESPHOME_VERSION); + ESP_LOGCONFIG("${TAG_CORE_COMMON}", "ESPHome build timestamp: %s", App.get_compilation_time().c_str()); - id: publish_device_info mode: restart @@ -229,6 +275,18 @@ script: tx_fw_version->publish_state("${version}"); tx_device_name->publish_state(App.get_name().c_str()); + - id: restore_from_nvs + mode: single + then: + - select.set_index: + id: sl_tx_model_format + index: !lambda if (id(is_us_model)) return 1; else return 0; + - select.set_index: + id: sl_tx_model_gang + index: !lambda |- + if (id(gang_count) < 1 || id(gang_count) > 4) return 0; // default to 1-Gang + return static_cast(id(gang_count)) - 1; + select: - id: sl_tx_model_format name: Model (Format) @@ -238,7 +296,7 @@ select: - "${TX_MODEL_FORMAT_US_TEXT}" initial_option: "${TX_MODEL_FORMAT_EU_TEXT}" optimistic: true - restore_value: true + restore_value: false internal: false entity_category: config disabled_by_default: false @@ -250,7 +308,6 @@ select: state: true - lambda: |- id(is_us_model) = (x == "${TX_MODEL_FORMAT_US_TEXT}"); - ESP_LOGI("core", "New model selected: %s", id(is_us_model) ? "US" : "EU"); - id: sl_tx_model_gang name: Model (Gang) @@ -262,7 +319,7 @@ select: - "${TX_MODEL_4_GANG_TEXT}" initial_option: "${TX_MODEL_1_GANG_TEXT}" optimistic: true - restore_value: true + restore_value: false internal: false entity_category: config disabled_by_default: false @@ -274,7 +331,6 @@ select: 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: @@ -314,6 +370,9 @@ text_sensor: } return result; +tx_ultimate_easy: + id: tx_ultimate + wifi: id: wifi_component ... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml index d2be16f..b5b22fd 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml @@ -36,6 +36,8 @@ substitutions: BUTTON_3_ID: '3' BUTTON_4_ID: '4' + TAG_CORE_HW_BUTTONS: core.hw.buttons + binary_sensor: - &binary_sensor_button_base id: bs_button_2 @@ -63,6 +65,14 @@ esphome: - -D TX_ULTIMATE_EASY_CORE_HW_BUTTONS globals: + # Packed button actions - replaces 4 separate selects + - id: button_actions_packed + type: uint16_t + restore_value: true + initial_value: '4369' # Binary: 0001000100010001 = all buttons set to option 1 (default action) + # Bit layout: [button4(4)][button3(4)][button2(4)][button1(4)] + # Each button uses 4 bits: 0=None, 1=Toggle, 2=Failsafe, 3-15=Reserved for future options + - id: button_press_button type: uint8_t restore_value: false @@ -125,7 +135,7 @@ script: {"count", std::to_string(count)}, {"position", std::to_string(id(button_press_position))} }); - ESP_LOGI("core_hw_buttons", "Button %" PRIu8 " action: '%s'", button, action.c_str()); + ESP_LOGI("${TAG_CORE_HW_BUTTONS}", "Button %" PRIu8 " action: '%s'", button, action.c_str()); if (action == "click") { switch (button) { case ${BUTTON_1_ID}: @@ -211,37 +221,48 @@ script: - lambda: |- std::string gang_count_plural_suffix = id(gang_count) > 1 ? "s" : ""; // Button's actions - ESP_LOGCONFIG("core_hw_buttons", "Button%s action%s:", + ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", "Button%s action%s:", gang_count_plural_suffix.c_str(), gang_count_plural_suffix.c_str()); - ESP_LOGCONFIG("core_hw_buttons", " Relay 1: %s", sl_button_1_action->has_state() - ? sl_button_1_action->state.c_str() - : "Unknown"); + ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 1: %s", sl_button_1_action->has_state() ? + sl_button_1_action->state.c_str() : "Unknown"); if (id(gang_count) >= 2) - ESP_LOGCONFIG("core_hw_buttons", " Relay 2: %s", sl_button_2_action->has_state() - ? sl_button_2_action->state.c_str() - : "Unknown"); + ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 2: %s", sl_button_2_action->has_state() ? + sl_button_2_action->state.c_str() : "Unknown"); if (id(gang_count) >= 3) - ESP_LOGCONFIG("core_hw_buttons", " Relay 3: %s", sl_button_3_action->has_state() - ? sl_button_3_action->state.c_str() - : "Unknown"); + ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 3: %s", sl_button_3_action->has_state() ? + sl_button_3_action->state.c_str() : "Unknown"); if (id(gang_count) >= 4) - ESP_LOGCONFIG("core_hw_buttons", " Relay 4: %s", sl_button_4_action->has_state() - ? sl_button_4_action->state.c_str() - : "Unknown"); + ESP_LOGCONFIG("${TAG_CORE_HW_BUTTONS}", " Relay 4: %s", sl_button_4_action->has_state() ? + sl_button_4_action->state.c_str() : "Unknown"); - id: !extend dump_config_list_packages then: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif // Identify itself ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Core - Hardware - Buttons"); + - id: !extend restore_from_nvs + then: + - select.set_index: + id: sl_button_1_action + index: !lambda return std::min((uint8_t)(id(button_actions_packed) & 0x0F), (uint8_t)2); + - select.set_index: + id: sl_button_2_action + index: !lambda return std::min((uint8_t)((id(button_actions_packed) >> 4) & 0x0F), (uint8_t)2); + - select.set_index: + id: sl_button_3_action + index: !lambda return std::min((uint8_t)((id(button_actions_packed) >> 8) & 0x0F), (uint8_t)2); + - select.set_index: + id: sl_button_4_action + index: !lambda return std::min((uint8_t)((id(button_actions_packed) >> 12) & 0x0F), (uint8_t)2); + - id: !extend touch_on_multi_touch_release then: - script.execute: buttons_release @@ -294,19 +315,20 @@ script: id(button_press_start_time) < current_time) { uint32_t press_duration = current_time - id(button_press_start_time); // Handle overflow (optional, since it's unlikely to happen here) - ESP_LOGI("core_hw_buttons", "Button press duration: %" PRIu32 " ms", press_duration); + ESP_LOGI("${TAG_CORE_HW_BUTTONS}", "Button press duration: %" PRIu32 " ms", press_duration); if (press_duration < ${BUTTON_CLICK_MIN_LENGTH}) { - ESP_LOGW("core_hw_buttons", "Ignoring button press (too short)"); + ESP_LOGW("${TAG_CORE_HW_BUTTONS}", "Ignoring button press (too short)"); } else if (press_duration >= ${BUTTON_CLICK_MIN_LENGTH} and press_duration <= ${BUTTON_CLICK_MAX_LENGTH}) { // Short/normal click button_click_event->execute(id(button_press_button), id(click_counter)); } else if (press_duration >= ${BUTTON_LONG_PRESS_DELAY} and press_duration <= ${BUTTON_PRESS_TIMEOUT}) { button_action->execute(id(button_press_button), "long_press", 1); } else if (press_duration > ${BUTTON_PRESS_TIMEOUT}) { // Timeout or invalid - ESP_LOGW("core_hw_buttons", "Button press cancelled or timed out after ${BUTTON_PRESS_TIMEOUT} ms"); + ESP_LOGW("${TAG_CORE_HW_BUTTONS}", + "Button press cancelled or timed out after ${BUTTON_PRESS_TIMEOUT} ms"); } } else { - ESP_LOGW("core_hw_buttons", "Press event timestamp not recorded yet"); + ESP_LOGW("${TAG_CORE_HW_BUTTONS}", "Press event timestamp not recorded yet"); } id(button_press_start_time) = 0; @@ -329,11 +351,16 @@ select: - "${BUTTON_1_ACTION_FAILSAFE_TEXT}" initial_option: "${BUTTON_1_ACTION_TEXT}" optimistic: true - restore_value: true + restore_value: false internal: true entity_category: config disabled_by_default: false icon: mdi:dip-switch + on_value: + then: + - lambda: |- + id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 0)) | + ((id(sl_button_1_action).active_index().value_or(1) & 0x0F) << 0); - id: sl_button_2_action name: Button 2 action @@ -344,6 +371,11 @@ select: - "${BUTTON_2_ACTION_FAILSAFE_TEXT}" initial_option: "${BUTTON_2_ACTION_TEXT}" <<: *button_select_action_base + on_value: + then: + - lambda: |- + id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 4)) | + ((id(sl_button_2_action).active_index().value_or(1) & 0x0F) << 4); - id: sl_button_3_action name: Button 3 action @@ -354,6 +386,11 @@ select: - "${BUTTON_3_ACTION_FAILSAFE_TEXT}" initial_option: "${BUTTON_3_ACTION_TEXT}" <<: *button_select_action_base + on_value: + then: + - lambda: |- + id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 8)) | + ((id(sl_button_3_action).active_index().value_or(1) & 0x0F) << 8); - id: sl_button_4_action name: Button 4 action @@ -364,4 +401,9 @@ select: - "${BUTTON_4_ACTION_FAILSAFE_TEXT}" initial_option: "${BUTTON_4_ACTION_TEXT}" <<: *button_select_action_base + on_value: + then: + - lambda: |- + id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 12)) | + ((id(sl_button_4_action).active_index().value_or(1) & 0x0F) << 12); ... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml index 8af7ff7..a345ea6 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml @@ -25,9 +25,14 @@ substitutions: LIGHT_TRANSITION_TURN_OFF: '0' # Transition time in msec LIGHT_BRIGHTNESS_TURN_ON: '0' # Brightness (1 to 100%, 0 to not set it) - LIGHT_FULL_RESTORE_MODE: RESTORE_DEFAULT_OFF - LIGHT_SIDES_RESTORE_MODE: RESTORE_DEFAULT_OFF - LIGHT_INDIVIDUAL_RESTORE_MODE: RESTORE_DEFAULT_OFF + LIGHT_FULL_RESTORE_MODE: ALWAYS_OFF + LIGHT_SIDES_RESTORE_MODE: ALWAYS_OFF + LIGHT_INDIVIDUAL_RESTORE_MODE: ALWAYS_OFF + + LIGHT_ATTRS_DEFAULT: '0x64FFFFFF' # brightness=100, RGB=255,255,255 + LIGHT_DUAL_ATTRS_DEFAULT: '0x64FFFFFF64FFFFFF' # Both lights: brightness=100, RGB=255,255,255 + + TAG_CORE_HW_LEDS: core.hw.leds esphome: platformio_options: @@ -35,12 +40,39 @@ esphome: - -D TX_ULTIMATE_EASY_CORE_HW_LEDS globals: - - id: gb_lights_1 - type: std::vector - restore_value: false - - id: gb_lights_2 - type: std::vector - restore_value: false + # light_full with effect support + - id: led_full_attrs + type: uint32_t + restore_value: true + initial_value: '${LIGHT_ATTRS_DEFAULT}' + # Basic attributes: brightness + RGB + + # For light_full effects + - id: led_full_effect_packed + type: uint32_t + restore_value: true + initial_value: '0x00000000' + # Stores: effect_index(16 bits) + effect_speed(8 bits) + reserved(8 bits) + # 0 = no effect/solid color, 1-10 = effect indices + + - id: led_main_packed_attrs + type: uint64_t + restore_value: true + initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}' + # light_eu(group1) + light_us(group2) - the main effect lights + + - id: led_main_packed_effect + type: uint32_t + restore_value: true + initial_value: '0x00000000' + # Shared between light_eu and light_us + + # Main user-facing LED lights only + - id: led_sides_packed_attrs + type: uint64_t + restore_value: true + initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}' + # EU: bottom(group1) + top(group2) OR US: left(group1) + right(group2) light: # These lights are controlling the LEDs on the device @@ -111,6 +143,17 @@ light: - id: light_full from: 0 to: 27 + on_state: + then: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu); + attr_global_ptr: !lambda return &id(led_main_packed_attrs); + group_index: 1 + - script.execute: + id: light_effect_save + light_ptr: !lambda return id(light_eu); + effect_global_ptr: !lambda return &id(led_main_packed_effect); - &light_partition_with_effects_us id: light_us @@ -163,6 +206,17 @@ light: - id: light_full from: 0 to: 31 + on_state: + then: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us); + attr_global_ptr: !lambda return &id(led_main_packed_attrs); + group_index: 2 + - script.execute: + id: light_effect_save + light_ptr: !lambda return id(light_us); + effect_global_ptr: !lambda return &id(led_main_packed_effect); - &light_partition_no_effects_disabled id: light_eu_bottom @@ -177,6 +231,13 @@ light: from: 6 to: 12 reversed: true + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_bottom); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 1 + - id: light_eu_left name: Light - Left EU <<: *light_partition_no_effects_disabled @@ -185,6 +246,12 @@ light: from: 13 to: 19 reversed: true + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_left); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 1 - id: light_eu_right name: Light - Right EU <<: *light_partition_no_effects_disabled @@ -197,6 +264,12 @@ light: from: 0 to: 5 reversed: false + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_right); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 2 - id: light_eu_top name: Light - Top EU <<: *light_partition_no_effects_disabled @@ -205,6 +278,12 @@ light: from: 20 to: 26 reversed: false + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_top); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 2 - id: light_us_bottom name: Light - Bottom US <<: *light_partition_no_effects_disabled @@ -213,6 +292,12 @@ light: from: 9 to: 14 reversed: true + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_bottom); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 1 - id: light_us_left name: Light - Left US <<: *light_partition_no_effects_disabled @@ -221,6 +306,12 @@ light: from: 15 to: 24 reversed: true + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_left); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 1 - id: light_us_right name: Light - Right US <<: *light_partition_no_effects_disabled @@ -233,6 +324,12 @@ light: from: 0 to: 8 reversed: false + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_right); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 2 - id: light_us_top name: Light - Top US <<: *light_partition_no_effects_disabled @@ -241,6 +338,12 @@ light: from: 25 to: 30 reversed: false + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_top); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 2 - &light_partition_individual_led id: light_00 @@ -497,179 +600,246 @@ script: - id: !extend dump_config then: - lambda: |- - std::string gang_count_plural_suffix = id(gang_count) > 1 ? "s" : ""; - // Relay's LEDs modes - ESP_LOGCONFIG("core_hw_leds", "Relay%s LEDs mode%s:", - gang_count_plural_suffix.c_str(), - gang_count_plural_suffix.c_str()); - ESP_LOGCONFIG("core_hw_leds", " Relay 1: %s", id(is_us_model) - ? sl_relay_1_light_mode_us->state.c_str() - : sl_relay_1_light_mode_eu->state.c_str()); - if (id(gang_count) >= 2) - ESP_LOGCONFIG("core_hw_leds", " Relay 2: %s", id(is_us_model) - ? sl_relay_2_light_mode_us->state.c_str() - : sl_relay_2_light_mode_eu->state.c_str()); - if (id(gang_count) >= 3) - ESP_LOGCONFIG("core_hw_leds", " Relay 3: %s", id(is_us_model) - ? sl_relay_3_light_mode_us->state.c_str() - : sl_relay_3_light_mode_eu->state.c_str()); - if (id(gang_count) >= 4) - ESP_LOGCONFIG("core_hw_leds", " Relay 4: %s", id(is_us_model) - ? sl_relay_4_light_mode_us->state.c_str() - : sl_relay_4_light_mode_eu->state.c_str()); - // Transition times - ESP_LOGCONFIG("core_hw_leds", "Transition times:"); - ESP_LOGCONFIG("core_hw_leds", " Default: ${default_transition_length}"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", "Transition times:"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Default: ${default_transition_length}"); if (${LIGHT_TRANSITION_TURN_ON} > 0) - ESP_LOGCONFIG("core_hw_leds", " Turning on: ${LIGHT_TRANSITION_TURN_ON}ms"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning on: ${LIGHT_TRANSITION_TURN_ON}ms"); else - ESP_LOGCONFIG("core_hw_leds", " Turning on: Disabled"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning on: Disabled"); if (${LIGHT_TRANSITION_TURN_OFF} > 0) - ESP_LOGCONFIG("core_hw_leds", " Turning off: ${LIGHT_TRANSITION_TURN_OFF}ms"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning off: ${LIGHT_TRANSITION_TURN_OFF}ms"); else - ESP_LOGCONFIG("core_hw_leds", " Turning off: Disabled"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning off: Disabled"); // Restore modes - ESP_LOGCONFIG("core_hw_leds", "LED restore modes:"); - ESP_LOGCONFIG("core_hw_leds", " Light - All: ${LIGHT_FULL_RESTORE_MODE}"); - ESP_LOGCONFIG("core_hw_leds", " Sides: ${LIGHT_SIDES_RESTORE_MODE}"); - ESP_LOGCONFIG("core_hw_leds", " Individual: ${LIGHT_INDIVIDUAL_RESTORE_MODE}"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", "LED restore modes:"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Light - All: ${LIGHT_FULL_RESTORE_MODE}"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Sides: ${LIGHT_SIDES_RESTORE_MODE}"); + ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Individual: ${LIGHT_INDIVIDUAL_RESTORE_MODE}"); - id: !extend dump_config_list_packages then: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif // Identify itself ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Core - Hardware - LEDs"); - - id: light_set_state + - id: light_attributes_restore mode: parallel parameters: - light_group: uint8_t - light_index: uint8_t - state: bool + light_ptr: light::LightState* + attr_global_ptr: uint32_t* then: - lambda: |- - static const uint32_t LIGHT_TRANSITION_TURN_ON = ${LIGHT_TRANSITION_TURN_ON}; - static const uint32_t LIGHT_TRANSITION_TURN_OFF = ${LIGHT_TRANSITION_TURN_OFF}; - static const uint8_t LIGHT_BRIGHTNESS_TURN_ON = ${LIGHT_BRIGHTNESS_TURN_ON}; + 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); + auto call = light_ptr->make_call(); + call.set_brightness(attrs.brightness / 100.0f); + call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f); + call.perform(); + ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored light attributes: brightness=%d%%, RGB=(%d,%d,%d)", + attrs.brightness, attrs.red, attrs.green, attrs.blue); - 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}"; + - id: light_attributes_restore_dual + mode: parallel + parameters: + light_ptr: light::LightState* + attr_global_ptr: uint64_t* + group_index: uint8_t # 1 or 2 + then: + - lambda: |- + if (!light_ptr) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer"); + return; + } + if (!attr_global_ptr) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer"); + return; + } + if (group_index != 1 && group_index != 2) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid group_index=%d (expected 1 or 2)", group_index); + return; + } + auto attrs_pair = tx_ultimate_easy::unpack_dual_light_attributes(*attr_global_ptr); + tx_ultimate_easy::LightAttributes attrs = (group_index == 1) ? attrs_pair.first : attrs_pair.second; - switch (light_group) { - case 1: - if (light_index >= id(gb_lights_1).size()) { - ESP_LOGE("core_hw_leds", "Invalid set with %s light for Relay %" PRIu8, - light_mode_group_1.c_str(), light_index+1); - return; - } - if (state and !id(gb_lights_1)[light_index]->current_values.is_on()) { - ESP_LOGI("core_hw_leds", "Turn-on %s light for Relay %" PRIu8, - light_mode_group_1.c_str(), light_index+1); - auto call = id(gb_lights_1)[light_index]->turn_on(); - if (LIGHT_TRANSITION_TURN_ON > 0) - call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms - if (LIGHT_BRIGHTNESS_TURN_ON > 0 and LIGHT_BRIGHTNESS_TURN_ON <= 100) - call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f); - call.perform(); - } else if (!state and id(gb_lights_1)[light_index]->current_values.is_on()) { - ESP_LOGI("core_hw_leds", "Turn-off %s light for Relay %" PRIu8, - light_mode_group_1.c_str(), light_index+1); - auto call = id(gb_lights_1)[light_index]->turn_off(); - if (LIGHT_TRANSITION_TURN_OFF > 0) - call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms - call.perform(); - } - break; - case 2: - if (light_index >= id(gb_lights_2).size()) { - ESP_LOGE("core_hw_leds", "Invalid set with %s light for Relay %" PRIu8, - light_mode_group_2.c_str(), light_index+1); - return; - } - if (state and !id(gb_lights_2)[light_index]->current_values.is_on()) { - ESP_LOGI("core_hw_leds", "Turn-on %s light for Relay %" PRIu8, - light_mode_group_2.c_str(), light_index+1); - auto call = id(gb_lights_2)[light_index]->turn_on(); - if (LIGHT_TRANSITION_TURN_ON > 0) - call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms - if (LIGHT_BRIGHTNESS_TURN_ON > 0 and LIGHT_BRIGHTNESS_TURN_ON <= 100) - call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f); - call.perform(); - } else if (!state and id(gb_lights_2)[light_index]->current_values.is_on()) { - ESP_LOGI("core_hw_leds", "Turn-off %s light for Relay %" PRIu8, - light_mode_group_2.c_str(), light_index+1); - auto call = id(gb_lights_2)[light_index]->turn_off(); - if (LIGHT_TRANSITION_TURN_OFF > 0) - call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms - call.perform(); - } - break; + auto call = light_ptr->make_call(); + call.set_brightness(attrs.brightness / 100.0f); + call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f); + call.perform(); + ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored (group=%d) attributes: brightness=%d%%, RGB=(%d,%d,%d)", + group_index, attrs.brightness, attrs.red, attrs.green, attrs.blue); + + - id: light_attributes_save + mode: parallel + parameters: + light_ptr: light::LightState* + attr_global_ptr: uint32_t* + then: + - lambda: |- + if (!light_ptr) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer"); + return; + } + if (!attr_global_ptr) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer"); + return; + } + auto current = light_ptr->current_values; + tx_ultimate_easy::LightAttributes attrs = { + .brightness = uint8_t(current.get_brightness() * 100.0f), + .red = uint8_t(current.get_red() * 255.0f), + .green = uint8_t(current.get_green() * 255.0f), + .blue = uint8_t(current.get_blue() * 255.0f) + }; + + *attr_global_ptr = tx_ultimate_easy::pack_light_attributes(attrs); + ESP_LOGD("${TAG_CORE_HW_LEDS}", "Saved light attributes: brightness=%d%%, RGB=(%d,%d,%d)", + attrs.brightness, attrs.red, attrs.green, attrs.blue); + + - id: light_attributes_save_dual + mode: queued # To prevent two instances trying to change the same global + parameters: + light_ptr: light::LightState* + attr_global_ptr: uint64_t* + group_index: uint8_t # 1 or 2 (which group within the dual pack) + then: + - lambda: |- + if (!light_ptr) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer"); + return; + } + if (!attr_global_ptr) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer"); + return; + } + if (group_index != 1 && group_index != 2) { + ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid group_index=%d (expected 1 or 2)", group_index); + return; + } + auto current = light_ptr->current_values; + tx_ultimate_easy::LightAttributes new_attrs = { + .brightness = uint8_t(current.get_brightness() * 100.0f), + .red = uint8_t(current.get_red() * 255.0f), + .green = uint8_t(current.get_green() * 255.0f), + .blue = uint8_t(current.get_blue() * 255.0f) + }; + + // Unpack existing dual attributes + auto existing = tx_ultimate_easy::unpack_dual_light_attributes(*attr_global_ptr); + + // Update the appropriate group + if (group_index == 1) { + *attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(new_attrs, existing.second); + } else { + *attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(existing.first, new_attrs); } -select: - # EU relay light mode template - - &relay_light_mode_eu - id: sl_relay_1_light_mode_eu - name: Relay 1 light mode EU - platform: template - options: - - "${LIGHT_MODE_DISABLED_TEXT}" - - "${LIGHT_MODE_BOTTOM_TEXT}" - - "${LIGHT_MODE_TOP_TEXT}" - - "${LIGHT_MODE_BOTH_TEXT}" - initial_option: "${LIGHT_MODE_BOTTOM_TEXT}" - optimistic: true - restore_value: true - internal: true - entity_category: config - disabled_by_default: false - icon: mdi:dots-square - - id: sl_relay_2_light_mode_eu - name: Relay 2 light mode EU - <<: *relay_light_mode_eu - - id: sl_relay_3_light_mode_eu - name: Relay 3 light mode EU - <<: *relay_light_mode_eu - - id: sl_relay_4_light_mode_eu - name: Relay 4 light mode EU - <<: *relay_light_mode_eu + - id: light_effect_save + mode: queued + parameters: + light_ptr: light::LightState* + effect_global_ptr: uint32_t* + then: + - lambda: |- + if (!light_ptr || !effect_global_ptr) return; - # US relay light mode template - - &relay_light_mode_us - id: sl_relay_1_light_mode_us - name: Relay 1 light mode US - platform: template - options: - - "${LIGHT_MODE_DISABLED_TEXT}" - - "${LIGHT_MODE_LEFT_TEXT}" - - "${LIGHT_MODE_RIGHT_TEXT}" - - "${LIGHT_MODE_BOTH_TEXT}" - initial_option: "${LIGHT_MODE_RIGHT_TEXT}" - optimistic: true - restore_value: true - internal: true - entity_category: config - disabled_by_default: false - icon: mdi:dots-square - - id: sl_relay_2_light_mode_us - name: Relay 2 light mode US - <<: *relay_light_mode_us - - id: sl_relay_3_light_mode_us - name: Relay 3 light mode US - <<: *relay_light_mode_us - - id: sl_relay_4_light_mode_us - name: Relay 4 light mode US - <<: *relay_light_mode_us + uint32_t effect_index = light_ptr->get_current_effect_index(); + // Store effect index (16 bits) + reserved space for future use + *effect_global_ptr = effect_index & 0xFFFF; + + ESP_LOGD("${TAG_CORE_HW_LEDS}", "Saved effect index: %d", effect_index); + + - id: light_effect_restore + mode: parallel + parameters: + light_ptr: light::LightState* + effect_global_ptr: uint32_t* + then: + - lambda: |- + if (!light_ptr || !effect_global_ptr) return; + + uint32_t effect_index = *effect_global_ptr & 0xFFFF; + + if (effect_index == 0) return; // No effect + if (effect_index > light_ptr->get_effect_count()) { + ESP_LOGW("${TAG_CORE_HW_LEDS}", "Invalid effect index: %d (max: %d)", + effect_index, light_ptr->get_effect_count()); + return; + } + + auto call = light_ptr->make_call(); + call.set_effect(effect_index); + call.perform(); + + ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored effect: %s (index=%d)", + light_ptr->get_effect_name_by_index(effect_index).c_str(), effect_index); + + - id: !extend restore_from_nvs + then: + # LED restoration + - if: + condition: + lambda: return id(is_us_model); + then: + # US model restoration + - script.execute: + id: light_attributes_restore_dual + light_ptr: !lambda return id(light_us); + attr_global_ptr: !lambda return &id(led_main_packed_attrs); + group_index: 2 + - script.execute: + id: light_effect_restore + light_ptr: !lambda return id(light_us); + effect_global_ptr: !lambda return &id(led_main_packed_effect); + - script.execute: + id: light_attributes_restore_dual + light_ptr: !lambda return id(light_us_left); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 1 + - script.execute: + id: light_attributes_restore_dual + light_ptr: !lambda return id(light_us_right); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 2 + else: + # EU model restoration + - script.execute: + id: light_attributes_restore_dual + light_ptr: !lambda return id(light_eu); + attr_global_ptr: !lambda return &id(led_main_packed_attrs); + group_index: 1 + - script.execute: + id: light_effect_restore + light_ptr: !lambda return id(light_eu); + effect_global_ptr: !lambda return &id(led_main_packed_effect); + - script.execute: + id: light_attributes_restore_dual + light_ptr: !lambda return id(light_eu_bottom); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 1 + - script.execute: + id: light_attributes_restore_dual + light_ptr: !lambda return id(light_eu_top); + attr_global_ptr: !lambda return &id(led_sides_packed_attrs); + group_index: 2 + + # Light full restoration + - script.execute: + id: light_attributes_restore + light_ptr: !lambda return id(light_full); + attr_global_ptr: !lambda return &id(led_full_attrs); ... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml index 0bf617c..5d1d7a2 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_touch.yaml @@ -14,6 +14,8 @@ substitutions: TOUCH_POSITION_MAX_VALUE: '10' # Maximum touch position value returned by the touch pad via uart + TAG_CORE_HW_TOUCH: core.hw.touch + binary_sensor: - &bs_button_event_template id: bs_button_event_template @@ -203,15 +205,6 @@ esphome: build_flags: - -D TX_ULTIMATE_EASY_CORE_HW_TOUCH -external_components: - - source: - type: git - url: https://github.com/edwardtfn/TX-Ultimate-Easy - ref: v${version} - refresh: 1h - components: - - tx_ultimate_easy - number: - id: nr_touch_duration name: Touch event duration @@ -293,18 +286,18 @@ script: - lambda: |- // Touch panel's state if (sw_touch_panel_power->state) - ESP_LOGCONFIG("core_hw_touch", "Touch panel - Power: On"); + ESP_LOGCONFIG("${TAG_CORE_HW_TOUCH}", "Touch panel - Power: On"); else - ESP_LOGW("core_hw_touch", "Touch panel - Power: OFF"); - ESP_LOGCONFIG("core_hw_touch", "Touch event duration: %.0fms", nr_touch_duration->state); + ESP_LOGW("${TAG_CORE_HW_TOUCH}", "Touch panel - Power: OFF"); + ESP_LOGCONFIG("${TAG_CORE_HW_TOUCH}", "Touch event duration: %.0fms", nr_touch_duration->state); - id: !extend dump_config_list_packages then: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif // Identify itself @@ -373,16 +366,15 @@ switch: entity_category: config tx_ultimate_easy: - id: tx_ultimate uart: uart_touch on_long_touch_release: - lambda: |- const uint8_t touch_position = static_cast(touch.x); if (touch_position > ${TOUCH_POSITION_MAX_VALUE}) { // Check for valid range - ESP_LOGE("core_hw_touch", "Invalid long-touch position: %" PRIu8, touch_position); + ESP_LOGE("${TAG_CORE_HW_TOUCH}", "Invalid long-touch position: %" PRIu8, touch_position); } else { - ESP_LOGI("core_hw_touch", "Long-touch released at position %" PRIu8, touch_position); + ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Long-touch released at position %" PRIu8, touch_position); } on_multi_touch_release: @@ -397,7 +389,7 @@ tx_ultimate_easy: action: release position: !lambda return std::to_string(touch.x); - lambda: |- - ESP_LOGI("core_hw_touch", "Multi-touch released"); + ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Multi-touch released"); touch_on_multi_touch_release->execute(); on_press: @@ -405,15 +397,16 @@ tx_ultimate_easy: - lambda: |- const uint8_t position = static_cast(touch.x); if (position > ${TOUCH_POSITION_MAX_VALUE}) { // Check for valid range - ESP_LOGE("core_hw_touch", "Invalid touch position: %" PRIu8, position); + ESP_LOGE("${TAG_CORE_HW_TOUCH}", "Invalid touch position: %" PRIu8, position); } else { - ESP_LOGI("core_hw_touch", "Pressed at position %" PRIu8, position); + ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Pressed at position %" PRIu8, position); touch_on_press->execute(static_cast(touch.button), position); } on_release: then: - - lambda: ESP_LOGI("core_hw_touch", "Released"); + - lambda: |- + ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Released"); - script.execute: touch_on_release on_swipe_left: @@ -437,7 +430,7 @@ tx_ultimate_easy: return "down"; position: !lambda return std::to_string(touch.x); - lambda: |- - ESP_LOGI("core_hw_touch", "Swipe %s", + ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Swipe %s", (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}") ? "left" : "down"); touch_swipe_left->execute(); @@ -462,15 +455,15 @@ tx_ultimate_easy: return "up"; position: !lambda return std::to_string(touch.x); - lambda: |- - ESP_LOGI("core_hw_touch", "Swipe %s", + ESP_LOGI("${TAG_CORE_HW_TOUCH}", "Swipe %s", (sl_tx_model_format->state == "${TX_MODEL_FORMAT_EU_TEXT}") ? "right" : "up"); touch_swipe_right->execute(); on_touch_event: - lambda: |- - ESP_LOGD("core_hw_touch", "Touch event:"); - ESP_LOGD("core_hw_touch", " State: %i (%s)", touch.state, touch.state_str.c_str()); - ESP_LOGD("core_hw_touch", " Position: %i", touch.x); + ESP_LOGD("${TAG_CORE_HW_TOUCH}", "Touch event:"); + ESP_LOGD("${TAG_CORE_HW_TOUCH}", " State: %i (%s)", touch.state, touch.state_str.c_str()); + ESP_LOGD("${TAG_CORE_HW_TOUCH}", " Position: %i", touch.x); uart: - id: uart_touch diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml index 0894e3a..a11e595 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml @@ -14,6 +14,8 @@ substitutions: SWITCH_AUDIO_AMPLIFIER_RESTORE_MODE: RESTORE_DEFAULT_ON + TAG_STD_HW_AUDIO: std.hw.audio + esphome: platformio_options: build_flags: @@ -30,8 +32,8 @@ script: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif // Identify itself diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml index 0f019eb..8fe7287 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_relays.yaml @@ -16,8 +16,14 @@ substitutions: 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 + # 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 esphome: platformio_options: @@ -25,6 +31,30 @@ esphome: - -D TX_ULTIMATE_EASY_STANDARD_HW_RELAYS 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 type: bool restore_value: false @@ -46,6 +76,36 @@ globals: restore_value: false initial_value: 'false' + - id: gb_lights_1 + type: std::vector + restore_value: false + - id: gb_lights_2 + type: std::vector + 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 + - id: relay_modes_packed + type: uint16_t + restore_value: true + initial_value: '0' # Binary: 0000000000000000 = all relay modes set to option 0 (default action - switch) + # 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 + + # Packed switch states - replaces 5 separate switches + - id: relay_switches_packed + type: uint8_t + restore_value: true + initial_value: '0' # All switches default to OFF + # Bit layout: [sw_relay4][sw_relay3][sw_relay2][sw_relay1][sw_expose_leds] + light: # These lights are used for the physical relays to be shown as lights - id: light_output_1 @@ -53,7 +113,7 @@ light: output: output_relay_1 platform: binary internal: true - restore_mode: ${RELAY_RESTORE_MODE} + restore_mode: ${RELAYS_LIGHT_RESTORE_MODE} on_turn_on: then: - if: @@ -73,6 +133,7 @@ light: output: output_relay_2 platform: binary internal: true + restore_mode: ${RELAYS_LIGHT_RESTORE_MODE} on_turn_on: then: - if: @@ -92,6 +153,7 @@ light: output: output_relay_3 platform: binary internal: true + restore_mode: ${RELAYS_LIGHT_RESTORE_MODE} on_turn_on: then: - if: @@ -111,6 +173,7 @@ light: output: output_relay_4 platform: binary internal: true + restore_mode: ${RELAYS_LIGHT_RESTORE_MODE} on_turn_on: then: - if: @@ -134,11 +197,17 @@ light: internal: true disabled_by_default: false default_transition_length: ${default_transition_length} - restore_mode: ${LIGHT_RELAYS_RESTORE_MODE} + restore_mode: ${RELAYS_LIGHT_PARTITION_RESTORE_MODE} segments: - id: light_full from: 9 to: 9 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_1_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_1_1_both); + group_index: 1 - id: light_eu_rl_1_1_top name: Light - Relay 1 of 1 (top) <<: *light_partition_relays @@ -146,6 +215,12 @@ light: - id: light_full from: 23 to: 23 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_1_top); + attr_global_ptr: !lambda return &id(attr_light_rl_1_1_both); + group_index: 2 - id: light_eu_rl_1_2_bottom name: Light - Relay 1 of 2 (bottom) <<: *light_partition_relays @@ -153,6 +228,12 @@ light: - id: light_full from: 11 to: 11 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_2_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_1_2_both); + group_index: 1 - id: light_eu_rl_1_2_top name: Light - Relay 1 of 2 (top) <<: *light_partition_relays @@ -160,6 +241,12 @@ light: - id: light_full from: 21 to: 21 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_2_top); + attr_global_ptr: !lambda return &id(attr_light_rl_1_2_both); + group_index: 2 - id: light_eu_rl_2_2_bottom name: Light - Relay 2 of 2 (bottom) <<: *light_partition_relays @@ -167,6 +254,12 @@ light: - id: light_full from: 7 to: 7 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_2_2_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_2_2_both); + group_index: 1 - id: light_eu_rl_2_2_top name: Light - Relay 2 of 2 (top) <<: *light_partition_relays @@ -174,6 +267,12 @@ light: - id: light_full from: 25 to: 25 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_2_2_top); + attr_global_ptr: !lambda return &id(attr_light_rl_2_2_both); + group_index: 2 - id: light_eu_rl_1_3_bottom name: Light - Relay 1 of 3 (bottom) <<: *light_partition_relays @@ -181,6 +280,12 @@ light: - id: light_full from: 12 to: 12 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_3_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_1_3_both); + group_index: 1 - id: light_eu_rl_1_3_top name: Light - Relay 1 of 3 (top) <<: *light_partition_relays @@ -188,6 +293,12 @@ light: - id: light_full from: 20 to: 20 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_3_top); + attr_global_ptr: !lambda return &id(attr_light_rl_1_3_both); + group_index: 2 - id: light_eu_rl_2_3_bottom name: Light - Relay 2 of 3 (bottom) <<: *light_partition_relays @@ -195,6 +306,12 @@ light: - id: light_full from: 9 to: 9 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_2_3_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_2_3_both); + group_index: 1 - id: light_eu_rl_2_3_top name: Light - Relay 2 of 3 (top) <<: *light_partition_relays @@ -202,6 +319,12 @@ light: - id: light_full from: 23 to: 23 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_2_3_top); + attr_global_ptr: !lambda return &id(attr_light_rl_2_3_both); + group_index: 2 - id: light_eu_rl_3_3_bottom name: Light - Relay 3 of 3 (bottom) <<: *light_partition_relays @@ -209,6 +332,12 @@ light: - id: light_full from: 6 to: 6 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_3_3_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_3_3_both); + group_index: 1 - id: light_eu_rl_3_3_top name: Light - Relay 3 of 3 (top) <<: *light_partition_relays @@ -216,6 +345,12 @@ light: - id: light_full from: 26 to: 26 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_3_3_top); + attr_global_ptr: !lambda return &id(attr_light_rl_3_3_both); + group_index: 2 - id: light_eu_rl_1_4_bottom name: Light - Relay 1 of 4 (bottom) <<: *light_partition_relays @@ -223,6 +358,12 @@ light: - id: light_full from: 12 to: 12 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_4_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_1_4_both); + group_index: 1 - id: light_eu_rl_1_4_top name: Light - Relay 1 of 4 (top) <<: *light_partition_relays @@ -230,6 +371,12 @@ light: - id: light_full from: 20 to: 20 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_1_4_top); + attr_global_ptr: !lambda return &id(attr_light_rl_1_4_both); + group_index: 2 - id: light_eu_rl_2_4_bottom name: Light - Relay 2 of 4 (bottom) <<: *light_partition_relays @@ -237,6 +384,12 @@ light: - id: light_full from: 10 to: 10 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_2_4_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_2_4_both); + group_index: 1 - id: light_eu_rl_2_4_top name: Light - Relay 2 of 4 (top) <<: *light_partition_relays @@ -244,6 +397,12 @@ light: - id: light_full from: 22 to: 22 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_2_4_top); + attr_global_ptr: !lambda return &id(attr_light_rl_2_4_both); + group_index: 2 - id: light_eu_rl_3_4_bottom name: Light - Relay 3 of 4 (bottom) <<: *light_partition_relays @@ -251,6 +410,12 @@ light: - id: light_full from: 8 to: 8 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_3_4_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_3_4_both); + group_index: 1 - id: light_eu_rl_3_4_top name: Light - Relay 3 of 4 (top) <<: *light_partition_relays @@ -258,6 +423,12 @@ light: - id: light_full from: 24 to: 24 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_3_4_top); + attr_global_ptr: !lambda return &id(attr_light_rl_3_4_both); + group_index: 2 - id: light_eu_rl_4_4_bottom name: Light - Relay 4 of 4 (bottom) <<: *light_partition_relays @@ -265,6 +436,12 @@ light: - id: light_full from: 6 to: 6 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_4_4_bottom); + attr_global_ptr: !lambda return &id(attr_light_rl_4_4_both); + group_index: 1 - id: light_eu_rl_4_4_top name: Light - Relay 4 of 4 (top) <<: *light_partition_relays @@ -272,6 +449,12 @@ light: - id: light_full from: 26 to: 26 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_eu_rl_4_4_top); + attr_global_ptr: !lambda return &id(attr_light_rl_4_4_both); + group_index: 2 - id: light_us_rl_1_1_left name: Light - Relay 1 of 1 (left) <<: *light_partition_relays @@ -279,6 +462,12 @@ light: - id: light_full from: 15 to: 24 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_1_left); + attr_global_ptr: !lambda return &id(attr_light_rl_1_1_both); + group_index: 1 - id: light_us_rl_1_1_right name: Light - Relay 1 of 1 (right) <<: *light_partition_relays @@ -289,6 +478,12 @@ light: - id: light_full from: 0 to: 8 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_1_right); + attr_global_ptr: !lambda return &id(attr_light_rl_1_1_both); + group_index: 2 - id: light_us_rl_1_2_left name: Light - Relay 1 of 2 (left) <<: *light_partition_relays @@ -296,6 +491,12 @@ light: - id: light_full from: 20 to: 24 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_2_left); + attr_global_ptr: !lambda return &id(attr_light_rl_1_2_both); + group_index: 1 - id: light_us_rl_1_2_right name: Light - Relay 1 of 2 (right) <<: *light_partition_relays @@ -306,6 +507,12 @@ light: - id: light_full from: 0 to: 3 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_2_right); + attr_global_ptr: !lambda return &id(attr_light_rl_1_2_both); + group_index: 2 - id: light_us_rl_2_2_left name: Light - Relay 2 of 2 (left) <<: *light_partition_relays @@ -313,6 +520,12 @@ light: - id: light_full from: 15 to: 19 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_2_2_left); + attr_global_ptr: !lambda return &id(attr_light_rl_2_2_both); + group_index: 1 - id: light_us_rl_2_2_right name: Light - Relay 2 of 2 (right) <<: *light_partition_relays @@ -320,6 +533,12 @@ light: - id: light_full from: 4 to: 8 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_2_2_right); + attr_global_ptr: !lambda return &id(attr_light_rl_2_2_both); + group_index: 2 - id: light_us_rl_1_3_left name: Light - Relay 1 of 3 (left) <<: *light_partition_relays @@ -327,6 +546,12 @@ light: - id: light_full from: 22 to: 23 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_3_left); + attr_global_ptr: !lambda return &id(attr_light_rl_1_3_both); + group_index: 1 - id: light_us_rl_1_3_right name: Light - Relay 1 of 3 (right) <<: *light_partition_relays @@ -334,6 +559,12 @@ light: - id: light_full from: 0 to: 1 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_3_right); + attr_global_ptr: !lambda return &id(attr_light_rl_1_3_both); + group_index: 2 - id: light_us_rl_2_3_left name: Light - Relay 2 of 3 (left) <<: *light_partition_relays @@ -341,6 +572,12 @@ light: - id: light_full from: 19 to: 20 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_2_3_left); + attr_global_ptr: !lambda return &id(attr_light_rl_2_3_both); + group_index: 1 - id: light_us_rl_2_3_right name: Light - Relay 2 of 3 (right) <<: *light_partition_relays @@ -348,6 +585,12 @@ light: - id: light_full from: 3 to: 4 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_2_3_right); + attr_global_ptr: !lambda return &id(attr_light_rl_2_3_both); + group_index: 2 - id: light_us_rl_3_3_left name: Light - Relay 3 of 3 (left) <<: *light_partition_relays @@ -355,6 +598,12 @@ light: - id: light_full from: 16 to: 17 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_3_3_left); + attr_global_ptr: !lambda return &id(attr_light_rl_3_3_both); + group_index: 1 - id: light_us_rl_3_3_right name: Light - Relay 3 of 3 (right) <<: *light_partition_relays @@ -362,6 +611,12 @@ light: - id: light_full from: 6 to: 7 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_3_3_right); + attr_global_ptr: !lambda return &id(attr_light_rl_3_3_both); + group_index: 2 - id: light_us_rl_1_4_left name: Light - Relay 1 of 4 (left) <<: *light_partition_relays @@ -369,6 +624,12 @@ light: - id: light_full from: 23 to: 24 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_4_left); + attr_global_ptr: !lambda return &id(attr_light_rl_1_4_both); + group_index: 1 - id: light_us_rl_1_4_right name: Light - Relay 1 of 4 (right) <<: *light_partition_relays @@ -379,6 +640,12 @@ light: - id: light_full from: 0 to: 0 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_1_4_right); + attr_global_ptr: !lambda return &id(attr_light_rl_1_4_both); + group_index: 2 - id: light_us_rl_2_4_left name: Light - Relay 2 of 4 (left) <<: *light_partition_relays @@ -386,6 +653,12 @@ light: - id: light_full from: 21 to: 22 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_2_4_left); + attr_global_ptr: !lambda return &id(attr_light_rl_2_4_both); + group_index: 1 - id: light_us_rl_2_4_right name: Light - Relay 2 of 4 (right) <<: *light_partition_relays @@ -393,6 +666,12 @@ light: - id: light_full from: 1 to: 2 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_2_4_right); + attr_global_ptr: !lambda return &id(attr_light_rl_2_4_both); + group_index: 2 - id: light_us_rl_3_4_left name: Light - Relay 3 of 4 (left) <<: *light_partition_relays @@ -400,6 +679,12 @@ light: - id: light_full from: 17 to: 18 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_3_4_left); + attr_global_ptr: !lambda return &id(attr_light_rl_3_4_both); + group_index: 1 - id: light_us_rl_3_4_right name: Light - Relay 3 of 4 (right) <<: *light_partition_relays @@ -407,6 +692,12 @@ light: - id: light_full from: 5 to: 6 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_3_4_right); + attr_global_ptr: !lambda return &id(attr_light_rl_3_4_both); + group_index: 2 - id: light_us_rl_4_4_left name: Light - Relay 4 of 4 (left) <<: *light_partition_relays @@ -414,6 +705,12 @@ light: - id: light_full from: 15 to: 16 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_4_4_left); + attr_global_ptr: !lambda return &id(attr_light_rl_4_4_both); + group_index: 1 - id: light_us_rl_4_4_right name: Light - Relay 4 of 4 (right) <<: *light_partition_relays @@ -421,6 +718,12 @@ light: - id: light_full from: 7 to: 8 + on_state: + - script.execute: + id: light_attributes_save_dual + light_ptr: !lambda return id(light_us_rl_4_4_right); + attr_global_ptr: !lambda return &id(attr_light_rl_4_4_both); + group_index: 2 output: - id: output_relay_1 @@ -512,7 +815,7 @@ script: } break; default: - ESP_LOGE("standard_hw_relays", "Invalid gang count: %" PRIu8, id(gang_count)); + ESP_LOGE("${TAG_STD_HW_RELAYS}", "Invalid gang count: %" PRIu8, id(gang_count)); break; } id(boot_initialization_relays_group_assignments) = true; @@ -610,8 +913,8 @@ script: // 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); + light_output_1->set_internal(relay_mode_index.value() != 1); + sw_relay_1->set_internal(relay_mode_index.value() != 0); } relay_mode_index = sl_relay_2_mode->active_index(); if (relay_mode_index.has_value()) { @@ -653,13 +956,13 @@ script: 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); + ESP_LOGW("${TAG_STD_HW_RELAYS}", "Toggle relays:"); + ESP_LOGW("${TAG_STD_HW_RELAYS}", " API state: DISCONNECTED"); + ESP_LOGW("${TAG_STD_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); + ESP_LOGVV("${TAG_STD_HW_RELAYS}", "Toggle relays:"); + ESP_LOGVV("${TAG_STD_HW_RELAYS}", " API state: Connected"); + ESP_LOGVV("${TAG_STD_HW_RELAYS}", " Button: %" PRIu8, button); } switch (button) { case ${BUTTON_1_ID}: @@ -692,12 +995,12 @@ script: break; } if (toggle_relay) { - ESP_LOGI("standard_hw_relays", "Toggle relay %" PRIu8 ": (%s)", button, select_state.c_str()); + ESP_LOGI("${TAG_STD_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'", + ESP_LOGV("${TAG_STD_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); + ESP_LOGE("${TAG_STD_HW_RELAYS}", "No button action selected for button %" PRIu8, button); } - id: !extend dump_config @@ -705,72 +1008,284 @@ script: - lambda: |- std::string gang_count_plural_suffix = id(gang_count) > 1 ? "s" : ""; // Relay's modes - ESP_LOGCONFIG("standard_hw_relays", "Relay%s mode%s:", - gang_count_plural_suffix.c_str(), - 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"); + ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", "Relay%s mode%s:", gang_count_plural_suffix.c_str(), + gang_count_plural_suffix.c_str()); + ESP_LOGCONFIG("${TAG_STD_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"); + ESP_LOGCONFIG("${TAG_STD_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"); + ESP_LOGCONFIG("${TAG_STD_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"); + ESP_LOGCONFIG("${TAG_STD_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:", - gang_count_plural_suffix.c_str(), - gang_count_plural_suffix.c_str()); - ESP_LOGCONFIG("standard_hw_relays", " Relay 1: %s", sw_relay_1->state ? "ON" : "OFF"); + ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", "Relay%s state%s:", gang_count_plural_suffix.c_str(), + gang_count_plural_suffix.c_str()); + ESP_LOGCONFIG("${TAG_STD_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"); + ESP_LOGCONFIG("${TAG_STD_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"); + ESP_LOGCONFIG("${TAG_STD_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)); + ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Relay 4: %s", sw_relay_4->state ? "ON" : "OFF"); + ESP_LOGCONFIG("${TAG_STD_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))); + ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", "Boot initialization flags:"); + ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Overall: %s", YESNO(id(boot_initialization_relays))); + ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Groups: %s", + YESNO(id(boot_initialization_relays_group_assignments))); + ESP_LOGCONFIG("${TAG_STD_HW_RELAYS}", " Light modes: %s", YESNO(id(boot_initialization_relays_light_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))); - // 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}"); + // 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 then: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif + #if !defined(TX_ULTIMATE_EASY_CORE_HW_LEDS) + #error "The package TX-Ultimate-Easy-ESPHome_core_hw_leds.yaml is required." + #endif // TX_ULTIMATE_EASY_CORE_HW_LEDS + // Identify itself ESP_LOGCONFIG(ESPHOME_PROJECT_NAME, " - Standard - Hardware - Relays"); + - id: light_set_state + mode: parallel + parameters: + light_group: uint8_t + light_index: uint8_t + state: bool + then: + - lambda: |- + static const uint32_t LIGHT_TRANSITION_TURN_ON = ${LIGHT_TRANSITION_TURN_ON}; + static const uint32_t LIGHT_TRANSITION_TURN_OFF = ${LIGHT_TRANSITION_TURN_OFF}; + static const uint8_t LIGHT_BRIGHTNESS_TURN_ON = ${LIGHT_BRIGHTNESS_TURN_ON}; + + const std::string light_mode_group_1 = id(is_us_model) + ? "${LIGHT_MODE_LEFT_TEXT}" + : "${LIGHT_MODE_BOTTOM_TEXT}"; + const std::string light_mode_group_2 = id(is_us_model) + ? "${LIGHT_MODE_RIGHT_TEXT}" + : "${LIGHT_MODE_TOP_TEXT}"; + + switch (light_group) { + case 1: + if (light_index >= id(gb_lights_1).size()) { + ESP_LOGE("${TAG_STD_HW_RELAYS}", "Invalid set with %s light for Relay %" PRIu8, light_mode_group_1.c_str(), light_index+1); + return; + } + if (state and !id(gb_lights_1)[light_index]->current_values.is_on()) { + ESP_LOGI("${TAG_STD_HW_RELAYS}", "Turn-on %s light for Relay %" PRIu8, light_mode_group_1.c_str(), light_index+1); + auto call = id(gb_lights_1)[light_index]->turn_on(); + if (LIGHT_TRANSITION_TURN_ON > 0) + call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms + if (LIGHT_BRIGHTNESS_TURN_ON > 0 and LIGHT_BRIGHTNESS_TURN_ON <= 100) + call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f); + call.perform(); + } else if (!state and id(gb_lights_1)[light_index]->current_values.is_on()) { + ESP_LOGI("${TAG_STD_HW_RELAYS}", "Turn-off %s light for Relay %" PRIu8, light_mode_group_1.c_str(), light_index+1); + auto call = id(gb_lights_1)[light_index]->turn_off(); + if (LIGHT_TRANSITION_TURN_OFF > 0) + call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms + call.perform(); + } + break; + case 2: + if (light_index >= id(gb_lights_2).size()) { + ESP_LOGE("${TAG_STD_HW_RELAYS}", + "Invalid set with %s light for Relay %" PRIu8, light_mode_group_2.c_str(), light_index+1); + return; + } + 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); + auto call = id(gb_lights_2)[light_index]->turn_on(); + if (LIGHT_TRANSITION_TURN_ON > 0) + call.set_transition_length(LIGHT_TRANSITION_TURN_ON); // in ms + if (LIGHT_BRIGHTNESS_TURN_ON > 0 and LIGHT_BRIGHTNESS_TURN_ON <= 100) + call.set_brightness(LIGHT_BRIGHTNESS_TURN_ON / 100.0f); + call.perform(); + } else if (!state and id(gb_lights_2)[light_index]->current_values.is_on()) { + ESP_LOGI("${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(); + if (LIGHT_TRANSITION_TURN_OFF > 0) + call.set_transition_length(LIGHT_TRANSITION_TURN_OFF); // in ms + call.perform(); + } + break; + default: + ESP_LOGE("${TAG_STD_HW_RELAYS}", "Invalid light_group=%" PRIu8 " (expected 1 or 2)", light_group); + break; + } + + - id: !extend restore_from_nvs + then: + # Relay modes + - select.set_index: + id: sl_relay_1_mode + index: !lambda return std::min((uint8_t)(id(relay_modes_packed) & 0x0F), (uint8_t)2); + - select.set_index: + id: sl_relay_2_mode + index: !lambda return std::min((uint8_t)((id(relay_modes_packed) >> 4) & 0x0F), (uint8_t)2); + - select.set_index: + id: sl_relay_3_mode + index: !lambda return std::min((uint8_t)((id(relay_modes_packed) >> 8) & 0x0F), (uint8_t)2); + - select.set_index: + id: sl_relay_4_mode + 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.control: + id: sw_expose_relays_leds_to_ha + state: !lambda return (id(relay_switches_packed) & 0x01); + - switch.control: + id: sw_relay_1 + state: !lambda return (id(relay_switches_packed) & 0x02); + - switch.control: + id: sw_relay_2 + state: !lambda return (id(relay_switches_packed) & 0x04); + - switch.control: + id: sw_relay_3 + state: !lambda return (id(relay_switches_packed) & 0x08); + - switch.control: + id: sw_relay_4 + state: !lambda return (id(relay_switches_packed) & 0x10); + + # update lights based on new relay statuses (catch up, as this should be done when each relay is updated) + - script.execute: show_relay_status + + # Light attributes restoration + - lambda: |- + // Restore all light attributes based on current model and gang count + switch (id(gang_count)) { + case 1: // 1-Gang + if (id(is_us_model)) { + light_attributes_restore_dual->execute(id(light_us_rl_1_1_left), &id(attr_light_rl_1_1_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_1_1_right), &id(attr_light_rl_1_1_both), 2); + } else { + light_attributes_restore_dual->execute(id(light_eu_rl_1_1_bottom), &id(attr_light_rl_1_1_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_1_1_top), &id(attr_light_rl_1_1_both), 2); + } + break; + case 2: // 2-Gang + if (id(is_us_model)) { + light_attributes_restore_dual->execute(id(light_us_rl_1_2_left), &id(attr_light_rl_1_2_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_1_2_right), &id(attr_light_rl_1_2_both), 2); + light_attributes_restore_dual->execute(id(light_us_rl_2_2_left), &id(attr_light_rl_2_2_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_2_2_right), &id(attr_light_rl_2_2_both), 2); + } else { + light_attributes_restore_dual->execute(id(light_eu_rl_1_2_bottom), &id(attr_light_rl_1_2_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_1_2_top), &id(attr_light_rl_1_2_both), 2); + light_attributes_restore_dual->execute(id(light_eu_rl_2_2_bottom), &id(attr_light_rl_2_2_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_2_2_top), &id(attr_light_rl_2_2_both), 2); + } + break; + case 3: // 3-Gang + if (id(is_us_model)) { + light_attributes_restore_dual->execute(id(light_us_rl_1_3_left), &id(attr_light_rl_1_3_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_1_3_right), &id(attr_light_rl_1_3_both), 2); + light_attributes_restore_dual->execute(id(light_us_rl_2_3_left), &id(attr_light_rl_2_3_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_2_3_right), &id(attr_light_rl_2_3_both), 2); + light_attributes_restore_dual->execute(id(light_us_rl_3_3_left), &id(attr_light_rl_3_3_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_3_3_right), &id(attr_light_rl_3_3_both), 2); + } else { + light_attributes_restore_dual->execute(id(light_eu_rl_1_3_bottom), &id(attr_light_rl_1_3_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_1_3_top), &id(attr_light_rl_1_3_both), 2); + light_attributes_restore_dual->execute(id(light_eu_rl_2_3_bottom), &id(attr_light_rl_2_3_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_2_3_top), &id(attr_light_rl_2_3_both), 2); + light_attributes_restore_dual->execute(id(light_eu_rl_3_3_bottom), &id(attr_light_rl_3_3_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_3_3_top), &id(attr_light_rl_3_3_both), 2); + } + break; + case 4: // 4-Gang + if (id(is_us_model)) { + light_attributes_restore_dual->execute(id(light_us_rl_1_4_left), &id(attr_light_rl_1_4_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_1_4_right), &id(attr_light_rl_1_4_both), 2); + light_attributes_restore_dual->execute(id(light_us_rl_2_4_left), &id(attr_light_rl_2_4_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_2_4_right), &id(attr_light_rl_2_4_both), 2); + light_attributes_restore_dual->execute(id(light_us_rl_3_4_left), &id(attr_light_rl_3_4_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_3_4_right), &id(attr_light_rl_3_4_both), 2); + light_attributes_restore_dual->execute(id(light_us_rl_4_4_left), &id(attr_light_rl_4_4_both), 1); + light_attributes_restore_dual->execute(id(light_us_rl_4_4_right), &id(attr_light_rl_4_4_both), 2); + } else { + light_attributes_restore_dual->execute(id(light_eu_rl_1_4_bottom), &id(attr_light_rl_1_4_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_1_4_top), &id(attr_light_rl_1_4_both), 2); + light_attributes_restore_dual->execute(id(light_eu_rl_2_4_bottom), &id(attr_light_rl_2_4_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_2_4_top), &id(attr_light_rl_2_4_both), 2); + light_attributes_restore_dual->execute(id(light_eu_rl_3_4_bottom), &id(attr_light_rl_3_4_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_3_4_top), &id(attr_light_rl_3_4_both), 2); + light_attributes_restore_dual->execute(id(light_eu_rl_4_4_bottom), &id(attr_light_rl_4_4_both), 1); + light_attributes_restore_dual->execute(id(light_eu_rl_4_4_top), &id(attr_light_rl_4_4_both), 2); + } + break; + } + - 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"); + ESP_LOGW("${TAG_STD_HW_RELAYS}", "Relays not initialized yet"); if (not boot_initialize_relays->is_running()) boot_initialize_relays->execute(); return; @@ -813,26 +1328,132 @@ script: 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()); + ESP_LOGV("${TAG_STD_HW_RELAYS}", "Relay %" PRIu8 " is %s", i + 1, relay_state ? "ON" : "OFF"); + ESP_LOGV("${TAG_STD_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_DISABLED_TEXT}") { if (light_mode == light_mode_group_1 || light_mode == "${LIGHT_MODE_BOTH_TEXT}") { - light_set_state->execute(1, i, true); // Left/bottom light + light_set_state->execute(1, i, relay_state); // 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 + light_set_state->execute(2, i, relay_state); // 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 + # 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 name: Relay 1 display mode platform: template options: @@ -841,7 +1462,7 @@ select: - "${RELAY_MODE_TEXT_NOT_USED}" initial_option: "${RELAY_MODE_TEXT_SWITCH}" optimistic: true - restore_value: true + restore_value: false internal: true entity_category: config disabled_by_default: false @@ -851,121 +1472,441 @@ select: - binary_sensor.template.publish: id: bs_pending_restart state: true + - lambda: |- + id(relay_modes_packed) = (id(relay_modes_packed) & ~(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 name: Relay 2 display mode - <<: *relay_select_mode_base + 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: false + 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 + - lambda: |- + id(relay_modes_packed) = (id(relay_modes_packed) & ~(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 name: Relay 3 display mode - <<: *relay_select_mode_base + 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: false + 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 + - lambda: |- + id(relay_modes_packed) = (id(relay_modes_packed) & ~(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 name: Relay 4 display mode - <<: *relay_select_mode_base + 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: false + 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 + - lambda: |- + id(relay_modes_packed) = (id(relay_modes_packed) & ~(0x0F << 12)) | + ((id(sl_relay_4_mode).active_index().value_or(0) & 0x0F) << 12); switch: - id: sw_expose_relays_leds_to_ha name: Expose Relay's LEDs to Home Assistant platform: template - restore_mode: RESTORE_DEFAULT_OFF + restore_mode: ${RELAYS_EXPOSE_LEDS_TO_HA_RESTORE_MODE} optimistic: true internal: false entity_category: config - on_turn_on: - then: - - binary_sensor.template.publish: - id: bs_pending_restart - state: true - on_turn_off: + on_state: then: - binary_sensor.template.publish: id: bs_pending_restart state: true + - if: + condition: + - lambda: return x; // Turned on? + then: + - lambda: id(relay_switches_packed) |= 0x01; // Set bit 0 + else: + - lambda: id(relay_switches_packed) &= ~0x01; // Clear bit 0 - id: sw_relay_1 name: Relay 1 output: output_relay_1 platform: output - restore_mode: ${RELAY_RESTORE_MODE} + restore_mode: ${RELAYS_SWITCH_RESTORE_MODE} internal: true - on_turn_on: + on_state: then: - script.execute: show_relay_status + on_turn_on: + then: - if: condition: - light.is_off: light_output_1 then: light.turn_on: light_output_1 + - lambda: id(relay_switches_packed) |= 0x02; // Set bit 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 + - lambda: id(relay_switches_packed) &= ~0x02; // Clear bit 1 - id: sw_relay_2 name: Relay 2 output: output_relay_2 platform: output - restore_mode: ${RELAY_RESTORE_MODE} + restore_mode: ${RELAYS_SWITCH_RESTORE_MODE} internal: true - on_turn_on: + on_state: then: - script.execute: show_relay_status + on_turn_on: + then: - if: condition: - light.is_off: light_output_2 then: light.turn_on: light_output_2 + - lambda: id(relay_switches_packed) |= 0x04; // Set bit 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 + - lambda: id(relay_switches_packed) &= ~0x04; // Clear bit 2 - id: sw_relay_3 name: Relay 3 output: output_relay_3 platform: output - restore_mode: ${RELAY_RESTORE_MODE} + restore_mode: ${RELAYS_SWITCH_RESTORE_MODE} internal: true - on_turn_on: + on_state: then: - script.execute: show_relay_status + on_turn_on: + then: - if: condition: - light.is_off: light_output_3 then: - light.turn_on: light_output_3 + - lambda: id(relay_switches_packed) |= 0x08; // Set bit 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 + - lambda: id(relay_switches_packed) &= ~0x08; // Clear bit 3 - id: sw_relay_4 name: Relay 4 output: output_relay_4 platform: output - restore_mode: ${RELAY_RESTORE_MODE} + restore_mode: ${RELAYS_SWITCH_RESTORE_MODE} internal: true - on_turn_on: + on_state: then: - script.execute: show_relay_status + on_turn_on: + then: - if: condition: - light.is_off: light_output_4 then: - light.turn_on: light_output_4 + - lambda: id(relay_switches_packed) |= 0x10; // Set bit 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 + - lambda: id(relay_switches_packed) &= ~0x10; // Clear bit 4 + ... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml index e84a8a1..3cd8678 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml @@ -16,6 +16,8 @@ substitutions: # d=32 (32nd note), o=5 (octave 5), b=100 (tempo 100) tone_volume_change: "scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b" # Use "none" to disable sound + TAG_STD_HW_SPEAKER: std.hw.speaker + packages: standard_hw_audio: !include TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml @@ -31,7 +33,7 @@ api: - lambda: |- rtttl_speaker->stop(); if (speaker_volume->state > 0) { - ESP_LOGI("standard_speaker", "Play tone: '%s'", tone.c_str()); + ESP_LOGI("${TAG_STD_HW_SPEAKER}", "Play tone: '%s'", tone.c_str()); rtttl_speaker->play(tone.c_str()); } @@ -76,8 +78,8 @@ script: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif #if !defined(TX_ULTIMATE_EASY_STANDARD_HW_AUDIO) #error "The package TX-Ultimate-Easy-ESPHome_standard_hw_audio.yaml is required." diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml index bf7205c..2dba617 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_hw_vibration.yaml @@ -14,6 +14,8 @@ substitutions: vibration_max_duration: 2s + TAG_STD_HW_VIBRATION: std.hw.vibration + binary_sensor: - id: bs_vibrating name: Vibrating @@ -62,24 +64,24 @@ script: then: - lambda: |- // Configuration - ESP_LOGCONFIG("standard_hw_vibration", "Touch - Vibration feedback: %s", + ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Touch - Vibration feedback: %s", sl_touch_vibration_feedback->state.c_str()); - ESP_LOGCONFIG("standard_hw_vibration", "Vibrate duration: %.0fms", nr_vibrating_duration->state); - ESP_LOGCONFIG("standard_hw_vibration", "Vibrate max duration: ${vibration_max_duration}"); + ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Vibrate duration: %.0fms", nr_vibrating_duration->state); + ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Vibrate max duration: ${vibration_max_duration}"); // State if (bs_vibrating->state) - ESP_LOGW("standard_hw_vibration", "Vibrating now: YES"); + ESP_LOGW("${TAG_STD_HW_VIBRATION}", "Vibrating now: YES"); else - ESP_LOGCONFIG("standard_hw_vibration", "Vibrating now: No"); + ESP_LOGCONFIG("${TAG_STD_HW_VIBRATION}", "Vibrating now: No"); - id: !extend dump_config_list_packages then: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif // Identify itself @@ -149,20 +151,17 @@ switch: pin: GPIO21 restore_mode: ALWAYS_OFF internal: true - on_turn_on: + on_state: then: - binary_sensor.template.publish: id: bs_vibrating - state: ON # yamllint disable-line rule:truthy + state: !lambda return x; + on_turn_on: + then: - delay: ${vibration_max_duration} - if: condition: switch.is_on: sw_vibration_motor then: - switch.turn_off: sw_vibration_motor - on_turn_off: - then: - - binary_sensor.template.publish: - id: bs_vibrating - state: OFF # yamllint disable-line rule:truthy ... diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml index 203ee28..3d51e86 100644 --- a/ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_standard_media_player.yaml @@ -11,6 +11,9 @@ ##### - For normal system use, modifications to this file are NOT required. ##### #################################################################################################### --- +substitutions: + TAG_STD_MEDIA_PLAYER: "std.media-player" + packages: standard_hw_speaker: !include TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml @@ -54,7 +57,8 @@ media_player: id(persistent_media_player_volume) = static_cast(mp_media_player->volume * 100.0f); if (id(persistent_media_player_volume) != id(last_media_player_volume)) { id(last_media_player_volume) = id(persistent_media_player_volume); - ESP_LOGD("media_player_volume", "Media player volume changed to %" PRIu8 "%%, updating number entity", id(persistent_media_player_volume)); + ESP_LOGD("${TAG_STD_MEDIA_PLAYER}", "Media player volume changed to %" PRIu8 "%%, updating number entity", + id(persistent_media_player_volume)); } script: @@ -66,17 +70,18 @@ script: then: - lambda: |- // Volume sync configuration - ESP_LOGCONFIG("standard_media_player", "Media Player Volume Sync:"); - ESP_LOGCONFIG("standard_media_player", " Persistent volume: %" PRIu8 "%", id(persistent_media_player_volume)); - ESP_LOGCONFIG("standard_media_player", " Current volume: %.1f%%", mp_media_player->volume * 100.0f); + ESP_LOGCONFIG("${TAG_STD_MEDIA_PLAYER}", "Media Player Volume Sync:"); + ESP_LOGCONFIG("${TAG_STD_MEDIA_PLAYER}", " Persistent volume: %" PRIu8 "%", + id(persistent_media_player_volume)); + ESP_LOGCONFIG("${TAG_STD_MEDIA_PLAYER}", " Current volume: %.1f%%", mp_media_player->volume * 100.0f); - id: !extend dump_config_list_packages then: - script.wait: dump_config - lambda: |- // Check for requirements - #if !defined(TX_ULTIMATE_EASY_CORE) - #error "The package TX-Ultimate-Easy-ESPHome_core.yaml is required." + #if !defined(TX_ULTIMATE_EASY_CORE_COMMON) + #error "The package TX-Ultimate-Easy-ESPHome_core_common.yaml is required." #endif // TX_ULTIMATE_EASY_CORE #if !defined(TX_ULTIMATE_EASY_STANDARD_HW_SPEAKER) #error "The package TX-Ultimate-Easy-ESPHome_standard_hw_speaker.yaml is required." @@ -94,7 +99,8 @@ script: volume: !lambda return id(persistent_media_player_volume) / 100.0f; - lambda: |- id(last_media_player_volume) = id(persistent_media_player_volume); - ESP_LOGI("media_player_volume", "Initial volume sync: %.1f%% (restored from persistence)", id(persistent_media_player_volume)); + ESP_LOGI("${TAG_STD_MEDIA_PLAYER}", "Initial volume sync: %.1f%% (restored from persistence)", + id(persistent_media_player_volume)); speaker: - platform: mixer diff --git a/README.md b/README.md index 8368b31..a667872 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ [buymeacoffee-shield]: https://img.shields.io/static/v1?label=Buy%20me%20an%20ice%20cream&message=❄&color=blue [buymeacoffee]: https://www.buymeacoffee.com/edwardfirmo +> [!WARNING] +> ESPHome builder v2025.8.0+ is required + ## Framework Migration to ESP-IDF > [!IMPORTANT] @@ -348,7 +351,7 @@ If the device isn't discovered automatically: - Button actions 3. Optional: Configure advanced features - LED behaviors - - Touch sensitivity + - Touch duration - Haptic feedback - Audio feedback 4. Test your configuration: @@ -374,7 +377,7 @@ After installation, you can: TX Ultimate Easy offers extensive configuration options: -- Touch panel sensitivity and gestures +- Touch panel gestures - LED colors, patterns, and behaviors - Relay modes and functions - Audio and haptic feedback settings diff --git a/components/tx_ultimate_easy/tx_ultimate_easy_light.cpp b/components/tx_ultimate_easy/tx_ultimate_easy_light.cpp new file mode 100644 index 0000000..c6e0123 --- /dev/null +++ b/components/tx_ultimate_easy/tx_ultimate_easy_light.cpp @@ -0,0 +1,58 @@ +// tx_ultimate_easy_light.cpp + +#ifdef TX_ULTIMATE_EASY_CORE_HW_LEDS + +#include "esphome/core/log.h" +#include "tx_ultimate_easy_light.h" +#include +#include + +namespace esphome { + namespace tx_ultimate_easy { + + // Log tag + static const char *TAG = "tx_ultimate_easy.light"; + + // Constant definitions + const LightAttributes LIGHT_ATTRS_DEFAULT = {100, 255, 255, 255}; + const uint32_t LIGHT_ATTRS_DEFAULT_PACKED = 0x64FFFFFF; + const uint64_t DEFAULT_DUAL_LIGHT_ATTRS_PACKED = 0x64FFFFFF64FFFFFF; + + // Function implementations + uint32_t pack_light_attributes(const LightAttributes& attr) { + return (uint32_t(std::min(attr.brightness, uint8_t(100))) << 24) | + (uint32_t(attr.red) << 16) | + (uint32_t(attr.green) << 8) | + (uint32_t(attr.blue)); + } + + LightAttributes unpack_light_attributes(uint32_t packed) { + return { + .brightness = std::min(uint8_t((packed >> 24) & 0xFF), uint8_t(100)), + .red = uint8_t((packed >> 16) & 0xFF), + .green = uint8_t((packed >> 8) & 0xFF), + .blue = uint8_t(packed & 0xFF) + }; + } + + uint64_t pack_dual_light_attributes(const LightAttributes& group1_attrs, const LightAttributes& group2_attrs) { + uint32_t group1_packed = pack_light_attributes(group1_attrs); + uint32_t group2_packed = pack_light_attributes(group2_attrs); + + return (uint64_t(group2_packed) << 32) | uint64_t(group1_packed); + } + + std::pair unpack_dual_light_attributes(uint64_t packed) { + uint32_t group1_packed = uint32_t(packed & 0xFFFFFFFF); // Lower 32 bits + uint32_t group2_packed = uint32_t((packed >> 32) & 0xFFFFFFFF); // Upper 32 bits + + return std::make_pair( + unpack_light_attributes(group1_packed), + unpack_light_attributes(group2_packed) + ); + } + + } // namespace tx_ultimate_easy +} // namespace esphome + +#endif // TX_ULTIMATE_EASY_CORE_HW_LEDS diff --git a/components/tx_ultimate_easy/tx_ultimate_easy_light.h b/components/tx_ultimate_easy/tx_ultimate_easy_light.h new file mode 100644 index 0000000..8e3da9c --- /dev/null +++ b/components/tx_ultimate_easy/tx_ultimate_easy_light.h @@ -0,0 +1,116 @@ +// tx_ultimate_easy_light.h + +#pragma once + +#ifdef TX_ULTIMATE_EASY_CORE_HW_LEDS + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/uart/uart_component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/light/addressable_light.h" +#include + +namespace esphome { + namespace tx_ultimate_easy { + /** + * @brief Light attributes structure for storing color and brightness data. + * + * This structure represents the visual attributes of a light without the state (on/off). + * It's designed to be packed efficiently into a 32-bit integer for NVS storage. + * RGB components use the full 8-bit range (0-255) while brightness uses percentage (0-100). + */ + struct LightAttributes { + uint8_t brightness; ///< Light brightness percentage (0-100, where 100 = full brightness) + uint8_t red; ///< Red color component (0-255) + uint8_t green; ///< Green color component (0-255) + uint8_t blue; ///< Blue color component (0-255) + }; + + /** + * @brief Packs LightAttributes structure into a 32-bit integer for storage. + * + * Bit layout: [brightness(8)][red(8)][green(8)][blue(8)] + * This allows efficient storage in ESPHome globals with restore_value support. + * Note: Brightness values > 100 will be clamped to 100 during packing. + * + * @param attr The LightAttributes structure to pack + * @return uint32_t Packed representation suitable for NVS storage + * + * @example + * LightAttributes attrs = {75, 255, 128, 64}; + * uint32_t packed = pack_light_attributes(attrs); // Returns 0x4BFF8040 + */ + uint32_t pack_light_attributes(const LightAttributes& attr); + + /** + * @brief Unpacks a 32-bit integer back into LightAttributes structure. + * + * Reverses the packing operation to extract individual color and brightness values. + * Brightness values > 100 in the packed data will be clamped to 100. + * + * @param packed The packed 32-bit representation + * @return LightAttributes The unpacked structure with individual components + * + * @example + * uint32_t packed = 0x4BFF8040; + * LightAttributes attrs = unpack_light_attributes(packed); + * // attrs.brightness = 75, attrs.red = 255, attrs.green = 128, attrs.blue = 64 + */ + LightAttributes unpack_light_attributes(uint32_t packed); + + /** + * @brief Default light attributes for initialization. + * + * Provides sensible defaults for new lights: + * - Brightness: 100 (full brightness) + * - Red: 255 (full intensity) + * - Green: 255 (full intensity) + * - Blue: 255 (full intensity) + * Result: Pure white color at full brightness + */ + extern const LightAttributes LIGHT_ATTRS_DEFAULT; + + /** + * @brief Pre-packed version of DEFAULT_LIGHT_ATTRS for convenience. + * + * This constant contains LIGHT_ATTRS_DEFAULT already packed into uint32_t format, + * suitable for direct use in ESPHome YAML initial_value fields. + * + * Value: 0x64FFFFFF (brightness=100, RGB=255,255,255) + */ + extern const uint32_t LIGHT_ATTRS_DEFAULT_PACKED; + + /** + * @brief Packs two LightAttributes into a single uint64_t for dual light storage. + * + * Bit layout: [group2_light(32)][group1_light(32)] + * Each light uses 32 bits: [brightness(8)][red(8)][green(8)][blue(8)] + * + * @param group1_attrs First light attributes (stored in lower 32 bits) + * @param group2_attrs Second light attributes (stored in upper 32 bits) + * @return uint64_t Packed representation suitable for NVS storage + */ + uint64_t pack_dual_light_attributes(const LightAttributes& group1_attrs, const LightAttributes& group2_attrs); + + /** + * @brief Unpacks uint64_t back into two LightAttributes structures. + * + * @param packed The packed 64-bit representation + * @return std::pair Pair of (group1, group2) attributes + */ + std::pair unpack_dual_light_attributes(uint64_t packed); + + /** + * @brief Pre-packed version of dual default attributes for convenience. + * + * Value: 0x64FFFFFF64FFFFFF (both lights: brightness=100, RGB=255,255,255) + */ + extern const uint64_t DEFAULT_DUAL_LIGHT_ATTRS_PACKED; + + } // namespace tx_ultimate_easy +} // namespace esphome + +#endif // TX_ULTIMATE_EASY_CORE_HW_LEDS diff --git a/components/tx_ultimate_easy/tx_ultimate_easy.cpp b/components/tx_ultimate_easy/tx_ultimate_easy_touch.cpp similarity index 95% rename from components/tx_ultimate_easy/tx_ultimate_easy.cpp rename to components/tx_ultimate_easy/tx_ultimate_easy_touch.cpp index 82f560b..7c1b4d3 100644 --- a/components/tx_ultimate_easy/tx_ultimate_easy.cpp +++ b/components/tx_ultimate_easy/tx_ultimate_easy_touch.cpp @@ -1,13 +1,18 @@ -// tx_ultimate_easy.cpp +// tx_ultimate_easy_touch.cpp + +#ifdef TX_ULTIMATE_EASY_CORE_HW_TOUCH #include "esphome/core/log.h" -#include "tx_ultimate_easy.h" +#include "tx_ultimate_easy_touch.h" #include #include namespace esphome { namespace tx_ultimate_easy { + // Log tag + static const char *TAG = "tx_ultimate_easy.touch"; + void TxUltimateEasy::setup() { ESP_LOGI(TAG, "TX Ultimate Easy is initialized"); } @@ -69,8 +74,9 @@ namespace esphome { if (this->gang_count_ == 1) return 1; - // Calculate button number Change to round up instead of truncate (integer division) - const uint8_t width = (TOUCH_MAX_POSITION + gang_count_) / this->gang_count_; // Width of each button region + // Calculate button width (rounds up to ensure full coverage) + const uint8_t width = + (TOUCH_MAX_POSITION + this->gang_count_) / this->gang_count_; // Width of each button region if (width < 1) // Invalid width - and prevents division by zero return 0; const uint8_t button = std::min( @@ -225,3 +231,5 @@ namespace esphome { } // namespace tx_ultimate_easy } // namespace esphome + +#endif // TX_ULTIMATE_EASY_CORE_HW_TOUCH diff --git a/components/tx_ultimate_easy/tx_ultimate_easy.h b/components/tx_ultimate_easy/tx_ultimate_easy_touch.h similarity index 97% rename from components/tx_ultimate_easy/tx_ultimate_easy.h rename to components/tx_ultimate_easy/tx_ultimate_easy_touch.h index cfb9e8b..867d0ec 100644 --- a/components/tx_ultimate_easy/tx_ultimate_easy.h +++ b/components/tx_ultimate_easy/tx_ultimate_easy_touch.h @@ -1,7 +1,9 @@ -// tx_ultimate_easy.h +// tx_ultimate_easy_touch.h #pragma once +#ifdef TX_ULTIMATE_EASY_CORE_HW_TOUCH + #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/components/uart/uart.h" @@ -31,9 +33,6 @@ namespace esphome { constexpr int VALID_DATA_BYTE_2 = 0x01; constexpr int VALID_DATA_BYTE_3 = 0x02; - // Log tag - static const char *TAG = "tx_ultimate_easy"; - struct TouchPoint { uint8_t button = 0; int8_t x = -1; @@ -84,3 +83,5 @@ namespace esphome { } // namespace tx_ultimate_easy } // namespace esphome + +#endif // TX_ULTIMATE_EASY_CORE_HW_TOUCH diff --git a/versioning/VERSION_YAML b/versioning/VERSION_YAML index 34cd29d..0a1a61a 100644 --- a/versioning/VERSION_YAML +++ b/versioning/VERSION_YAML @@ -1 +1 @@ -version: 2025.8.2 +version: 2025.8.2dev