Browse Source

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.
pull/44/head v1.1.0-RC2
Maurice Makaay 3 years ago
committed by GitHub
parent
commit
af5c1a8d49
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 401 additions and 57 deletions
  1. +8
    -0
      CHANGELOG.md
  2. +8
    -3
      README.md
  3. +19
    -2
      components/xiaomi_bslamp2/__init__.py
  4. +1
    -0
      components/xiaomi_bslamp2/binary_sensor/__init__.py
  5. +93
    -39
      components/xiaomi_bslamp2/front_panel_hal.h
  6. +71
    -4
      components/xiaomi_bslamp2/output/__init__.py
  7. +54
    -0
      components/xiaomi_bslamp2/output/automation.h
  8. +23
    -3
      components/xiaomi_bslamp2/output/output.h
  9. +97
    -5
      doc/configuration.md
  10. +27
    -1
      doc/technical_details.md

+ 8
- 0
CHANGELOG.md View File

@ -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. **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 ### Changed
- Made it possible to use lambdas with the `preset.activate` automation. This makes it - 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 possible to link the action to an api service, which exposes the preset functionality


+ 8
- 3
README.md View File

@ -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. 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 * **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 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 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! :-) 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 * **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, 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 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) * [Why custom firmware?](doc/why_custom_firmware.md)
* [Installation guide](doc/installation.md) * [Installation guide](doc/installation.md)
* [Configuration_guide](doc/configuration.md)
* [Configuration guide](doc/configuration.md)
* [Flashing guide](doc/flashing.md) * [Flashing guide](doc/flashing.md)
* [Technical details](doc/technical_details.md) * [Technical details](doc/technical_details.md)
* [Sponsoring](doc/sponsoring.md) * [Sponsoring](doc/sponsoring.md)

+ 19
- 2
components/xiaomi_bslamp2/__init__.py View File

@ -13,7 +13,6 @@ from esphome.const import (
CODEOWNERS = ["@mmakaay"] CODEOWNERS = ["@mmakaay"]
CONF_HUB_ID = "xiaomi_bslamp2_hub_id"
CONF_RED_ID = "red_id" CONF_RED_ID = "red_id"
CONF_GREEN_ID = "green_id" CONF_GREEN_ID = "green_id"
CONF_BLUE_ID = "blue_id" CONF_BLUE_ID = "blue_id"
@ -25,8 +24,8 @@ CONF_MASTER2_ID = "master2_id"
CONF_FP_I2C_ID = "front_panel_i2c_id" CONF_FP_I2C_ID = "front_panel_i2c_id"
CONF_LIGHT_HAL_ID = "light_hal_id" CONF_LIGHT_HAL_ID = "light_hal_id"
CONF_FRONT_PANEL_HAL_ID = "front_panel_hal_id" CONF_FRONT_PANEL_HAL_ID = "front_panel_hal_id"
CONF_ON_BRIGHTNESS = "on_brightness" CONF_ON_BRIGHTNESS = "on_brightness"
CONF_LEDS = "leds"
AUTO_LOAD = ["ledc", "output", "i2c"] AUTO_LOAD = ["ledc", "output", "i2c"]
@ -35,6 +34,24 @@ 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,
"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({ 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),


+ 1
- 0
components/xiaomi_bslamp2/binary_sensor/__init__.py View File

@ -33,6 +33,7 @@ def validate_binary_sensor(conf):
if CONF_PART in conf and CONF_FOR in conf: if CONF_PART in conf and CONF_FOR in conf:
raise cv.Invalid("Specify only one of [part] or [for]") raise cv.Invalid("Specify only one of [part] or [for]")
if CONF_PART in conf and not CONF_FOR in conf: if CONF_PART in conf and not CONF_FOR in conf:
# Backward compatibility.
conf[CONF_FOR] = conf[CONF_PART] conf[CONF_FOR] = conf[CONF_PART]
if CONF_FOR not in conf: if CONF_FOR not in conf:
raise cv.Invalid("'for' is a required option for [binary_sensor.xiaomi_bslamp2]") raise cv.Invalid("'for' is a required option for [binary_sensor.xiaomi_bslamp2]")


+ 93
- 39
components/xiaomi_bslamp2/front_panel_hal.h View File

@ -5,32 +5,41 @@
#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 {
namespace bslamp2 { namespace bslamp2 {
static const uint8_t MSG_LEN = 7; 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 // 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. // Bit flags that are used for specifying an event.
// Events are registered using the following bit pattern // 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). * 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. * 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) {
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: protected:
@ -253,6 +303,10 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice {
volatile int event_id_ = 0; volatile int event_id_ = 0;
int last_event_id_ = 0; int last_event_id_ = 0;
CallbackManager<void(EVENT)> event_callback_{}; CallbackManager<void(EVENT)> event_callback_{};
uint16_t led_state_ = 0;
uint16_t last_led_state_ = 0;
MSG led_msg_ = {0x02, 0x03, 0x00, 0x00, 0x64, 0x00, 0x00};
}; };
/** /**


+ 71
- 4
components/xiaomi_bslamp2/output/__init__.py View File

@ -2,19 +2,23 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import output from esphome.components import output
from esphome.const import CONF_ID from esphome.const import CONF_ID
from esphome import automation
from .. import ( from .. import (
bslamp2_ns, CODEOWNERS, 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"] 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( 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), cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.use_id(FrontPanelHAL),
} }
).extend(cv.COMPONENT_SCHEMA) ).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]) 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 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

+ 54
- 0
components/xiaomi_bslamp2/output/automation.h View File

@ -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 <cmath>
namespace esphome {
namespace xiaomi {
namespace bslamp2 {
template<typename... Ts> class SetLEDsAction : public Action<Ts...> {
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<typename... Ts> class UpdateLEDsAction : public Action<Ts...> {
public:
explicit UpdateLEDsAction(XiaomiBslamp2FrontPanelOutput *parent) : parent_(parent) {}
void play(Ts... x) override {
parent_->update_leds();
}
protected:
XiaomiBslamp2FrontPanelOutput *parent_;
};
} // namespace bslamp2
} // namespace xiaomi
} // namespace esphome

+ 23
- 3
components/xiaomi_bslamp2/output/output.h View File

@ -13,11 +13,31 @@ namespace bslamp2 {
* An output, used for controlling the front panel illumination and * An output, used for controlling the front panel illumination and
* level indicator on the Xiaomi Mijia Bedside Lamp 2 front panel. * 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: 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: protected:
FrontPanelHAL *front_panel_; FrontPanelHAL *front_panel_;


+ 97
- 5
doc/configuration.md View File

@ -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 [example.yaml](../example.yaml), in which holding the power button is bound to activating the night
light. 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 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. 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 The possible configuration options for this Action are the same as those for the standard
`light.turn_on` Action. `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 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. 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 duplicate preset names within a single group, then the last preset will override the earlier
one(s).* one(s).*
@ -348,8 +348,9 @@ sensor:
## Component: output ## Component: output
The (float) output component is linked to the front panel illumination + level indicator. Setting 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 ```yaml
output: output:
@ -362,6 +363,97 @@ output:
* **id** (**Required**, ID): The id to use for this output component. * **id** (**Required**, ID): The id to use for this output component.
* All other options from [Output](https://esphome.io/components/output/index.html) * 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 ## Component: text_sensor
The text sensor component publishes changes in the active [light mode](#light-modes). Possible The text sensor component publishes changes in the active [light mode](#light-modes). Possible


+ 27
- 1
doc/technical_details.md View File

@ -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. 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 | | 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. *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.* 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** **Reading events from the front panel**
The types of events that can occur can be summarized as: The types of events that can occur can be summarized as:


Loading…
Cancel
Save