mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 06:58:11 +01:00
Add addressable_light display platform (#1272)
This commit is contained in:
parent
b5b2036971
commit
f63f9168ff
7 changed files with 226 additions and 0 deletions
|
@ -13,6 +13,7 @@ esphome/core/* @esphome/core
|
||||||
# Integrations
|
# Integrations
|
||||||
esphome/components/ac_dimmer/* @glmnet
|
esphome/components/ac_dimmer/* @glmnet
|
||||||
esphome/components/adc/* @esphome/core
|
esphome/components/adc/* @esphome/core
|
||||||
|
esphome/components/addressable_light/* @justfalter
|
||||||
esphome/components/animation/* @syndlex
|
esphome/components/animation/* @syndlex
|
||||||
esphome/components/api/* @OttoWinter
|
esphome/components/api/* @OttoWinter
|
||||||
esphome/components/async_tcp/* @OttoWinter
|
esphome/components/async_tcp/* @OttoWinter
|
||||||
|
|
0
esphome/components/addressable_light/__init__.py
Normal file
0
esphome/components/addressable_light/__init__.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#include "addressable_light_display.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace addressable_light {
|
||||||
|
|
||||||
|
static const char* TAG = "addressable_light.display";
|
||||||
|
|
||||||
|
int AddressableLightDisplay::get_width_internal() { return this->width_; }
|
||||||
|
int AddressableLightDisplay::get_height_internal() { return this->height_; }
|
||||||
|
|
||||||
|
void AddressableLightDisplay::setup() {
|
||||||
|
this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressableLightDisplay::update() {
|
||||||
|
if (!this->enabled_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->do_update_();
|
||||||
|
this->display();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressableLightDisplay::display() {
|
||||||
|
bool dirty = false;
|
||||||
|
uint8_t old_r, old_g, old_b, old_w;
|
||||||
|
Color* c;
|
||||||
|
|
||||||
|
for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
|
||||||
|
c = &(this->addressable_light_buffer_[offset]);
|
||||||
|
|
||||||
|
light::ESPColorView pixel = (*this->light_)[offset];
|
||||||
|
|
||||||
|
// Track the original values for the pixel view. If it has changed updating, then
|
||||||
|
// we trigger a redraw. Avoiding redraws == avoiding flicker!
|
||||||
|
old_r = pixel.get_red();
|
||||||
|
old_g = pixel.get_green();
|
||||||
|
old_b = pixel.get_blue();
|
||||||
|
old_w = pixel.get_white();
|
||||||
|
|
||||||
|
pixel.set_rgbw(c->r, c->g, c->b, c->w);
|
||||||
|
|
||||||
|
// If the actual value of the pixel changed, then schedule a redraw.
|
||||||
|
if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b ||
|
||||||
|
pixel.get_white() != old_w) {
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty) {
|
||||||
|
this->light_->schedule_show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||||
|
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this->pixel_mapper_f_.has_value()) {
|
||||||
|
// Params are passed by reference, so they may be modified in call.
|
||||||
|
this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color;
|
||||||
|
} else {
|
||||||
|
this->addressable_light_buffer_[y * this->get_width_internal() + x] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace addressable_light
|
||||||
|
} // namespace esphome
|
|
@ -0,0 +1,59 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
#include "esphome/components/display/display_buffer.h"
|
||||||
|
#include "esphome/components/light/addressable_light.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace addressable_light {
|
||||||
|
|
||||||
|
class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent {
|
||||||
|
public:
|
||||||
|
light::AddressableLight *get_light() const { return this->light_; }
|
||||||
|
|
||||||
|
void set_width(int32_t width) { width_ = width; }
|
||||||
|
void set_height(int32_t height) { height_ = height; }
|
||||||
|
void set_light(light::LightState *state) {
|
||||||
|
light_state_ = state;
|
||||||
|
light_ = static_cast<light::AddressableLight *>(state->get_output());
|
||||||
|
}
|
||||||
|
void set_enabled(bool enabled) {
|
||||||
|
if (light_state_) {
|
||||||
|
if (enabled_ && !enabled) { // enabled -> disabled
|
||||||
|
// - Tell the parent light to refresh, effectively wiping the display. Also
|
||||||
|
// restores the previous effect (if any).
|
||||||
|
light_state_->make_call().set_effect(this->last_effect_).perform();
|
||||||
|
|
||||||
|
} else if (!enabled_ && enabled) { // disabled -> enabled
|
||||||
|
// - Save the current effect.
|
||||||
|
this->last_effect_ = light_state_->get_effect_name();
|
||||||
|
// - Disable any current effect.
|
||||||
|
light_state_->make_call().set_effect(0).perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enabled_ = enabled;
|
||||||
|
}
|
||||||
|
bool get_enabled() { return enabled_; }
|
||||||
|
|
||||||
|
void set_pixel_mapper(std::function<int(int, int)> &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; }
|
||||||
|
void setup() override;
|
||||||
|
void display();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int get_width_internal() override;
|
||||||
|
int get_height_internal() override;
|
||||||
|
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
light::LightState *light_state_;
|
||||||
|
light::AddressableLight *light_;
|
||||||
|
bool enabled_{true};
|
||||||
|
int32_t width_;
|
||||||
|
int32_t height_;
|
||||||
|
std::vector<Color> addressable_light_buffer_;
|
||||||
|
optional<std::string> last_effect_;
|
||||||
|
optional<std::function<int(int, int)>> pixel_mapper_f_;
|
||||||
|
};
|
||||||
|
} // namespace addressable_light
|
||||||
|
} // namespace esphome
|
63
esphome/components/addressable_light/display.py
Normal file
63
esphome/components/addressable_light/display.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import display, light
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_LAMBDA,
|
||||||
|
CONF_PAGES,
|
||||||
|
CONF_ADDRESSABLE_LIGHT_ID,
|
||||||
|
CONF_HEIGHT,
|
||||||
|
CONF_WIDTH,
|
||||||
|
CONF_UPDATE_INTERVAL,
|
||||||
|
CONF_PIXEL_MAPPER,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@justfalter"]
|
||||||
|
|
||||||
|
addressable_light_ns = cg.esphome_ns.namespace("addressable_light")
|
||||||
|
AddressableLightDisplay = addressable_light_ns.class_(
|
||||||
|
"AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(AddressableLightDisplay),
|
||||||
|
cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id(
|
||||||
|
light.AddressableLightState
|
||||||
|
),
|
||||||
|
cv.Required(CONF_WIDTH): cv.positive_int,
|
||||||
|
cv.Required(CONF_HEIGHT): cv.positive_int,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_UPDATE_INTERVAL, default="16ms"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
|
||||||
|
cg.add(var.set_width(config[CONF_WIDTH]))
|
||||||
|
cg.add(var.set_height(config[CONF_HEIGHT]))
|
||||||
|
cg.add(var.set_light(wrapped_light))
|
||||||
|
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield display.register_display(var, config)
|
||||||
|
|
||||||
|
if CONF_PIXEL_MAPPER in config:
|
||||||
|
pixel_mapper_template_ = yield cg.process_lambda(
|
||||||
|
config[CONF_PIXEL_MAPPER],
|
||||||
|
[(int, "x"), (int, "y")],
|
||||||
|
return_type=cg.int_,
|
||||||
|
)
|
||||||
|
cg.add(var.set_pixel_mapper(pixel_mapper_template_))
|
||||||
|
|
||||||
|
if CONF_LAMBDA in config:
|
||||||
|
lambda_ = yield cg.process_lambda(
|
||||||
|
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
|
||||||
|
)
|
||||||
|
cg.add(var.set_writer(lambda_))
|
|
@ -53,6 +53,7 @@ CONF_ACCURACY = "accuracy"
|
||||||
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
|
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
|
||||||
CONF_ACTION_ID = "action_id"
|
CONF_ACTION_ID = "action_id"
|
||||||
CONF_ADDRESS = "address"
|
CONF_ADDRESS = "address"
|
||||||
|
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
|
||||||
CONF_ALPHA = "alpha"
|
CONF_ALPHA = "alpha"
|
||||||
CONF_AND = "and"
|
CONF_AND = "and"
|
||||||
CONF_AP = "ap"
|
CONF_AP = "ap"
|
||||||
|
@ -223,6 +224,7 @@ CONF_HEARTBEAT = "heartbeat"
|
||||||
CONF_HEAT_ACTION = "heat_action"
|
CONF_HEAT_ACTION = "heat_action"
|
||||||
CONF_HEAT_MODE = "heat_mode"
|
CONF_HEAT_MODE = "heat_mode"
|
||||||
CONF_HEATER = "heater"
|
CONF_HEATER = "heater"
|
||||||
|
CONF_HEIGHT = "height"
|
||||||
CONF_HIDDEN = "hidden"
|
CONF_HIDDEN = "hidden"
|
||||||
CONF_HIDE_TIMESTAMP = "hide_timestamp"
|
CONF_HIDE_TIMESTAMP = "hide_timestamp"
|
||||||
CONF_HIGH = "high"
|
CONF_HIGH = "high"
|
||||||
|
@ -388,6 +390,7 @@ CONF_PIN_B = "pin_b"
|
||||||
CONF_PIN_C = "pin_c"
|
CONF_PIN_C = "pin_c"
|
||||||
CONF_PIN_D = "pin_d"
|
CONF_PIN_D = "pin_d"
|
||||||
CONF_PINS = "pins"
|
CONF_PINS = "pins"
|
||||||
|
CONF_PIXEL_MAPPER = "pixel_mapper"
|
||||||
CONF_PLATFORM = "platform"
|
CONF_PLATFORM = "platform"
|
||||||
CONF_PLATFORMIO_OPTIONS = "platformio_options"
|
CONF_PLATFORMIO_OPTIONS = "platformio_options"
|
||||||
CONF_PM_1_0 = "pm_1_0"
|
CONF_PM_1_0 = "pm_1_0"
|
||||||
|
|
|
@ -99,3 +99,36 @@ switch:
|
||||||
- platform: tuya
|
- platform: tuya
|
||||||
id: tuya_switch
|
id: tuya_switch
|
||||||
switch_datapoint: 1
|
switch_datapoint: 1
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: fastled_clockless
|
||||||
|
id: led_matrix_32x8
|
||||||
|
name: "led_matrix_32x8"
|
||||||
|
chipset: WS2812B
|
||||||
|
pin: GPIO15
|
||||||
|
num_leds: 256
|
||||||
|
rgb_order: GRB
|
||||||
|
default_transition_length: 0s
|
||||||
|
color_correct: [50%, 50%, 50%]
|
||||||
|
|
||||||
|
display:
|
||||||
|
- platform: addressable_light
|
||||||
|
id: led_matrix_32x8_display
|
||||||
|
addressable_light_id: led_matrix_32x8
|
||||||
|
width: 32
|
||||||
|
height: 8
|
||||||
|
pixel_mapper: |-
|
||||||
|
if (x % 2 == 0) {
|
||||||
|
return (x * 8) + y;
|
||||||
|
}
|
||||||
|
return (x * 8) + (7 - y);
|
||||||
|
lambda: |-
|
||||||
|
Color red = Color(0xFF0000);
|
||||||
|
Color green = Color(0x00FF00);
|
||||||
|
Color blue = Color(0x0000FF);
|
||||||
|
it.rectangle(0, 0, it.get_width(), it.get_height(), red);
|
||||||
|
it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green);
|
||||||
|
it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue);
|
||||||
|
it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red);
|
||||||
|
rotation: 0°
|
||||||
|
update_interval: 16ms
|
||||||
|
|
Loading…
Reference in a new issue