diff --git a/binary_sensor/__init__.py b/binary_sensor/__init__.py index 45ada32..5a7549a 100644 --- a/binary_sensor/__init__.py +++ b/binary_sensor/__init__.py @@ -1,7 +1,8 @@ +import re import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_FOR from .. import ( bslamp2_ns, CODEOWNERS, CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL @@ -11,29 +12,46 @@ AUTO_LOAD = ["xiaomi_bslamp2"] CONF_PART = "part" +# The identifier values match the bit values of the events as defined +# in ../front_panel_hal.h. PARTS = { - "ANY" : 0, - "POWER_BUTTON" : 1, - "POWER" : 1, - "COLOR_BUTTON" : 2, - "COLOR" : 2, - "SLIDER" : 3, + "POWER_BUTTON" : 0b001 << 1, + "POWER" : 0b001 << 1, + "COLOR_BUTTON" : 0b010 << 1, + "COLOR" : 0b010 << 1, + "SLIDER" : 0b100 << 1, } -def validate_part(value): - value = cv.string(value) - return cv.enum(PARTS, upper=True, space='_')(value) - XiaomiBslamp2TouchBinarySensor = bslamp2_ns.class_( "XiaomiBslamp2TouchBinarySensor", binary_sensor.BinarySensor, cg.Component) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(XiaomiBslamp2TouchBinarySensor), - cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.use_id(FrontPanelHAL), - cv.Optional(CONF_PART, default="ANY"): validate_part, - } -).extend(cv.COMPONENT_SCHEMA) +def validate_for(value): + value = cv.string(value) + return cv.enum(PARTS, upper=True, space='_')(value) + +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: + 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]") + return conf + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(XiaomiBslamp2TouchBinarySensor), + cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.use_id(FrontPanelHAL), + # This option is not advertised in the documentation. It must be + # considered deprecated. I'm not announcing it as such yet. Not sure + # if it's useful to do so. + cv.Optional(CONF_PART): validate_for, + cv.Optional(CONF_FOR): validate_for, + } + ).extend(cv.COMPONENT_SCHEMA), + validate_binary_sensor, +) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) @@ -42,4 +60,4 @@ 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)) - cg.add(var.set_part(config[CONF_PART])) + cg.add(var.set_for(config[CONF_FOR])) diff --git a/binary_sensor/touch_binary_sensor.h b/binary_sensor/touch_binary_sensor.h index 5771091..97f8144 100644 --- a/binary_sensor/touch_binary_sensor.h +++ b/binary_sensor/touch_binary_sensor.h @@ -18,38 +18,33 @@ public: front_panel_ = front_panel; } - void set_part(int part) { - part_ = part; + void set_for(int part) { + for_ = part; } void setup() { front_panel_->add_on_event_callback( [this](EVENT ev) { - // Filter events by part, when requested. - if (part_ > 0) { - if ((ev & FLAG_PART_MASK) != (part_ << FLAG_PART_SHIFT)) { - return; - } + auto part_in_event = ev & FLAG_PART_MASK; + if (for_ == 0 || part_in_event == for_) { + auto new_state = (ev & FLAG_TYPE_MASK) == FLAG_TYPE_TOUCH; + this->publish_state(new_state); } - // Publish the new state, based on the touch/release status.. - auto on_or_off = (ev & FLAG_TYPE_MASK) == FLAG_TYPE_TOUCH; - this->publish_state(on_or_off); } ); } void dump_config() { ESP_LOGCONFIG(TAG, "Front panel binary_sensor:"); - ESP_LOGCONFIG(TAG, " Part: %s (id %d)", - (part_ == 1 ? "power button" : - part_ == 2 ? "color button" : - part_ == 3 ? "slider" : "any"), - part_); + ESP_LOGCONFIG(TAG, " For: %s", + for_ & FLAG_PART_POWER ? "power button" : + for_ & FLAG_PART_COLOR ? "color button" : + for_ & FLAG_PART_SLIDER ? "slider" : "ERR"); } protected: FrontPanelHAL *front_panel_; - EVENT part_; + EVENT for_ = 0; }; } // namespace bslamp2 diff --git a/doc/configuration.md b/doc/configuration.md index 40115c3..ee88dcf 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -2,14 +2,15 @@ # Configuration guide -I think, the best starting point for creating your own yaml configuration, is to -look at the [example.yaml](example.yaml) file from the project documentation. -This configuration was written with the functionality of the original firmware in mind -and it makes use of all available options. This configuration guide can be used to -fill in the blanks. +I think, the best starting point for creating your own yaml configuration, +is to look at the [example.yaml](example.yaml) file from the project +documentation. This configuration was written with the functionality of the +original firmware in mind and it makes use of all available options. This +configuration guide can be used to fill in the blanks. -The `xiaomi_bslamp2` platform provides various components that expose the core functionalities of the lamp. -In the following table, you can find what components are used for exposing what parts of the lamp. +The `xiaomi_bslamp2` platform provides various components that expose the +core functionalities of the lamp. In the following table, you can find what +components are used for exposing what physical components of the lamp. | Part | Component(s) | | -------------------------- |------------------------------------------------------------------| @@ -24,16 +25,17 @@ In the following table, you can find what components are used for exposing what ## Platform: xiaomi_bslamp2 -At the core of the hardware support is the `xiaomi_bslamp2` platform, which provides two -hub-style hardware abstraction layer (HAL) components that are used by the other components: -one for driving the GPIO's for the RGBWW leds and one for the I2C communication between -the ESP32 and the front panel. +At the core of the hardware support is the `xiaomi_bslamp2` platform, which +provides two hub-style hardware abstraction layer (HAL) components that are +used by the other components: one for driving the GPIO's for the RGBWW leds +and one for the I2C communication between the ESP32 and the front panel. -I do mention it here for completeness sake, but generally you will not have to add the -following configuration option to your yaml file. It is loaded automatically by the -components that need it, and the GPIO + I2C configurations are fully prepared to work -for the Bedside Lamp 2 wiring out of the box. -Therefore, you will not find this piece of configuration in the [example.yaml](example.yaml). +I do mention the platform configuration here for completeness sake, but +generally you will not have to add the following configuration option to +your yaml file. It is loaded automatically by the components that need it, +and the GPIO + I2C configurations are fully prepared to work for the Bedside +Lamp 2 wiring out of the box. Therefore, you will not find this piece of +configuration in the [example.yaml](example.yaml). Having said that, here are the configuration options: @@ -54,14 +56,14 @@ xiaomi_bslamp2: trigger_pin: "GPIO16" ``` -The only reason that I can think of for adding this platform configuration to your yaml -file, would be if you blew one or more or the ESP32 pins, and need to rewire functions -to different pins. +The only reason that I can think of for adding this platform configuration +to your yaml file, would be if you blew one or more or the ESP32 pins, and +need to rewire functionality. In other casis, simply omit the section. ## Component: light -The light component creates an RGBWW light. This means that it can do colored light and -cold/warm white light based on a color temperature. +The light component creates an RGBWW light. This means that it can do +colored light and cold/warm white light based on a color temperature. ```yaml light: @@ -95,60 +97,68 @@ light: ### Configuration variables: * **name** (**Required**, string): The name of the light. -* **id** (*Optional*, ID): Manually specify the ID used for code generation. By providing an id, - you can reference the light from automation rules (e.g. to turn on the light when the power - button is tapped) -* **default_transition_length** (*Optional*, Time): The transition length to use when - no transition length is set in a light call. Defaults to 1s. -* **effects** (*Optional*, list): A list of [light effects](https://esphome.io/components/light/index.html#light-effects) +* **id** (*Optional*, ID): Manually specify the ID used for code generation. + By providing an id, you can reference the light from automation rules + (e.g. to turn on the light when the power button is tapped) +* **default_transition_length** (*Optional*, Time): The transition length to + use when no transition length is set in a light call. Defaults to 1s. +* **effects** (*Optional*, list): A list of + [light effects](https://esphome.io/components/light/index.html#light-effects) to use for this light. -* **presets** (*Optional*, dict): Used to define presets, that can be used from automations. - See [below](#light-presets) for detailed information. -* **on_brightness** (*Optional*, Action): An automation to perform when the brightness of the light is modified. -* All other options from [the base Light implementation](https://esphome.io/components/light/index.html#config-light), - except for options that handle color correction options like `gamma_correct` and `color_correct`. - These options are superceded by the fact that the light component has a fully customized - light model, that closely follows the light model of the original lamp's firmware. +* **presets** (*Optional*, dict): Used to define presets, that can be used + from automations. See [below](#light-presets) for detailed information. +* **on_brightness** (*Optional*, Action): An automation to perform when the + brightness of the light is modified. +* All other options from [the base Light + implementation](https://esphome.io/components/light/index.html#config-light), + except for options that handle color correction options like + `gamma_correct` and `color_correct`. These options are superceded by the + fact that the light component has a fully customized light model, that + closely follows the light model of the original lamp's firmware. ### Light modes The lamp supports multiple light modes. These are: -* **RGB light** (input: RGB + brightness) -* **White light** (input: Color Temperature + brightness) -* **Night light** (input: either RGB or Color Temperature + brightness at 1%) +* **RGB light** (input: RGB + brightness > 1%) +* **White light** (input: Color Temperature + brightness > 1%) +* **Night light** (input: RGB or White light + brightness at 1%) -In the original firmware + Yeelight Home Assistant integration, the night light feature is -implemented through a switch component. The switch can be turned on to activate the night -light mode. In this ESPHome firmware, setting the brightness to its lowest value triggers -the night light mode. This makes things a lot easier to control. +In the original firmware + Yeelight Home Assistant integration, the night +light feature is implemented through a switch component. The switch can be +turned on to activate the night light mode. In this ESPHome firmware, +setting the brightness to its lowest value triggers the night light mode. +This makes things a lot easier to control. -It is possible to control the night light mode separately. An example of this can be -found in the [example.yaml](example.yaml), in which holding the power button is bound -to activating the night light. +It is possible to control the night light mode separately. An example of +this can be found in the [example.yaml](example.yaml), in which holding the +power button is bound to activating the night light. ### Light presets -The presets functionality was written with the original lamp firemware functionality in mind: -the user has two groups of presets available: one for RGB light presets and one for white light -presets (based on color temperature). The color button (the top one on the front panel) can be -tapped to switch to the next preset within the active preset group. The same button can be -held for a little while, to switch to the other preset group. +The presets functionality was written with the original lamp firemware +functionality in mind: the user has two groups of presets available: one for +RGB light presets and one for white light presets (based on color +temperature). The color button (the top one on the front panel) can be +tapped to switch to the next preset within the active preset group. The same +button can be held for a little while, to switch to the other preset group. -In your light configuration, you can mimic this behavior (in fact: it is done so in the -[example.yaml](example.yaml)) by means of the presets system. This system consists of two -parts: +In your light configuration, you can mimic this behavior (in fact: it is +done so in the [example.yaml](example.yaml)) by means of the presets system. +This system consists of two parts: * Defining presets * Activating presets from automations **Defining presets** -Presets can be configured in the `presets` option of the `light` configuration. +Presets can be configured in the `presets` option of the `light` +configuration. -Presets are arranged in groups. You can define as little or as many groups as you like. -The example configuration uses two groups, but that is only to mimic the original behavior. -If you only need one group, then create one group. If you need ten, go ahead and knock yourself out. +Presets are arranged in groups. You can define as little or as many groups +as you like. The example configuration uses two groups, but that is only to +mimic the original behavior. If you only need one group, then create one +group. If you need ten, go ahead and knock yourself out. The general structure of the presets configuration is: @@ -166,9 +176,9 @@ light: .. ``` -*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).* +*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).* A preset can define one of the following: @@ -189,8 +199,9 @@ A preset can define one of the following: **Activating presets from automations** -Once presets have been configured, they can be activated using the `preset.activate` action. -The following options are available for this action: +Once presets have been configured, they can be activated using the +`preset.activate` action. The following options are available for this +action: * Switch to next preset group (and after the last, switch to the first): ```yaml @@ -198,7 +209,8 @@ preset.activate: next: group ``` -* Switch to next preset within currentl preset group (and after the last, switch to the first): +* Switch to next preset within currentl preset group (and after the last, + switch to the first): ```yaml preset.activate: next: preset @@ -228,57 +240,69 @@ preset.activate: white.warm **Handling of invalid input** -When a group or template is specified that does not exist, or if next group/preset -is used while no presets have been defined at all, then the action will be ignored -and an error will be logged. +When a group or template is specified that does not exist, or if next +group/preset is used while no presets have been defined at all, then the +action will be ignored and an error will be logged. + +*Note: This is validation at run time. It would be a lot better to +validate the names at compile time more strictly, so the firmware will not +even compile when invalid names are in use. +[Issue #15](https://github.com/mmakaay/esphome-xiaomi_bslamp2/issues/15) +was created for implementing this. However, a new feature in ESPHome is +required to be able to do this implementation. Good news is that this +is already well on its way.* -This is of course validation at run time. It would be better to validate the -names at compile time more strictly, so the firmware won't compile when invalid -names are in use. [Issue #15 was created for implementing this](https://github.com/mmakaay/esphome-xiaomi_bslamp2/issues/15). ## Component: binary_sensor -Binary sensors can be added to the configuration for handling touch/release events -for the front panel. On touch, a binary_sensor will publish `True`, on release it -will publish `False`. The configuration of a binary_sensor determines what part -or parts of the front panel are involved in the touch events. +Binary sensors can be added to the configuration for handling touch/release +events for the front panel. On touch, a binary_sensor will publish `True`, +on release it will publish `False`. The configuration of a binary_sensor +determines what part of the front panel is involved in the touch events. ```yaml binary_sensor: - platform: xiaomi_bslamp2 id: my_bedside_lamp_power_button - part: POWER_BUTTON + for: POWER_BUTTON on_press: then: - light.toggle: my_bedside_lamp ``` -For specifying specific parts of the front panel in the upcoming configuration variables, -the following identifiers are available: +For referencing the parts of the front panel, the following part identifiers +are available: * POWER_BUTTON (or its alias: POWER) * COLOR_BUTTON (or its alias: COLOR) * SLIDER +If personal taste dictates so, you can use lower case characters and spaces +instead of underscores. This means that for example "Power Button" and +"power" would be valid identifiers for the power button. + ### Configuration variables: -* **name** (*Optional*, string): The name of the binary sensor. Setting a name will expose the - binary sensor as an entity in Home Assistant. If you do not need this, you can omit the name. -* **id** (*Optional*, ID): Manually specify the ID used for code generation. By providing an id, - you can reference the binary_sensor from automation rules (to retrieve the current state - of the binary_sensor). -* **part** (*Optional*, part identifier): This specifies what part of the front panel the binary sensor must - look at. Valid options are: "any" (the default) or one of the abovementioned part identifiers. -* All other options from [Binary Sensor](https://esphome.io/components/binary_sensor/index.html#config-binary-sensor). +* **name** (*Optional*, string): The name of the binary sensor. Setting a + name will expose the binary sensor as an entity in Home Assistant. If you + do not need this, you can omit the name. +* **id** (*Optional*, ID): Manually specify the ID used for code generation. + By providing an id, you can reference the binary_sensor from automation + rules (to retrieve the current state of the binary_sensor). +* **for** (*Mandatory*, part identifier): This specifies to for part of the + front panel the binary sensor must report touch events. +* All other options from + [Binary Sensor](https://esphome.io/components/binary_sensor/index.html#config-binary-sensor). ## Component: sensor -The sensor component publishes touch events for the front panel slider. The published value -represents the level at which the slider was touched. +The sensor component publishes touch events for the front panel slider. The +published value represents the level at which the slider was touched. -*Note: This sensor only reports the touched slider level. It cannot be used for detecting release -events. If you want to handle touch/release events for the slider, then you can make use of -the [binary_sensor](#component-binary_sensor) instead.* +*Note: This sensor only reports the touched slider level. It cannot be used +for detecting release events. If you want to handle touch/release events for +the slider, then you can make use of the +[binary_sensor](#component-binary_sensor) instead.* ```yaml sensor: @@ -295,22 +319,27 @@ sensor: ### Configuration variables: -* **name** (*Optional*, string): The name of the sensor. Setting a name will expose the - sensor as an entity in Home Assistant. If you do not need this, you can omit the name. -* **id** (*Optional*, ID): Manually specify the ID used for code generation. By providing an id, - you can reference the sensor from automation rules (e.g. to retrieve the current state - of the binary_sensor). -* **range_from** (*Optional*, float): By default, published values vary from the range 0.01 to 1.00, - in 20 steps. This option modifies the lower bound of the range. -* **range_to** (*Optional*, float): This option modifies the upper bound of the range. -* All other options from [Sensor](https://esphome.io/components/sensor/index.html#config-sensor). +* **name** (*Optional*, string): The name of the sensor. Setting a name will + expose the sensor as an entity in Home Assistant. If you do not need this, + you can omit the name. +* **id** (*Optional*, ID): Manually specify the ID used for code generation. + By providing an id, you can reference the sensor from automation rules + (e.g. to retrieve the current state of the binary_sensor). +* **range_from** (*Optional*, float): By default, published values vary from + the range 0.01 to 1.00, in 20 steps. This option modifies the lower bound + of the range. +* **range_to** (*Optional*, float): This option modifies the upper bound of + the range. +* All other options from + [Sensor](https://esphome.io/components/sensor/index.html#config-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). +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). ```yaml output: @@ -325,16 +354,19 @@ output: ## Component: text_sensor -The text sensor component publishes changes in the active [light mode](#light-modes). -Possible output values for this sensor are: "off", "rgb", "white" and "night". +The text sensor component publishes changes in the active +[light mode](#light-modes). Possible output values for this sensor are: "off", +"rgb", "white" and "night". ### Configuration variables: -* **name** (*Optional*, string): The name of the text sensor. Setting a name will expose the - text sensor as an entity in Home Assistant. If you do not need this, you can omit the name. -* **id** (*Optional*, ID): Manually specify the ID used for code generation. By providing an id, - you can reference the text sensor from automation rules (to retrieve the current state - of the text_sensor). -* All other options from [Text Sensor](https://esphome.io/components/text_sensor/index.html) +* **name** (*Optional*, string): The name of the text sensor. Setting a name + will expose the text sensor as an entity in Home Assistant. If you do not + need this, you can omit the name. +* **id** (*Optional*, ID): Manually specify the ID used for code generation. + By providing an id, you can reference the text sensor from automation + rules (to retrieve the current state of the text_sensor). +* All other options from + [Text Sensor](https://esphome.io/components/text_sensor/index.html) < [Installation guide](installation.md) | [Index](../README.md) | [Flashing guide](flashing.md) > diff --git a/doc/example.yaml b/doc/example.yaml index 919876b..ffefd90 100644 --- a/doc/example.yaml +++ b/doc/example.yaml @@ -64,8 +64,8 @@ esphome: board: esp32doit-devkit-v1 platformio_options: platform: espressif32@3.2.0 - platform_packages: |-4 - framework-arduinoespressif32 @ https://github.com/pauln/arduino-esp32.git#solo-no-mac-crc/1.0.6 + platform_packages: |- + framework-arduinoespressif32 @ https://github.com/pauln/arduino-esp32.git#solo-no-mac-crc/1.0.6 # This component controls the LED lights of the lamp. light: diff --git a/front_panel_hal.h b/front_panel_hal.h index d435928..2235298 100644 --- a/front_panel_hal.h +++ b/front_panel_hal.h @@ -37,39 +37,39 @@ using EVENT = uint16_t; // BITS INDICATE PATTERN RESULT // 1 status 0 parsing event failed // 1 parsing event successful -// 2-3 part 00 part unknown -// 01 power button -// 10 color button -// 11 slider -// 4-5 type 00 type unknown +// 2-4 part 000 part unknown +// 001 power button +// 010 color button +// 100 slider +// 5-6 type 00 type unknown // 01 touch // 10 release -// 6-10 slider 00000 level known (or part is not "slider") +// 7-11 slider 00000 level known (or part is not "slider") // level 00001 level 1 // ... up to // 10101 level 21 // -static const EVENT FLAG_INIT = 0b0000000000; +static const EVENT FLAG_INIT = 0b00000000000; -static const EVENT FLAG_ERR = 0b0000000000; -static const EVENT FLAG_OK = 0b0000000001; +static const EVENT FLAG_ERR = 0b00000000000; +static const EVENT FLAG_OK = 0b00000000001; static const EVENT FLAG_PART_SHIFT = 1; -static const EVENT FLAG_PART_MASK = 0b0000000110; -static const EVENT FLAG_PART_UNKNOWN = 0b0000000000; -static const EVENT FLAG_PART_POWER = 0b0000000010; -static const EVENT FLAG_PART_COLOR = 0b0000000100; -static const EVENT FLAG_PART_SLIDER = 0b0000000110; +static const EVENT FLAG_PART_MASK = 0b00000001110; +static const EVENT FLAG_PART_UNKNOWN = 0b00000000000; +static const EVENT FLAG_PART_POWER = 0b00000000010; +static const EVENT FLAG_PART_COLOR = 0b00000000100; +static const EVENT FLAG_PART_SLIDER = 0b00000001000; -static const EVENT FLAG_TYPE_SHIFT = 3; -static const EVENT FLAG_TYPE_MASK = 0b0000011000; -static const EVENT FLAG_TYPE_UNKNOWN = 0b0000000000; -static const EVENT FLAG_TYPE_TOUCH = 0b0000001000; -static const EVENT FLAG_TYPE_RELEASE = 0b0000010000; +static const EVENT FLAG_TYPE_SHIFT = 4; +static const EVENT FLAG_TYPE_MASK = 0b00000110000; +static const EVENT FLAG_TYPE_UNKNOWN = 0b00000000000; +static const EVENT FLAG_TYPE_TOUCH = 0b00000010000; +static const EVENT FLAG_TYPE_RELEASE = 0b00000100000; -static const EVENT FLAG_LEVEL_SHIFT = 5; -static const EVENT FLAG_LEVEL_MASK = 0b1111100000; -static const EVENT FLAG_LEVEL_UNKNOWN = 0b0000000000; +static const EVENT FLAG_LEVEL_SHIFT = 6; +static const EVENT FLAG_LEVEL_MASK = 0b11111000000; +static const EVENT FLAG_LEVEL_UNKNOWN = 0b00000000000; /** * This class implements a parser that translates event byte codes from the