From ffbcfff0a330b7a50c5558b6c9ffa74d29514c34 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 7 Jul 2021 02:00:59 +0200 Subject: [PATCH] Working on a good HAL layer to update the front panel LEDs individually. I got a flicker-free version going now, so next step is to provide some good interfaces for the YAML config. --- components/xiaomi_bslamp2/__init__.py | 17 +++++ components/xiaomi_bslamp2/front_panel_hal.h | 70 +++++++++---------- components/xiaomi_bslamp2/output/__init__.py | 59 ++++++++++++---- components/xiaomi_bslamp2/output/automation.h | 15 +++- components/xiaomi_bslamp2/output/output.h | 8 +++ 5 files changed, 116 insertions(+), 53 deletions(-) diff --git a/components/xiaomi_bslamp2/__init__.py b/components/xiaomi_bslamp2/__init__.py index ebc0dd5..9aa7eee 100644 --- a/components/xiaomi_bslamp2/__init__.py +++ b/components/xiaomi_bslamp2/__init__.py @@ -34,6 +34,23 @@ bslamp2_ns = xiaomi_ns.namespace("bslamp2") LightHAL = bslamp2_ns.class_("LightHAL", cg.Component) FrontPanelHAL = bslamp2_ns.class_("FrontPanelHAL", cg.Component, I2CDevice) +FrontPanelLEDs = bslamp2_ns.enum("FrontPanelLEDs") +FRONT_PANEL_LED_OPTIONS = { + "NONE": FrontPanelLEDs.LED_NONE, + "POWER": FrontPanelLEDs.LED_POWER, + "COLOR": FrontPanelLEDs.LED_COLOR, + "1": FrontPanelLEDs.LED_1, + "2": FrontPanelLEDs.LED_2, + "3": FrontPanelLEDs.LED_3, + "4": FrontPanelLEDs.LED_4, + "5": FrontPanelLEDs.LED_5, + "6": FrontPanelLEDs.LED_6, + "7": FrontPanelLEDs.LED_7, + "8": FrontPanelLEDs.LED_8, + "9": FrontPanelLEDs.LED_9, + "10": FrontPanelLEDs.LED_10, +} + CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({ # RGBWW Light cv.GenerateID(CONF_LIGHT_HAL_ID): cv.declare_id(LightHAL), diff --git a/components/xiaomi_bslamp2/front_panel_hal.h b/components/xiaomi_bslamp2/front_panel_hal.h index 26942dc..02d81f6 100644 --- a/components/xiaomi_bslamp2/front_panel_hal.h +++ b/components/xiaomi_bslamp2/front_panel_hal.h @@ -5,6 +5,7 @@ #include "esphome/core/component.h" #include "esphome/core/esphal.h" #include +#include namespace esphome { namespace xiaomi { @@ -17,7 +18,10 @@ using EVENT = uint16_t; // clang-format off -enum FrontPanelLeds { +// Bit flags that are used for indicating the LEDs in the front panel. +// LED_1 is the slider LED closest to the power button. +// LED_10 is the one closest to the color button. +enum FrontPanelLEDs { LED_NONE = 0, LED_POWER = 1 << 14, LED_COLOR = 1 << 12, @@ -33,20 +37,6 @@ enum FrontPanelLeds { LED_10 = 1, }; -// Combinations of LEDs that are use by the original firmware to -// indicate the current brightness setting of the lamp.. -static const LED LED_LEVEL_0 = LED_NONE; -static const LED LED_LEVEL_1 = LED_POWER | LED_COLOR | LED_1; -static const LED LED_LEVEL_2 = LED_LEVEL_1 | LED_2; -static const LED LED_LEVEL_3 = LED_LEVEL_2 | LED_3; -static const LED LED_LEVEL_4 = LED_LEVEL_3 | LED_4; -static const LED LED_LEVEL_5 = LED_LEVEL_4 | LED_5; -static const LED LED_LEVEL_6 = LED_LEVEL_5 | LED_6; -static const LED LED_LEVEL_7 = LED_LEVEL_6 | LED_7; -static const LED LED_LEVEL_8 = LED_LEVEL_7 | LED_8; -static const LED LED_LEVEL_9 = LED_LEVEL_8 | LED_9; -static const LED LED_LEVEL_10 = LED_LEVEL_9 | LED_10; - // This I2C command is used during front panel event handling. static const MSG READY_FOR_EV = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; @@ -230,6 +220,10 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { } } } + + if (led_state_ != last_led_state_) { + update_leds(); + } } /** @@ -237,7 +231,7 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { * The input value is a bitwise OR-ed set of LED constants. */ void turn_on_leds(uint16_t leds) { - set_leds_(led_state_ | leds); + led_state_ = led_state_ | 0b0000110000000000 | leds; } /** @@ -245,7 +239,7 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { * The input value is a bitwise OR-ed set of LED constants. */ void turn_off_leds(uint16_t leds) { - set_leds_(led_state_ & ~leds); + led_state_ = (led_state_ | 0b0000110000000000) & ~leds; } /** @@ -254,7 +248,14 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { * LEDs that must be turned on. All other LEDs are turned off. */ void set_leds(uint16_t leds) { - set_leds_(leds); + led_state_ = 0b0000110000000000 | leds; + } + + void update_leds() { + led_msg_[2] = led_state_ >> 8; + led_msg_[3] = led_state_ & 0xff; + write_bytes_raw(led_msg_, MSG_LEN); + last_led_state_ = led_state_; } /** @@ -266,31 +267,32 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { * Level 0.0 means: turn off the front panel illumination. * The other levels are translated to one of the available levels, * represented by the level indicator (i.e. the illumination of the - * slider bar.) + * slider bar.) The power and color button are also turned on. */ void set_light_level(float level) { + const LED base = LED_POWER | LED_COLOR | LED_1; if (level == 0.0f) - set_leds(LED_LEVEL_0); + set_leds(LED_NONE); else if (level < 0.15) - set_leds(LED_LEVEL_1); + set_leds(base); else if (level < 0.25) - set_leds(LED_LEVEL_2); + set_leds(base|LED_2); else if (level < 0.35) - set_leds(LED_LEVEL_3); + set_leds(base|LED_2|LED_3); else if (level < 0.45) - set_leds(LED_LEVEL_4); + set_leds(base|LED_2|LED_3|LED_4); else if (level < 0.55) - set_leds(LED_LEVEL_5); + set_leds(base|LED_2|LED_3|LED_4|LED_5); else if (level < 0.65) - set_leds(LED_LEVEL_6); + set_leds(base|LED_2|LED_3|LED_4|LED_5|LED_6); else if (level < 0.75) - set_leds(LED_LEVEL_7); + set_leds(base|LED_2|LED_3|LED_4|LED_5|LED_6|LED_7); else if (level < 0.85) - set_leds(LED_LEVEL_8); + set_leds(base|LED_2|LED_3|LED_4|LED_5|LED_6|LED_7|LED_8); else if (level < 0.95) - set_leds(LED_LEVEL_9); + set_leds(base|LED_2|LED_3|LED_4|LED_5|LED_6|LED_7|LED_8|LED_9); else - set_leds(LED_LEVEL_10); + set_leds(base|LED_2|LED_3|LED_4|LED_5|LED_6|LED_7|LED_8|LED_9|LED_10); } protected: @@ -302,13 +304,7 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { MSG led_msg_ = {0x02, 0x03, 0x00, 0x00, 0x64, 0x00, 0x00}; uint16_t led_state_ = 0; - - void set_leds_(uint16_t leds) { - led_state_ = 0b0000110000000000 | leds; - led_msg_[2] = led_state_ >> 8; - led_msg_[3] = led_state_ & 0xff; - write_bytes_raw(led_msg_, MSG_LEN); - } + uint16_t last_led_state_ = 0; }; /** diff --git a/components/xiaomi_bslamp2/output/__init__.py b/components/xiaomi_bslamp2/output/__init__.py index ee69fc8..04eafe4 100644 --- a/components/xiaomi_bslamp2/output/__init__.py +++ b/components/xiaomi_bslamp2/output/__init__.py @@ -5,7 +5,8 @@ from esphome.const import CONF_ID from esphome import automation from .. import ( bslamp2_ns, CODEOWNERS, - CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL, CONF_LEDS + CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL, FRONT_PANEL_LED_OPTIONS, + CONF_LEDS ) AUTO_LOAD = ["xiaomi_bslamp2"] @@ -29,7 +30,6 @@ def to_code(config): front_panel_hal_var = yield cg.get_variable(config[CONF_FRONT_PANEL_HAL_ID]) cg.add(var.set_parent(front_panel_hal_var)) - def maybe_simple_leds_value(schema): def validator(value): if isinstance(value, dict): @@ -37,19 +37,48 @@ def maybe_simple_leds_value(schema): return schema({ "leds": value }) return validator -@automation.register_action( - "output.set_leds", - SetLEDsAction, - cv.Schema( - maybe_simple_leds_value(cv.Schema({ - cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2FrontPanelOutput), - cv.Required(CONF_LEDS): cv.templatable(cv.uint16_t), - })) - ) +FRONT_PANEL_LED_SCHEMA = cv.Schema( + maybe_simple_leds_value(cv.Schema({ + cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2FrontPanelOutput), + cv.Required(CONF_LEDS): cv.ensure_list(cv.enum(FRONT_PANEL_LED_OPTIONS, upper=True)), + })) ) + +@automation.register_action("front_panel.set_leds", SetLEDsAction, FRONT_PANEL_LED_SCHEMA) async def set_leds_to_code(config, action_id, template_arg, args): output_var = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, output_var) - template_ = await cg.templatable(config[CONF_LEDS], args, cg.uint16) - cg.add(var.set_leds(template_)) - return var + action_var = cg.new_Pvariable(action_id, template_arg, output_var) + bits = ( + [FRONT_PANEL_LED_OPTIONS['NONE']] + + [FRONT_PANEL_LED_OPTIONS[led] for led in config[CONF_LEDS]] + ) + value = cg.RawExpression("|".join(map(str, bits))) + cg.add(action_var.set_mode(2)) + cg.add(action_var.set_leds(value)) + return action_var + +@automation.register_action("front_panel.turn_on_leds", SetLEDsAction, FRONT_PANEL_LED_SCHEMA) +async def turn_on_leds_to_code(config, action_id, template_arg, args): + output_var = await cg.get_variable(config[CONF_ID]) + action_var = cg.new_Pvariable(action_id, template_arg, output_var) + bits = ( + [FRONT_PANEL_LED_OPTIONS['NONE']] + + [FRONT_PANEL_LED_OPTIONS[led] for led in config[CONF_LEDS]] + ) + value = cg.RawExpression("|".join(map(str, bits))) + cg.add(action_var.set_mode(1)) + cg.add(action_var.set_leds(value)) + return action_var + +@automation.register_action("front_panel.turn_off_leds", SetLEDsAction, FRONT_PANEL_LED_SCHEMA) +async def turn_off_leds_to_code(config, action_id, template_arg, args): + output_var = await cg.get_variable(config[CONF_ID]) + action_var = cg.new_Pvariable(action_id, template_arg, output_var) + bits = ( + [FRONT_PANEL_LED_OPTIONS['NONE']] + + [FRONT_PANEL_LED_OPTIONS[led] for led in config[CONF_LEDS]] + ) + value = cg.RawExpression("|".join(map(str, bits))) + cg.add(action_var.set_mode(0)) + cg.add(action_var.set_leds(value)) + return action_var diff --git a/components/xiaomi_bslamp2/output/automation.h b/components/xiaomi_bslamp2/output/automation.h index c68ad6e..7913691 100644 --- a/components/xiaomi_bslamp2/output/automation.h +++ b/components/xiaomi_bslamp2/output/automation.h @@ -2,6 +2,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "../front_panel_hal.h" #include "output.h" #include @@ -13,11 +14,23 @@ template class SetLEDsAction : public Action { public: explicit SetLEDsAction(XiaomiBslamp2FrontPanelOutput *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(int, mode) TEMPLATABLE_VALUE(uint16_t, leds) void play(Ts... x) override { + uint16_t mode = this->mode_.value(x...); uint16_t value = this->leds_.value(x...); - parent_->set_leds(value); + switch (mode) { + case 0: + parent_->turn_off_leds(value); + break; + case 1: + parent_->turn_on_leds(value); + break; + case 2: + parent_->set_leds(value); + break; + } } protected: diff --git a/components/xiaomi_bslamp2/output/output.h b/components/xiaomi_bslamp2/output/output.h index b58d926..10c79d0 100644 --- a/components/xiaomi_bslamp2/output/output.h +++ b/components/xiaomi_bslamp2/output/output.h @@ -27,6 +27,14 @@ class XiaomiBslamp2FrontPanelOutput : public output::FloatOutput, public Compone front_panel_->set_leds(leds); } + void turn_on_leds(uint16_t leds) { + front_panel_->turn_on_leds(leds); + } + + void turn_off_leds(uint16_t leds) { + front_panel_->turn_off_leds(leds); + } + protected: FrontPanelHAL *front_panel_; };