substitutions: brightness_icon: "\U0000e900" saturation_icon: "\U0000e901" exit_icon: "\U0000e902" globals: - id: ${widget_name}_light_is_on type: bool initial_value: 'false' - id: ${widget_name}_current_hue type: float initial_value: '0.0' - id: ${widget_name}_current_saturation type: float initial_value: '0.0' - id: ${widget_name}_current_brightness type: float initial_value: '1.0' - id: ${widget_name}_current_color_temp type: float initial_value: '3000.0' - id: ${widget_name}_updating_from_ha type: bool initial_value: 'false' - id: ${widget_name}_ha_data_received type: bool initial_value: 'false' - id: ${widget_name}_sensors_ready_count type: int initial_value: '0' - id: ${widget_name}_is_temp_mode type: bool initial_value: 'true' - id: ${widget_name}_mode_switching type: bool initial_value: 'false' sensor: # Brightness sensor - platform: homeassistant id: ${widget_name}_light_rgb_sensor_brightness entity_id: "${light_entity}" attribute: brightness on_value: - if: condition: lambda: 'return !std::isnan(x);' then: - lambda: |- static bool first_brightness_received = false; if (!first_brightness_received) { first_brightness_received = true; id(${widget_name}_sensors_ready_count) += 1; } - if: condition: lambda: 'return !id(${widget_name}_updating_from_ha);' then: - globals.set: id: ${widget_name}_current_brightness value: !lambda return x; - lvgl.slider.update: id: ${widget_name}_light_rgb_brightness_slider value: !lambda return int(x); - lvgl.label.update: id: ${widget_name}_light_rgb_brightness_state text: !lambda |- int percent = int((x / 255.0f) * 100 + 0.5f); char buf[8]; snprintf(buf, sizeof(buf), "%d%%", percent); return std::string(buf); - script.execute: ${widget_name}_check_all_data_ready # Color temp sensor - platform: homeassistant id: ${widget_name}_light_rgb_sensor_color_temp entity_id: "${light_entity}" attribute: color_temp_kelvin on_value: - if: condition: lambda: 'return !std::isnan(x);' then: - lambda: |- static bool first_temp_received = false; if (!first_temp_received) { first_temp_received = true; id(${widget_name}_sensors_ready_count) += 1; } - if: condition: lambda: 'return !id(${widget_name}_updating_from_ha);' then: - globals.set: id: ${widget_name}_current_color_temp value: !lambda return x; - lvgl.slider.update: id: ${widget_name}_light_rgb_color_temp_slider value: !lambda return int(x); - if: condition: lambda: 'return id(${widget_name}_is_temp_mode) && !id(${widget_name}_mode_switching);' then: - script.execute: id: ${widget_name}_update_color_temp_preview kelvin: !lambda return x; - script.execute: ${widget_name}_check_all_data_ready text_sensor: - platform: homeassistant id: ${widget_name}_light_rgb_state entity_id: "${light_entity}" on_value: - lambda: |- bool is_on = (x == "on"); bool was_on = id(${widget_name}_light_is_on); id(${widget_name}_light_is_on) = is_on; static bool first_state_received = false; if (!first_state_received) { first_state_received = true; id(${widget_name}_sensors_ready_count) += 1; } - if: condition: lambda: 'return !id(${widget_name}_light_is_on);' then: - lvgl.label.update: id: ${widget_name}_light_on text_color: color_steel_blue - lvgl.button.update: id: ${widget_name}_light_rgb_light_on bg_color: color_white shadow_opa: TRANSP else: - lambda: |- if (id(${widget_name}_mode_switching)) return; std::string rgb_str = id(${widget_name}_light_rgb_color).state; size_t start = rgb_str.find('(') + 1; size_t first_comma = rgb_str.find(',', start); size_t second_comma = rgb_str.find(',', first_comma + 1); size_t end = rgb_str.find(')', second_comma); if (start < first_comma && first_comma < second_comma && second_comma < end) { int red = atoi(rgb_str.substr(start, first_comma - start).c_str()); int green = atoi(rgb_str.substr(first_comma + 1, second_comma - first_comma - 1).c_str()); int blue = atoi(rgb_str.substr(second_comma + 1, end - second_comma - 1).c_str()); id(${widget_name}_update_light_button_color).execute(red, green, blue); } - script.execute: ${widget_name}_check_all_data_ready - platform: homeassistant id: ${widget_name}_light_rgb_sensor_hs entity_id: "${light_entity}" attribute: hs_color on_value: - if: condition: lambda: "return x != \"None\" && !x.empty();" then: - lambda: |- static bool first_hs_received = false; if (!first_hs_received) { first_hs_received = true; id(${widget_name}_sensors_ready_count) += 1; } - lambda: |- id(${widget_name}_updating_from_ha) = true; std::string s = x; size_t start = s.find('(') + 1; size_t comma = s.find(','); size_t end = s.find(')'); float hue = 0.0f, sat = 0.0f; if (start < comma && comma < end) { hue = atof(s.substr(start, comma - start).c_str()); sat = atof(s.substr(comma + 1, end - comma - 1).c_str()); } id(${widget_name}_current_hue) = hue; id(${widget_name}_current_saturation) = sat; - lvgl.slider.update: id: ${widget_name}_light_rgb_hue_slider value: !lambda "return id(${widget_name}_current_hue);" - lvgl.slider.update: id: ${widget_name}_light_rgb_saturation_slider value: !lambda "return id(${widget_name}_current_saturation) * 255.0f / 100.0f;" - lvgl.label.update: id: ${widget_name}_light_rgb_saturation_state text: !lambda |- return (std::to_string((int)id(${widget_name}_current_saturation)) + "%").c_str(); - if: condition: lambda: 'return !id(${widget_name}_is_temp_mode) && !id(${widget_name}_mode_switching);' then: - script.execute: ${widget_name}_update_light_color_from_current_values - lambda: |- id(${widget_name}_updating_from_ha) = false; else: - lambda: |- static bool first_hs_received = false; if (!first_hs_received) { first_hs_received = true; id(${widget_name}_sensors_ready_count) += 1; } - script.execute: ${widget_name}_check_all_data_ready - platform: homeassistant id: ${widget_name}_light_rgb_color entity_id: "${light_entity}" attribute: rgb_color - platform: homeassistant id: ${widget_name}_light_rgb_name entity_id: "${light_entity}" attribute: friendly_name on_value: then: - lvgl.label.update: id: ${widget_name}_light_rgb_lable_name text: !lambda return x; lvgl: gradients: - id: ${widget_name}_hue_gradient direction: ver dither: none stops: - color: 0xFF0000 position: 0 - color: 0xFF00FF position: 42 - color: 0x0000FF position: 84 - color: 0x00FFFF position: 127 - color: 0x00FF00 position: 169 - color: 0xFFFF00 position: 212 - color: 0xFF0000 position: 255 - id: ${widget_name}_color_temp_gradient direction: ver dither: none stops: - color: 0xA0C8FF position: 0 - color: 0xE0F7FF position: 80 - color: 0xFFFFFF position: 128 - color: 0xFFFFE0 position: 175 - color: 0xFFD6A0 position: 255 pages: - id: ${widget_name}_light_rgb_page bg_color: color_slate_blue_gray widgets: - button: id: ${widget_name}_light_rgb_bg_name y: 20 width: 440 height: 40 align: TOP_MID bg_color: color_steel_blue bg_opa: 20% shadow_opa: TRANSP radius: 10 widgets: - label: id: ${widget_name}_light_rgb_lable_name align: CENTER text_font: nunito_16 text_color: color_misty_blue text: "lightbulb" - button: id: ${widget_name}_light_rgb_bg_lightbulb x: 20 y: 80 width: 200 height: 180 align: TOP_LEFT bg_color: color_steel_blue bg_opa: 20% shadow_opa: TRANSP radius: 10 widgets: - button: id: ${widget_name}_light_rgb_light_on width: 70 height: 70 y: 24 align: TOP_MID bg_color: color_white shadow_color: color_white shadow_ofs_y: -3 shadow_spread: 3 shadow_width: 15 radius: 40 - image: y: 30 align: CENTER src: lightbulb_image id: ${widget_name}_lightbulb_image - button: id: ${widget_name}_light_rgb_light_on_btn width: 200 height: 200 align: CENTER bg_opa: TRANSP shadow_opa: TRANSP on_press: - homeassistant.service: service: light.toggle data: entity_id: "${light_entity}" - button: id: ${widget_name}_light_rgb_mode_btn x: 20 y: 60 width: 200 height: 40 align: LEFT_MID bg_color: color_steel_blue bg_opa: 20% border_opa: TRANSP shadow_opa: TRANSP radius: 10 checkable: true state: checked: false checked: bg_color: color_misty_blue bg_opa: 30% widgets: - label: id: ${widget_name}_light_rgb_mode_label text_color: color_misty_blue align: CENTER text_font: nunito_16 text: "TEMP MODE" on_press: - lambda: |- id(${widget_name}_is_temp_mode) = !id(${widget_name}_is_temp_mode); - if: condition: lambda: 'return id(${widget_name}_is_temp_mode);' then: - lvgl.label.update: id: ${widget_name}_light_rgb_mode_label text: "TEMP MODE" - script.execute: ${widget_name}_switch_to_temp_mode else: - lvgl.label.update: id: ${widget_name}_light_rgb_mode_label text: "RGB MODE" - script.execute: ${widget_name}_switch_to_rgb_mode - obj: id: ${widget_name}_light_rgb_bg_slider x: -20 y: 80 width: 220 height: 240 align: TOP_RIGHT bg_color: color_steel_blue bg_opa: 20% border_opa: TRANSP shadow_opa: TRANSP radius: 10 widgets: - button: id: ${widget_name}_light_rgb_hue_gradient radius: 20 width: 100 height: 200 align: CENTER bg_grad: ${widget_name}_hue_gradient bg_grad_dir: ver shadow_opa: TRANSP hidden: true - slider: id: ${widget_name}_light_rgb_hue_slider radius: 20 align: CENTER bg_opa: TRANSP width: 100 height: 200 min_value: 0 max_value: 360 hidden: true indicator: bg_opa: TRANSP knob: bg_opa: TRANSP on_value: - if: condition: lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);' then: - globals.set: id: ${widget_name}_current_hue value: !lambda "return (float)x;" - script.execute: ${widget_name}_update_light_color_from_current_values on_release: - if: condition: lambda: 'return id(${widget_name}_ha_data_received);' then: - script.execute: ${widget_name}_send_hs_color_to_ha - button: id: ${widget_name}_light_rgb_color_temp_gradient radius: 20 width: 100 height: 200 align: CENTER bg_grad: ${widget_name}_color_temp_gradient bg_grad_dir: ver shadow_opa: TRANSP - slider: id: ${widget_name}_light_rgb_color_temp_slider radius: 20 width: 100 height: 200 align: CENTER bg_opa: TRANSP min_value: 2000 max_value: 6500 indicator: bg_opa: TRANSP knob: bg_opa: TRANSP on_value: - if: condition: lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);' then: - globals.set: id: ${widget_name}_current_color_temp value: !lambda return x; - script.execute: id: ${widget_name}_update_color_temp_preview kelvin: !lambda return x; on_release: - if: condition: lambda: 'return id(${widget_name}_ha_data_received);' then: - homeassistant.service: service: light.turn_on data: entity_id: "${light_entity}" color_temp_kelvin: !lambda return int(x); - obj: id: ${widget_name}_light_rgb_bg_bs_sliders x: -20 y: -20 width: 360 height: 120 align: BOTTOM_RIGHT bg_color: color_steel_blue bg_opa: 20% border_opa: TRANSP shadow_opa: TRANSP radius: 10 widgets: - obj: id: ${widget_name}_light_rgb_brightness_pos pad_all: 0 width: 320 height: 40 align: CENTER bg_opa: TRANSP border_width: 0 widgets: - label: id: ${widget_name}_light_rgb_brightness_icon x: 0 y: 0 align: LEFT_MID text_font: icons_24 text_color: color_misty_blue text: "${brightness_icon}" - label: id: ${widget_name}_light_rgb_brightness_state x: 0 y: 0 align: RIGHT_MID text_font: nunito_16 text_color: color_misty_blue text: "100%" - slider: id: ${widget_name}_light_rgb_brightness_slider bg_color: color_slate_blue_gray bg_opa: 100% align: CENTER x: -10 y: 1 width: 220 height: 10 min_value: 3 max_value: 255 indicator: bg_color: color_misty_blue radius: 30 knob: bg_color: color_misty_blue on_value: - if: condition: lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);' then: - globals.set: id: ${widget_name}_current_brightness value: !lambda return x; - lvgl.label.update: id: ${widget_name}_light_rgb_brightness_state text: !lambda |- int percent = int((x / 255.0f) * 100 + 0.5f); char buf[8]; snprintf(buf, sizeof(buf), "%d%%", percent); return std::string(buf); on_release: - if: condition: lambda: 'return id(${widget_name}_ha_data_received);' then: - homeassistant.service: service: light.turn_on data: entity_id: "${light_entity}" brightness: !lambda return int(x); - obj: id: ${widget_name}_light_rgb_saturation_container pad_all: 0 width: 320 height: 40 align: BOTTOM_MID bg_opa: TRANSP border_width: 0 hidden: true widgets: - label: id: ${widget_name}_light_rgb_saturation_icon x: 0 y: 0 align: LEFT_MID text_font: icons_24 text_color: color_misty_blue text: "${saturation_icon}" - label: id: ${widget_name}_light_rgb_saturation_state x: 0 y: 0 align: RIGHT_MID text_font: nunito_16 text_color: color_misty_blue text: "100%" - slider: id: ${widget_name}_light_rgb_saturation_slider bg_color: color_slate_blue_gray bg_opa: 100% align: CENTER x: -10 y: 0 width: 220 height: 10 min_value: 1 max_value: 255 indicator: bg_color: color_misty_blue knob: bg_color: color_misty_blue on_value: - if: condition: lambda: 'return !id(${widget_name}_updating_from_ha) && id(${widget_name}_ha_data_received);' then: - globals.set: id: ${widget_name}_current_saturation value: !lambda "return (float)x * 100.0f / 255.0f;" - lvgl.label.update: id: ${widget_name}_light_rgb_saturation_state text: !lambda |- int percent = int((float)x * 100.0f / 255.0f + 0.5f); return (std::to_string(percent) + "%").c_str(); - script.execute: ${widget_name}_update_light_color_from_current_values on_release: - if: condition: lambda: 'return id(${widget_name}_ha_data_received);' then: - script.execute: ${widget_name}_send_hs_color_to_ha - button: id: ${widget_name}_light_rgb_exit_on_btn x: 20 y: -20 width: 60 height: 120 align: BOTTOM_LEFT bg_color: color_steel_blue bg_opa: 20% border_opa: TRANSP shadow_opa: TRANSP radius: 10 widgets: - label: id: ${widget_name}_light_rgb_exit_on_icon align: CENTER text_font: icons_28 text_color: color_misty_blue text: "${exit_icon}" on_press: then: - lvgl.widget.show: menu_controls_main - lvgl.page.show: id: lights_group_page animation: OUT_RIGHT time: 300ms script: - id: ${widget_name}_check_all_data_ready mode: restart then: - delay: 500ms - lambda: |- if (id(${widget_name}_sensors_ready_count) >= 4) { if (!id(${widget_name}_ha_data_received)) { id(${widget_name}_ha_data_received) = true; id(${widget_name}_updating_from_ha) = false; id(${widget_name}_switch_to_temp_mode).execute(); } } - id: ${widget_name}_switch_to_temp_mode then: - globals.set: id: ${widget_name}_mode_switching value: 'true' - lvgl.widget.hide: ${widget_name}_light_rgb_hue_slider - lvgl.widget.hide: ${widget_name}_light_rgb_hue_gradient - lvgl.widget.hide: ${widget_name}_light_rgb_saturation_container - lvgl.widget.show: ${widget_name}_light_rgb_color_temp_slider - lvgl.widget.show: ${widget_name}_light_rgb_color_temp_gradient - lvgl.obj.update: id: ${widget_name}_light_rgb_brightness_pos align: CENTER - delay: 500ms - globals.set: id: ${widget_name}_mode_switching value: 'false' - id: ${widget_name}_switch_to_rgb_mode then: - globals.set: id: ${widget_name}_mode_switching value: 'true' - lvgl.widget.show: ${widget_name}_light_rgb_hue_slider - lvgl.widget.show: ${widget_name}_light_rgb_hue_gradient - lvgl.widget.show: ${widget_name}_light_rgb_saturation_container - lvgl.widget.hide: ${widget_name}_light_rgb_color_temp_slider - lvgl.widget.hide: ${widget_name}_light_rgb_color_temp_gradient - lvgl.obj.update: id: ${widget_name}_light_rgb_brightness_pos align: TOP_MID - delay: 500ms - globals.set: id: ${widget_name}_mode_switching value: 'false' - id: ${widget_name}_update_light_color_from_current_values then: - if: condition: lambda: 'return id(${widget_name}_light_is_on) && !id(${widget_name}_mode_switching);' then: - script.execute: id: ${widget_name}_hsv_to_rgb_and_update hue: !lambda "return id(${widget_name}_current_hue);" saturation: !lambda "return id(${widget_name}_current_saturation);" brightness: 1.0 - id: ${widget_name}_update_color_temp_preview parameters: kelvin: float then: - if: condition: lambda: 'return id(${widget_name}_light_is_on) && !id(${widget_name}_mode_switching);' then: - lambda: |- float temp = kelvin / 100.0f; float r, g, b; if (temp <= 66.0f) { r = 255.0f; g = 99.4708025861f * logf(temp) - 161.1195681661f; if (temp <= 19.0f) { b = 0.0f; } else { b = 138.5177312231f * logf(temp - 10.0f) - 305.0447927307f; } } else { r = 329.698727446f * powf(temp - 60.0f, -0.1332047592f); g = 288.1221695283f * powf(temp - 60.0f, -0.0755148492f); b = 255.0f; } r = std::clamp(r, 0.0f, 255.0f); g = std::clamp(g, 0.0f, 255.0f); b = std::clamp(b, 0.0f, 255.0f); int red = static_cast(r); int green = static_cast(g); int blue = static_cast(b); id(${widget_name}_update_light_button_color).execute(red, green, blue); - id: ${widget_name}_update_light_button_color parameters: red: int green: int blue: int then: - if: condition: lambda: 'return id(${widget_name}_light_is_on);' then: - lvgl.button.update: id: ${widget_name}_light_rgb_light_on bg_color: !lambda |- return lv_color_make(red, green, blue); shadow_color: !lambda |- return lv_color_make(red, green, blue); shadow_opa: 100% - lvgl.label.update: id: ${widget_name}_light_on text_color: !lambda |- return lv_color_make(red, green, blue); - id: ${widget_name}_hsv_to_rgb_and_update parameters: hue: float saturation: float brightness: float then: - if: condition: lambda: 'return id(${widget_name}_light_is_on);' then: - lambda: |- float h = hue; float s = saturation / 100.0f; float v = brightness; float c = v * s; float x_val = c * (1.0f - abs(fmod(h / 60.0f, 2.0f) - 1.0f)); float m = v - c; float r, g, b; if (h >= 0 && h < 60) { r = c; g = x_val; b = 0; } else if (h >= 60 && h < 120) { r = x_val; g = c; b = 0; } else if (h >= 120 && h < 180) { r = 0; g = c; b = x_val; } else if (h >= 180 && h < 240) { r = 0; g = x_val; b = c; } else if (h >= 240 && h < 300) { r = x_val; g = 0; b = c; } else { r = c; g = 0; b = x_val; } int red = (int)((r + m) * 255.0f); int green = (int)((g + m) * 255.0f); int blue = (int)((b + m) * 255.0f); id(${widget_name}_update_light_button_color).execute(red, green, blue); - id: ${widget_name}_send_hs_color_to_ha then: - if: condition: lambda: 'return id(${widget_name}_ha_data_received);' then: - homeassistant.service: service: light.turn_on data: entity_id: "${light_entity}" data_template: hs_color: "{{ (hue|float, sat|float)|list }}" variables: hue: !lambda "return id(${widget_name}_current_hue);" sat: !lambda "return id(${widget_name}_current_saturation);"