diff --git a/__init__.py b/__init__.py index 7c3b156..ca71f2e 100644 --- a/__init__.py +++ b/__init__.py @@ -35,38 +35,33 @@ bslamp2_ns = xiaomi_ns.namespace("bslamp2") LightHAL = bslamp2_ns.class_("LightHAL", cg.Component) FrontPanelHAL = bslamp2_ns.class_("FrontPanelHAL", cg.Component, I2CDevice) -def make_config_schema(): - schema = cv.COMPONENT_SCHEMA.extend({ - # RGBWW Light - cv.GenerateID(CONF_LIGHT_HAL_ID): cv.declare_id(LightHAL), - cv.GenerateID(CONF_RED_ID): cv.declare_id(LEDCOutput), - cv.Optional(CONF_RED, default="GPIO13"): pins.validate_gpio_pin, - cv.GenerateID(CONF_GREEN_ID): cv.declare_id(LEDCOutput), - cv.Optional(CONF_GREEN, default="GPIO14"): pins.validate_gpio_pin, - cv.GenerateID(CONF_BLUE_ID): cv.declare_id(LEDCOutput), - cv.Optional(CONF_BLUE, default="GPIO5"): pins.validate_gpio_pin, - cv.GenerateID(CONF_WHITE_ID): cv.declare_id(LEDCOutput), - cv.Optional(CONF_WHITE, default="GPIO12"): pins.validate_gpio_pin, - cv.GenerateID(CONF_MASTER1_ID): cv.declare_id(GPIOBinaryOutput), - cv.Optional(CONF_MASTER1, default="GPIO33"): pins.validate_gpio_pin, - cv.GenerateID(CONF_MASTER2_ID): cv.declare_id(GPIOBinaryOutput), - cv.Optional(CONF_MASTER2, default="GPIO4"): pins.validate_gpio_pin, - - # Front panel I2C - cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.declare_id(FrontPanelHAL), - cv.GenerateID(CONF_FP_I2C_ID): cv.use_id(I2CComponent), - cv.Optional(CONF_SDA, default="GPIO21"): pins.validate_gpio_pin, - cv.Optional(CONF_SCL, default="GPIO19"): pins.validate_gpio_pin, - cv.Optional(CONF_ADDRESS, default="0x2C"): cv.i2c_address, - cv.Optional(CONF_TRIGGER_PIN, default="GPIO16"): cv.All( - pins.validate_gpio_pin, - pins.validate_has_interrupt - ), - }) - - return schema; - -CONFIG_SCHEMA = make_config_schema() +CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({ + # RGBWW Light + cv.GenerateID(CONF_LIGHT_HAL_ID): cv.declare_id(LightHAL), + cv.GenerateID(CONF_RED_ID): cv.declare_id(LEDCOutput), + cv.Optional(CONF_RED, default="GPIO13"): pins.validate_gpio_pin, + cv.GenerateID(CONF_GREEN_ID): cv.declare_id(LEDCOutput), + cv.Optional(CONF_GREEN, default="GPIO14"): pins.validate_gpio_pin, + cv.GenerateID(CONF_BLUE_ID): cv.declare_id(LEDCOutput), + cv.Optional(CONF_BLUE, default="GPIO5"): pins.validate_gpio_pin, + cv.GenerateID(CONF_WHITE_ID): cv.declare_id(LEDCOutput), + cv.Optional(CONF_WHITE, default="GPIO12"): pins.validate_gpio_pin, + cv.GenerateID(CONF_MASTER1_ID): cv.declare_id(GPIOBinaryOutput), + cv.Optional(CONF_MASTER1, default="GPIO33"): pins.validate_gpio_pin, + cv.GenerateID(CONF_MASTER2_ID): cv.declare_id(GPIOBinaryOutput), + cv.Optional(CONF_MASTER2, default="GPIO4"): pins.validate_gpio_pin, + + # Front panel I2C + cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.declare_id(FrontPanelHAL), + cv.GenerateID(CONF_FP_I2C_ID): cv.use_id(I2CComponent), + cv.Optional(CONF_SDA, default="GPIO21"): pins.validate_gpio_pin, + cv.Optional(CONF_SCL, default="GPIO19"): pins.validate_gpio_pin, + cv.Optional(CONF_ADDRESS, default="0x2C"): cv.i2c_address, + cv.Optional(CONF_TRIGGER_PIN, default="GPIO16"): cv.All( + pins.validate_gpio_pin, + pins.validate_has_interrupt + ), +}) @coroutine def make_gpio(number, mode="OUTPUT"): diff --git a/doc/configuration.md b/doc/configuration.md index 8a8e855..40115c3 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -170,12 +170,12 @@ light: 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 light types: +A preset can define one of the following: * **RGB light** - * **red** (**Required**, percentage): the red component of the RGB value. - * **green** (**Required**, percentage): the green component of the RGB value. - * **blue** (**Required**, percentage): the blue component of the RGB value. + * **red** (**Optional**, percentage): the red component of the RGB value (default = 0%). + * **green** (**Optional**, percentage): the green component of the RGB value (default = 0%). + * **blue** (**Optional**, percentage): the blue component of the RGB value (default = 0%). * **brightness** (*Optional*, percentage): the brightness to use (default = current brightness). * **transition_length** (*Optional*, time): the transition length to use. * **White light** @@ -184,6 +184,8 @@ A preset can define one of the following light types: * **transition_length** (*Optional*, time): the transition length to use. * **Light effect** * **effect** (**Required**, string): the name of a light effect to activate. +* **Brightness change only** + * **brightness** (**Required**, percentage): the brightness to use. **Activating presets from automations** diff --git a/light/__init__.py b/light/__init__.py index 025113d..b550ca6 100644 --- a/light/__init__.py +++ b/light/__init__.py @@ -22,6 +22,9 @@ CONF_NEXT = "next" CONF_GROUP = "group" CONF_PRESET = "preset" +MIRED_MIN = 153 +MIRED_MAX = 588 + XiaomiBslamp2LightState = bslamp2_ns.class_("XiaomiBslamp2LightState", light.LightState) XiaomiBslamp2LightOutput = bslamp2_ns.class_("XiaomiBslamp2LightOutput", light.LightOutput) PresetsContainer = bslamp2_ns.class_("PresetsContainer", cg.Component) @@ -35,29 +38,46 @@ PRESETS_SCHEMA = cv.Schema({ }) }) -PRESET_SCHEMA_BASE = cv.Schema( - { - cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2LightState), - cv.GenerateID(CONF_PRESET_ID): cv.declare_id(Preset), - } -) +def validate_preset(conf): + has_rgb = CONF_RED in conf or CONF_GREEN in conf or CONF_BLUE in conf + has_white = CONF_COLOR_TEMPERATURE in conf + has_effect = CONF_EFFECT in conf + + # Check mutual exclusivity of preset options. + if (has_rgb + has_white + has_effect) > 1: + raise cv.Invalid("Use only one of RGB light, white (color temperature) light or an effect") -PRESET_SCHEMA = cv.Any( - PRESET_SCHEMA_BASE.extend({ - cv.Required(CONF_EFFECT): cv.string - }), - PRESET_SCHEMA_BASE.extend({ - cv.Required(CONF_COLOR_TEMPERATURE): cv.color_temperature, - cv.Optional(CONF_BRIGHTNESS): cv.percentage, - cv.Optional(CONF_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - }), - PRESET_SCHEMA_BASE.extend({ - cv.Required(CONF_RED): cv.percentage, - cv.Required(CONF_GREEN): cv.percentage, - cv.Required(CONF_BLUE): cv.percentage, - cv.Optional(CONF_BRIGHTNESS): cv.percentage, - cv.Optional(CONF_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, - }), + # Check the color temperature value range. + if has_white: + if conf[CONF_COLOR_TEMPERATURE] < MIRED_MIN or conf[CONF_COLOR_TEMPERATURE] > MIRED_MAX: + raise cv.Invalid(f"The color temperature must be in the range {MIRED_MIN} - {MIRED_MAX}") + + # When defining an RGB color, it is allowed to omit RGB components that have value 0. + if has_rgb: + if CONF_RED not in conf: + conf[CONF_RED] = 0 + if CONF_GREEN not in conf: + conf[CONF_GREEN] = 0 + if CONF_BLUE not in conf: + conf[CONF_BLUE] = 0 + + return conf + +PRESET_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.use_id(XiaomiBslamp2LightState), + cv.GenerateID(CONF_PRESET_ID): cv.declare_id(Preset), + cv.Optional(CONF_EFFECT): cv.string, + cv.Optional(CONF_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_RED): cv.percentage, + cv.Optional(CONF_GREEN): cv.percentage, + cv.Optional(CONF_BLUE): cv.percentage, + cv.Optional(CONF_BRIGHTNESS): cv.percentage, + cv.Optional(CONF_TRANSITION_LENGTH): cv.positive_time_period_milliseconds, + } + ), + validate_preset ) CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend(