A HUB component was introduced. This HUB component has all the knowledge about the Yeelight Bedside Lamp 2 hardware. It known what pins are used, that PWM frequencies to use, what pins to switch in binary mode, etc. etc. No configuration is required for this HUB component. It's automatically loaded when the light component is loaded. The light component will use the HUB component to access the pins that are required for driving the LED circuitry. Note that this simplifies the configuration by A LOT. There's no need anymore to configure the pinouts in the YAML file. This is a logical route to take, since we're talking about a factory-produced PCB with a soldered on ESP32 chip, which uses the same GPIO's and settings on all produced devices (I presume). It would be quite redundant to force every user into configuring these pinouts themselves. ** Beware to update your device yaml configuration ** There are a few pinouts left to move into the HUB. I will do that in the next commit. Your device yaml configuration can be simplified along with these changes. Some of the keys in the existing light configuration block will no longer work and will have to be removed (red, green, blue, white). ** Further development ** The HUB will be extended make it the central component that also handles the I2C communication. This way, there is a central place to regulate the traffic to and from the front panel. We will be able to build upon this by implementing extra, fully separated components that handle for example the front panel light level, the power button, the color button and the slider. ** Interrupt handler for the I2C IRQ trigger pin ** One requirement for the I2C communication has already been implemented: an interrupt handler for the GPIO that is used by the front panel to signal the ESP that a new touch or release event is avilable to be read. It doens't do anything functionally right now, but if you watch the log file, you will see that touch events are detected and that they trigger some log messages.pull/4/head
@ -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"] | 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) |
@ -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 |