Browse Source

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.

pull/39/head
Maurice Makaay 3 years ago
parent
commit
ffbcfff0a3
5 changed files with 116 additions and 53 deletions
  1. +17
    -0
      components/xiaomi_bslamp2/__init__.py
  2. +33
    -37
      components/xiaomi_bslamp2/front_panel_hal.h
  3. +44
    -15
      components/xiaomi_bslamp2/output/__init__.py
  4. +14
    -1
      components/xiaomi_bslamp2/output/automation.h
  5. +8
    -0
      components/xiaomi_bslamp2/output/output.h

+ 17
- 0
components/xiaomi_bslamp2/__init__.py View File

@ -34,6 +34,23 @@ bslamp2_ns = xiaomi_ns.namespace("bslamp2")
LightHAL = bslamp2_ns.class_("LightHAL", cg.Component) LightHAL = bslamp2_ns.class_("LightHAL", cg.Component)
FrontPanelHAL = bslamp2_ns.class_("FrontPanelHAL", cg.Component, I2CDevice) 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({ CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({
# RGBWW Light # RGBWW Light
cv.GenerateID(CONF_LIGHT_HAL_ID): cv.declare_id(LightHAL), cv.GenerateID(CONF_LIGHT_HAL_ID): cv.declare_id(LightHAL),


+ 33
- 37
components/xiaomi_bslamp2/front_panel_hal.h View File

@ -5,6 +5,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/esphal.h" #include "esphome/core/esphal.h"
#include <array> #include <array>
#include <cmath>
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
@ -17,7 +18,10 @@ using EVENT = uint16_t;
// clang-format off // 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_NONE = 0,
LED_POWER = 1 << 14, LED_POWER = 1 << 14,
LED_COLOR = 1 << 12, LED_COLOR = 1 << 12,
@ -33,20 +37,6 @@ enum FrontPanelLeds {
LED_10 = 1, 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. // This I2C command is used during front panel event handling.
static const MSG READY_FOR_EV = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; 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. * The input value is a bitwise OR-ed set of LED constants.
*/ */
void turn_on_leds(uint16_t leds) { 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. * The input value is a bitwise OR-ed set of LED constants.
*/ */
void turn_off_leds(uint16_t leds) { 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. * LEDs that must be turned on. All other LEDs are turned off.
*/ */
void set_leds(uint16_t leds) { 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. * Level 0.0 means: turn off the front panel illumination.
* The other levels are translated to one of the available levels, * The other levels are translated to one of the available levels,
* represented by the level indicator (i.e. the illumination of the * 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) { void set_light_level(float level) {
const LED base = LED_POWER | LED_COLOR | LED_1;
if (level == 0.0f) if (level == 0.0f)
set_leds(LED_LEVEL_0);
set_leds(LED_NONE);
else if (level < 0.15) else if (level < 0.15)
set_leds(LED_LEVEL_1);
set_leds(base);
else if (level < 0.25) else if (level < 0.25)
set_leds(LED_LEVEL_2);
set_leds(base|LED_2);
else if (level < 0.35) else if (level < 0.35)
set_leds(LED_LEVEL_3);
set_leds(base|LED_2|LED_3);
else if (level < 0.45) else if (level < 0.45)
set_leds(LED_LEVEL_4);
set_leds(base|LED_2|LED_3|LED_4);
else if (level < 0.55) 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) 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) 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) 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) 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 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: protected:
@ -302,13 +304,7 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice {
MSG led_msg_ = {0x02, 0x03, 0x00, 0x00, 0x64, 0x00, 0x00}; MSG led_msg_ = {0x02, 0x03, 0x00, 0x00, 0x64, 0x00, 0x00};
uint16_t led_state_ = 0; 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;
}; };
/** /**


+ 44
- 15
components/xiaomi_bslamp2/output/__init__.py View File

@ -5,7 +5,8 @@ from esphome.const import CONF_ID
from esphome import automation from esphome import automation
from .. import ( from .. import (
bslamp2_ns, CODEOWNERS, 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"] 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]) front_panel_hal_var = yield cg.get_variable(config[CONF_FRONT_PANEL_HAL_ID])
cg.add(var.set_parent(front_panel_hal_var)) cg.add(var.set_parent(front_panel_hal_var))
def maybe_simple_leds_value(schema): def maybe_simple_leds_value(schema):
def validator(value): def validator(value):
if isinstance(value, dict): if isinstance(value, dict):
@ -37,19 +37,48 @@ def maybe_simple_leds_value(schema):
return schema({ "leds": value }) return schema({ "leds": value })
return validator 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): async def set_leds_to_code(config, action_id, template_arg, args):
output_var = await cg.get_variable(config[CONF_ID]) 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

+ 14
- 1
components/xiaomi_bslamp2/output/automation.h View File

@ -2,6 +2,7 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "../front_panel_hal.h"
#include "output.h" #include "output.h"
#include <cmath> #include <cmath>
@ -13,11 +14,23 @@ template<typename... Ts> class SetLEDsAction : public Action<Ts...> {
public: public:
explicit SetLEDsAction(XiaomiBslamp2FrontPanelOutput *parent) : parent_(parent) {} explicit SetLEDsAction(XiaomiBslamp2FrontPanelOutput *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(int, mode)
TEMPLATABLE_VALUE(uint16_t, leds) TEMPLATABLE_VALUE(uint16_t, leds)
void play(Ts... x) override { void play(Ts... x) override {
uint16_t mode = this->mode_.value(x...);
uint16_t value = this->leds_.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: protected:


+ 8
- 0
components/xiaomi_bslamp2/output/output.h View File

@ -27,6 +27,14 @@ class XiaomiBslamp2FrontPanelOutput : public output::FloatOutput, public Compone
front_panel_->set_leds(leds); 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: protected:
FrontPanelHAL *front_panel_; FrontPanelHAL *front_panel_;
}; };


Loading…
Cancel
Save