#################################################################################################### ##### TX Ultimate Easy for ESPHome ##### ##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy ##### #################################################################################################### ##### Purpose: ESPHome Core - Hardware - LEDs ##### #################################################################################################### ##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo ##### #################################################################################################### ##### NOTE: ##### ##### - Make changes ONLY if absolutely necessary and you have the required knowledge. ##### ##### - For normal system use, modifications to this file are NOT required. ##### #################################################################################################### --- substitutions: default_transition_length: 500ms LIGHT_MODE_BOTH_TEXT: "Both" LIGHT_MODE_DISABLED_TEXT: "Disabled" LIGHT_MODE_LEFT_TEXT: "Left" LIGHT_MODE_RIGHT_TEXT: "Right" LIGHT_MODE_BOTTOM_TEXT: "Bottom" LIGHT_MODE_TOP_TEXT: "Top" LIGHT_TRANSITION_TURN_ON: '0' # Transition time in msec LIGHT_TRANSITION_TURN_OFF: '0' # Transition time in msec LIGHT_BRIGHTNESS_TURN_ON: '0' # Brightness (1 to 100%, 0 to not set it) LIGHT_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: build_flags: - -D TX_ULTIMATE_EASY_CORE_HW_LEDS external_components: - source: github://pr#10195 # To-do: Remove this when this PR is merged components: [light] refresh: 1h globals: # light_full with effect support - id: led_full_attrs type: uint32_t restore_value: true initial_value: '${LIGHT_ATTRS_DEFAULT}' # Basic attributes: brightness + RGB # For light_full effects - id: led_full_effect_packed type: uint32_t restore_value: true initial_value: '0x00000000' # Stores: effect_index(16 bits) + effect_speed(8 bits) + reserved(8 bits) # 0 = no effect/solid color, 1-10 = effect indices - id: led_main_packed_attrs type: uint64_t restore_value: true initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}' # light_eu(group1) + light_us(group2) - the main effect lights - id: led_main_packed_effect type: uint32_t restore_value: true initial_value: '0x00000000' # Shared between light_eu and light_us # Main user-facing LED lights only - id: led_sides_packed_attrs type: uint64_t restore_value: true initial_value: '${LIGHT_DUAL_ATTRS_DEFAULT}' # EU: bottom(group1) + top(group2) OR US: left(group1) + right(group2) light: # These lights are controlling the LEDs on the device - id: light_full name: Light - All internal: true num_leds: 32 pin: GPIO13 restore_mode: ${LIGHT_FULL_RESTORE_MODE} platform: esp32_rmt_led_strip rgb_order: GRB chipset: ws2812 max_refresh_rate: 33ms use_psram: false # platform: neopixelbus # type: GRB # variant: WS2811 # Those lights are available based on the model selections - &light_partition_with_effects_eu id: light_eu name: Light EU platform: partition default_transition_length: ${default_transition_length} internal: true restore_mode: ${LIGHT_SIDES_RESTORE_MODE} effects: - addressable_rainbow: name: "Rainbow" width: 28 - addressable_rainbow: name: "Rainbow - Fast" width: 28 speed: 50 - pulse: name: "Pulse" transition_length: 1s update_interval: 1s - pulse: name: "Pulse - Slow" transition_length: 2s update_interval: 2s - addressable_scan: name: "Scan" move_interval: 100ms scan_width: 3 - addressable_twinkle: name: "Twinkle" twinkle_probability: 5% progress_interval: 4ms - addressable_random_twinkle: name: "Random Twinkle" twinkle_probability: 20% progress_interval: 32ms - addressable_fireworks: name: "Fireworks" update_interval: 32ms spark_probability: 10% use_random_color: false fade_out_rate: 120 - addressable_flicker: name: "Flicker" update_interval: 16ms intensity: 5% - random: name: "Random" segments: - id: light_full from: 0 to: 27 on_state: then: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_eu); attr_global_ptr: !lambda return &id(led_main_packed_attrs); group_index: 1 - script.execute: id: light_effect_save light_ptr: !lambda return id(light_eu); effect_global_ptr: !lambda return &id(led_main_packed_effect); - &light_partition_with_effects_us id: light_us name: Light US platform: partition default_transition_length: ${default_transition_length} internal: true restore_mode: ${LIGHT_SIDES_RESTORE_MODE} effects: - addressable_rainbow: name: "Rainbow" width: 32 - addressable_rainbow: name: "Rainbow - Fast" width: 32 speed: 50 - pulse: name: "Pulse" transition_length: 1s update_interval: 1s - pulse: name: "Pulse - Slow" transition_length: 2s update_interval: 2s - addressable_scan: name: "Scan" move_interval: 100ms scan_width: 3 - addressable_twinkle: name: "Twinkle" twinkle_probability: 5% progress_interval: 4ms - addressable_random_twinkle: name: "Random Twinkle" twinkle_probability: 20% progress_interval: 32ms - addressable_fireworks: name: "Fireworks" update_interval: 32ms spark_probability: 10% use_random_color: false fade_out_rate: 120 - addressable_flicker: name: "Flicker" update_interval: 16ms intensity: 5% - random: name: "Random" segments: - id: light_full from: 0 to: 31 on_state: then: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_us); attr_global_ptr: !lambda return &id(led_main_packed_attrs); group_index: 2 - script.execute: id: light_effect_save light_ptr: !lambda return id(light_us); effect_global_ptr: !lambda return &id(led_main_packed_effect); - &light_partition_no_effects_disabled id: light_eu_bottom name: Light - Bottom EU platform: partition disabled_by_default: true default_transition_length: ${default_transition_length} restore_mode: ${LIGHT_SIDES_RESTORE_MODE} internal: true segments: - id: light_full from: 6 to: 12 reversed: true on_state: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_eu_bottom); attr_global_ptr: !lambda return &id(led_sides_packed_attrs); group_index: 1 - id: light_eu_left name: Light - Left EU <<: *light_partition_no_effects_disabled segments: - id: light_full from: 13 to: 19 reversed: true on_state: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_eu_left); attr_global_ptr: !lambda return &id(led_sides_packed_attrs); group_index: 1 - id: light_eu_right name: Light - Right EU <<: *light_partition_no_effects_disabled segments: - id: light_full from: 27 to: 27 reversed: false - id: light_full 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 segments: - id: light_full from: 20 to: 26 reversed: false on_state: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_eu_top); attr_global_ptr: !lambda return &id(led_sides_packed_attrs); group_index: 2 - id: light_us_bottom name: Light - Bottom US <<: *light_partition_no_effects_disabled segments: - id: light_full from: 9 to: 14 reversed: true on_state: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_us_bottom); attr_global_ptr: !lambda return &id(led_sides_packed_attrs); group_index: 1 - id: light_us_left name: Light - Left US <<: *light_partition_no_effects_disabled segments: - id: light_full from: 15 to: 24 reversed: true on_state: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_us_left); attr_global_ptr: !lambda return &id(led_sides_packed_attrs); group_index: 1 - id: light_us_right name: Light - Right US <<: *light_partition_no_effects_disabled segments: - id: light_full from: 31 to: 31 reversed: false - id: light_full 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 segments: - id: light_full from: 25 to: 30 reversed: false on_state: - script.execute: id: light_attributes_save_dual light_ptr: !lambda return id(light_us_top); attr_global_ptr: !lambda return &id(led_sides_packed_attrs); group_index: 2 - &light_partition_individual_led id: light_00 name: Light - LED 00 platform: partition disabled_by_default: true default_transition_length: ${default_transition_length} restore_mode: ${LIGHT_INDIVIDUAL_RESTORE_MODE} segments: - id: light_full from: 0 to: 0 - id: light_01 name: Light - LED 01 <<: *light_partition_individual_led segments: - id: light_full from: 1 to: 1 - id: light_02 name: Light - LED 02 <<: *light_partition_individual_led segments: - id: light_full from: 2 to: 2 - id: light_03 name: Light - LED 03 <<: *light_partition_individual_led segments: - id: light_full from: 3 to: 3 - id: light_04 name: Light - LED 04 <<: *light_partition_individual_led segments: - id: light_full from: 4 to: 4 - id: light_05 name: Light - LED 05 <<: *light_partition_individual_led segments: - id: light_full from: 5 to: 5 - id: light_06 name: Light - LED 06 <<: *light_partition_individual_led segments: - id: light_full from: 6 to: 6 - id: light_07 name: Light - LED 07 <<: *light_partition_individual_led segments: - id: light_full from: 7 to: 7 - id: light_08 name: Light - LED 08 <<: *light_partition_individual_led segments: - id: light_full from: 8 to: 8 - id: light_09 name: Light - LED 09 <<: *light_partition_individual_led segments: - id: light_full from: 9 to: 9 - id: light_10 name: Light - LED 10 <<: *light_partition_individual_led segments: - id: light_full from: 10 to: 10 - id: light_11 name: Light - LED 11 <<: *light_partition_individual_led segments: - id: light_full from: 11 to: 11 - id: light_12 name: Light - LED 12 <<: *light_partition_individual_led segments: - id: light_full from: 12 to: 12 - id: light_13 name: Light - LED 13 <<: *light_partition_individual_led segments: - id: light_full from: 13 to: 13 - id: light_14 name: Light - LED 14 <<: *light_partition_individual_led segments: - id: light_full from: 14 to: 14 - id: light_15 name: Light - LED 15 <<: *light_partition_individual_led segments: - id: light_full from: 15 to: 15 - id: light_16 name: Light - LED 16 <<: *light_partition_individual_led segments: - id: light_full from: 16 to: 16 - id: light_17 name: Light - LED 17 <<: *light_partition_individual_led segments: - id: light_full from: 17 to: 17 - id: light_18 name: Light - LED 18 <<: *light_partition_individual_led segments: - id: light_full from: 18 to: 18 - id: light_19 name: Light - LED 19 <<: *light_partition_individual_led segments: - id: light_full from: 19 to: 19 - id: light_20 name: Light - LED 20 <<: *light_partition_individual_led segments: - id: light_full from: 20 to: 20 - id: light_21 name: Light - LED 21 <<: *light_partition_individual_led segments: - id: light_full from: 21 to: 21 - id: light_22 name: Light - LED 22 <<: *light_partition_individual_led segments: - id: light_full from: 22 to: 22 - id: light_23 name: Light - LED 23 <<: *light_partition_individual_led segments: - id: light_full from: 23 to: 23 - id: light_24 name: Light - LED 24 <<: *light_partition_individual_led segments: - id: light_full from: 24 to: 24 - id: light_25 name: Light - LED 25 <<: *light_partition_individual_led segments: - id: light_full from: 25 to: 25 - id: light_26 name: Light - LED 26 <<: *light_partition_individual_led segments: - id: light_full from: 26 to: 26 - id: light_27 name: Light - LED 27 <<: *light_partition_individual_led segments: - id: light_full from: 27 to: 27 - id: light_28 # US Model only name: Light - LED 28 internal: true <<: *light_partition_individual_led segments: - id: light_full from: 28 to: 28 - id: light_29 # US Model only name: Light - LED 29 internal: true <<: *light_partition_individual_led segments: - id: light_full from: 29 to: 29 - id: light_30 # US Model only name: Light - LED 30 internal: true <<: *light_partition_individual_led segments: - id: light_full from: 30 to: 30 - id: light_31 # US Model only name: Light - LED 31 internal: true <<: *light_partition_individual_led segments: - id: light_full from: 31 to: 31 script: - id: !extend boot_initialize then: - lambda: |- light_28->set_internal(!id(is_us_model)); light_29->set_internal(!id(is_us_model)); light_30->set_internal(!id(is_us_model)); light_31->set_internal(!id(is_us_model)); light_eu->set_internal(id(is_us_model)); light_eu_bottom->set_internal(id(is_us_model)); light_eu_left->set_internal(id(is_us_model)); light_eu_right->set_internal(id(is_us_model)); light_eu_top->set_internal(id(is_us_model)); light_us->set_internal(!id(is_us_model)); light_us_bottom->set_internal(!id(is_us_model)); light_us_left->set_internal(!id(is_us_model)); light_us_right->set_internal(!id(is_us_model)); light_us_top->set_internal(!id(is_us_model)); - id: !extend dump_config then: - lambda: |- // Transition times 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("${TAG_CORE_HW_LEDS}", " Turning on: ${LIGHT_TRANSITION_TURN_ON}ms"); else ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning on: Disabled"); if (${LIGHT_TRANSITION_TURN_OFF} > 0) ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning off: ${LIGHT_TRANSITION_TURN_OFF}ms"); else ESP_LOGCONFIG("${TAG_CORE_HW_LEDS}", " Turning off: Disabled"); // Restore modes 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_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_attributes_restore 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; } tx_ultimate_easy::LightAttributes attrs = tx_ultimate_easy::unpack_light_attributes(*attr_global_ptr); auto call = light_ptr->make_call(); call.set_brightness(attrs.brightness / 100.0f); call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f); call.perform(); ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored light attributes: brightness=%d%%, RGB=(%d,%d,%d)", attrs.brightness, attrs.red, attrs.green, attrs.blue); - id: light_attributes_restore_dual mode: parallel parameters: light_ptr: light::LightState* attr_global_ptr: uint64_t* group_index: uint8_t # 1 or 2 then: - lambda: |- if (!light_ptr) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer"); return; } if (!attr_global_ptr) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer"); return; } if (group_index != 1 && group_index != 2) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid group_index=%d (expected 1 or 2)", group_index); return; } auto attrs_pair = tx_ultimate_easy::unpack_dual_light_attributes(*attr_global_ptr); tx_ultimate_easy::LightAttributes attrs = (group_index == 1) ? attrs_pair.first : attrs_pair.second; auto call = light_ptr->make_call(); call.set_brightness(attrs.brightness / 100.0f); call.set_rgb(attrs.red / 255.0f, attrs.green / 255.0f, attrs.blue / 255.0f); call.perform(); ESP_LOGI("${TAG_CORE_HW_LEDS}", "Restored (group=%d) attributes: brightness=%d%%, RGB=(%d,%d,%d)", group_index, attrs.brightness, attrs.red, attrs.green, attrs.blue); - id: light_attributes_save mode: parallel parameters: light_ptr: light::LightState* attr_global_ptr: uint32_t* then: - lambda: |- if (!light_ptr) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer"); return; } if (!attr_global_ptr) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer"); return; } auto current = light_ptr->current_values; tx_ultimate_easy::LightAttributes attrs = { .brightness = uint8_t(current.get_brightness() * 100.0f), .red = uint8_t(current.get_red() * 255.0f), .green = uint8_t(current.get_green() * 255.0f), .blue = uint8_t(current.get_blue() * 255.0f) }; *attr_global_ptr = tx_ultimate_easy::pack_light_attributes(attrs); ESP_LOGD("${TAG_CORE_HW_LEDS}", "Saved light attributes: brightness=%d%%, RGB=(%d,%d,%d)", attrs.brightness, attrs.red, attrs.green, attrs.blue); - id: light_attributes_save_dual mode: queued # To prevent two instances trying to change the same global parameters: light_ptr: light::LightState* attr_global_ptr: uint64_t* group_index: uint8_t # 1 or 2 (which group within the dual pack) then: - lambda: |- if (!light_ptr) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid light pointer"); return; } if (!attr_global_ptr) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid attributes pointer"); return; } if (group_index != 1 && group_index != 2) { ESP_LOGE("${TAG_CORE_HW_LEDS}", "Invalid group_index=%d (expected 1 or 2)", group_index); return; } auto current = light_ptr->current_values; tx_ultimate_easy::LightAttributes new_attrs = { .brightness = uint8_t(current.get_brightness() * 100.0f), .red = uint8_t(current.get_red() * 255.0f), .green = uint8_t(current.get_green() * 255.0f), .blue = uint8_t(current.get_blue() * 255.0f) }; // Unpack existing dual attributes auto existing = tx_ultimate_easy::unpack_dual_light_attributes(*attr_global_ptr); // Update the appropriate group if (group_index == 1) { *attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(new_attrs, existing.second); } else { *attr_global_ptr = tx_ultimate_easy::pack_dual_light_attributes(existing.first, new_attrs); } - 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; 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->turn_on(); 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); ...