diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f232f71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.cmd +*.bat diff --git a/Assets/Logo.webp b/Assets/Logo.webp new file mode 100644 index 0000000..1a5cc8d Binary files /dev/null and b/Assets/Logo.webp differ diff --git a/ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml b/ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml new file mode 100644 index 0000000..e0194d2 --- /dev/null +++ b/ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml @@ -0,0 +1,1887 @@ +substitutions: + name: tx-ultimate-easy + friendly_name: TX Ultimate Easy + + latitude: '0' + longitude: '0' + zone_home: 'zone.home' + default_transition_length: 500ms + + version: '0.0.1' + +api: + id: api_server + services: + - service: set_float + variables: + component: string + val: float + then: + - lambda: |- + if (!isnan(val)) { + if (component == "latitude") sun_entity->set_latitude(val); + else if (component == "longitude") sun_entity->set_longitude(val); + } + +binary_sensor: + - id: bs_vibrating + name: Vibrating + icon: mdi:vibrate + internal: false + platform: template + lambda: return sw_vibration_motor->state; + + - id: bs_button_1 + name: Button 1 + icon: mdi:gesture-tap-box + internal: false + platform: template + on_press: + then: + - switch.toggle: sw_relay_1 + + - id: bs_button_2 + name: Button 2 + icon: mdi:gesture-tap-box + internal: true + platform: template + on_press: + then: + - switch.toggle: sw_relay_2 + + - id: bs_button_3 + name: Button 3 + icon: mdi:gesture-tap-box + internal: true + platform: template + on_press: + then: + - switch.toggle: sw_relay_3 + + - id: bs_button_4 + name: Button 4 + icon: mdi:gesture-tap-box + internal: true + platform: template + on_press: + then: + - switch.toggle: sw_relay_4 + + - id: bs_swipe_left + name: Swipe left + icon: mdi:gesture-swipe-left + internal: true + platform: template + + - id: bs_swipe_right + name: Swipe right + icon: mdi:gesture-swipe-right + internal: true + platform: template + + - id: bs_swipe_up + name: Swipe up + icon: mdi:gesture-swipe-up + internal: true + platform: template + + - id: bs_swipe_down + name: Swipe down + icon: mdi:gesture-swipe-down + internal: true + platform: template + +button: + - id: bt_restart + name: Restart + internal: false + platform: restart + + - id: bt_vibrate + name: Vibrate + icon: mdi:vibrate + internal: false + platform: template + on_press: + then: + - script.execute: vibrate + +captive_portal: + +debug: + +esp32: + board: esp32dev + flash_size: 8MB + framework: + # type: esp-idf + type: arduino +# sdkconfig_options: +# CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y" +# CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y" +# CONFIG_ESP32_REV_MIN_3: "y" +# CONFIG_MBEDTLS_DYNAMIC_BUFFER: "y" +# CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT: "y" +# CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA: "y" +# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y" +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY: "y" +# CONFIG_SPIRAM_RODATA: "y" +# CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP: "y" + +esphome: + name: ${name} + friendly_name: ${friendly_name} + platformio_options: + build_flags: + - -DCONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST + - -DCONFIG_BT_BLE_DYNAMIC_ENV_MEMORY + - -DCONFIG_ESP32_REV_MIN_3 + - -DCONFIG_MBEDTLS_DYNAMIC_BUFFER + - -DCONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT + - -DCONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + - -DCONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC + - -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY + - -DCONFIG_SPIRAM_RODATA + - -DCONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP + - -DCONFIG_SPIRAM_USE + - -DCONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY + - -DCONFIG_SPIRAM_CACHE_LIBCHAR_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBENV_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBFILE_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBIO_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBJMP_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBMATH_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBMEM_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBMISC_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBNUMPARSER_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBRAND_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBSTR_IN_IRAM + - -DCONFIG_SPIRAM_CACHE_LIBTIME_IN_IRAM + + on_boot: + - priority: 701 + then: + - wait_until: + condition: + - lambda: return sl_tx_model_format->active_index().has_value(); + - lambda: |- + auto index = sl_tx_model_format->active_index(); + if (index.has_value()) { + uint8_t idx = index.value(); + bs_swipe_down->set_internal(idx == 0); + bs_swipe_left->set_internal(idx == 1); + bs_swipe_right->set_internal(idx == 1); + bs_swipe_up->set_internal(idx == 0); + light_28->set_internal(idx == 0); + light_29->set_internal(idx == 0); + light_30->set_internal(idx == 0); + light_31->set_internal(idx == 0); + light_eu_bottom->set_internal(idx == 1); + light_eu_left->set_internal(idx == 1); + light_eu_right->set_internal(idx == 1); + light_eu_top->set_internal(idx == 1); + light_us_bottom->set_internal(idx == 0); + light_us_left->set_internal(idx == 0); + light_us_right->set_internal(idx == 0); + light_us_top->set_internal(idx == 0); + sl_relay_light_mode_eu->set_internal(idx == 1); + sl_relay_light_mode_us->set_internal(idx == 0); + } + - priority: 700 + then: + - wait_until: + condition: + - lambda: return sl_tx_model_gang->active_index().has_value(); + - lambda: |- + auto index = sl_tx_model_gang->active_index(); + if (index.has_value()) { + uint8_t idx = index.value(); + sw_relay_1->set_internal(false); + sw_relay_2->set_internal(idx < 1); + sw_relay_3->set_internal(idx < 2); + sw_relay_4->set_internal(idx < 3); + bs_button_1->set_internal(false); + bs_button_2->set_internal(idx < 1); + bs_button_3->set_internal(idx < 2); + bs_button_4->set_internal(idx < 3); + bs_button_1->publish_state(false); + bs_button_2->publish_state(false); + bs_button_3->publish_state(false); + bs_button_4->publish_state(false); + bs_swipe_left->publish_state(false); + bs_swipe_right->publish_state(false); + } + - priority: 699 + then: + - wait_until: + condition: + - lambda: return sl_tx_model_format->active_index().has_value(); + - lambda: return sl_tx_model_gang->active_index().has_value(); + - lambda: |- + auto model_format = sl_tx_model_format->active_index(); + auto model_gang = sl_tx_model_gang->active_index(); + if (model_format.has_value() and model_gang.has_value()) { + bool us_model = (model_gang.value() == 1); + switch (model_gang.value() + 1) { + case 1: // 1 Gang + if (us_model) { + id(gb_lights_1) = { light_us_rl_1_1_left }; + id(gb_lights_2) = { light_us_rl_1_1_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_1_bottom }; + id(gb_lights_2) = { light_eu_rl_1_1_top }; + } + break; + case 2: // 2 Gang + if (us_model) { + id(gb_lights_1) = { light_us_rl_1_2_left, light_us_rl_2_2_left }; + id(gb_lights_2) = { light_us_rl_1_2_right, light_us_rl_2_2_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_2_bottom, light_eu_rl_2_2_bottom }; + id(gb_lights_2) = { light_eu_rl_1_2_top, light_eu_rl_2_2_top }; + } + break; + case 3: // 3 Gang + if (us_model) { + id(gb_lights_1) = { light_us_rl_1_3_left, light_us_rl_2_3_left, light_us_rl_3_3_left }; + id(gb_lights_2) = { light_us_rl_1_3_right, light_us_rl_2_3_right, light_us_rl_3_3_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_3_bottom, light_eu_rl_2_3_bottom, light_eu_rl_3_3_bottom }; + id(gb_lights_2) = { light_eu_rl_1_3_top, light_eu_rl_2_3_top, light_eu_rl_3_3_top }; + } + break; + case 4: // 3 Gang + if (us_model) { + id(gb_lights_1) = { light_us_rl_1_4_left, light_us_rl_2_4_left, light_us_rl_3_4_left, light_us_rl_4_4_left }; + id(gb_lights_2) = { light_us_rl_1_4_right, light_us_rl_2_4_right, light_us_rl_3_4_right, light_us_rl_4_4_right }; + } else { + id(gb_lights_1) = { light_eu_rl_1_4_bottom, light_eu_rl_2_4_bottom, light_eu_rl_3_4_bottom, light_eu_rl_4_4_bottom }; + id(gb_lights_2) = { light_eu_rl_1_4_top, light_eu_rl_2_4_top, light_eu_rl_3_4_top, light_eu_rl_4_4_top }; + } + break; + } + } + - priority: 600 # This is where most sensors are set up. + then: + - lambda: |- + tx_fw_version->publish_state("${version}"); + tx_device_name->publish_state("${name}"); + +external_components: + - source: + type: git + url: https://github.com/edwardtfn/TX-Ultimate-Easy + ref: dev + components: + - tx_ultimate_easy + +globals: + - id: gb_lights_1 + type: std::vector + restore_value: false + - id: gb_lights_2 + type: std::vector + restore_value: false + +i2s_audio: + id: if_i2s_audio + i2s_bclk_pin: GPIO2 + i2s_lrclk_pin: GPIO4 + +light: + - id: light_full + name: Light - All + internal: true + platform: neopixelbus + type: GRB + variant: WS2811 + # platform: esp32_rmt_led_strip + # rgb_order: GRB + # rmt_channel: 0 + # chipset: ws2812 + num_leds: 32 + pin: GPIO13 + + - id: light_eu + name: Light + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 0 + to: 28 + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_us + name: Light + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 0 + to: 31 + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + + - id: light_eu_bottom + name: Light - Bottom + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 6 + to: 12 + reversed: true + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_eu_left + name: Light - Left + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 13 + to: 19 + reversed: true + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_eu_right + name: Light - Right + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 27 + to: 27 + reversed: false + - id: light_full + from: 0 + to: 5 + reversed: false + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_eu_top + name: Light - Top + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 20 + to: 26 + reversed: false + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_us_bottom + name: Light - Bottom + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 10 + to: 15 + reversed: true + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_us_left + name: Light - Left + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 15 + to: 24 + reversed: true + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_us_right + name: Light - Right + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 31 + to: 31 + reversed: false + - id: light_full + from: 0 + to: 8 + reversed: false + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + - id: light_us_top + name: Light - Top + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 25 + to: 30 + reversed: false + effects: + - addressable_rainbow: + - addressable_rainbow: + name: "Rainbow - Fast" + speed: 50 + - pulse: + - pulse: + name: "Pulse - Fast" + transition_length: 0.5s + update_interval: 0.5s + - pulse: + name: "Pulse - Slow" + update_interval: 2s + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + scan_width: 3 + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 20% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - random: + + - id: light_00 + name: Light - LED 00 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 0 + to: 0 + - id: light_01 + name: Light - LED 01 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 1 + to: 1 + - id: light_02 + name: Light - LED 02 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 2 + to: 2 + - id: light_03 + name: Light - LED 03 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 3 + to: 3 + - id: light_04 + name: Light - LED 04 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 4 + to: 4 + - id: light_05 + name: Light - LED 05 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 5 + to: 5 + - id: light_06 + name: Light - LED 06 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 6 + to: 6 + - id: light_07 + name: Light - LED 07 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 7 + to: 7 + - id: light_08 + name: Light - LED 08 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 8 + to: 8 + - id: light_09 + name: Light - LED 09 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 9 + to: 9 + - id: light_10 + name: Light - LED 10 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 10 + to: 10 + - id: light_11 + name: Light - LED 11 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 11 + to: 11 + - id: light_12 + name: Light - LED 12 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 12 + to: 12 + - id: light_13 + name: Light - LED 13 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 13 + to: 13 + - id: light_14 + name: Light - LED 14 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 14 + to: 14 + - id: light_15 + name: Light - LED 15 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 15 + to: 15 + - id: light_16 + name: Light - LED 16 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 16 + to: 16 + - id: light_17 + name: Light - LED 17 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 17 + to: 17 + - id: light_18 + name: Light - LED 18 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 18 + to: 18 + - id: light_19 + name: Light - LED 19 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 19 + to: 19 + - id: light_20 + name: Light - LED 20 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 20 + to: 20 + - id: light_21 + name: Light - LED 21 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 21 + to: 21 + - id: light_22 + name: Light - LED 22 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 22 + to: 22 + - id: light_23 + name: Light - LED 23 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 23 + to: 23 + - id: light_24 + name: Light - LED 24 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 24 + to: 24 + - id: light_25 + name: Light - LED 25 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 25 + to: 25 + - id: light_26 + name: Light - LED 26 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 26 + to: 26 + - id: light_27 + name: Light - LED 27 + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 27 + to: 27 + - id: light_28 + name: Light - LED 28 + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 28 + to: 28 + - id: light_29 + name: Light - LED 29 + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 29 + to: 29 + - id: light_30 + name: Light - LED 30 + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 30 + to: 30 + - id: light_31 + name: Light - LED 31 + internal: true + disabled_by_default: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 31 + to: 31 + + - id: light_eu_rl_1_1_bottom + name: Light - Relay 1 of 1 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 9 + to: 9 + - id: light_eu_rl_1_1_top + name: Light - Relay 1 of 1 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 23 + to: 23 + - id: light_eu_rl_1_2_bottom + name: Light - Relay 1 of 2 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 11 + to: 11 + - id: light_eu_rl_1_2_top + name: Light - Relay 1 of 2 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 21 + to: 21 + - id: light_eu_rl_2_2_bottom + name: Light - Relay 2 of 2 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 7 + to: 7 + - id: light_eu_rl_2_2_top + name: Light - Relay 2 of 2 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 25 + to: 25 + - id: light_eu_rl_1_3_bottom + name: Light - Relay 1 of 3 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 11 + to: 11 + - id: light_eu_rl_1_3_top + name: Light - Relay 1 of 3 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 21 + to: 21 + - id: light_eu_rl_2_3_bottom + name: Light - Relay 2 of 3 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 9 + to: 9 + - id: light_eu_rl_2_3_top + name: Light - Relay 2 of 3 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 23 + to: 23 + - id: light_eu_rl_3_3_bottom + name: Light - Relay 3 of 3 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 7 + to: 7 + - id: light_eu_rl_3_3_top + name: Light - Relay 3 of 3 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 25 + to: 25 + - id: light_eu_rl_1_4_bottom + name: Light - Relay 1 of 4 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 12 + to: 12 + - id: light_eu_rl_1_4_top + name: Light - Relay 1 of 4 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 20 + to: 20 + - id: light_eu_rl_2_4_bottom + name: Light - Relay 2 of 4 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 10 + to: 10 + - id: light_eu_rl_2_4_top + name: Light - Relay 2 of 4 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 22 + to: 22 + - id: light_eu_rl_3_4_bottom + name: Light - Relay 3 of 4 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 8 + to: 8 + - id: light_eu_rl_3_4_top + name: Light - Relay 3 of 4 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 24 + to: 24 + - id: light_eu_rl_4_4_bottom + name: Light - Relay 4 of 4 (bottom) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 6 + to: 6 + - id: light_eu_rl_4_4_top + name: Light - Relay 4 of 4 (top) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 26 + to: 26 + - id: light_us_rl_1_1_left + name: Light - Relay 1 of 1 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 15 + to: 24 + - id: light_us_rl_1_1_right + name: Light - Relay 1 of 1 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 31 + to: 31 + - id: light_full + from: 0 + to: 8 + - id: light_us_rl_1_2_left + name: Light - Relay 1 of 2 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 20 + to: 24 + - id: light_us_rl_1_2_right + name: Light - Relay 1 of 2 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 31 + to: 31 + - id: light_full + from: 0 + to: 3 + - id: light_us_rl_2_2_left + name: Light - Relay 2 of 2 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 15 + to: 19 + - id: light_us_rl_2_2_right + name: Light - Relay 2 of 2 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 4 + to: 8 + - id: light_us_rl_1_3_left + name: Light - Relay 1 of 3 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 22 + to: 23 + - id: light_us_rl_1_3_right + name: Light - Relay 1 of 3 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 0 + to: 1 + - id: light_us_rl_2_3_left + name: Light - Relay 2 of 3 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 19 + to: 20 + - id: light_us_rl_2_3_right + name: Light - Relay 2 of 3 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 3 + to: 4 + - id: light_us_rl_3_3_left + name: Light - Relay 3 of 3 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 17 + to: 18 + - id: light_us_rl_3_3_right + name: Light - Relay 3 of 3 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 6 + to: 7 + - id: light_us_rl_1_4_left + name: Light - Relay 1 of 4 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 12 + to: 12 + - id: light_us_rl_1_4_right + name: Light - Relay 1 of 4 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 31 + to: 31 + - id: light_us_rl_2_4_left + name: Light - Relay 2 of 4 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 24 + to: 24 + - id: light_us_rl_2_4_right + name: Light - Relay 2 of 4 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 2 + to: 2 + - id: light_us_rl_3_4_left + name: Light - Relay 3 of 4 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 21 + to: 21 + - id: light_us_rl_3_4_right + name: Light - Relay 3 of 4 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 4 + to: 4 + - id: light_us_rl_4_4_left + name: Light - Relay 4 of 4 (left) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 19 + to: 19 + - id: light_us_rl_4_4_right + name: Light - Relay 4 of 4 (right) + internal: true + platform: partition + default_transition_length: ${default_transition_length} + segments: + - id: light_full + from: 7 + to: 7 + +# - id: light_status +# name: Light - Status +# platform: esp32_rmt_led_strip +# rgb_order: GRB +# pin: GPIO13 +# num_leds: 1 +# rmt_channel: 0 +# chipset: ws2812 + +logger: + +media_player: + - id: mp_speaker + name: Speaker + platform: i2s_audio + i2s_dout_pin: GPIO15 + i2s_audio_id: if_i2s_audio + i2s_comm_fmt: lsb + dac_type: external + mode: mono + mute_pin: + number: GPIO26 + inverted: true + allow_other_uses: true + internal: false + +number: + - id: nr_vibrating_duration + name: Vibrate duration + icon: mdi:vibrate + unit_of_measurement: ms + # mode: box + internal: false + entity_category: config + platform: template + min_value: 1 + max_value: 500 + step: 1 + initial_value: 15 + optimistic: true + restore_value: true + +ota: + +psram: + +script: + - id: show_relay_status + mode: restart + then: + - lambda: |- + auto model_format = sl_tx_model_format->active_index(); + auto light_index = ((model_format.has_value() and model_format.value() == 0) + ? sl_relay_light_mode_eu->active_index() + : sl_relay_light_mode_us->active_index()); + if (model_format.has_value() and light_index.has_value() and light_index.value() > 0) { + std::vector switches = {sw_relay_1, sw_relay_2, sw_relay_3, sw_relay_4}; + uint8_t light_idx = light_index.value(); + if (light_idx == 1 or light_idx == 3) { + for (int i = 0; i < id(gb_lights_1).size(); ++i) { + if (switches[i]->state) + id(gb_lights_1)[i]->turn_on().perform(); + else + id(gb_lights_1)[i]->turn_off().perform(); + } + } + if (light_idx == 2 or light_idx == 3) { + for (int i = 0; i < id(gb_lights_2).size(); ++i) { + if (switches[i]->state) + id(gb_lights_2)[i]->turn_on().perform(); + else + id(gb_lights_2)[i]->turn_off().perform(); + } + } + } + + - id: vibrate + mode: restart + then: + - switch.turn_on: sw_vibration_motor + - delay: !lambda return nr_vibrating_duration->state; + - switch.turn_off: sw_vibration_motor + +select: + - id: sl_relay_light_mode_eu + name: Relay light mode + platform: template + options: + - "Disabled" + - "Bottom" + - "Top" + - "Both" + initial_option: "Bottom" + optimistic: true + restore_value: true + internal: true + entity_category: config + disabled_by_default: false + icon: mdi:dots-square + + - id: sl_relay_light_mode_us + name: Relay light mode + platform: template + options: + - "Disabled" + - "Left" + - "Right" + - "Both" + initial_option: "Right" + optimistic: true + restore_value: true + internal: true + entity_category: config + disabled_by_default: false + icon: mdi:dots-square + + - id: sl_touch_vibration_feedback + name: Touch - Vibration feedback + platform: template + options: + - "Disabled" + - "On press" + - "On release" + - "Always" + initial_option: "Disabled" + optimistic: true + restore_value: true + internal: false + entity_category: config + disabled_by_default: false + icon: mdi:vibrate + + - id: sl_tx_model_format + name: Model (Format) + platform: template + options: + - "EU (Square, T5-xC-86)" + - "US (Rectangle, T5-xC-120)" + initial_option: "EU (Square, T5-xC-86)" + optimistic: true + restore_value: true + internal: false + entity_category: config + disabled_by_default: false + icon: mdi:tablet-cellphone + + - id: sl_tx_model_gang + name: Model (Gang) + platform: template + options: + - "1 Gang" + - "2 Gang" + - "3 Gang" + - "4 Gang" + initial_option: "1 Gang" + optimistic: true + restore_value: true + internal: false + entity_category: config + disabled_by_default: false + icon: mdi:dip-switch + +sensor: + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + psram: + name: "Free PSRAM" + + - id: sun_elevation + name: Sun Elevation + platform: sun + type: elevation + internal: true + + - id: zone_home_latitude + platform: homeassistant + entity_id: ${zone_home} + attribute: latitude + internal: false + on_value: + - lambda: if (!isnan(x)) sun_entity->set_latitude(x); + + - id: zone_home_longitude + platform: homeassistant + entity_id: ${zone_home} + attribute: longitude + internal: false + on_value: + - lambda: if (!isnan(x)) sun_entity->set_longitude(x); + +sun: + id: sun_entity + latitude: ${latitude} + longitude: ${longitude} + +switch: + - id: sw_speaker_amplifier + name: Speaker - Amplifier + platform: gpio + pin: + number: GPIO26 + inverted: true + allow_other_uses: true + restore_mode: RESTORE_DEFAULT_ON + internal: true + entity_category: config + + - id: sw_relay_1 + name: Relay 1 + platform: gpio + pin: GPIO18 + restore_mode: RESTORE_DEFAULT_OFF + internal: false + on_turn_on: + then: + - script.execute: show_relay_status + on_turn_off: + then: + - script.execute: show_relay_status + + - id: sw_relay_2 + name: Relay 2 + platform: gpio + pin: GPIO17 + restore_mode: RESTORE_DEFAULT_OFF + internal: true + on_turn_on: + then: + - script.execute: show_relay_status + on_turn_off: + then: + - script.execute: show_relay_status + + - id: sw_relay_3 + name: Relay 3 + platform: gpio + pin: GPIO27 + restore_mode: RESTORE_DEFAULT_OFF + internal: true + on_turn_on: + then: + - script.execute: show_relay_status + on_turn_off: + then: + - script.execute: show_relay_status + + - id: sw_relay_4 + name: Relay 4 + platform: gpio + pin: GPIO23 + restore_mode: RESTORE_DEFAULT_OFF + internal: true + on_turn_on: + then: + - script.execute: show_relay_status + on_turn_off: + then: + - script.execute: show_relay_status + + - id: sw_touch_panel_power + name: Touch panel - Power + platform: gpio + pin: + number: GPIO5 + inverted: true + restore_mode: ALWAYS_ON + internal: false + entity_category: config + + - id: sw_vibration_motor + name: Vibration motor + platform: gpio + pin: GPIO21 + restore_mode: ALWAYS_OFF + internal: true + on_turn_on: + then: + - lambda: bs_vibrating->publish_state(true); + on_turn_off: + then: + - lambda: bs_vibrating->publish_state(false); + +text_sensor: + - id: tx_fw_version + name: Firmware version + platform: template + entity_category: diagnostic + icon: mdi:tag-text-outline + internal: false + update_interval: never + lambda: |- + return {"${version}"}; + + - id: tx_device_name + name: Device Name + platform: template + icon: mdi:identifier + entity_category: diagnostic + internal: false + disabled_by_default: false + lambda: |- + return {"${name}"}; + filters: + - lambda: |- + const std::string raw_name = x; + std::string result; + bool last_was_underscore = false; + for (const char& c : raw_name) { + if (isalnum(c)) { + result += tolower(c); // Add alphanumeric characters as lowercase + last_was_underscore = false; + } else if (!last_was_underscore) { // Replace non-alphanumeric with '_' but avoid consecutive '_' + result += '_'; + last_was_underscore = true; + } + } + return result; + +time: + - id: time_source + platform: homeassistant + +tx_ultimate_easy: + id: tx_ultimate + uart: uart_touch + + on_press: + - lambda: |- + if (sl_touch_vibration_feedback->state == "On press" or sl_touch_vibration_feedback->state == "Always") + vibrate->execute(); + auto model_index = sl_tx_model_gang->active_index(); + if (model_index.has_value()) { + uint8_t model_idx = model_index.value() + 1; + switch (model_idx) { + case 1: // 1 Gang + bs_button_1->publish_state(true); + break; + case 2: // 2 Gang + if (touch.x <= 5) bs_button_1->publish_state(true); + else bs_button_2->publish_state(true); + break; + case 3: // 3 Gang + if (touch.x <= 3) bs_button_1->publish_state(true); + else if (touch.x <= 7) bs_button_2->publish_state(true); + else bs_button_3->publish_state(true); + break; + case 4: // 4 Gang + if (touch.x <= 2) bs_button_1->publish_state(true); + else if (touch.x <= 5) bs_button_2->publish_state(true); + else if (touch.x <= 8) bs_button_3->publish_state(true); + else bs_button_4->publish_state(true); + break; + } + } + + on_release: + - lambda: |- + if (sl_touch_vibration_feedback->state == "On release" or sl_touch_vibration_feedback->state == "Always") + vibrate->execute(); + if (bs_button_1->state) bs_button_1->publish_state(false); + if (bs_button_2->state) bs_button_2->publish_state(false); + if (bs_button_3->state) bs_button_3->publish_state(false); + if (bs_button_4->state) bs_button_4->publish_state(false); + if (bs_swipe_left->state) bs_swipe_left->publish_state(false); + if (bs_swipe_right->state) bs_swipe_right->publish_state(false); + + on_swipe_left: + - binary_sensor.template.publish: + id: bs_swipe_left + state: ON + + on_swipe_right: + - binary_sensor.template.publish: + id: bs_swipe_right + state: ON + + on_touch_event: + - lambda: |- + ESP_LOGD("tx_ultimate_easy.on_touch_event", "Touch event:"); + ESP_LOGD("tx_ultimate_easy.on_touch_event", " State: %i (%s)", touch.state, touch.state_str.c_str()); + ESP_LOGD("tx_ultimate_easy.on_touch_event", " Position: %i", touch.x); + +uart: + id: uart_touch + tx_pin: GPIO19 + rx_pin: GPIO22 + baud_rate: 115200 + +web_server: + +wifi: + ap: diff --git a/README.md b/README.md index e5c5ecc..64ef6f6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,59 @@ -# TX-Ultimate-Easy -ESPHome based custom firmware for Sonoff TX Ultimate. Easy to setup. +# TX Ultimate Easy + +|  ![TX Ultimate Easy Logo](Assets/Logo.webp) | Welcome to TX Ultimate Easy! This project aims to provide custom firmware for Sonoff TX Ultimate devices based on ESPHome, making it easy for even the least tech-savvy users to configure and use. Our goal is to have all the configurations manageable directly through the Home Assistant UI or via blueprints, eliminating the need to edit YAML files manually. | +| --- | :-- | + +## Features + +- **Simple Setup:** Configure your Sonoff TX Ultimate through Home Assistant UI. +- **No YAML Editing:** All settings are adjustable without touching YAML files after the initial setup. +- **User-Friendly:** Designed for users of all technical levels. +- **Customizable:** Flexibility to adapt the firmware to your needs. +- **Seamless Integration:** Works flawlessly with Home Assistant. + +## Getting Started + +### Prerequisites + +- Sonoff TX Ultimate device +- Home Assistant installed +- ESPHome add-on installed in Home Assistant + +### Installation +To be written. + +### Configuration + +- **Via Home Assistant UI:** + All device settings can be managed through the Home Assistant interface. No need to edit YAML files directly. + +- **Using Blueprints:** + Import and use blueprints to quickly configure your device for common use cases. + +## Documentation +To be written. + +## Contributing + +We welcome contributions from the community. If you have ideas for new features, improvements, or bug fixes, please open an issue or submit a pull request. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +This project is highly inspired by these other projects: +- [SmartHome yourself - SONOFF TX Ultimate for ESPHome (Custom Component)](https://github.com/SmartHome-yourself/sonoff-tx-ultimate-for-esphome) +- [Un loco y su tecnologĂ­a - Sonoff TX Ultimate with ESPHome | Absolute power](https://www.youtube.com/watch?v=58v8oqSQgXQ) + +Thanks a lot for sharing all your discoveries and ideas. + +## Support + +If you find this project helpful, consider supporting us through Buy Me a Coffee: + +[![Buy Me a Coffee](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://www.buymeacoffee.com/edwardfirmo) + + +TX Ultimate Easy Logo diff --git a/TX-Ultimate-Easy-ESPHome.yaml b/TX-Ultimate-Easy-ESPHome.yaml new file mode 100644 index 0000000..958c3c4 --- /dev/null +++ b/TX-Ultimate-Easy-ESPHome.yaml @@ -0,0 +1,19 @@ +substitutions: + name: tx-ultimate-easy + friendly_name: TX Ultimate Easy + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +packages: + remote_package: + url: https://github.com/edwardtfn/TX-Ultimate-Easy + ref: dev + refresh: 1h + files: + - ESPHome/TX-Ultimate-Easy-ESPHome_core.yaml + +### Uncomment the lines bellow if you have issues with OTA and cannot flash via serial at this moment. ### +#esp32: +# flash_size: 4MB diff --git a/components/tx_ultimate_easy/__init__.py b/components/tx_ultimate_easy/__init__.py new file mode 100644 index 0000000..545ee57 --- /dev/null +++ b/components/tx_ultimate_easy/__init__.py @@ -0,0 +1,105 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import ( + CONF_ID, +) + +from esphome import automation + +CODEOWNERS = ["@edwardtfn"] +DEPENDENCIES = ['uart'] + +CONF_TX_ULTIMATE_EASY = "tx_ultimate_easy" + +CONF_UART = "uart" + +CONF_ON_TOUCH_EVENT = "on_touch_event" +CONF_ON_PRESS = "on_press" +CONF_ON_RELEASE = "on_release" +CONF_ON_SWIPE_LEFT = "on_swipe_left" +CONF_ON_SWIPE_RIGHT = "on_swipe_right" +CONF_ON_MULTI_TOUCH_RELEASE = "on_multi_touch_release" +CONF_ON_LONG_TOUCH_RELEASE = "on_long_touch_release" + +tx_ultimate_easy_ns = cg.esphome_ns.namespace('tx_ultimate_easy') +TouchPoint = tx_ultimate_easy_ns.struct("TouchPoint") + +TxUltimateTouch = tx_ultimate_easy_ns.class_( + 'TxUltimateEasy', cg.Component, uart.UARTDevice) + + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(TxUltimateTouch), + + cv.Required(CONF_UART): cv.use_id(uart), + + cv.Optional(CONF_ON_TOUCH_EVENT): automation.validate_automation(single=True), + cv.Optional(CONF_ON_PRESS): automation.validate_automation(single=True), + cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True), + cv.Optional(CONF_ON_SWIPE_LEFT): automation.validate_automation(single=True), + cv.Optional(CONF_ON_SWIPE_RIGHT): automation.validate_automation(single=True), + cv.Optional(CONF_ON_MULTI_TOUCH_RELEASE): automation.validate_automation(single=True), + cv.Optional(CONF_ON_LONG_TOUCH_RELEASE): automation.validate_automation(single=True), + +}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA) + + +async def register_tx_ultimate_easy(var, config): + uart_component = await cg.get_variable(config[CONF_UART]) + cg.add(var.set_uart_component(uart_component)) + + if CONF_ON_TOUCH_EVENT in config: + await automation.build_automation( + var.get_trigger_touch_event(), + [(TouchPoint, "touch")], + config[CONF_ON_TOUCH_EVENT], + ) + + if CONF_ON_PRESS in config: + await automation.build_automation( + var.get_trigger_touch(), + [(TouchPoint, "touch")], + config[CONF_ON_PRESS], + ) + + if CONF_ON_RELEASE in config: + await automation.build_automation( + var.get_trigger_release(), + [(TouchPoint, "touch")], + config[CONF_ON_RELEASE], + ) + + if CONF_ON_SWIPE_LEFT in config: + await automation.build_automation( + var.get_trigger_swipe_left(), + [(TouchPoint, "touch")], + config[CONF_ON_SWIPE_LEFT], + ) + + if CONF_ON_SWIPE_RIGHT in config: + await automation.build_automation( + var.get_trigger_swipe_right(), + [(TouchPoint, "touch")], + config[CONF_ON_SWIPE_RIGHT], + ) + + if CONF_ON_MULTI_TOUCH_RELEASE in config: + await automation.build_automation( + var.get_trigger_multi_touch_release(), + [(TouchPoint, "touch")], + config[CONF_ON_MULTI_TOUCH_RELEASE], + ) + + if CONF_ON_LONG_TOUCH_RELEASE in config: + await automation.build_automation( + var.get_trigger_long_touch_release(), + [(TouchPoint, "touch")], + config[CONF_ON_LONG_TOUCH_RELEASE], + ) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + await register_tx_ultimate_easy(var, config) diff --git a/components/tx_ultimate_easy/tx_ultimate_easy.cpp b/components/tx_ultimate_easy/tx_ultimate_easy.cpp new file mode 100644 index 0000000..5443904 --- /dev/null +++ b/components/tx_ultimate_easy/tx_ultimate_easy.cpp @@ -0,0 +1,154 @@ +#include "esphome/core/log.h" +#include "tx_ultimate_easy.h" + +namespace esphome { + namespace tx_ultimate_easy { + static const char *TAG = "tx_ultimate_easy"; + + void TxUltimateEasy::setup() { + ESP_LOGI("log", "%s", "Tx Ultimate Easy is initialized"); + } + + void TxUltimateEasy::loop() { + bool found = false; + int uart_received_bytes[15] = {}; + int byte = -1; + int i = 0; + while (this->available()) { + byte = this->read(); + if (byte == 170) { + handle_touch(uart_received_bytes); + i = 0; + } + uart_received_bytes[i] = byte; + i++; + if (byte != 0) { + found = true; + } + }; + if (found) handle_touch(uart_received_bytes); + } + + void TxUltimateEasy::handle_touch(int uart_received_bytes[]) { + ESP_LOGVV(TAG, "------------"); + ESP_LOGVV(TAG, "- UART-Log -"); + ESP_LOGVV(TAG, "------------"); + for (int i = 0; i < 15; i++) ESP_LOGVV(TAG, "UART - Log - Byte[%i]: %i", i, uart_received_bytes[i]); + if (is_valid_data(uart_received_bytes)) send_touch_(get_touch_point(uart_received_bytes)); + } + + void TxUltimateEasy::dump_config() { ESP_LOGCONFIG(TAG, "Tx Ultimate Easy"); } + + void TxUltimateEasy::send_touch_(TouchPoint tp) { + this->trigger_touch_event_.trigger(tp); + switch (tp.state) { + case TOUCH_STATE_RELEASE: + if (tp.x >= 17) { + tp.x = tp.x - 16; + ESP_LOGV(TAG, "Long touch - Released (x=%d)", tp.x); + this->trigger_long_touch_release_.trigger(tp); + } else { + ESP_LOGV(TAG, "Touch - Released (x=%d)", tp.x); + this->trigger_release_.trigger(tp); + } + break; + + case TOUCH_STATE_PRESS: + ESP_LOGV(TAG, "Touch - Pressed (x=%d)", tp.x); + this->trigger_touch_.trigger(tp); + break; + + case TOUCH_STATE_SWIPE_LEFT: + ESP_LOGV(TAG, "Swipe - Left (x=%d)", tp.x); + this->trigger_swipe_left_.trigger(tp); + break; + + case TOUCH_STATE_SWIPE_RIGHT: + ESP_LOGV(TAG, "Swipe - Right (x=%d)", tp.x); + this->trigger_swipe_right_.trigger(tp); + break; + + case TOUCH_STATE_MULTI_TOUCH: + ESP_LOGV(TAG, "Multi touch - Released"); + this->trigger_multi_touch_release_.trigger(tp); + break; + + default: + break; + } + } + + bool TxUltimateEasy::is_valid_data(int uart_received_bytes[]) { + bool valid = true; + if (!(uart_received_bytes[0] == 170 && uart_received_bytes[1] == 85 && uart_received_bytes[2] == 1 && uart_received_bytes[3] == 2)) return false; + + int state = get_touch_state(uart_received_bytes); + if (state != TOUCH_STATE_PRESS && + state != TOUCH_STATE_RELEASE && + state != TOUCH_STATE_SWIPE_LEFT && + state != TOUCH_STATE_SWIPE_RIGHT && + state != TOUCH_STATE_MULTI_TOUCH) + return false; + + if (uart_received_bytes[6] < 0 && state != TOUCH_STATE_MULTI_TOUCH) return false; + + return true; + } + + int TxUltimateEasy::get_touch_position_x(int uart_received_bytes[]) { + int state = uart_received_bytes[4]; + switch (state) { + case TOUCH_STATE_RELEASE: + case TOUCH_STATE_MULTI_TOUCH: + case TOUCH_STATE_SWIPE_LEFT: + case TOUCH_STATE_SWIPE_RIGHT: + return uart_received_bytes[5]; + break; + + default: + return uart_received_bytes[6]; + break; + } + } + + int TxUltimateEasy::get_touch_state(int uart_received_bytes[]) { + int state = uart_received_bytes[4]; + if (state == TOUCH_STATE_PRESS && uart_received_bytes[5] != 0) state = TOUCH_STATE_RELEASE; + if (state == TOUCH_STATE_RELEASE && uart_received_bytes[5] == TOUCH_STATE_MULTI_TOUCH) state = TOUCH_STATE_MULTI_TOUCH; + if (state == TOUCH_STATE_SWIPE) { + if (uart_received_bytes[5] == TOUCH_STATE_SWIPE_RIGHT) state = TOUCH_STATE_SWIPE_RIGHT; + else if (uart_received_bytes[5] == TOUCH_STATE_SWIPE_LEFT) state = TOUCH_STATE_SWIPE_LEFT; + } + return state; + } + + TouchPoint TxUltimateEasy::get_touch_point(int uart_received_bytes[]) { + TouchPoint tp; + tp.x = get_touch_position_x(uart_received_bytes); + tp.state = get_touch_state(uart_received_bytes); + tp.state_str = "Unknown"; + switch (tp.state) { + case TOUCH_STATE_RELEASE: + tp.state_str = "RELEASE"; + break; + case TOUCH_STATE_PRESS: + tp.state_str = "PRESS"; + break; + case TOUCH_STATE_SWIPE: + tp.state_str = "SWIPE"; + break; + case TOUCH_STATE_MULTI_TOUCH: + tp.state_str = "MULTI_TOUCH"; + break; + case TOUCH_STATE_SWIPE_RIGHT: + tp.state_str = "SWIPE_RIGHT"; + break; + case TOUCH_STATE_SWIPE_LEFT: + tp.state_str = "SWIPE_LEFT"; + break; + } + return tp; + } + + } // namespace tx_ultimate_easy +} // namespace esphome \ No newline at end of file diff --git a/components/tx_ultimate_easy/tx_ultimate_easy.h b/components/tx_ultimate_easy/tx_ultimate_easy.h new file mode 100644 index 0000000..033e471 --- /dev/null +++ b/components/tx_ultimate_easy/tx_ultimate_easy.h @@ -0,0 +1,62 @@ +#pragma once + +#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" + +namespace esphome { + namespace tx_ultimate_easy { + const static uint8_t TOUCH_STATE_RELEASE = 1; + const static uint8_t TOUCH_STATE_PRESS = 2; + const static uint8_t TOUCH_STATE_SWIPE = 3; + const static uint8_t TOUCH_STATE_MULTI_TOUCH = 11; + const static uint8_t TOUCH_STATE_SWIPE_RIGHT = 12; + const static uint8_t TOUCH_STATE_SWIPE_LEFT = 13; + + struct TouchPoint { + int8_t x = -1; + int8_t state = -1; + std::string state_str = "Unknown"; + }; + + class TxUltimateEasy : public uart::UARTDevice, public Component { + public: + Trigger *get_trigger_touch_event() { return &this->trigger_touch_event_; } + Trigger *get_trigger_touch() { return &this->trigger_touch_; } + Trigger *get_trigger_release() { return &this->trigger_release_; } + Trigger *get_trigger_swipe_left() { return &this->trigger_swipe_left_; } + Trigger *get_trigger_swipe_right() { return &this->trigger_swipe_right_; } + Trigger *get_trigger_multi_touch_release() { return &this->trigger_multi_touch_release_; } + Trigger *get_trigger_long_touch_release() { return &this->trigger_long_touch_release_; } + + void set_uart_component(esphome::uart::UARTComponent *uart_component) { this->set_uart_parent(uart_component); } + + void setup() override; + void loop() override; + void dump_config() override; + + protected: + void send_touch_(TouchPoint tp); + void handle_touch(int bytes[]); + + TouchPoint get_touch_point(int bytes[]); + bool is_valid_data(int bytes[]); + int get_touch_position_x(int bytes[]); + int get_touch_state(int bytes[]); + + Trigger trigger_touch_event_; + Trigger trigger_touch_; + Trigger trigger_release_; + Trigger trigger_swipe_left_; + Trigger trigger_swipe_right_; + Trigger trigger_multi_touch_release_; + Trigger trigger_long_touch_release_; + + }; // class TxUltimateEasy + + } // namespace tx_ultimate_easy +} // namespace esphome \ No newline at end of file