diff --git a/__init__.py b/__init__.py index 00c85c7..1d57ba2 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1,119 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import output +from esphome.components.ledc.output import LEDCOutput, validate_frequency +from esphome.core import coroutine +from esphome.core import CORE +from esphome.config import get_platform +from esphome.const import ( + CONF_ID, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_WHITE, + CONF_TRIGGER_PIN, + CONF_OUTPUT_ID, + CONF_TRIGGER_ID, + CONF_PIN, + CONF_FREQUENCY, + CONF_CHANNEL +) + +CONF_HUB_ID = "yeelight_bs2_hub_id" +CONF_RED_ID = "red_id" +CONF_GREEN_ID = "green_id" +CONF_BLUE_ID = "blue_id" +CONF_WHITE_ID = "white_id" +CONF_MASTER1 = "master1" +CONF_MASTER1_ID = "master1_id" +CONF_MASTER2 = "master2" +CONF_MASTER2_ID = "master2_id" +CONF_ON_BRIGHTNESS = "on_brightness" + CODEOWNERS = ["@mmakaay"] + +AUTO_LOAD = ["ledc"] + +yeelight_ns = cg.esphome_ns.namespace("yeelight") +bs2_ns = yeelight_ns.namespace("bs2") +YeelightBS2Hub = bs2_ns.class_("YeelightBS2Hub", cg.Component) + +LEDC_PINS = { + # Config key ID PIN FREQ CH + CONF_RED : ( CONF_RED_ID, "GPIO13", 3000, 0 ), + CONF_GREEN : ( CONF_GREEN_ID, "GPIO14", 3000, 1 ), + CONF_BLUE : ( CONF_BLUE_ID, "GPIO5", 3000, 2 ), + CONF_WHITE : ( CONF_WHITE_ID, "GPIO12", 10000, 4 ), +} + +def make_config_schema(): + schema = cv.COMPONENT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(YeelightBS2Hub), + cv.Optional(CONF_TRIGGER_PIN, default="GPIO16"): cv.All( + pins.validate_gpio_pin, + pins.validate_has_interrupt + ), + }) + + for key, pin_config in LEDC_PINS.items(): + id_, pin, *_ = pin_config + schema = schema.extend({ + cv.GenerateID(id_): cv.declare_id(LEDCOutput), + cv.Optional(key, default=pin): pins.validate_gpio_pin + }) + + return schema; + + +CONFIG_SCHEMA = make_config_schema() + +@coroutine +def make_ledc_pin(key, config): + id_, _, frequency, channel = LEDC_PINS[key] + gpio_var = yield cg.gpio_pin_expression({ + "number": config[key], + "mode": "OUTPUT" + }); + ledc_var = cg.new_Pvariable(config[id_], gpio_var) + cg.add(ledc_var.set_frequency(frequency)); + cg.add(ledc_var.set_channel(channel)); + yield from cg.register_component(ledc_var, {}) # TODO last arg? + +def to_code(config): + hub_var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(hub_var, config) + + for key in LEDC_PINS: + ledc_pin = yield make_ledc_pin(key, config) + setter = getattr(hub_var, "set_%s_pin" % key) + cg.add(setter(ledc_pin)) + + trigger_pin = yield cg.gpio_pin_expression({ + "number": config[CONF_TRIGGER_PIN], + "mode": "INPUT", + "inverted": False + }) + cg.add(hub_var.set_trigger_pin(trigger_pin)) +# +# led_white = yield cg.get_variable(config[CONF_WHITE]) +# cg.add(var.set_white_output(led_white)) +# +# led_red = yield cg.get_variable(config[CONF_RED]) +# cg.add(var.set_red_output(led_red)) +# +# led_green = yield cg.get_variable(config[CONF_GREEN]) +# cg.add(var.set_green_output(led_green)) +# +# led_blue = yield cg.get_variable(config[CONF_BLUE]) +# cg.add(var.set_blue_output(led_blue)) +# +# master1 = yield cg.get_variable(config[CONF_MASTER1]) +# cg.add(var.set_master1_output(master1)) +# +# master2 = yield cg.get_variable(config[CONF_MASTER2]) +# cg.add(var.set_master2_output(master2)) +# +# for conf in config.get(CONF_ON_BRIGHTNESS, []): +# trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) +# yield automation.build_automation(trigger, [(float, "x")], conf) diff --git a/light/common.h b/common.h similarity index 84% rename from light/common.h rename to common.h index b0aafb3..3babcaf 100644 --- a/light/common.h +++ b/common.h @@ -4,9 +4,8 @@ namespace esphome { namespace yeelight { namespace bs2 { -/** A tag, used for logging. */ static const char *TAG = "yeelight_bs2"; - + } // namespace bs2 } // namespace yeelight } // namespace esphome diff --git a/doc/example.yaml b/doc/example.yaml index e417259..4705d1e 100644 --- a/doc/example.yaml +++ b/doc/example.yaml @@ -35,10 +35,6 @@ esphome: light: - platform: yeelight_bs2 name: ${friendly_name} RGBW Light - red: led_red - green: led_green - blue: led_blue - white: led_white master1: master1 master2: master2 default_transition_length: ${transition_length} @@ -73,39 +69,10 @@ i2c: scl: GPIO19 scan: True -# The device uses six GPIO pins for driving the LED circuitry. output: - # master1 + master2 are used for turning on and off the - # LED circuitry in the lamp. So these are light the - # internal light switch. - platform: gpio id: master1 pin: GPIO33 - platform: gpio id: master2 pin: GPIO4 - # The following ledc outputs are used to drive the color of the light - # in two different modes: white light mode (using color temperature - # to set the type of white light) and RGB light (using an RGB mix). - # Note: it is important to include the channels and frequencies as-is. - - platform: ledc - id: led_red - pin: GPIO13 - channel: 0 - frequency: 3kHz - - platform: ledc - id: led_green - pin: GPIO14 - channel: 1 - frequency: 3kHz - - platform: ledc - id: led_blue - pin: GPIO5 - channel: 2 - frequency: 3kHz - - platform: ledc - id: led_white - pin: GPIO12 - channel: 4 - frequency: 10kHz - diff --git a/light/__init__.py b/light/__init__.py index 12f2c67..87d9be3 100644 --- a/light/__init__.py +++ b/light/__init__.py @@ -4,13 +4,14 @@ import esphome.components.gpio.output as gpio_output from esphome.components import light, gpio, ledc from esphome.const import CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_OUTPUT_ID, CONF_TRIGGER_ID from esphome import automation +from .. import bs2_ns, CODEOWNERS, CONF_HUB_ID, YeelightBS2Hub + +AUTO_LOAD = ["yeelight_bs2"] CONF_MASTER1 = "master1" CONF_MASTER2 = "master2" CONF_ON_BRIGHTNESS = "on_brightness" -yeelight_ns = cg.esphome_ns.namespace("yeelight") -bs2_ns = yeelight_ns.namespace("bs2") light_state = bs2_ns.class_("YeelightBS2LightState", light.LightState) light_output = bs2_ns.class_("YeelightBS2LightOutput", light.LightOutput) @@ -19,15 +20,10 @@ BrightnessTrigger = bs2_ns.class_("BrightnessTrigger", automation.Trigger.templa CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(light_state), + cv.GenerateID(CONF_HUB_ID): cv.use_id(YeelightBS2Hub), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(light_output), - cv.Required(CONF_RED): cv.use_id(ledc), - cv.Required(CONF_GREEN): cv.use_id(ledc), - cv.Required(CONF_BLUE): cv.use_id(ledc), - cv.Required(CONF_WHITE): cv.use_id(ledc), - cv.Required(CONF_WHITE): cv.use_id(ledc), cv.Required(CONF_MASTER1): cv.use_id(gpio_output.GPIOBinaryOutput), cv.Required(CONF_MASTER2): cv.use_id(gpio_output.GPIOBinaryOutput), - cv.Optional(CONF_ON_BRIGHTNESS): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BrightnessTrigger), @@ -40,17 +36,8 @@ def to_code(config): var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) yield light.register_light(var, config) - led_white = yield cg.get_variable(config[CONF_WHITE]) - cg.add(var.set_white_output(led_white)) - - led_red = yield cg.get_variable(config[CONF_RED]) - cg.add(var.set_red_output(led_red)) - - led_green = yield cg.get_variable(config[CONF_GREEN]) - cg.add(var.set_green_output(led_green)) - - led_blue = yield cg.get_variable(config[CONF_BLUE]) - cg.add(var.set_blue_output(led_blue)) + hub_var = yield cg.get_variable(config[CONF_HUB_ID]) + cg.add(var.set_hub(hub_var)) master1 = yield cg.get_variable(config[CONF_MASTER1]) cg.add(var.set_master1_output(master1)) diff --git a/light/color_instant_handler.h b/light/color_instant_handler.h index b3c23a6..44194ab 100644 --- a/light/color_instant_handler.h +++ b/light/color_instant_handler.h @@ -3,7 +3,7 @@ #include #include -#include "common.h" +#include "../common.h" #include "gpio_outputs.h" #include "color_off.h" #include "color_night_light.h" diff --git a/light/color_night_light.h b/light/color_night_light.h index 6de8a4c..fa3efc4 100644 --- a/light/color_night_light.h +++ b/light/color_night_light.h @@ -1,6 +1,6 @@ #pragma once -#include "common.h" +#include "../common.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/color_off.h b/light/color_off.h index 23c36d5..0230d38 100644 --- a/light/color_off.h +++ b/light/color_off.h @@ -3,7 +3,7 @@ #include #include -#include "common.h" +#include "../common.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/color_rgb_light.h b/light/color_rgb_light.h index 18b25da..f7ff10c 100644 --- a/light/color_rgb_light.h +++ b/light/color_rgb_light.h @@ -3,7 +3,7 @@ #include #include -#include "common.h" +#include "../common.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/color_transition_handler.h b/light/color_transition_handler.h index e6de4d9..3e386e6 100644 --- a/light/color_transition_handler.h +++ b/light/color_transition_handler.h @@ -1,6 +1,6 @@ #pragma once -#include "common.h" +#include "../common.h" #include "gpio_outputs.h" #include "color_instant_handler.h" diff --git a/light/color_white_light.h b/light/color_white_light.h index 69008e9..a74209e 100644 --- a/light/color_white_light.h +++ b/light/color_white_light.h @@ -3,7 +3,7 @@ #include #include -#include "common.h" +#include "../common.h" #include "gpio_outputs.h" namespace esphome { diff --git a/light/light_output.h b/light/light_output.h index 8cf78bb..9c75560 100644 --- a/light/light_output.h +++ b/light/light_output.h @@ -1,8 +1,10 @@ #pragma once -#include "common.h" +#include "../common.h" +#include "../yeelight_bs2_hub.h" #include "color_instant_handler.h" #include "color_transition_handler.h" +#include "esphome/components/ledc/ledc_output.h" namespace esphome { namespace yeelight { @@ -18,17 +20,8 @@ namespace bs2 { */ class YeelightBS2LightOutput : public Component, public light::LightOutput { public: - /** Sets the LEDC output for the red LED circuitry channel. */ - void set_red_output(ledc::LEDCOutput *red) { red_ = red; } - - /** Sets the LEDC output for the green LED circuitry channel. */ - void set_green_output(ledc::LEDCOutput *green) { green_ = green; } - - /** Sets the LEDC output for the blue LED circuitry channel. */ - void set_blue_output(ledc::LEDCOutput *blue) { blue_ = blue; } - - /** Sets the LEDC output for the white LED circuitry channel. */ - void set_white_output(ledc::LEDCOutput *white) { white_ = white; } + /** Sets the Yeelight BS2 hub component. */ + void set_hub(YeelightBS2Hub *hub) { hub_ = hub; } /** * Sets the first GPIO binary output, used as internal master switch for @@ -94,10 +87,10 @@ public: } // Apply the current GPIO output levels from the selected handler. - red_->set_level(delegate->red); - green_->set_level(delegate->green); - blue_->set_level(delegate->blue); - white_->set_level(delegate->white); + hub_->red->set_level(delegate->red); + hub_->green->set_level(delegate->green); + hub_->blue->set_level(delegate->blue); + hub_->white->set_level(delegate->white); if (values.get_state() == 0) { @@ -109,10 +102,7 @@ public: } protected: - ledc::LEDCOutput *red_; - ledc::LEDCOutput *green_; - ledc::LEDCOutput *blue_; - ledc::LEDCOutput *white_; + YeelightBS2Hub *hub_; esphome::gpio::GPIOBinaryOutput *master1_; esphome::gpio::GPIOBinaryOutput *master2_; GPIOOutputs *transition_handler_; diff --git a/yeelight_bs2_hub.h b/yeelight_bs2_hub.h new file mode 100644 index 0000000..176b0d0 --- /dev/null +++ b/yeelight_bs2_hub.h @@ -0,0 +1,77 @@ +#pragma once + +#include "common.h" +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" +#include "esphome/components/ledc/ledc_output.h" + +namespace esphome { +namespace yeelight { +namespace bs2 { + +struct TriggerPinStore { + volatile int queue_length = 0; + static void isr(TriggerPinStore *store); +}; + +/** + * This ISR is used to handle IRQ triggers from the front panel. + * + * The front panel pulls the trigger pin low when a new event + * is available. All we do here to handle the interrupt, is + * increment a simple queue length counter. Reading the event + * from the I2C bus will be handled in the main loop, based + * on this counter. + */ +void ICACHE_RAM_ATTR HOT TriggerPinStore::isr(TriggerPinStore *store) { + store->queue_length++; +} + +class YeelightBS2Hub : public Component { +public: + // Pins that are used for the RGBWW light. + ledc::LEDCOutput *red; + ledc::LEDCOutput *green; + ledc::LEDCOutput *blue; + ledc::LEDCOutput *white; + + void set_trigger_pin(GPIOPin *pin) { i2c_trigger_pin_ = pin; } + void set_red_pin(ledc::LEDCOutput *pin) { red = pin; } + void set_green_pin(ledc::LEDCOutput *pin) { green = pin; } + void set_blue_pin(ledc::LEDCOutput *pin) { blue = pin; } + void set_white_pin(ledc::LEDCOutput *pin) { white = pin; } + + void setup() { + ESP_LOGCONFIG(TAG, "Setting up I2C trigger pin interrupt..."); + this->i2c_trigger_pin_->setup(); + this->i2c_trigger_pin_->attach_interrupt( + TriggerPinStore::isr, &this->trigger_pin_store_, FALLING); + } + + void dump_config() { + ESP_LOGCONFIG(TAG, "I2C"); + LOG_PIN(" Interrupt pin: ", this->i2c_trigger_pin_); + } + + void loop() { + if (this->trigger_pin_store_.queue_length > 0) { + this->counter_++; + ESP_LOGD(TAG, "Front Panel interrupt queue=%d, counter=%d", + this->trigger_pin_store_.queue_length, this->counter_); + this->trigger_pin_store_.queue_length--; + } + } + +protected: + // Pin that is used by the front panel to notify the ESP that + // a touch/release event can be read using I2C. + GPIOPin *i2c_trigger_pin_; + + // Fields that are used for trigger pin interrupt handling. + int counter_ = 0; + TriggerPinStore trigger_pin_store_{}; +}; + +} // namespace bs2 +} // namespace yeelight +} // namespace esphome