Browse Source

Implement disco-mode actions.

pull/24/head
Maurice Makaay 3 years ago
parent
commit
aa3f8add4b
6 changed files with 238 additions and 21 deletions
  1. +33
    -0
      doc/configuration.md
  2. +50
    -2
      light/__init__.py
  3. +46
    -0
      light/automation.h
  4. +1
    -16
      light/color_transition_handler.h
  5. +56
    -0
      light/interfaces.h
  6. +52
    -3
      light/light_state.h

+ 33
- 0
doc/configuration.md View File

@ -134,6 +134,39 @@ It is possible to control the night light mode separately. An example of
this can be found in the [example.yaml](example.yaml), in which holding the this can be found in the [example.yaml](example.yaml), in which holding the
power button is bound to activating the night light. power button is bound to activating the night light.
### light.disco_on Action
This action sets the state of the light immediately
(i.e. without waiting for the next main loop iteration),
without saving the state to memory and without publishing
the state change.
```yaml
on_...:
then:
- light.disco_on:
id: my_bedside_lamp
brightness: 80%
red: 70%
green: 0%
blue: 100%
```
The possible configuration options for this Action are the same
as those for the standard `light.turn_on` Action.
### light.disco_off Action
This action turns off the disco mode by restoring the state
of the lamp to the last known state that was saved to memory.
```yaml
on_...:
then:
- light.disco_off:
id: my_bedside_lamp
```
### Light presets ### Light presets
The presets functionality was written with the original lamp firemware The presets functionality was written with the original lamp firemware


+ 50
- 2
light/__init__.py View File

@ -5,8 +5,8 @@ from esphome import automation
from esphome.core import coroutine from esphome.core import coroutine
from esphome.const import ( from esphome.const import (
CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_COLOR_TEMPERATURE, CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_COLOR_TEMPERATURE,
CONF_OUTPUT_ID, CONF_TRIGGER_ID, CONF_ID,
CONF_TRANSITION_LENGTH, CONF_BRIGHTNESS, CONF_EFFECT
CONF_STATE, CONF_OUTPUT_ID, CONF_TRIGGER_ID, CONF_ID,
CONF_TRANSITION_LENGTH, CONF_BRIGHTNESS, CONF_EFFECT, CONF_FLASH_LENGTH
) )
from .. import bslamp2_ns, CODEOWNERS, CONF_LIGHT_HAL_ID, LightHAL from .. import bslamp2_ns, CODEOWNERS, CONF_LIGHT_HAL_ID, LightHAL
@ -31,6 +31,7 @@ PresetsContainer = bslamp2_ns.class_("PresetsContainer", cg.Component)
Preset = bslamp2_ns.class_("Preset", cg.Component) Preset = bslamp2_ns.class_("Preset", cg.Component)
BrightnessTrigger = bslamp2_ns.class_("BrightnessTrigger", automation.Trigger.template()) BrightnessTrigger = bslamp2_ns.class_("BrightnessTrigger", automation.Trigger.template())
ActivatePresetAction = bslamp2_ns.class_("ActivatePresetAction", automation.Action) ActivatePresetAction = bslamp2_ns.class_("ActivatePresetAction", automation.Action)
DiscoAction = bslamp2_ns.class_("DiscoAction", automation.Action)
PRESETS_SCHEMA = cv.Schema({ PRESETS_SCHEMA = cv.Schema({
str.lower: cv.Schema({ str.lower: cv.Schema({
@ -125,6 +126,53 @@ def maybe_simple_preset_action(schema):
return validator return validator
@automation.register_action(
"light.disco_on", DiscoAction, light.automation.LIGHT_TURN_ON_ACTION_SCHEMA
)
def disco_action_on_to_code(config, action_id, template_arg, args):
light_var = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, light_var)
if CONF_STATE in config:
template_ = yield cg.templatable(config[CONF_STATE], args, bool)
cg.add(var.set_state(template_))
if CONF_TRANSITION_LENGTH in config:
template_ = yield cg.templatable(
config[CONF_TRANSITION_LENGTH], args, cg.uint32
)
cg.add(var.set_transition_length(template_))
if CONF_FLASH_LENGTH in config:
template_ = yield cg.templatable(config[CONF_FLASH_LENGTH], args, cg.uint32)
cg.add(var.set_flash_length(template_))
if CONF_BRIGHTNESS in config:
template_ = yield cg.templatable(config[CONF_BRIGHTNESS], args, float)
cg.add(var.set_brightness(template_))
if CONF_RED in config:
template_ = yield cg.templatable(config[CONF_RED], args, float)
cg.add(var.set_red(template_))
if CONF_GREEN in config:
template_ = yield cg.templatable(config[CONF_GREEN], args, float)
cg.add(var.set_green(template_))
if CONF_BLUE in config:
template_ = yield cg.templatable(config[CONF_BLUE], args, float)
cg.add(var.set_blue(template_))
if CONF_COLOR_TEMPERATURE in config:
template_ = yield cg.templatable(config[CONF_COLOR_TEMPERATURE], args, float)
cg.add(var.set_color_temperature(template_))
if CONF_EFFECT in config:
template_ = yield cg.templatable(config[CONF_EFFECT], args, cg.std_string)
cg.add(var.set_effect(template_))
yield var
@automation.register_action(
"light.disco_off", DiscoAction, light.automation.LIGHT_TURN_OFF_ACTION_SCHEMA
)
def disco_action_off_to_code(config, action_id, template_arg, args):
light_var = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, light_var)
cg.add(var.set_disco_state(False))
yield var
@automation.register_action( @automation.register_action(
"preset.activate", "preset.activate",
ActivatePresetAction, ActivatePresetAction,


+ 46
- 0
light/automation.h View File

@ -2,7 +2,9 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "interfaces.h"
#include "light_output.h" #include "light_output.h"
#include "light_state.h"
#include "presets.h" #include "presets.h"
#include <cmath> #include <cmath>
@ -29,6 +31,50 @@ class BrightnessTrigger : public Trigger<float> {
float last_brightness_ = -1.0f; float last_brightness_ = -1.0f;
}; };
template<typename... Ts> class DiscoAction : public Action<Ts...> {
public:
explicit DiscoAction(LightStateDiscoSupport *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(bool, disco_state)
TEMPLATABLE_VALUE(bool, state)
TEMPLATABLE_VALUE(uint32_t, transition_length)
TEMPLATABLE_VALUE(uint32_t, flash_length)
TEMPLATABLE_VALUE(float, brightness)
TEMPLATABLE_VALUE(float, red)
TEMPLATABLE_VALUE(float, green)
TEMPLATABLE_VALUE(float, blue)
TEMPLATABLE_VALUE(float, color_temperature)
TEMPLATABLE_VALUE(std::string, effect)
void play(Ts... x) override {
if (this->disco_state_.has_value()) {
auto p = this->disco_state_.optional_value(x...);
if (!*p) {
parent_->disco_stop();
return;
}
}
auto call = this->parent_->make_disco_call(false);
call.set_state(this->state_.optional_value(x...));
call.set_brightness(this->brightness_.optional_value(x...));
call.set_red(this->red_.optional_value(x...));
call.set_green(this->green_.optional_value(x...));
call.set_blue(this->blue_.optional_value(x...));
call.set_color_temperature(this->color_temperature_.optional_value(x...));
call.set_effect(this->effect_.optional_value(x...));
call.set_flash_length(this->flash_length_.optional_value(x...));
call.set_transition_length(this->transition_length_.optional_value(x...));
// Force the light to update right now, not in the next loop.
call.perform();
parent_->disco_apply();
}
protected:
LightStateDiscoSupport *parent_;
};
template<typename... Ts> class ActivatePresetAction : public Action<Ts...> { template<typename... Ts> class ActivatePresetAction : public Action<Ts...> {
public: public:
explicit ActivatePresetAction(PresetsContainer *presets) : presets_(presets) {} explicit ActivatePresetAction(PresetsContainer *presets) : presets_(presets) {}


+ 1
- 16
light/color_transition_handler.h View File

@ -2,28 +2,13 @@
#include "../common.h" #include "../common.h"
#include "color_instant_handler.h" #include "color_instant_handler.h"
#include "interfaces.h"
#include "gpio_outputs.h" #include "gpio_outputs.h"
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
namespace bslamp2 { namespace bslamp2 {
/**
* This is an interface definition that is used to extend the LightState
* class with functionality to inspect LightTransformer data from
* within other classes.
*
* This interface is required for the ColorTransitionHandler, so it can
* check whether or not a light color transition is in progress.
*/
class LightStateTransformerInspector {
public:
virtual bool is_active() = 0;
virtual bool is_transition() = 0;
virtual light::LightColorValues get_end_values() = 0;
virtual float get_progress() = 0;
};
/** /**
* This class is used to handle specific light color transition requirements * This class is used to handle specific light color transition requirements
* for the device. * for the device.


+ 56
- 0
light/interfaces.h View File

@ -0,0 +1,56 @@
#pragma once
namespace esphome {
namespace xiaomi {
namespace bslamp2 {
/**
* This is an interface definition that is used to extend the LightState
* class with functionality to inspect LightTransformer data from
* within other classes.
*
* This interface is required for the ColorTransitionHandler, so it can
* check whether or not a light color transition is in progress.
*/
class LightStateTransformerInspector {
public:
virtual bool is_active() = 0;
virtual bool is_transition() = 0;
virtual light::LightColorValues get_end_values() = 0;
virtual float get_progress() = 0;
};
/**
* This is an interface definition that is used to extend the LightState
* class with functionality for disco actions (immediate light updates,
* not publishing or saving the light state).
*
* This interface is required by the DiscoAction class.
*/
class LightStateDiscoSupport {
public:
/**
* Stop the disco, by restoring the previously remembered light state.
*/
virtual void disco_stop() = 0;
/**
* Do not wait until the next loop() call for the light to write the
* requested state to the light output, but write the new state
* right away.
*
* This allows us to update the state of the light, even when we are
* being called in the middle of another component's loop().
*/
virtual void disco_apply() = 0;
/**
* Create a light::LightCall object, with some properties already
* configured for using it as a disco call.
*/
virtual light::LightCall make_disco_call(bool save_and_publish) = 0;
};
} // namespace bslamp2
} // namespace xiaomi
} // namespace esphome

+ 52
- 3
light/light_state.h View File

@ -1,19 +1,36 @@
#pragma once #pragma once
#include "../common.h" #include "../common.h"
#include "interfaces.h"
#include "esphome/components/light/light_state.h"
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
namespace bslamp2 { namespace bslamp2 {
// Can be replaced with light::LightStateRTCState once pull request
// https://github.com/esphome/esphome/pull/1735 is merged.
struct MyLightStateRTCState {
bool state{false};
float brightness{1.0f};
float red{1.0f};
float green{1.0f};
float blue{1.0f};
float white{1.0f};
float color_temp{1.0f};
uint32_t effect{0};
};
/** /**
* This custom LightState class is used to provide access to the protected
* LightTranformer information in the LightState class.
* A custom LightState class for the Xiaomi Bedside Lamp 2.
* *
* This class is used by the ColorTransitionHandler class to inspect if * This class is used by the ColorTransitionHandler class to inspect if
* an ongoing light color transition is active in the LightState object. * an ongoing light color transition is active in the LightState object.
*
* It is also used by the DiscoAction to apply immediate light output
* updates, without saving or publishing the new state.
*/ */
class XiaomiBslamp2LightState : public light::LightState, public LightStateTransformerInspector {
class XiaomiBslamp2LightState : public light::LightState, public LightStateTransformerInspector, public LightStateDiscoSupport {
public: public:
XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) {
output->set_transformer_inspector(this); output->set_transformer_inspector(this);
@ -23,6 +40,38 @@ class XiaomiBslamp2LightState : public light::LightState, public LightStateTrans
bool is_transition() { return transformer_->is_transition(); } bool is_transition() { return transformer_->is_transition(); }
light::LightColorValues get_end_values() { return transformer_->get_end_values(); } light::LightColorValues get_end_values() { return transformer_->get_end_values(); }
float get_progress() { return transformer_->get_progress(); } float get_progress() { return transformer_->get_progress(); }
void disco_stop() {
MyLightStateRTCState recovered{};
if (this->rtc_.load(&recovered)) {
auto call = make_disco_call(true);
call.set_state(recovered.state);
call.set_brightness_if_supported(recovered.brightness);
call.set_red_if_supported(recovered.red);
call.set_green_if_supported(recovered.green);
call.set_blue_if_supported(recovered.blue);
call.set_white_if_supported(recovered.white);
call.set_color_temperature_if_supported(recovered.color_temp);
if (recovered.effect != 0) {
call.set_effect(recovered.effect);
} else {
call.set_transition_length_if_supported(0);
}
call.perform();
}
}
void disco_apply() {
this->output_->write_state(this);
this->next_write_ = false;
}
light::LightCall make_disco_call(bool save_and_publish) {
auto call = this->make_call();
call.set_save(save_and_publish);
call.set_publish(save_and_publish);
return call;
}
}; };
} // namespace bslamp2 } // namespace bslamp2


Loading…
Cancel
Save