You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
5.1 KiB

  1. #pragma once
  2. #include "common.h"
  3. #include "gpio_outputs.h"
  4. #include "color_instant_handler.h"
  5. namespace esphome {
  6. namespace yeelight {
  7. namespace bs2 {
  8. /**
  9. * This is an interface definition that is used to extend the LightState
  10. * class with functionality to inspect LightTransformer data from
  11. * within other classes.
  12. *
  13. * This interface is required for the ColorTransitionHandler, so it can
  14. * check whether or not a light color transition is in progress.
  15. */
  16. class LightStateTransformerInspector {
  17. public:
  18. virtual bool is_active() = 0;
  19. virtual bool is_transition() = 0;
  20. virtual light::LightColorValues get_end_values() = 0;
  21. virtual float get_progress() = 0;
  22. };
  23. /**
  24. * This class is used to handle specific light color transition requirements
  25. * for the device.
  26. *
  27. * When using the default ESPHome logic, transitioning is done by
  28. * transitioning all light properties linearly from the original values to
  29. * the new values, and letting the light output object translate these
  30. * properties into light outputs on every step of the way. While this does
  31. * work, it does not work nicely.
  32. *
  33. * For example, when transitioning from warm to cold white light, the color
  34. * temperature would be transitioned from the old value to the new value.
  35. * While doing so, the transition hits the middle white light setting, which
  36. * shows up as a bright flash in the middle of the transition. The original
  37. * firmware however, shows a smooth transition from warm to cold white
  38. * light, without any flash.
  39. *
  40. * This class handles transitions by not varying the light properties over
  41. * time, but by transitioning the LEDC duty cycle output levels over time.
  42. * This matches the behavior of the original firmware.
  43. */
  44. class ColorTransitionHandler : public GPIOOutputs {
  45. public:
  46. ColorTransitionHandler(LightStateTransformerInspector *inspector) : transformer_(inspector) {}
  47. protected:
  48. bool set_light_color_values(light::LightColorValues values) {
  49. if (!light_state_has_active_transition_()) {
  50. // Remember the last active light color values. When a transition
  51. // is detected, we'll use these as the starting point. It is not
  52. // possible to use the current values at that point, because the
  53. // transition is already in progress by the time the transition
  54. // is detected.
  55. start_light_values_ = values;
  56. active_ = false;
  57. return false;
  58. }
  59. // When a fresh transition is started, then compute the GPIO outputs
  60. // to use for both the start and end point. This transition handler
  61. // will then transition linearly between these two.
  62. if (is_fresh_transition_()) {
  63. start_->set_light_color_values(start_light_values_);
  64. end_light_values_ = transformer_->get_end_values();
  65. end_->set_light_color_values(end_light_values_);
  66. active_ = true;
  67. }
  68. // When a transition is modified, then use the current GPIO outputs
  69. // as the new starting point.
  70. else if (is_modified_transition_()) {
  71. this->copy_to(start_);
  72. end_light_values_ = transformer_->get_end_values();
  73. end_->set_light_color_values(end_light_values_);
  74. }
  75. // Determine required GPIO outputs for current transition progress.
  76. progress_ = transformer_->get_progress();
  77. auto smoothed = light::LightTransitionTransformer::smoothed_progress(progress_);
  78. red = esphome::lerp(smoothed, start_->red, end_->red);
  79. green = esphome::lerp(smoothed, start_->green, end_->green);
  80. blue = esphome::lerp(smoothed, start_->blue, end_->blue);
  81. white = esphome::lerp(smoothed, start_->white, end_->white);
  82. return true;
  83. }
  84. bool active_ = false;
  85. float progress_ = 0.0f;
  86. LightStateTransformerInspector *transformer_;
  87. light::LightColorValues start_light_values_;
  88. light::LightColorValues end_light_values_;
  89. GPIOOutputs *start_ = new ColorInstantHandler();
  90. GPIOOutputs *end_ = new ColorInstantHandler();
  91. /**
  92. * Checks if the LightState currently has an active LightTransformer.
  93. */
  94. bool light_state_has_active_transition_() {
  95. if (!transformer_->is_active())
  96. return false;
  97. if (!transformer_->is_transition())
  98. return false;
  99. return true;
  100. }
  101. /**
  102. * Checks if a fresh transitioning is started.
  103. * A transitioning is fresh when no existing transition is active.
  104. */
  105. bool is_fresh_transition_() {
  106. return active_ == false;
  107. }
  108. /**
  109. * Checks if a new end state is set, while an existing transition
  110. * is active. This might be detected in two ways:
  111. * - the end color has been updated
  112. * - the progress has been reverted
  113. */
  114. bool is_modified_transition_() {
  115. auto new_end_light_values = transformer_->get_end_values();
  116. auto new_progress = transformer_->get_progress();
  117. return (
  118. new_end_light_values != end_light_values_ ||
  119. new_progress < progress_
  120. );
  121. }
  122. };
  123. } // namespace yeelight_bs2
  124. } // namespace yeelight
  125. } // namespace bs2