diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c7644..962d8fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.0] -**Note**: This release requires ESPHome v1.19.0 or newer. +**Note**: This release requires ESPHome v1.20.0 or newer. ### Added - It is now possible to address the LEDs in the front panel of the device individually. @@ -14,7 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 used by the original firmware to represent the lamp's current brightness setting. The `output` component for the lamp was updated to provide access to the individual LEDs. Check out the [documentation guide](https://github.com/mmakaay/esphome-xiaomi_bslamp2/blob/main/doc/configuration.md) - for details on how to control the individual LEDs. + for details on how to control these. + Thanks to @Stewie3112 for the feature request that triggered this development! +- Implemented support for visual feedback during the OTA update process in the + `example.yaml` file: the light becomes blue during flahsing, the brightness slider + represents the progress, on failure the light flashes red and on success the + light flashes green. ### Changed - Made it possible to use lambdas with the `preset.activate` automation. This makes it diff --git a/components/xiaomi_bslamp2/front_panel_hal.h b/components/xiaomi_bslamp2/front_panel_hal.h index fe8662a..e07ad36 100644 --- a/components/xiaomi_bslamp2/front_panel_hal.h +++ b/components/xiaomi_bslamp2/front_panel_hal.h @@ -22,20 +22,21 @@ using EVENT = uint16_t; // LED_1 is the slider LED closest to the power button. // LED_10 is the one closest to the color button. enum FrontPanelLEDs { - LED_ALL = 16384 + 4096 + 1023, - LED_POWER = 16384, - LED_COLOR = 4096, - LED_1 = 512, - LED_2 = 256, - LED_3 = 128, - LED_4 = 64, - LED_5 = 32, - LED_6 = 16, - LED_7 = 8, - LED_8 = 4, - LED_9 = 2, - LED_10 = 1, - LED_NONE = 0, + LED_ALL = 16384 + 4096 + 1023, + LED_ALL_SLIDER = 512 + 256 + 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1, + LED_POWER = 16384, + LED_COLOR = 4096, + LED_1 = 512, + LED_2 = 256, + LED_3 = 128, + LED_4 = 64, + LED_5 = 32, + LED_6 = 16, + LED_7 = 8, + LED_8 = 4, + LED_9 = 2, + LED_10 = 1, + LED_NONE = 0, }; // This I2C command is used during front panel event handling. @@ -272,20 +273,18 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { } /** - * Sets the front panel illumination to the provided level (0.0 - 1.0). + * Sets the front panel slider illumination to the provided level, + * based on a float input (0.0 - 1.0). * * This implements the behavior of the original firmware for representing * the lamp's brightness. * - * 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.) The power and color button are also turned on. + * Level 0.0 means: turn off the slider illumination. + * The other levels are translated to one of the available levels. */ - void set_light_level(float level) { - turn_off_leds(LED_ALL); + void set_slider_level(float level) { + turn_off_leds(LED_ALL_SLIDER); if (level == 0.00f) return; - turn_on_leds(LED_POWER | LED_COLOR | LED_1); if (level >= 0.15f) turn_on_leds(LED_2); if (level >= 0.25f) turn_on_leds(LED_3); if (level >= 0.35f) turn_on_leds(LED_4); diff --git a/components/xiaomi_bslamp2/output/__init__.py b/components/xiaomi_bslamp2/output/__init__.py index ffa5e89..0e13e9e 100644 --- a/components/xiaomi_bslamp2/output/__init__.py +++ b/components/xiaomi_bslamp2/output/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import output -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_LEVEL from esphome import automation from .. import ( bslamp2_ns, CODEOWNERS, @@ -14,6 +14,7 @@ AUTO_LOAD = ["xiaomi_bslamp2"] XiaomiBslamp2FrontPanelOutput = bslamp2_ns.class_( "XiaomiBslamp2FrontPanelOutput", output.FloatOutput, cg.Component) SetLEDsAction = bslamp2_ns.class_("SetLEDsAction", automation.Action) +SetLevelAction = bslamp2_ns.class_("SetLevelAction", automation.Action) UpdateLEDsAction = bslamp2_ns.class_("UpdateLEDsAction", automation.Action) CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( @@ -31,6 +32,13 @@ 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_level_value(schema): + def validator(value): + if isinstance(value, dict): + return schema(value) + return schema({ "level": value }) + return validator + def maybe_simple_leds_value(schema): def validator(value): if isinstance(value, dict): @@ -42,13 +50,30 @@ FRONT_PANEL_SCHEMA = cv.Schema({ cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2FrontPanelOutput), }) +FRONT_PANEL_LEVEL_SCHEMA = cv.Schema( + maybe_simple_level_value(FRONT_PANEL_SCHEMA.extend( + { + cv.Required(CONF_LEVEL): cv.templatable(cv.percentage), + } + )) +) + 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)), - })) + maybe_simple_leds_value(FRONT_PANEL_SCHEMA.extend( + { + cv.Required(CONF_LEDS): cv.ensure_list(cv.enum(FRONT_PANEL_LED_OPTIONS, upper=True)), + } + )) ) +@automation.register_action("front_panel.set_level", SetLevelAction, FRONT_PANEL_LEVEL_SCHEMA) +async def set_level_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) + template_ = await cg.templatable(config[CONF_LEVEL], args, float) + cg.add(action_var.set_level(template_)) + return action_var + @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]) diff --git a/components/xiaomi_bslamp2/output/automation.h b/components/xiaomi_bslamp2/output/automation.h index b826ad7..37c69fe 100644 --- a/components/xiaomi_bslamp2/output/automation.h +++ b/components/xiaomi_bslamp2/output/automation.h @@ -37,6 +37,21 @@ template class SetLEDsAction : public Action { XiaomiBslamp2FrontPanelOutput *parent_; }; +template class SetLevelAction : public Action { + public: + explicit SetLevelAction(XiaomiBslamp2FrontPanelOutput *parent) : parent_(parent) {} + + TEMPLATABLE_VALUE(float, level) + + void play(Ts... x) override { + parent_->set_level(this->level_.value(x...)); + parent_->update_leds(); + } + + protected: + XiaomiBslamp2FrontPanelOutput *parent_; +}; + template class UpdateLEDsAction : public Action { public: explicit UpdateLEDsAction(XiaomiBslamp2FrontPanelOutput *parent) : parent_(parent) {} diff --git a/components/xiaomi_bslamp2/output/output.h b/components/xiaomi_bslamp2/output/output.h index 58c8206..e51f0b1 100644 --- a/components/xiaomi_bslamp2/output/output.h +++ b/components/xiaomi_bslamp2/output/output.h @@ -20,7 +20,16 @@ class XiaomiBslamp2FrontPanelOutput : public output::FloatOutput, public Compone } void write_state(float level) { - front_panel_->set_light_level(level); + if (level > 0) { + turn_on_leds(LED_POWER | LED_COLOR); + set_level(level); + } else { + turn_off_leds(LED_ALL); + } + } + + void set_level(float level) { + front_panel_->set_slider_level(level); } void set_leds(uint16_t leds) { diff --git a/doc/configuration.md b/doc/configuration.md index 3404c6e..858d9f6 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -435,6 +435,17 @@ The LEDs to affect are specified in the same wat as above for `front_panel.set_l This action turns off the provided LEDs, and leaves the rest of the LEDs as-is. The LEDs to affect are specified in the same wat as above for `front_panel.set_leds`. +#### `front_panel.set_level` Action + +This action works like the `output.set_level` action, but it only updates the +LEDs of the slider. The LEDs for the power and color button are left as-is. + +```yaml + on_...: + then: + - front_panel.set_leds: 0.5 +``` + #### `front_panel.update_leds` Action The previous actions only modify the required state for the front panel LEDs. diff --git a/example.yaml b/example.yaml index 940a59f..d5769ac 100644 --- a/example.yaml +++ b/example.yaml @@ -58,6 +58,46 @@ api: ota: password: "Password-For-Flashing-This-Device-Over-The-Air" + # These OTA triggers are used to provide some visual feedback during the OTA + # flashing process. The light is turned blue when the upgrade starts, the + # brightness indicator will represent the update progress (fills up from 0% + # to 100%), the light will flash red when the upgrade fails or green when the + # upgrade succeeds. + # You can safely remove these if you don't want the visual feedback. + on_begin: + then: + - light.disco_on: + id: ${id_light} + red: 0% + green: 0% + blue: 100% + brightness: 2% + transition_length: 0s + on_progress: + then: + - front_panel.set_level: !lambda return (x / 100.0f); + - front_panel.update_leds: + on_end: + then: + - light.disco_on: + id: ${id_light} + red: 0% + green: 100% + blue: 0% + brightness: 2% + transition_length: 0s + on_error: + then: + - light.disco_on: + id: ${id_light} + red: 100% + green: 0% + blue: 0% + brightness: 2% + - delay: 1s + - light.disco_off: + id: ${id_light} + # The log level can be raised when needed for debugging the firmware. For # production, a low log level is recommended. Mainly because high volume log # output might interfere with the API/WiFi connection stability. So when