Browse Source

Introduced a HUB component + front panel IRQ handling

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
Maurice Makaay 3 years ago
parent
commit
e29968d86a
12 changed files with 218 additions and 80 deletions
  1. +118
    -0
      __init__.py
  2. +1
    -2
      common.h
  3. +0
    -33
      doc/example.yaml
  4. +6
    -19
      light/__init__.py
  5. +1
    -1
      light/color_instant_handler.h
  6. +1
    -1
      light/color_night_light.h
  7. +1
    -1
      light/color_off.h
  8. +1
    -1
      light/color_rgb_light.h
  9. +1
    -1
      light/color_transition_handler.h
  10. +1
    -1
      light/color_white_light.h
  11. +10
    -20
      light/light_output.h
  12. +77
    -0
      yeelight_bs2_hub.h

+ 118
- 0
__init__.py View File

@ -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)

light/common.h → common.h View File

@ -4,9 +4,8 @@ namespace esphome {
namespace yeelight { namespace yeelight {
namespace bs2 { namespace bs2 {
/** A tag, used for logging. */
static const char *TAG = "yeelight_bs2"; static const char *TAG = "yeelight_bs2";
} // namespace bs2 } // namespace bs2
} // namespace yeelight } // namespace yeelight
} // namespace esphome } // namespace esphome

+ 0
- 33
doc/example.yaml View File

@ -35,10 +35,6 @@ esphome:
light: light:
- platform: yeelight_bs2 - platform: yeelight_bs2
name: ${friendly_name} RGBW Light name: ${friendly_name} RGBW Light
red: led_red
green: led_green
blue: led_blue
white: led_white
master1: master1 master1: master1
master2: master2 master2: master2
default_transition_length: ${transition_length} default_transition_length: ${transition_length}
@ -73,39 +69,10 @@ i2c:
scl: GPIO19 scl: GPIO19
scan: True scan: True
# The device uses six GPIO pins for driving the LED circuitry.
output: 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 - platform: gpio
id: master1 id: master1
pin: GPIO33 pin: GPIO33
- platform: gpio - platform: gpio
id: master2 id: master2
pin: GPIO4 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

+ 6
- 19
light/__init__.py View File

@ -4,13 +4,14 @@ import esphome.components.gpio.output as gpio_output
from esphome.components import light, gpio, ledc 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.const import CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_OUTPUT_ID, CONF_TRIGGER_ID
from esphome import automation from esphome import automation
from .. import bs2_ns, CODEOWNERS, CONF_HUB_ID, YeelightBS2Hub
AUTO_LOAD = ["yeelight_bs2"]
CONF_MASTER1 = "master1" CONF_MASTER1 = "master1"
CONF_MASTER2 = "master2" CONF_MASTER2 = "master2"
CONF_ON_BRIGHTNESS = "on_brightness" 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_state = bs2_ns.class_("YeelightBS2LightState", light.LightState)
light_output = bs2_ns.class_("YeelightBS2LightOutput", light.LightOutput) 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( CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(light_state), 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.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_MASTER1): cv.use_id(gpio_output.GPIOBinaryOutput),
cv.Required(CONF_MASTER2): 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.Optional(CONF_ON_BRIGHTNESS): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BrightnessTrigger), 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]) var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
yield light.register_light(var, config) 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]) master1 = yield cg.get_variable(config[CONF_MASTER1])
cg.add(var.set_master1_output(master1)) cg.add(var.set_master1_output(master1))


+ 1
- 1
light/color_instant_handler.h View File

@ -3,7 +3,7 @@
#include <array> #include <array>
#include <stdexcept> #include <stdexcept>
#include "common.h"
#include "../common.h"
#include "gpio_outputs.h" #include "gpio_outputs.h"
#include "color_off.h" #include "color_off.h"
#include "color_night_light.h" #include "color_night_light.h"


+ 1
- 1
light/color_night_light.h View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "common.h"
#include "../common.h"
#include "gpio_outputs.h" #include "gpio_outputs.h"
namespace esphome { namespace esphome {


+ 1
- 1
light/color_off.h View File

@ -3,7 +3,7 @@
#include <array> #include <array>
#include <stdexcept> #include <stdexcept>
#include "common.h"
#include "../common.h"
#include "gpio_outputs.h" #include "gpio_outputs.h"
namespace esphome { namespace esphome {


+ 1
- 1
light/color_rgb_light.h View File

@ -3,7 +3,7 @@
#include <array> #include <array>
#include <cmath> #include <cmath>
#include "common.h"
#include "../common.h"
#include "gpio_outputs.h" #include "gpio_outputs.h"
namespace esphome { namespace esphome {


+ 1
- 1
light/color_transition_handler.h View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "common.h"
#include "../common.h"
#include "gpio_outputs.h" #include "gpio_outputs.h"
#include "color_instant_handler.h" #include "color_instant_handler.h"


+ 1
- 1
light/color_white_light.h View File

@ -3,7 +3,7 @@
#include <array> #include <array>
#include <stdexcept> #include <stdexcept>
#include "common.h"
#include "../common.h"
#include "gpio_outputs.h" #include "gpio_outputs.h"
namespace esphome { namespace esphome {


+ 10
- 20
light/light_output.h View File

@ -1,8 +1,10 @@
#pragma once #pragma once
#include "common.h"
#include "../common.h"
#include "../yeelight_bs2_hub.h"
#include "color_instant_handler.h" #include "color_instant_handler.h"
#include "color_transition_handler.h" #include "color_transition_handler.h"
#include "esphome/components/ledc/ledc_output.h"
namespace esphome { namespace esphome {
namespace yeelight { namespace yeelight {
@ -18,17 +20,8 @@ namespace bs2 {
*/ */
class YeelightBS2LightOutput : public Component, public light::LightOutput { class YeelightBS2LightOutput : public Component, public light::LightOutput {
public: 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 * 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. // 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) if (values.get_state() == 0)
{ {
@ -109,10 +102,7 @@ public:
} }
protected: protected:
ledc::LEDCOutput *red_;
ledc::LEDCOutput *green_;
ledc::LEDCOutput *blue_;
ledc::LEDCOutput *white_;
YeelightBS2Hub *hub_;
esphome::gpio::GPIOBinaryOutput *master1_; esphome::gpio::GPIOBinaryOutput *master1_;
esphome::gpio::GPIOBinaryOutput *master2_; esphome::gpio::GPIOBinaryOutput *master2_;
GPIOOutputs *transition_handler_; GPIOOutputs *transition_handler_;


+ 77
- 0
yeelight_bs2_hub.h View File

@ -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

Loading…
Cancel
Save