Browse Source

Compatility 2021.10.0 (#57)

Made the lamp work with ESPHome 2021.10.0
pull/59/head
Maurice Makaay 3 years ago
committed by GitHub
parent
commit
6d83f80718
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 302 additions and 206 deletions
  1. +34
    -84
      components/xiaomi_bslamp2/__init__.py
  2. +1
    -1
      components/xiaomi_bslamp2/binary_sensor/__init__.py
  3. +58
    -37
      components/xiaomi_bslamp2/front_panel_hal.h
  4. +19
    -25
      components/xiaomi_bslamp2/light/__init__.py
  5. +4
    -3
      components/xiaomi_bslamp2/light/color_handler_rgb.h
  6. +1
    -1
      components/xiaomi_bslamp2/light/light_state.h
  7. +4
    -3
      components/xiaomi_bslamp2/output/__init__.py
  8. +1
    -1
      components/xiaomi_bslamp2/sensor/__init__.py
  9. +2
    -1
      components/xiaomi_bslamp2/sensor/slider_sensor.h
  10. +90
    -50
      example.yaml
  11. +88
    -0
      packages/core.yaml

+ 34
- 84
components/xiaomi_bslamp2/__init__.py View File

@ -3,31 +3,19 @@ import esphome.config_validation as cv
from esphome import pins
from esphome.components.ledc.output import LEDCOutput
from esphome.components.gpio.output import GPIOBinaryOutput
from esphome.components.i2c import I2CComponent, I2CDevice
from esphome.core import coroutine
from esphome.core import CORE
from esphome.components.i2c import I2CBus, I2CDevice
from esphome.const import (
CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_TRIGGER_PIN,
CONF_SDA, CONF_SCL, CONF_ADDRESS, CONF_PLATFORM
CONF_LIGHT, CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE,
CONF_I2C, CONF_ADDRESS, CONF_TRIGGER_PIN, CONF_ID
)
CODEOWNERS = ["@mmakaay"]
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_FP_I2C_ID = "front_panel_i2c_id"
CONF_FRONT_PANEL = "front_panel"
CONF_LIGHT_HAL_ID = "light_hal_id"
CONF_FRONT_PANEL_HAL_ID = "front_panel_hal_id"
CONF_ON_BRIGHTNESS = "on_brightness"
CONF_LEDS = "leds"
AUTO_LOAD = ["ledc", "output", "i2c"]
xiaomi_ns = cg.esphome_ns.namespace("xiaomi")
bslamp2_ns = xiaomi_ns.namespace("bslamp2")
@ -54,86 +42,48 @@ FRONT_PANEL_LED_OPTIONS = {
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,
cv.Required(CONF_LIGHT): cv.Schema(
{
cv.GenerateID(CONF_LIGHT_HAL_ID): cv.declare_id(LightHAL),
cv.Required(CONF_RED): cv.use_id(LEDCOutput),
cv.Required(CONF_GREEN): cv.use_id(LEDCOutput),
cv.Required(CONF_BLUE): cv.use_id(LEDCOutput),
cv.Required(CONF_WHITE): cv.use_id(LEDCOutput),
cv.Required(CONF_MASTER1): cv.use_id(GPIOBinaryOutput),
cv.Required(CONF_MASTER2): cv.use_id(GPIOBinaryOutput),
}
),
# 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
cv.Required(CONF_FRONT_PANEL): cv.Schema(
{
cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.declare_id(FrontPanelHAL),
cv.Required(CONF_I2C): cv.use_id(I2CBus),
cv.Required(CONF_ADDRESS): cv.i2c_address,
cv.Required(CONF_TRIGGER_PIN): cv.All(pins.internal_gpio_input_pin_schema)
}
),
})
async def make_gpio(number, mode="OUTPUT"):
return await cg.gpio_pin_expression({ "number": number, "mode": mode });
async def make_gpio_binary_output(id_, number):
gpio_var = await make_gpio(number)
output_var = cg.new_Pvariable(id_)
cg.add(output_var.set_pin(gpio_var))
return await cg.register_component(output_var, {})
async def make_ledc_output(id_, number, frequency, channel):
gpio_var = await make_gpio(number)
ledc_var = cg.new_Pvariable(id_, gpio_var)
cg.add(ledc_var.set_frequency(frequency));
cg.add(ledc_var.set_channel(channel));
return await cg.register_component(ledc_var, {})
async def make_light_hal(config):
r_var = await make_ledc_output(config[CONF_RED_ID], config[CONF_RED], 3000, 0)
g_var = await make_ledc_output(config[CONF_GREEN_ID], config[CONF_GREEN], 3000, 1)
b_var = await make_ledc_output(config[CONF_BLUE_ID], config[CONF_BLUE], 3000, 2)
w_var = await make_ledc_output(config[CONF_WHITE_ID], config[CONF_WHITE], 10000, 4)
m1_var = await make_gpio_binary_output(config[CONF_MASTER1_ID], config[CONF_MASTER1])
m2_var = await make_gpio_binary_output(config[CONF_MASTER2_ID], config[CONF_MASTER2])
light_hal = cg.new_Pvariable(config[CONF_LIGHT_HAL_ID])
light_hal = cg.new_Pvariable(config[CONF_LIGHT][CONF_LIGHT_HAL_ID])
await cg.register_component(light_hal, config)
cg.add(light_hal.set_red_pin(r_var))
cg.add(light_hal.set_green_pin(g_var))
cg.add(light_hal.set_blue_pin(b_var))
cg.add(light_hal.set_white_pin(w_var))
cg.add(light_hal.set_master1_pin(m1_var))
cg.add(light_hal.set_master2_pin(m2_var))
cg.add(light_hal.set_red_pin(await cg.get_variable(config[CONF_LIGHT][CONF_RED])))
cg.add(light_hal.set_green_pin(await cg.get_variable(config[CONF_LIGHT][CONF_GREEN])))
cg.add(light_hal.set_blue_pin(await cg.get_variable(config[CONF_LIGHT][CONF_BLUE])))
cg.add(light_hal.set_white_pin(await cg.get_variable(config[CONF_LIGHT][CONF_WHITE])))
cg.add(light_hal.set_master1_pin(await cg.get_variable(config[CONF_LIGHT][CONF_MASTER1])))
cg.add(light_hal.set_master2_pin(await cg.get_variable(config[CONF_LIGHT][CONF_MASTER2])))
async def make_front_panel_hal(config):
trigger_pin = await make_gpio(config[CONF_TRIGGER_PIN], "INPUT")
fp_hal = cg.new_Pvariable(config[CONF_FRONT_PANEL_HAL_ID])
fp_hal = cg.new_Pvariable(config[CONF_FRONT_PANEL][CONF_FRONT_PANEL_HAL_ID])
await cg.register_component(fp_hal, config)
trigger_pin = await cg.gpio_pin_expression(config[CONF_FRONT_PANEL][CONF_TRIGGER_PIN])
cg.add(fp_hal.set_trigger_pin(trigger_pin))
# The i2c component automatically sets up one I2C bus.
# Take that bus and update is to make it work for the
# front panel I2C communication.
fp_i2c_var = await cg.get_variable(config[CONF_FP_I2C_ID])
cg.add(fp_i2c_var.set_sda_pin(config[CONF_SDA]))
cg.add(fp_i2c_var.set_scl_pin(config[CONF_SCL]))
cg.add(fp_i2c_var.set_scan(True))
cg.add(fp_hal.set_i2c_parent(fp_i2c_var))
cg.add(fp_hal.set_i2c_address(config[CONF_ADDRESS]))
fp_i2c_var = await cg.get_variable(config[CONF_FRONT_PANEL][CONF_I2C])
cg.add(fp_hal.set_i2c_bus(fp_i2c_var))
cg.add(fp_hal.set_i2c_address(config[CONF_FRONT_PANEL][CONF_ADDRESS]))
async def to_code(config):
# Dirty little hack to make the ESPHome component loader include
# the code for the "gpio" platform for the "output" domain.
# Loading specific platform components is not possible using
# the AUTO_LOAD feature unfortunately.
CORE.config["output"].append({ CONF_PLATFORM: "gpio" })
await make_light_hal(config)
await make_front_panel_hal(config)

+ 1
- 1
components/xiaomi_bslamp2/binary_sensor/__init__.py View File

@ -8,7 +8,7 @@ from .. import (
CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL
)
AUTO_LOAD = ["xiaomi_bslamp2"]
DEPENDENCIES = ["xiaomi_bslamp2"]
CONF_PART = "part"


+ 58
- 37
components/xiaomi_bslamp2/front_panel_hal.h View File

@ -3,7 +3,7 @@
#include "common.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/core/log.h"
#include <array>
#include <cmath>
@ -100,7 +100,7 @@ class FrontPanelEventParser {
// All events use the prefix [04:04:01:00].
if (m[0] != 0x04 || m[1] != 0x04 || m[2] != 0x01 || m[3] != 0x00) {
return error_(ev, m, "prefix is not 04:04:01:00");
return this->error_(ev, m, "prefix is not 04:04:01:00");
}
// The next byte determines the part that is touched.
@ -114,23 +114,23 @@ class FrontPanelEventParser {
else if (m[5] == 0x02 && m[6] == (0x03 + m[4]))
ev |= FLAG_TYPE_RELEASE;
else
return error_(ev, m, "invalid event type for button");
return this->error_(ev, m, "invalid event type for button");
break;
case 0x03: // slider touch
case 0x04: // slider release
ev |= FLAG_PART_SLIDER;
ev |= (m[4] == 0x03 ? FLAG_TYPE_TOUCH : FLAG_TYPE_RELEASE);
if ((m[6] - m[5] - m[4] - 0x01) != 0)
return error_(ev, m, "invalid slider level crc");
return this->error_(ev, m, "invalid slider level crc");
else if (m[5] > 0x16 || m[5] < 0x01)
return error_(ev, m, "out of bounds slider value");
return this->error_(ev, m, "out of bounds slider value");
else {
auto level = 0x17 - m[5];
ev |= (level << FLAG_LEVEL_SHIFT);
}
break;
default:
return error_(ev, m, "invalid part id");
return this->error_(ev, m, "invalid part id");
return ev;
}
@ -148,8 +148,8 @@ class FrontPanelEventParser {
ESP_LOGE(TAG, "Front panel I2C event error:");
ESP_LOGE(TAG, " Error: %s", msg);
ESP_LOGE(TAG, " Event: [%02x:%02x:%02x:%02x:%02x:%02x:%02x]", m[0], m[1], m[2], m[3], m[4], m[5], m[6]);
ESP_LOGE(TAG, " Parsed part: %s", format_part(ev));
ESP_LOGE(TAG, " Parsed event type: %s", format_event_type(ev));
ESP_LOGE(TAG, " Parsed part: %s", this->format_part_(ev));
ESP_LOGE(TAG, " Parsed event type: %s", this->format_event_type_(ev));
if (has_(ev, FLAG_PART_MASK, FLAG_PART_SLIDER)) {
auto level = (ev & FLAG_LEVEL_MASK) >> FLAG_LEVEL_SHIFT;
if (level > 0) {
@ -160,7 +160,7 @@ class FrontPanelEventParser {
return ev;
}
const char *format_part(EVENT ev) {
const char *format_part_(EVENT ev) {
if (has_(ev, FLAG_PART_MASK, FLAG_PART_POWER))
return "power button";
if (has_(ev, FLAG_PART_MASK, FLAG_PART_COLOR))
@ -170,7 +170,7 @@ class FrontPanelEventParser {
return "n/a";
}
const char *format_event_type(EVENT ev) {
const char *format_event_type_(EVENT ev) {
if (has_(ev, FLAG_TYPE_MASK, FLAG_TYPE_TOUCH))
return "touch";
if (has_(ev, FLAG_TYPE_MASK, FLAG_TYPE_RELEASE))
@ -179,6 +179,23 @@ class FrontPanelEventParser {
}
};
struct FrontPanelTriggerStore {
volatile int event_id{0};
static void gpio_intr(FrontPanelTriggerStore *store);
};
/**
* This ISR is used to handle IRQ triggers from the front panel.
*
* The front panel pulls the trigger pin low for a short period of time
* when a new event is available. All we do here to handle the interrupt,
* is increment a simple event id counter. The main loop of the component
* will take care of actually reading and processing the event.
*/
void IRAM_ATTR HOT FrontPanelTriggerStore::gpio_intr(FrontPanelTriggerStore *store) {
store->event_id++;
}
/**
* This is a hardware abstraction layer that communicates with with front
* panel of the Xiaomi Mijia Bedside Lamp 2.
@ -194,37 +211,52 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice {
* Set the GPIO pin that is used by the front panel to notify the ESP
* that a touch/release event can be read using I2C.
*/
void set_trigger_pin(GPIOPin *pin) { trigger_pin_ = pin; }
void set_trigger_pin(InternalGPIOPin *pin) {
trigger_pin_ = pin;
}
void add_on_event_callback(std::function<void(EVENT)> &&callback) { event_callback_.add(std::move(callback)); }
void add_on_event_callback(std::function<void(EVENT)> &&callback) {
event_callback_.add(std::move(callback));
}
void setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C trigger pin interrupt...");
trigger_pin_->setup();
trigger_pin_->attach_interrupt(FrontPanelHAL::isr, this, FALLING);
this->trigger_pin_->setup();
this->trigger_pin_->attach_interrupt(
FrontPanelTriggerStore::gpio_intr,
&this->store_,
gpio::INTERRUPT_FALLING_EDGE);
}
void dump_config() {
ESP_LOGCONFIG(TAG, "FrontPanelHAL:");
LOG_I2C_DEVICE(this);
LOG_PIN(" I2C interrupt pin: ", trigger_pin_);
}
void loop() {
// Read and publish front panel events.
auto current_event_id = event_id_;
if (current_event_id != last_event_id_) {
last_event_id_ = current_event_id;
auto current_event_id = this->store_.event_id;
if (current_event_id != this->last_event_id_) {
this->last_event_id_ = current_event_id;
if (this->write(READY_FOR_EV, MSG_LEN) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Writing READY_FOR_EV to front panel failed");
}
MSG message;
if (write_bytes_raw(READY_FOR_EV, MSG_LEN) && read_bytes_raw(message, MSG_LEN)) {
auto ev = event.parse(message);
if (ev & FLAG_OK) {
event_callback_.call(ev);
}
if (this->read(message, MSG_LEN) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Reading message from front panel failed");
return;
}
auto ev = event.parse(message);
if (ev & FLAG_OK) {
this->event_callback_.call(ev);
} else {
ESP_LOGW(TAG, "Skipping unsupported message from front panel");
}
}
if (led_state_ != last_led_state_) {
update_leds();
update_leds();
}
}
@ -268,7 +300,7 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice {
void update_leds() {
led_msg_[2] = led_state_ >> 8;
led_msg_[3] = led_state_ & 0xff;
write_bytes_raw(led_msg_, MSG_LEN);
write(led_msg_, MSG_LEN);
last_led_state_ = led_state_;
}
@ -298,9 +330,8 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice {
}
protected:
GPIOPin *trigger_pin_;
static void isr(FrontPanelHAL *store);
volatile int event_id_ = 0;
InternalGPIOPin *trigger_pin_;
FrontPanelTriggerStore store_{};
int last_event_id_ = 0;
CallbackManager<void(EVENT)> event_callback_{};
@ -309,16 +340,6 @@ class FrontPanelHAL : public Component, public i2c::I2CDevice {
MSG led_msg_ = {0x02, 0x03, 0x00, 0x00, 0x64, 0x00, 0x00};
};
/**
* This ISR is used to handle IRQ triggers from the front panel.
*
* The front panel pulls the trigger pin low for a short period of time
* when a new event is available. All we do here to handle the interrupt,
* is increment a simple event id counter. The main loop of the component
* will take care of actually reading and processing the event.
*/
void ICACHE_RAM_ATTR HOT FrontPanelHAL::isr(FrontPanelHAL *store) { store->event_id_++; }
} // namespace bslamp2
} // namespace xiaomi
} // namespace esphome

+ 19
- 25
components/xiaomi_bslamp2/light/__init__.py View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import light
from esphome import automation
from esphome.core import coroutine, Lambda
from esphome.core import Lambda
from esphome.const import (
CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, CONF_COLOR_TEMPERATURE,
CONF_STATE, CONF_OUTPUT_ID, CONF_TRIGGER_ID, CONF_ID,
@ -10,17 +10,15 @@ from esphome.const import (
)
from .. import bslamp2_ns, CODEOWNERS, CONF_LIGHT_HAL_ID, LightHAL
AUTO_LOAD = ["xiaomi_bslamp2"]
DEPENDENCIES = ["xiaomi_bslamp2"]
CONF_MASTER1 = "master1"
CONF_MASTER2 = "master2"
CONF_ON_BRIGHTNESS = "on_brightness"
CONF_PRESET_ID = "preset_id"
CONF_PRESETS_ID = "presets_id"
CONF_PRESET = "preset"
CONF_PRESETS = "presets"
CONF_NEXT = "next"
CONF_GROUP = "group"
CONF_PRESET = "preset"
MIRED_MIN = 153
MIRED_MAX = 588
@ -219,23 +217,20 @@ def preset_activate_to_code(config, action_id, template_arg, args):
cg.add(action_var.set_group(group_template_))
yield action_var
@coroutine
def light_output_to_code(config):
async def light_output_to_code(config):
light_output_var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
yield light.register_light(light_output_var, config)
light_hal_var = yield cg.get_variable(config[CONF_LIGHT_HAL_ID])
await light.register_light(light_output_var, config)
light_hal_var = await cg.get_variable(config[CONF_LIGHT_HAL_ID])
cg.add(light_output_var.set_parent(light_hal_var))
@coroutine
def on_brightness_to_code(config):
light_output_var = yield cg.get_variable(config[CONF_OUTPUT_ID])
async def on_brightness_to_code(config):
light_output_var = await cg.get_variable(config[CONF_OUTPUT_ID])
for config in config.get(CONF_ON_BRIGHTNESS, []):
trigger = cg.new_Pvariable(config[CONF_TRIGGER_ID], light_output_var)
yield automation.build_automation(trigger, [(float, "x")], config)
await automation.build_automation(trigger, [(float, "x")], config)
@coroutine
def preset_to_code(config, preset_group, preset_name):
light_var = yield cg.get_variable(config[CONF_ID])
async def preset_to_code(config, preset_group, preset_name):
light_var = await cg.get_variable(config[CONF_ID])
preset_var = cg.new_Pvariable(
config[CONF_PRESET_ID], light_var, preset_group, preset_name)
if CONF_TRANSITION_LENGTH in config:
@ -254,22 +249,21 @@ def preset_to_code(config, preset_group, preset_name):
cg.add(preset_var.set_effect(config[CONF_EFFECT]))
else:
cg.add(preset_var.set_effect("None"))
yield cg.register_component(preset_var, config)
return await cg.register_component(preset_var, config)
@coroutine
def presets_to_code(config):
async def presets_to_code(config):
presets_var = cg.new_Pvariable(config[CONF_PRESETS_ID])
yield cg.register_component(presets_var, config)
await cg.register_component(presets_var, config)
for preset_group, presets in config.get(CONF_PRESETS, {}).items():
for preset_name, preset_config in presets.items():
preset = yield preset_to_code(preset_config, preset_group, preset_name)
preset = await preset_to_code(preset_config, preset_group, preset_name)
cg.add(presets_var.add_preset(preset))
def to_code(config):
yield light_output_to_code(config)
yield on_brightness_to_code(config)
yield presets_to_code(config)
async def to_code(config):
await light_output_to_code(config)
await on_brightness_to_code(config)
await presets_to_code(config)
def validate(config):
valid_presets = config.get(CONF_PRESETS, {});


+ 4
- 3
components/xiaomi_bslamp2/light/color_handler_rgb.h View File

@ -2,6 +2,7 @@
#include <array>
#include <cmath>
#include <algorithm>
#include "../common.h"
#include "../light_hal.h"
@ -259,7 +260,7 @@ class ColorHandlerRGB : public ColorHandler {
// Determine the ring level for the color. This is a value between 0
// and 7, determining in what ring of the RGB circle the requested
// color resides.
auto rgb_min = min(min(v.get_red(), v.get_green()), v.get_blue());
auto rgb_min = std::min(std::min(v.get_red(), v.get_green()), v.get_blue());
auto level = 7.0f * rgb_min;
// While the default color circle in Home Assistant presents only a
@ -343,8 +344,8 @@ class ColorHandlerRGB : public ColorHandler {
* Returns the position on an RGB ring in degrees (0 - 359).
*/
float ring_pos_(float red, float green, float blue) {
auto rgb_min = min(min(red, green), blue);
auto rgb_max = max(max(red, green), blue);
auto rgb_min = std::min(std::min(red, green), blue);
auto rgb_max = std::max(std::max(red, green), blue);
auto delta = rgb_max - rgb_min;
float pos;
if (delta == 0.0f)


+ 1
- 1
components/xiaomi_bslamp2/light/light_state.h View File

@ -29,7 +29,7 @@ struct MyLightStateRTCState {
*/
class XiaomiBslamp2LightState : public light::LightState, public LightStateDiscoSupport {
public:
XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { }
XiaomiBslamp2LightState(XiaomiBslamp2LightOutput *output) : light::LightState(output) { }
void disco_stop() {
MyLightStateRTCState recovered{};


+ 4
- 3
components/xiaomi_bslamp2/output/__init__.py View File

@ -5,11 +5,12 @@ from esphome.const import CONF_ID, CONF_LEVEL
from esphome import automation
from .. import (
bslamp2_ns, CODEOWNERS,
CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL, FRONT_PANEL_LED_OPTIONS,
CONF_LEDS
CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL, FRONT_PANEL_LED_OPTIONS
)
AUTO_LOAD = ["xiaomi_bslamp2"]
CONF_LEDS = "leds"
DEPENDENCIES = ["xiaomi_bslamp2"]
XiaomiBslamp2FrontPanelOutput = bslamp2_ns.class_(
"XiaomiBslamp2FrontPanelOutput", output.FloatOutput, cg.Component)


+ 1
- 1
components/xiaomi_bslamp2/sensor/__init__.py View File

@ -7,7 +7,7 @@ from .. import (
CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL
)
AUTO_LOAD = ["xiaomi_bslamp2"]
DEPENDENCIES = ["xiaomi_bslamp2"]
XiaomiBslamp2SliderSensor = bslamp2_ns.class_(
"XiaomiBslamp2SliderSensor", sensor.Sensor, cg.Component)


+ 2
- 1
components/xiaomi_bslamp2/sensor/slider_sensor.h View File

@ -4,6 +4,7 @@
#include "../front_panel_hal.h"
#include "esphome/components/sensor/sensor.h"
#include <cmath>
#include <algorithm>
namespace esphome {
namespace xiaomi {
@ -37,7 +38,7 @@ class XiaomiBslamp2SliderSensor : public sensor::Sensor, public Component {
// look like this one was ever meant to be used, or that
// the design was faulty on this. Therefore, level 1 is
// ignored. The resulting range of levels is 0-19.
float corrected_level = max(0.0f, level - 2.0f);
float corrected_level = std::max(0.0f, level - 2.0f);
float final_level = range_from_ + (slope_ * corrected_level);


+ 90
- 50
example.yaml View File

@ -7,32 +7,25 @@ substitutions:
friendly_name: Bedside Lamp
transition_length: 500ms
# Component identifiers.
prefix: bedside_lamp
id_light: ${prefix}
id_light_mode: ${prefix}_light_mode
id_power_button: ${prefix}_power_button
id_color_button: ${prefix}_color_button
id_slider_level: ${prefix}_slider_level
id_front_panel_illumination: ${prefix}_front_panel_illumination
# --------------------------------------------------------------------------
# Use your own preferences for these components.
# --------------------------------------------------------------------------
# The log level can be raised when needed for debugging the firmware. For
# production, a low log level is recommended. Mainly because high volume log
# output might interfere with the API/WiFi connection stability. So when
# raising the log level, beware that you might see dropped connections from
# Home Assistant and the network log viewer.
logger:
level: WARN
wifi:
ssid: "Your-SSID"
password: "Your-WiFi-Network-Password"
# Enable fallback hotspot (for captive portal) in case wifi connection fails
ap:
ssid: "ESPHome $friendly_name"
password: "Password-For-Connecting-To-Captive-Portal"
captive_portal:
api:
password: "Password-To-Link-HomeAssistant-To-This-Device"
password: "Password-For-Linking-HomeAssistant-To-This-Device"
# Disable the reboot timeout. By default, the lamp reboots after 15
# minutes without any client connections (e.g. when home assistant is off
# line, or when the WiFi is broken). Reboots are annoying though, because
@ -40,7 +33,7 @@ api:
# flicker.
reboot_timeout: 0s
# If you want to use light presets (see below) from Home Assistant,
# If you want to control light presets (see below) from Home Assistant,
# then you can expose the required functionality as a service here.
# This is an example of how you could expose the activation of a preset.
services:
@ -65,7 +58,7 @@ ota:
on_begin:
then:
- light.disco_on:
id: ${id_light}
id: my_light
red: 0%
green: 0%
blue: 100%
@ -78,7 +71,7 @@ ota:
on_end:
then:
- light.disco_on:
id: ${id_light}
id: my_light
red: 0%
green: 100%
blue: 0%
@ -87,28 +80,22 @@ ota:
on_error:
then:
- light.disco_on:
id: ${id_light}
id: my_light
red: 100%
green: 0%
blue: 0%
brightness: 2%
- delay: 1s
- light.disco_off:
id: ${id_light}
# The log level can be raised when needed for debugging the firmware. For
# production, a low log level is recommended. Mainly because high volume log
# output might interfere with the API/WiFi connection stability. So when
# raising the log level, beware that you might see dropped connections from
# Home Assistant and the network log viewer.
logger:
level: WARN
id: my_light
# --------------------------------------------------------------------------
# Configuration specific for the Xiaomi Mijia Bedside Lamp 2.
# This is just an example. You can of course modify it for your own needs.
# --------------------------------------------------------------------------
esphome:
name: ${name}
# Retrieve the code for the xiaomi_bslamp2 platform from GitHub.
external_components:
- source:
@ -120,19 +107,72 @@ external_components:
# A special platform package is used for enabling unicore and disabling the
# efuse mac crc check. These two changes are required for the ESP32-WROOM-32D
# chip that is used in the lamp.
esphome:
name: ${name}
platform: ESP32
esp32:
board: esp32doit-devkit-v1
platformio_options:
platform: espressif32@3.2.0
platform_packages: |-
framework-arduinoespressif32 @ https://github.com/mmakaay/arduino-esp32-unicore-no-mac-crc#v1.0.6
framework:
type: arduino
platform_version: 3.3.2
version: https://github.com/mmakaay/arduino-esp32-unicore-no-mac-crc#v1.0.6
version_hint: 1.0.6
# The I2C bus that is used for communicating to the front panel.
i2c:
id: front_panel_i2c
sda: GPIO21
scl: GPIO19
scan: true
# Outputs for driving the LEDs of the lamp.
output:
- platform: ledc
id: output_red
pin: GPIO13
frequency: 3000
- platform: ledc
id: output_green
pin: GPIO14
frequency: 3000
- platform: ledc
id: output_blue
pin: GPIO5
frequency: 3000
- platform: ledc
id: output_white
pin: GPIO12
frequency: 10000
- platform: gpio
id: output_master1
pin: GPIO33
- platform: gpio
id: output_master2
pin: GPIO4
mode: OUTPUT
# The main configuration for the lamp. This sets up the two hardware
# abstraction layers for the light and the front panel. These are
# used by the other components.
xiaomi_bslamp2:
light:
red: output_red
green: output_green
blue: output_blue
white: output_white
master1: output_master1
master2: output_master2
front_panel:
i2c: front_panel_i2c
address: 0x2C
trigger_pin: GPIO16
# --------------------------------------------------------------------------
# Configuration of the behaviors for the lamp.
# This is just an example. You can of course modify it for your own needs.
# --------------------------------------------------------------------------
# This component controls the LED lights of the lamp.
light:
- platform: xiaomi_bslamp2
id: ${id_light}
id: my_light
name: ${friendly_name} RGBWW Light
default_transition_length: ${transition_length}
# When the brightness is changed, then update the level indicator
@ -142,15 +182,15 @@ light:
- if:
condition:
text_sensor.state:
id: ${id_light_mode}
id: my_light_mode
state: night
then:
- output.set_level:
id: ${id_front_panel_illumination}
id: my_front_panel_illumination
level: 0
else:
- output.set_level:
id: ${id_front_panel_illumination}
id: my_front_panel_illumination
level: !lambda return x;
# You can use any effects that you like. These are just examples.
effects:
@ -190,14 +230,14 @@ light:
text_sensor:
- platform: xiaomi_bslamp2
name: ${friendly_name} Light Mode
id: ${id_light_mode}
id: my_light_mode
# This float output controls the front panel illumination + level indicator.
# Value 0.0 turns off the illumination. Other values (up to 1.0) turn on
# the illumination and set the level indicator to the requested level.
output:
- platform: xiaomi_bslamp2
id: ${id_front_panel_illumination}
id: my_front_panel_illumination
# Binary sensors can be created for handling front panel touch / release
# events. To specify what part of the front panel to look at, the "for"
@ -206,24 +246,24 @@ binary_sensor:
# When tapping the power button, toggle the light.
# When holding the power button, turn on night light mode.
- platform: xiaomi_bslamp2
id: ${id_power_button}
id: my_power_button
for: POWER_BUTTON
on_multi_click:
- timing:
- ON for at most 0.8s
then:
- light.toggle: ${id_light}
- light.toggle: my_light
- timing:
- ON for at least 0.8s
then:
- light.turn_on:
id: ${id_light}
id: my_light
brightness: 1%
# When tapping the color button, acivate the next preset.
# When holding the color button, activate the next preset group.
- platform: xiaomi_bslamp2
id: ${id_color_button}
id: my_color_button
for: COLOR_BUTTON
on_multi_click:
- timing:
@ -247,11 +287,11 @@ sensor:
# been handled above (by holding the power button). Therefore, brightness
# starts from 0.02 here, to not trigger night mode using the slider.
- platform: xiaomi_bslamp2
id: ${id_slider_level}
id: my_slider_level
range_from: 0.02
on_value:
then:
- light.turn_on:
id: ${id_light}
id: my_light
brightness: !lambda return x;

+ 88
- 0
packages/core.yaml View File

@ -0,0 +1,88 @@
# --------------------------------------------------------------------------
# Xiaomi Mija Bedside Lamp 2 core configuration
# --------------------------------------------------------------------------
substitutions:
name: bedside-lamp
friendly_name: Bedside Lamp
light_name: ${friendly_name} RGBWW Light
light_mode_text_sensor_name: ${friendly_name} Light Mode
default_transition_length: 800ms
esphome:
name: ${name}
esp32:
board: esp32doit-devkit-v1
framework:
type: esp-idf
sdkconfig_options:
CONFIG_FREERTOS_UNICORE: y
advanced:
ignore_efuse_mac_crc: true
# Retrieve the code for the xiaomi_bslamp2 platform from GitHub.
external_components:
- source: github://mmakaay/esphome-xiaomi_bslamp2@2021.10.0
refresh: 60
# Disable the reboot timeout. By default, the lamp reboots after 15
# minutes without any client connections (e.g. when home assistant is off
# line, or when the WiFi is broken). Reboots are annoying though, because
# the RGBWW LEDs will turn off during the reboot, causing the light to
# flicker.
api:
reboot_timeout: 0s
# ----------------------------------------------------------------------
# Hardware configuration
# ----------------------------------------------------------------------
i2c:
id: front_panel_i2c
sda: GPIO21
scl: GPIO19
scan: true
output:
- platform: ledc
id: output_red
pin: GPIO13
frequency: 3000
channel: 0
- platform: ledc
id: output_green
pin: GPIO14
frequency: 3000
channel: 1
- platform: ledc
id: output_blue
pin: GPIO5
frequency: 3000
channel: 2
- platform: ledc
id: output_white
pin: GPIO12
frequency: 10000
channel: 4
- platform: gpio
id: output_master1
pin: GPIO33
- platform: gpio
id: output_master2
pin:
number: GPIO4
mode: OUTPUT
xiaomi_bslamp2:
light:
red: output_red
green: output_green
blue: output_blue
white: output_white
master1: output_master1
master2: output_master2
front_panel:
i2c: front_panel_i2c
address: 0x2C
trigger_pin: GPIO16

Loading…
Cancel
Save