mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 12:15:33 +01:00
Add addressable_light display platform (#1272)
This commit is contained in:
parent
b5b2036971
commit
f63f9168ff
@ -13,6 +13,7 @@ esphome/core/* @esphome/core
|
||||
# Integrations
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/api/* @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_ACTION_ID = "action_id"
|
||||
CONF_ADDRESS = "address"
|
||||
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
|
||||
CONF_ALPHA = "alpha"
|
||||
CONF_AND = "and"
|
||||
CONF_AP = "ap"
|
||||
@ -223,6 +224,7 @@ CONF_HEARTBEAT = "heartbeat"
|
||||
CONF_HEAT_ACTION = "heat_action"
|
||||
CONF_HEAT_MODE = "heat_mode"
|
||||
CONF_HEATER = "heater"
|
||||
CONF_HEIGHT = "height"
|
||||
CONF_HIDDEN = "hidden"
|
||||
CONF_HIDE_TIMESTAMP = "hide_timestamp"
|
||||
CONF_HIGH = "high"
|
||||
@ -388,6 +390,7 @@ CONF_PIN_B = "pin_b"
|
||||
CONF_PIN_C = "pin_c"
|
||||
CONF_PIN_D = "pin_d"
|
||||
CONF_PINS = "pins"
|
||||
CONF_PIXEL_MAPPER = "pixel_mapper"
|
||||
CONF_PLATFORM = "platform"
|
||||
CONF_PLATFORMIO_OPTIONS = "platformio_options"
|
||||
CONF_PM_1_0 = "pm_1_0"
|
||||
|
@ -99,3 +99,36 @@ switch:
|
||||
- platform: tuya
|
||||
id: tuya_switch
|
||||
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 New Issue
Block a user