From af5c1a8d49be0fd221b5ed3fa9928d927279f758 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 8 Jul 2021 01:56:56 +0200 Subject: [PATCH] Better front panel HAL that allows controlling the individual LEDs. (#39) * Document the newly found per-LED option of the front panel. * Implement front panel HAL layer support. * Implement actions for the output component to be able to control the LEDs. --- CHANGELOG.md | 8 ++ README.md | 11 +- components/xiaomi_bslamp2/__init__.py | 21 ++- .../xiaomi_bslamp2/binary_sensor/__init__.py | 1 + components/xiaomi_bslamp2/front_panel_hal.h | 132 ++++++++++++------ components/xiaomi_bslamp2/output/__init__.py | 75 +++++++++- components/xiaomi_bslamp2/output/automation.h | 54 +++++++ components/xiaomi_bslamp2/output/output.h | 26 +++- doc/configuration.md | 102 +++++++++++++- doc/technical_details.md | 28 +++- 10 files changed, 401 insertions(+), 57 deletions(-) create mode 100644 components/xiaomi_bslamp2/output/automation.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a28d2d..53c7644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **Note**: This release requires ESPHome v1.19.0 or newer. +### Added +- It is now possible to address the LEDs in the front panel of the device individually. + There are 12 LEDs in total: the power button, the color button and 10 LEDs that are + 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. + ### Changed - Made it possible to use lambdas with the `preset.activate` automation. This makes it possible to link the action to an api service, which exposes the preset functionality diff --git a/README.md b/README.md index 379046a..f32753f 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,16 @@ aspect of the lamp and to integrate the lamp in your Home Assistant setup. light mode is missing in the Home Assistant GUI. * **Since the components of the lamp are exposed as ESPHome components, you don't have to stick with - the lamp's original behavior**. You can hook up the lamp in your home automation as you see fit. + the lamp's original behavior.** You can hook up the lamp in your home automation as you see fit. Use the slider to control the volume of your audio set? Long press the power button to put your house in night mode? Use the illumination behind the slider bar to represent the progress of your sour dough bread bulk fermentation? Go ahead, make it so! :-) - + +* **All LEDs that are used for illumination of the front panel (power button, color button and + 10 LEDs for the brightness slider) can be controlled individually.** This means that you have + 12 LEDs in total to use as you see fit, instead of sticking with the behavior of the original + firmware. + * **Possibilities to extend the device's functionality through hardware mods.** There are [GPIO pins that are not in use](doc/technical_details.md#esp32-pinout). If "tinkerer" is your middle name, you can use those pins to come up with your own hardware hacks to extend the device's @@ -60,7 +65,7 @@ For those who have experience with flashing ESPHome onto devices: * [Why custom firmware?](doc/why_custom_firmware.md) * [Installation guide](doc/installation.md) -* [Configuration_guide](doc/configuration.md) +* [Configuration guide](doc/configuration.md) * [Flashing guide](doc/flashing.md) * [Technical details](doc/technical_details.md) * [Sponsoring](doc/sponsoring.md) diff --git a/components/xiaomi_bslamp2/__init__.py b/components/xiaomi_bslamp2/__init__.py index a804686..fcf4877 100644 --- a/components/xiaomi_bslamp2/__init__.py +++ b/components/xiaomi_bslamp2/__init__.py @@ -13,7 +13,6 @@ from esphome.const import ( CODEOWNERS = ["@mmakaay"] -CONF_HUB_ID = "xiaomi_bslamp2_hub_id" CONF_RED_ID = "red_id" CONF_GREEN_ID = "green_id" CONF_BLUE_ID = "blue_id" @@ -25,8 +24,8 @@ CONF_MASTER2_ID = "master2_id" CONF_FP_I2C_ID = "front_panel_i2c_id" CONF_LIGHT_HAL_ID = "light_hal_id" CONF_FRONT_PANEL_HAL_ID = "front_panel_hal_id" - CONF_ON_BRIGHTNESS = "on_brightness" +CONF_LEDS = "leds" AUTO_LOAD = ["ledc", "output", "i2c"] @@ -35,6 +34,24 @@ 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, + "ALL": FrontPanelLEDs.LED_ALL, + "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/binary_sensor/__init__.py b/components/xiaomi_bslamp2/binary_sensor/__init__.py index 5a7549a..839f13a 100644 --- a/components/xiaomi_bslamp2/binary_sensor/__init__.py +++ b/components/xiaomi_bslamp2/binary_sensor/__init__.py @@ -33,6 +33,7 @@ def validate_binary_sensor(conf): if CONF_PART in conf and CONF_FOR in conf: raise cv.Invalid("Specify only one of [part] or [for]") if CONF_PART in conf and not CONF_FOR in conf: + # Backward compatibility. conf[CONF_FOR] = conf[CONF_PART] if CONF_FOR not in conf: raise cv.Invalid("'for' is a required option for [binary_sensor.xiaomi_bslamp2]") diff --git a/components/xiaomi_bslamp2/front_panel_hal.h b/components/xiaomi_bslamp2/front_panel_hal.h index d707fbc..fe8662a 100644 --- a/components/xiaomi_bslamp2/front_panel_hal.h +++ b/components/xiaomi_bslamp2/front_panel_hal.h @@ -5,32 +5,41 @@ #include "esphome/core/component.h" #include "esphome/core/esphal.h" #include +#include namespace esphome { namespace xiaomi { namespace bslamp2 { static const uint8_t MSG_LEN = 7; -using MSG = uint8_t[7]; +using MSG = uint8_t[MSG_LEN]; +using LED = uint16_t; +using EVENT = uint16_t; // clang-format off -// The commands that are supported by the front panel component. -static const MSG READY_FOR_EV = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; -static const MSG TURN_ON = {0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00}; -static const MSG TURN_OFF = {0x02, 0x03, 0x0C, 0x00, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_1 = {0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_2 = {0x02, 0x03, 0x5F, 0x00, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_3 = {0x02, 0x03, 0x5F, 0x80, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_4 = {0x02, 0x03, 0x5F, 0xC0, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_5 = {0x02, 0x03, 0x5F, 0xE0, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_6 = {0x02, 0x03, 0x5F, 0xF0, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_7 = {0x02, 0x03, 0x5F, 0xF8, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_8 = {0x02, 0x03, 0x5F, 0xFC, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_9 = {0x02, 0x03, 0x5F, 0xFE, 0x64, 0x00, 0x00}; -static const MSG SET_LEVEL_10 = {0x02, 0x03, 0x5F, 0xFF, 0x64, 0x00, 0x00}; +// 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_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, +}; -using EVENT = uint16_t; +// This I2C command is used during front panel event handling. +static const MSG READY_FOR_EV = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; // Bit flags that are used for specifying an event. // Events are registered using the following bit pattern @@ -212,39 +221,80 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { } } } + + if (led_state_ != last_led_state_) { + update_leds(); + } + } + + /** + * Turn on one or more LEDs (leaving the state of the other LEDs intact). + * The input value is a bitwise OR-ed set of LED constants. + * Only after a call to update_leds() (handled by default from the main loop), + * the new state will be activated. + */ + void turn_on_leds(uint16_t leds) { + led_state_ = led_state_ | 0b0000110000000000 | leds; + } + + /** + * Turn off one or more LEDs (leaving the state of the other LEDs intact). + * The input value is a bitwise OR-ed set of LED constants. + * Only after a call to update_leds() (handled by default from the main loop), + * the new state will be activated. + */ + void turn_off_leds(uint16_t leds) { + led_state_ = (led_state_ | 0b0000110000000000) & ~leds; + } + + /** + * Updates the state of the LEDs according to the provided input. + * The input value is a bitwise OR-ed set of LED constants, representing the + * LEDs that must be turned on. All other LEDs are turned off. + * Only after a call to update_leds() (handled by default from the main loop), + * the new state will be activated. + */ + void set_leds(uint16_t leds) { + turn_off_leds(LED_ALL); + turn_on_leds(leds); + } + + /** + * Activate the LEDs according to the currently stored LED state. This method + * will be called automatically by the main loop. You can call this method, + * in case you need to update the LED state right away. + */ + 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_; } /** * Sets the front panel illumination to the provided level (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.) + * slider bar.) The power and color button are also turned on. */ void set_light_level(float level) { - if (level == 0.0f) - write_bytes_raw(TURN_OFF, MSG_LEN); - else if (level < 0.15) - write_bytes_raw(SET_LEVEL_1, MSG_LEN); - else if (level < 0.25) - write_bytes_raw(SET_LEVEL_2, MSG_LEN); - else if (level < 0.35) - write_bytes_raw(SET_LEVEL_3, MSG_LEN); - else if (level < 0.45) - write_bytes_raw(SET_LEVEL_4, MSG_LEN); - else if (level < 0.55) - write_bytes_raw(SET_LEVEL_5, MSG_LEN); - else if (level < 0.65) - write_bytes_raw(SET_LEVEL_6, MSG_LEN); - else if (level < 0.75) - write_bytes_raw(SET_LEVEL_7, MSG_LEN); - else if (level < 0.85) - write_bytes_raw(SET_LEVEL_8, MSG_LEN); - else if (level < 0.95) - write_bytes_raw(SET_LEVEL_9, MSG_LEN); - else - write_bytes_raw(SET_LEVEL_10, MSG_LEN); + turn_off_leds(LED_ALL); + 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); + if (level >= 0.45f) turn_on_leds(LED_5); + if (level >= 0.55f) turn_on_leds(LED_6); + if (level >= 0.65f) turn_on_leds(LED_7); + if (level >= 0.75f) turn_on_leds(LED_8); + if (level >= 0.85f) turn_on_leds(LED_9); + if (level >= 0.95f) turn_on_leds(LED_10); } protected: @@ -253,6 +303,10 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice { volatile int event_id_ = 0; int last_event_id_ = 0; CallbackManager event_callback_{}; + + uint16_t led_state_ = 0; + uint16_t last_led_state_ = 0; + MSG led_msg_ = {0x02, 0x03, 0x00, 0x00, 0x64, 0x00, 0x00}; }; /** diff --git a/components/xiaomi_bslamp2/output/__init__.py b/components/xiaomi_bslamp2/output/__init__.py index 45dd168..ffa5e89 100644 --- a/components/xiaomi_bslamp2/output/__init__.py +++ b/components/xiaomi_bslamp2/output/__init__.py @@ -2,19 +2,23 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import output from esphome.const import CONF_ID +from esphome import automation from .. import ( bslamp2_ns, CODEOWNERS, - CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL + CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL, FRONT_PANEL_LED_OPTIONS, + CONF_LEDS ) AUTO_LOAD = ["xiaomi_bslamp2"] -XiaomiBslamp2FrontPanelLight = bslamp2_ns.class_( - "XiaomiBslamp2FrontPanelLight", output.FloatOutput, cg.Component) +XiaomiBslamp2FrontPanelOutput = bslamp2_ns.class_( + "XiaomiBslamp2FrontPanelOutput", output.FloatOutput, cg.Component) +SetLEDsAction = bslamp2_ns.class_("SetLEDsAction", automation.Action) +UpdateLEDsAction = bslamp2_ns.class_("UpdateLEDsAction", automation.Action) CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(XiaomiBslamp2FrontPanelLight), + cv.GenerateID(): cv.declare_id(XiaomiBslamp2FrontPanelOutput), cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.use_id(FrontPanelHAL), } ).extend(cv.COMPONENT_SCHEMA) @@ -26,3 +30,66 @@ 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): + return schema(value) + return schema({ "leds": value }) + return validator + +FRONT_PANEL_SCHEMA = cv.Schema({ + cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2FrontPanelOutput), +}) + +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]) + 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 + +@automation.register_action("front_panel.update_leds", UpdateLEDsAction, FRONT_PANEL_SCHEMA) +async def update_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) + return action_var diff --git a/components/xiaomi_bslamp2/output/automation.h b/components/xiaomi_bslamp2/output/automation.h new file mode 100644 index 0000000..b826ad7 --- /dev/null +++ b/components/xiaomi_bslamp2/output/automation.h @@ -0,0 +1,54 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "../front_panel_hal.h" +#include "output.h" +#include + +namespace esphome { +namespace xiaomi { +namespace bslamp2 { + +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...); + 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: + XiaomiBslamp2FrontPanelOutput *parent_; +}; + +template class UpdateLEDsAction : public Action { + public: + explicit UpdateLEDsAction(XiaomiBslamp2FrontPanelOutput *parent) : parent_(parent) {} + + void play(Ts... x) override { + parent_->update_leds(); + } + + protected: + XiaomiBslamp2FrontPanelOutput *parent_; +}; + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/components/xiaomi_bslamp2/output/output.h b/components/xiaomi_bslamp2/output/output.h index bfb92a8..58c8206 100644 --- a/components/xiaomi_bslamp2/output/output.h +++ b/components/xiaomi_bslamp2/output/output.h @@ -13,11 +13,31 @@ namespace bslamp2 { * An output, used for controlling the front panel illumination and * level indicator on the Xiaomi Mijia Bedside Lamp 2 front panel. */ -class XiaomiBslamp2FrontPanelLight : public output::FloatOutput, public Component { +class XiaomiBslamp2FrontPanelOutput : public output::FloatOutput, public Component { public: - void set_parent(FrontPanelHAL *front_panel) { front_panel_ = front_panel; } + void set_parent(FrontPanelHAL *front_panel) { + front_panel_ = front_panel; + } - void write_state(float level) { front_panel_->set_light_level(level); } + void write_state(float level) { + front_panel_->set_light_level(level); + } + + void set_leds(uint16_t 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); + } + + void update_leds() { + front_panel_->update_leds(); + } protected: FrontPanelHAL *front_panel_; diff --git a/doc/configuration.md b/doc/configuration.md index 00edc6f..3404c6e 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -129,7 +129,7 @@ It is possible to control the night light mode separately. An example of this ca [example.yaml](../example.yaml), in which holding the power button is bound to activating the night light. -### light.disco_on Action +### `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. @@ -148,7 +148,7 @@ on_...: The possible configuration options for this Action are the same as those for the standard `light.turn_on` Action. -### light.disco_off Action +### `light.disco_off` Action This action turns off the disco mode by restoring the state of the lamp to the last known state from before using the disco mode. @@ -198,7 +198,7 @@ light: .. ``` -*Note: duplicate template names are ok, as long as they are within their own group. If you use +*Note: Duplicate template names are ok, as long as they are within their own group. If you use duplicate preset names within a single group, then the last preset will override the earlier one(s).* @@ -348,8 +348,9 @@ sensor: ## Component: output The (float) output component is linked to the front panel illumination + level indicator. Setting -this output to value 0.0 will turn off the frontpanel illumination. Other values, up to 1.0, will -turn on the illumination and will set the level indicator to the requested level (in 10 steps). +this output (using the standard `output.set_level` action) to value 0.0 will turn off the frontpanel +illumination. Other values, up to 1.0, will turn on the illumination and will set the level indicator +to the requested level (in 10 steps). ```yaml output: @@ -362,6 +363,97 @@ output: * **id** (**Required**, ID): The id to use for this output component. * All other options from [Output](https://esphome.io/components/output/index.html) +### Addressing the LEDs of the illumination individually + +While the standard `output.set_level` action emulates the front panel illumination behavior +of the original device firmware, it is also possible to control all of the LEDs for this +illumination individually, in case you need some different behavior, e.g. leaving the +power button on at night, so the user can easily find it in the dark. + +To address the LEDs, the following identifiers can be used in your YAML configuration: + +* `POWER` : The power button illumination. +* `COLOR` : The color button illumination. +* `1`, `2`, .., `10` : The 10 LEDs on the slider, where LED `1` is closest to the + power button and LED `10` is closest to the color button. +* `ALL` : represents all of the available LEDs +* `NONE` : represents none of the available LEDs + +#### `front_panel.set_leds` Action + +This action turns on the provided LEDs, all other LEDs are turned off. + +```yaml + on_...: + then: + - front_panel.set_leds: + leds: + - POWER + - COLOR + - 1 + - 2 + - 3 +``` + +The `leds:` key can also be omitted here, making the following action calls +equivalent to the one above. + +```yaml + on_...: + then: + - front_panel.set_leds: + - POWER + - COLOR + - 1 + - 2 + - 3 +``` + +This can also be written as: + +```yaml + on_...: + then: + - front_panel.set_leds: [ POWER, COLOR, 1, 2, 3 ] +``` + +If only one LED is specified, you are allowed to omit the list definition: + +```yaml + on_...: + then: + - front_panel.set_leds: POWER +``` + +#### `front_panel.turn_on_leds` Action + +This action turns on 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.turn_off_leds` Action + +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.update_leds` Action + +The previous actions only modify the required state for the front panel LEDs. +Updating the actual state of the LEDs is done when the main loop for the +output component is run by ESPHome. + +If you need the required state to be pushed to the LEDs immediately, regardless +the main loop, then this action can ben used to take care of this. + +*Note: In most situations, you will not need to use this action explicitly +to make the LEDs update. Only use it when you are sure that this is required.* + +```yaml + on_...: + then: + - front_panel.set_leds: POWER + - front_panel.update_leds: +``` + ## Component: text_sensor The text sensor component publishes changes in the active [light mode](#light-modes). Possible diff --git a/doc/technical_details.md b/doc/technical_details.md index a90ffcb..57fe949 100644 --- a/doc/technical_details.md +++ b/doc/technical_details.md @@ -149,7 +149,7 @@ connector on the main board, including the functions of the cable pins: Commands can be written to the front panel at any time. -The available commands are: +The commands that are used by the original firmware are these: | Command | Byte sequence to send | |-----------------|-----------------------| @@ -170,6 +170,32 @@ The available commands are: *Note: The `READY FOR EVENT` command is only used when a new event is provided by the front panel. Information about this command can be found in the next section.* +Further experimentation has uncovered that the LEDs of the front panel can be controlled +individually. The original firmware does not use this feature, but I built support for it +into the custom firmware, because it opens up some nice possibilities. + +How this works, is that the general format of the "set LEDs" command is: `02 03 XX XX 64 00 00`. +The LEDs to enable are specified using the `XX XX` part. This is a 16 bit value, which can be +constructed by bitwise OR-ing the following LED bit values: + +| LED to enable | Bit pattern | +|---------------|-------------------| +| POWER | 01001100 00000000 | +| COLOR | 00011100 00000000 | +| LED 1 | 00001110 00000000 | +| LED 2 | 00001101 00000000 | +| LED 3 | 00001100 10000000 | +| LED 4 | 00001100 01000000 | +| LED 5 | 00001100 00100000 | +| LED 6 | 00001100 00010000 | +| LED 7 | 00001100 00001000 | +| LED 8 | 00001100 00000100 | +| LED 9 | 00001100 00000010 | +| LED 10 | 00001100 00000001 | + +LED 1 is the one closest to the power button. +LED 10 is the one closest to the color button. + **Reading events from the front panel** The types of events that can occur can be summarized as: