Browse Source

Copmleted implementation for controlling front panel LEDs individually. Also added docs.

pull/39/head
Maurice Makaay 3 years ago
parent
commit
ac0948a537
10 changed files with 150 additions and 13 deletions
  1. +8
    -0
      CHANGELOG.md
  2. +8
    -3
      README.md
  3. +1
    -0
      components/xiaomi_bslamp2/__init__.py
  4. +1
    -0
      components/xiaomi_bslamp2/binary_sensor/__init__.py
  5. +3
    -3
      components/xiaomi_bslamp2/front_panel_hal.h
  6. +11
    -0
      components/xiaomi_bslamp2/output/__init__.py
  7. +12
    -0
      components/xiaomi_bslamp2/output/automation.h
  8. +4
    -0
      components/xiaomi_bslamp2/output/output.h
  9. +97
    -5
      doc/configuration.md
  10. +5
    -2
      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)

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

@ -37,6 +37,7 @@ FrontPanelHAL = bslamp2_ns.class_("FrontPanelHAL", cg.Component, I2CDevice)
FrontPanelLEDs = bslamp2_ns.enum("FrontPanelLEDs") FrontPanelLEDs = bslamp2_ns.enum("FrontPanelLEDs")
FRONT_PANEL_LED_OPTIONS = { FRONT_PANEL_LED_OPTIONS = {
"NONE": FrontPanelLEDs.LED_NONE, "NONE": FrontPanelLEDs.LED_NONE,
"ALL": FrontPanelLEDs.LED_ALL,
"POWER": FrontPanelLEDs.LED_POWER, "POWER": FrontPanelLEDs.LED_POWER,
"COLOR": FrontPanelLEDs.LED_COLOR, "COLOR": FrontPanelLEDs.LED_COLOR,
"1": FrontPanelLEDs.LED_1, "1": FrontPanelLEDs.LED_1,


+ 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]")


+ 3
- 3
components/xiaomi_bslamp2/front_panel_hal.h View File

@ -22,9 +22,9 @@ using EVENT = uint16_t;
// LED_1 is the slider LED closest to the power button. // LED_1 is the slider LED closest to the power button.
// LED_10 is the one closest to the color button. // LED_10 is the one closest to the color button.
enum FrontPanelLEDs { enum FrontPanelLEDs {
LED_ALL = 32768 + 8192 + 1023,
LED_POWER = 32768,
LED_COLOR = 8192,
LED_ALL = 16384 + 4096 + 1023,
LED_POWER = 16384,
LED_COLOR = 4096,
LED_1 = 512, LED_1 = 512,
LED_2 = 256, LED_2 = 256,
LED_3 = 128, LED_3 = 128,


+ 11
- 0
components/xiaomi_bslamp2/output/__init__.py View File

@ -14,6 +14,7 @@ AUTO_LOAD = ["xiaomi_bslamp2"]
XiaomiBslamp2FrontPanelOutput = bslamp2_ns.class_( XiaomiBslamp2FrontPanelOutput = bslamp2_ns.class_(
"XiaomiBslamp2FrontPanelOutput", output.FloatOutput, cg.Component) "XiaomiBslamp2FrontPanelOutput", output.FloatOutput, cg.Component)
SetLEDsAction = bslamp2_ns.class_("SetLEDsAction", automation.Action) 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(
{ {
@ -37,6 +38,10 @@ def maybe_simple_leds_value(schema):
return schema({ "leds": value }) return schema({ "leds": value })
return validator return validator
FRONT_PANEL_SCHEMA = cv.Schema({
cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2FrontPanelOutput),
})
FRONT_PANEL_LED_SCHEMA = cv.Schema( FRONT_PANEL_LED_SCHEMA = cv.Schema(
maybe_simple_leds_value(cv.Schema({ maybe_simple_leds_value(cv.Schema({
cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2FrontPanelOutput), cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2FrontPanelOutput),
@ -82,3 +87,9 @@ async def turn_off_leds_to_code(config, action_id, template_arg, args):
cg.add(action_var.set_mode(0)) cg.add(action_var.set_mode(0))
cg.add(action_var.set_leds(value)) cg.add(action_var.set_leds(value))
return action_var 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

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

@ -37,6 +37,18 @@ template<typename... Ts> class SetLEDsAction : public Action<Ts...> {
XiaomiBslamp2FrontPanelOutput *parent_; 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 bslamp2
} // namespace xiaomi } // namespace xiaomi
} // namespace esphome } // namespace esphome

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

@ -35,6 +35,10 @@ class XiaomiBslamp2FrontPanelOutput : public output::FloatOutput, public Compone
front_panel_->turn_off_leds(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
```
and
```yaml
on_...:
then:
- front_panel.set_leds: [ POWER, COLOR, 1, 2, 3 ]
```
If only one let is specified, you can also omit the list;
```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


+ 5
- 2
doc/technical_details.md View File

@ -170,8 +170,8 @@ The commands that are used by the original firmware are these:
*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 (power, color, level 1 - 10)
can be enabled individually. The original firmware does not use this, but I built support for it
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. 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`. How this works, is that the general format of the "set LEDs" command is: `02 03 XX XX 64 00 00`.
@ -193,6 +193,9 @@ constructed by bitwise OR-ing the following LED bit values:
| LED 9 | 00001100 00000010 | | LED 9 | 00001100 00000010 |
| LED 10 | 00001100 00000001 | | 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