From 9621cef4077c6b36d288f03bbdbe75a7bfb3d071 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 16 Apr 2021 00:59:37 +0200 Subject: [PATCH] Implemented a yaml-configurable store for color presets. --- common.h | 3 +- doc/FLASHING.md | 8 ++--- doc/example.yaml | 12 +++++++ light/__init__.py | 72 +++++++++++++++++++++++++++++++++++---- light/color_night_light.h | 1 + light/color_off.h | 4 +-- light/color_rgb_light.h | 1 + light/color_white_light.h | 1 + light/gpio_outputs.h | 9 ++--- light/light_modes.h | 15 ++++++++ light/light_output.h | 20 ----------- light/light_state.h | 31 +++++++++++++++++ light/presets.h | 70 +++++++++++++++++++++++++++++++++++++ 13 files changed, 204 insertions(+), 43 deletions(-) create mode 100644 light/light_modes.h create mode 100644 light/light_state.h create mode 100644 light/presets.h diff --git a/common.h b/common.h index c46b185..44ebf17 100644 --- a/common.h +++ b/common.h @@ -4,8 +4,9 @@ namespace esphome { namespace xiaomi { namespace bslamp2 { +// Used for logging purposes. static const char *TAG = "xiaomi_bslamp2"; - + } // namespace bslamp2 } // namespace xiaomi } // namespace esphome diff --git a/doc/FLASHING.md b/doc/FLASHING.md index 02d1015..3f7c9d9 100644 --- a/doc/FLASHING.md +++ b/doc/FLASHING.md @@ -138,9 +138,9 @@ reconnect the power to boot into the restored firmware. ## Flash new firmware -Setup an ESPHome Project, see [README.md](../README.md) -Compile the firmware for the device and download the `firmware.bin` file -to the device to which the serial adapter is connected. +Setup an ESPHome Project (see [README.md](../README.md)),compile the firmware +for the device and download the `firmware.bin` file to the device to which +the serial adapter is connected. You can flash the device using esphome or esptool. I normally use the [esphome-flasher](https://github.com/esphome/esphome-flasher) @@ -187,5 +187,3 @@ https://github.com/arendst/Tasmota/tree/firmware/firmware/tasmota32/ESP32_needed (remember that the [esphome-flasher](https://github.com/esphome/esphome-flasher) will give you a bit less of a hard-core experience during flashing) - - diff --git a/doc/example.yaml b/doc/example.yaml index 5fad226..25956e4 100644 --- a/doc/example.yaml +++ b/doc/example.yaml @@ -94,6 +94,18 @@ light: name: "Fast Random" transition_length: 3s update_interval: 3s + presets: + - rgb: + - red_bright: { red: 1 } + - green: { green: 1 } + - blue_dimmed: { blue: 1 } + - yellow: { red: 1, green: 1 } + - something: { red: 0.2, green: 0.4, blue: 1 } + - white: + - warm: { color_temperature: 588 } + - luke: { color_temperature: 400 } + - chilly: { color_temperature: 275 } + - cold: { color_temperature: 153 } # This text sensor propagates the currently active light mode. # The possible light modes are: "off", "rgb", "white" and "night". diff --git a/light/__init__.py b/light/__init__.py index e4525b4..6581085 100644 --- a/light/__init__.py +++ b/light/__init__.py @@ -2,9 +2,10 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light from esphome import automation +from esphome.core import coroutine from esphome.const import ( - CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, - CONF_OUTPUT_ID, CONF_TRIGGER_ID, CONF_ID + CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_COLOR_TEMPERATURE, + CONF_OUTPUT_ID, CONF_TRIGGER_ID, CONF_ID, ) from .. import bslamp2_ns, CODEOWNERS, CONF_LIGHT_HAL_ID, LightHAL @@ -13,11 +14,38 @@ AUTO_LOAD = ["xiaomi_bslamp2"] CONF_MASTER1 = "master1" CONF_MASTER2 = "master2" CONF_ON_BRIGHTNESS = "on_brightness" +CONF_PRESETS_ID = "presets_id" +CONF_PRESETS = "presets" +# Classes. XiaomiBslamp2LightState = bslamp2_ns.class_("XiaomiBslamp2LightState", light.LightState) XiaomiBslamp2LightOutput = bslamp2_ns.class_("XiaomiBslamp2LightOutput", light.LightOutput) +PresetsContainer = bslamp2_ns.class_("PresetsContainer", cg.Component) + +# Trigger. BrightnessTrigger = bslamp2_ns.class_("BrightnessTrigger", automation.Trigger.template()) +# Actions. +#ActivatePresetGroup = bslamp2_ns.class_("ActivatePresetGroup", automation.Action) +#ActivatePreset = bslamp2_ns.class_("ActivatePreset", automation.Action) +#NextPresetGroup = bslamp2_ns.class_("NextPresetAction", automation.Action) +#NextPreset = bslamp2_ns.class_("NextPresetAction", automation.Action) + +PRESETS_SCHEMA = cv.Schema({ + str: cv.Schema({ + str: cv.Any( + cv.Schema({ + cv.Optional(CONF_RED, default=0): cv.percentage, + cv.Optional(CONF_GREEN, default=0): cv.percentage, + cv.Optional(CONF_BLUE, default=0): cv.percentage, + }), + cv.Schema({ + cv.Required(CONF_COLOR_TEMPERATURE): cv.int_range(min=153, max=588), + }), + ) + }) +}) + CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(XiaomiBslamp2LightState), @@ -28,16 +56,46 @@ CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BrightnessTrigger), } ), + cv.GenerateID(CONF_PRESETS_ID): cv.declare_id(PresetsContainer), + cv.Optional(CONF_PRESETS): PRESETS_SCHEMA, } ) -def to_code(config): - var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) - yield light.register_light(var, config) +#automation.register_action("preset.activate_group", ActivatePresetGroup, cv.string) +#def activate_group_to_code(config, action_id, template_arg, args): +# presets_var = yield cg.get_variable(config[CONF_PRESETS_ID]) +# cg.add(presets_var.activate_group(...)); +@coroutine +def light_output_to_code(config): + light_output_var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + yield light.register_light(light_output_var, config) light_hal_var = yield cg.get_variable(config[CONF_LIGHT_HAL_ID]) - cg.add(var.set_parent(light_hal_var)) + cg.add(light_output_var.set_parent(light_hal_var)) +@coroutine +def on_brightness_to_code(config): + light_output_var = yield cg.get_variable(config[CONF_OUTPUT_ID]) for conf in config.get(CONF_ON_BRIGHTNESS, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_output_var) yield automation.build_automation(trigger, [(float, "x")], conf) + +@coroutine +def presets_to_code(config): + light_state_var = yield cg.get_variable(config[CONF_ID]) + presets_var = cg.new_Pvariable(config[CONF_PRESETS_ID], light_state_var) + yield cg.register_component(presets_var, config) + + for preset_group, presets in config.get(CONF_PRESETS, {}).items(): + for name, preset in presets.items(): + if CONF_COLOR_TEMPERATURE in preset: + cg.add(presets_var.add( + preset_group, name, preset[CONF_COLOR_TEMPERATURE])) + else: + cg.add(presets_var.add( + preset_group, name, preset[CONF_RED], preset[CONF_GREEN], preset[CONF_BLUE])) + +def to_code(config): + yield light_output_to_code(config) + yield on_brightness_to_code(config) + yield presets_to_code(config) diff --git a/light/color_night_light.h b/light/color_night_light.h index 310f4c9..1c48d7a 100644 --- a/light/color_night_light.h +++ b/light/color_night_light.h @@ -1,6 +1,7 @@ #pragma once #include "../common.h" +#include "light_modes.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/color_off.h b/light/color_off.h index 6e684dd..8ffa236 100644 --- a/light/color_off.h +++ b/light/color_off.h @@ -1,9 +1,7 @@ #pragma once -#include -#include - #include "../common.h" +#include "light_modes.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/color_rgb_light.h b/light/color_rgb_light.h index 9eb2a68..7a3a6a9 100644 --- a/light/color_rgb_light.h +++ b/light/color_rgb_light.h @@ -4,6 +4,7 @@ #include #include "../common.h" +#include "light_modes.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/color_white_light.h b/light/color_white_light.h index b41e4ce..5042be7 100644 --- a/light/color_white_light.h +++ b/light/color_white_light.h @@ -4,6 +4,7 @@ #include #include "../common.h" +#include "light_modes.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/gpio_outputs.h b/light/gpio_outputs.h index bb7c995..f8c7877 100644 --- a/light/gpio_outputs.h +++ b/light/gpio_outputs.h @@ -1,16 +1,11 @@ #pragma once +#include "light_modes.h" + namespace esphome { namespace xiaomi { namespace bslamp2 { -// Light modes that can be reported by implementations of GPIOOutputs. -static const std::string LIGHT_MODE_UNKNOWN { "unknown" }; -static const std::string LIGHT_MODE_OFF { "off" }; -static const std::string LIGHT_MODE_RGB { "rgb" }; -static const std::string LIGHT_MODE_WHITE { "white" }; -static const std::string LIGHT_MODE_NIGHT { "night" }; - /** * This abstract class is used for implementing classes that translate * LightColorValues into the required GPIO PWM duty cycle levels to represent diff --git a/light/light_modes.h b/light/light_modes.h new file mode 100644 index 0000000..6df31a1 --- /dev/null +++ b/light/light_modes.h @@ -0,0 +1,15 @@ +#pragma once + +namespace esphome { +namespace xiaomi { +namespace bslamp2 { + +static const std::string LIGHT_MODE_UNKNOWN { "unknown" }; +static const std::string LIGHT_MODE_OFF { "off" }; +static const std::string LIGHT_MODE_RGB { "rgb" }; +static const std::string LIGHT_MODE_WHITE { "white" }; +static const std::string LIGHT_MODE_NIGHT { "night" }; + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/light_output.h b/light/light_output.h index c58c1b6..0c4afd0 100644 --- a/light/light_output.h +++ b/light/light_output.h @@ -108,26 +108,6 @@ protected: transition_handler_ = new ColorTransitionHandler(exposer); } }; - -/** - * This custom LightState class is used to provide access to the protected - * LightTranformer information in the LightState class. - * - * This class is used by the ColorTransitionHandler class to inspect if - * an ongoing light color transition is active in a LightState object. - */ -class XiaomiBslamp2LightState : public light::LightState, public LightStateTransformerInspector -{ -public: - XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { - output->set_transformer_inspector(this); - } - - bool is_active() { return transformer_ != nullptr; } - bool is_transition() { return transformer_->is_transition(); } - light::LightColorValues get_end_values() { return transformer_->get_end_values(); } - float get_progress() { return transformer_->get_progress(); } -}; } // namespace bslamp2 } // namespace xiaomi diff --git a/light/light_state.h b/light/light_state.h new file mode 100644 index 0000000..06d72ca --- /dev/null +++ b/light/light_state.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../common.h" + +namespace esphome { +namespace xiaomi { +namespace bslamp2 { + +/** + * This custom LightState class is used to provide access to the protected + * LightTranformer information in the LightState class. + * + * This class is used by the ColorTransitionHandler class to inspect if + * an ongoing light color transition is active in the LightState object. + */ +class XiaomiBslamp2LightState : public light::LightState, public LightStateTransformerInspector +{ +public: + XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { + output->set_transformer_inspector(this); + } + + bool is_active() { return transformer_ != nullptr; } + bool is_transition() { return transformer_->is_transition(); } + light::LightColorValues get_end_values() { return transformer_->get_end_values(); } + float get_progress() { return transformer_->get_progress(); } +}; + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/presets.h b/light/presets.h new file mode 100644 index 0000000..c597bdf --- /dev/null +++ b/light/presets.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include "../common.h" + +namespace esphome { +namespace xiaomi { +namespace bslamp2 { + +using Order = std::vector; +using Presets = std::map; +using PresetOrder = std::map; +using PresetGroups = std::map; +using PresetGroupOrder = Order; + +class PresetsContainer : public Component { +public: + explicit PresetsContainer(light::LightState *light) : _light(light) { } + + void dump_config() { + if (map_.empty()) { + return; + } + ESP_LOGCONFIG(TAG, "Light Presets:"); + for (auto group : group_order_) { + ESP_LOGCONFIG(TAG, " Preset group: %s", group.c_str()); + for (auto name : preset_order_[group]) { + ESP_LOGCONFIG(TAG, " Preset: %s", name.c_str()); + } + } + } + + void add(std::string group, std::string name, float red, float green, float blue) { + auto call = make_preset_slot_(group, name); + call->set_red(red); + call->set_green(green); + call->set_blue(blue); + } + + void add(std::string group, std::string name, float color_temperature) { + auto call = make_preset_slot_(group, name); + call->set_color_temperature(color_temperature); + } + +protected: + light::LightState *_light; + PresetGroups map_; + PresetGroupOrder group_order_; + PresetOrder preset_order_; + + light::LightCall *make_preset_slot_(std::string group, std::string name) { + // Check if the group already exists. If not, then create it. + if (map_.find(group) == map_.end()) { + map_[group] = Presets(); + group_order_.push_back(group); + preset_order_[group] = Order(); + } + if (map_[group].find(name) == map_[group].end()) { + map_[group][name] = new light::LightCall(_light); + preset_order_[group].push_back(name); + } + + return map_[group][name]; + } +}; + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome