diff --git a/services/lampBehaviour.yaml b/services/lampBehaviour.yaml index 0bacf2c..71d1b49 100644 --- a/services/lampBehaviour.yaml +++ b/services/lampBehaviour.yaml @@ -173,4 +173,127 @@ sensor: text_sensor: - platform: xiaomi_bslamp2 name: ${light_mode_text_sensor_name} - id: my_light_mode \ No newline at end of file + id: my_light_mode + +# ------------------------------------------------------------------------ +# Night light color-temperature calibration +# +# This package exposes Home Assistant number entities for tuning the night +# light tint without manipulating raw RGB attenuation values directly. +# Values are restored by the standard ESPHome template number component. +# ------------------------------------------------------------------------ + +substitutions: + night_light_calibration_red_base: "0.968" + night_light_calibration_green_base: "0.968" + night_light_calibration_blue_base: "0.968" + night_light_tint_name: Night Light Tint + night_light_temperature_name: Night Light Temperature + night_light_level_name: Night Light Level + night_light_tint_initial: "0.0" + night_light_temperature_initial: "0.0" + night_light_level_initial: "0.0" + night_light_calibration_min: "0.920" + night_light_calibration_max: "0.980" + +esphome: + on_boot: + - priority: 600 + then: + - script.execute: apply_night_light_calibration + +script: + - id: apply_night_light_calibration + mode: restart + then: + - lambda: |- + if (isnan(id(my_night_light_tint).state) || + isnan(id(my_night_light_temperature).state) || + isnan(id(my_night_light_level).state)) { + return; + } + + const float tint = id(my_night_light_tint).state; + const float temperature = id(my_night_light_temperature).state; + const float level = id(my_night_light_level).state; + + const float base_red_drive = 1.0f - ${night_light_calibration_red_base}f; + const float base_green_drive = 1.0f - ${night_light_calibration_green_base}f; + const float base_blue_drive = 1.0f - ${night_light_calibration_blue_base}f; + + auto approx_exp = [](float x) -> float { + return 1.0f + x + 0.5f * x * x; + }; + + // Work in drive space, where larger values mean a brighter channel. + // Quadratic gain approximation keeps tint and temperature perceptible + // without pulling in runtime exponential math. + const float level_gain = approx_exp(level * 0.027f); + const float temperature_amount = temperature * 0.027f; + const float tint_amount = tint * 0.035f; + + const float red_drive = base_red_drive * level_gain * + approx_exp(temperature_amount * 0.5f) * + approx_exp(tint_amount * 0.5f); + const float green_drive = base_green_drive * level_gain * + approx_exp(temperature_amount * 0.25f) * + approx_exp(-tint_amount); + const float blue_drive = base_blue_drive * level_gain * + approx_exp(-temperature_amount * 0.75f) * + approx_exp(tint_amount * 0.5f); + + const float red = clamp(1.0f - red_drive, + ${night_light_calibration_min}f, ${night_light_calibration_max}f); + const float green = clamp(1.0f - green_drive, + ${night_light_calibration_min}f, ${night_light_calibration_max}f); + const float blue = clamp(1.0f - blue_drive, + ${night_light_calibration_min}f, ${night_light_calibration_max}f); + + id(my_light).set_night_light_color_temperature_calibration(red, green, blue); + +number: + - platform: template + id: my_night_light_tint + name: ${night_light_tint_name} + icon: mdi:palette + entity_category: config + mode: slider + optimistic: true + restore_value: true + min_value: -10.0 + max_value: 10.0 + step: 0.1 + initial_value: ${night_light_tint_initial} + on_value: + then: + - script.execute: apply_night_light_calibration + - platform: template + id: my_night_light_temperature + name: ${night_light_temperature_name} + icon: mdi:thermometer + entity_category: config + mode: slider + optimistic: true + restore_value: true + min_value: -10.0 + max_value: 20.0 + step: 0.1 + initial_value: ${night_light_temperature_initial} + on_value: + then: + - script.execute: apply_night_light_calibration + - platform: template + id: my_night_light_level + name: ${night_light_level_name} + icon: mdi:brightness-6 + entity_category: config + mode: slider + optimistic: true + restore_value: true + min_value: -10.0 + max_value: 20.0 + step: 0.1 + initial_value: ${night_light_level_initial} + on_value: + then: + - script.execute: apply_night_light_calibration \ No newline at end of file