mirror of
https://github.com/esphome/esphome.git
synced 2025-09-04 04:12:23 +01:00
[epaper_spi] New epaper component
This commit is contained in:
1
esphome/components/epaper_spi/__init__.py
Normal file
1
esphome/components/epaper_spi/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CODEOWNERS = ["@esphome/core"]
|
99
esphome/components/epaper_spi/display.py
Normal file
99
esphome/components/epaper_spi/display.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
from esphome import core, pins
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import display, spi
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_BUSY_PIN,
|
||||||
|
CONF_DC_PIN,
|
||||||
|
CONF_FULL_UPDATE_EVERY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_LAMBDA,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_PAGES,
|
||||||
|
CONF_RESET_DURATION,
|
||||||
|
CONF_RESET_PIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
AUTO_LOAD = ["split_buffer"]
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
|
epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi")
|
||||||
|
EPaperBase = epaper_spi_ns.class_(
|
||||||
|
"EPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
||||||
|
)
|
||||||
|
EPaper6Color = epaper_spi_ns.class_("EPaper6Color", EPaperBase)
|
||||||
|
EPaper7p3InE = epaper_spi_ns.class_("EPaper7p3InE", EPaper6Color)
|
||||||
|
|
||||||
|
MODELS = {
|
||||||
|
"7.30in-e": ("b", EPaper7p3InE),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_full_update_every_only_types_ac(value):
|
||||||
|
if CONF_FULL_UPDATE_EVERY not in value:
|
||||||
|
return value
|
||||||
|
if MODELS[value[CONF_MODEL]][0] == "b":
|
||||||
|
full_models = []
|
||||||
|
for key, val in sorted(MODELS.items()):
|
||||||
|
if val[0] != "b":
|
||||||
|
full_models.append(key)
|
||||||
|
raise cv.Invalid(
|
||||||
|
"The 'full_update_every' option is only available for models "
|
||||||
|
+ ", ".join(full_models)
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(EPaperBase),
|
||||||
|
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True),
|
||||||
|
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
|
||||||
|
cv.Optional(CONF_FULL_UPDATE_EVERY): cv.int_range(min=1, max=4294967295),
|
||||||
|
cv.Optional(CONF_RESET_DURATION): cv.All(
|
||||||
|
cv.positive_time_period_milliseconds,
|
||||||
|
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("1s"))
|
||||||
|
.extend(spi.spi_device_schema()),
|
||||||
|
validate_full_update_every_only_types_ac,
|
||||||
|
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||||
|
)
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
|
||||||
|
"epaper_spi", require_miso=False, require_mosi=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
_, model = MODELS[config[CONF_MODEL]]
|
||||||
|
|
||||||
|
rhs = model.new()
|
||||||
|
var = cg.Pvariable(config[CONF_ID], rhs, model)
|
||||||
|
|
||||||
|
await display.register_display(var, config)
|
||||||
|
await spi.register_spi_device(var, config)
|
||||||
|
|
||||||
|
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||||
|
cg.add(var.set_dc_pin(dc))
|
||||||
|
|
||||||
|
if CONF_LAMBDA in config:
|
||||||
|
lambda_ = await cg.process_lambda(
|
||||||
|
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
|
||||||
|
)
|
||||||
|
cg.add(var.set_writer(lambda_))
|
||||||
|
if CONF_RESET_PIN in config:
|
||||||
|
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||||
|
cg.add(var.set_reset_pin(reset))
|
||||||
|
if CONF_BUSY_PIN in config:
|
||||||
|
reset = await cg.gpio_pin_expression(config[CONF_BUSY_PIN])
|
||||||
|
cg.add(var.set_busy_pin(reset))
|
||||||
|
if CONF_FULL_UPDATE_EVERY in config:
|
||||||
|
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
||||||
|
if CONF_RESET_DURATION in config:
|
||||||
|
cg.add(var.set_reset_duration(config[CONF_RESET_DURATION]))
|
362
esphome/components/epaper_spi/epaper_spi.cpp
Normal file
362
esphome/components/epaper_spi/epaper_spi.cpp
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
#include "epaper_spi.h"
|
||||||
|
#include <bitset>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome::epaper_spi {
|
||||||
|
|
||||||
|
static const char *const TAG = "epaper_spi";
|
||||||
|
|
||||||
|
static const uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run
|
||||||
|
|
||||||
|
void EPaperBase::setup() {
|
||||||
|
this->init_internal_(this->get_buffer_length());
|
||||||
|
this->setup_pins_();
|
||||||
|
this->spi_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperBase::setup_pins_() {
|
||||||
|
this->dc_pin_->setup(); // OUTPUT
|
||||||
|
this->dc_pin_->digital_write(false);
|
||||||
|
if (this->reset_pin_ != nullptr) {
|
||||||
|
this->reset_pin_->setup(); // OUTPUT
|
||||||
|
this->reset_pin_->digital_write(true);
|
||||||
|
}
|
||||||
|
if (this->busy_pin_ != nullptr) {
|
||||||
|
this->busy_pin_->setup(); // INPUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||||
|
|
||||||
|
void EPaperBase::command(uint8_t value) {
|
||||||
|
this->start_command_();
|
||||||
|
this->write_byte(value);
|
||||||
|
this->end_command_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperBase::data(uint8_t value) {
|
||||||
|
this->start_data_();
|
||||||
|
this->write_byte(value);
|
||||||
|
this->end_data_();
|
||||||
|
}
|
||||||
|
|
||||||
|
// write a command followed by one or more bytes of data.
|
||||||
|
// The command is the first byte, length is the total including cmd.
|
||||||
|
void EPaperBase::cmd_data(const uint8_t *c_data, size_t length) {
|
||||||
|
this->dc_pin_->digital_write(false);
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(c_data[0]);
|
||||||
|
this->dc_pin_->digital_write(true);
|
||||||
|
this->write_array(c_data + 1, length - 1);
|
||||||
|
this->disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EPaperBase::is_idle_() {
|
||||||
|
if (this->busy_pin_ == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !this->busy_pin_->digital_read();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperBase::reset_() {
|
||||||
|
if (this->reset_pin_ != nullptr) {
|
||||||
|
this->reset_pin_->digital_write(false);
|
||||||
|
this->disable_loop();
|
||||||
|
this->set_timeout(this->reset_duration_, [this] {
|
||||||
|
this->reset_pin_->digital_write(true);
|
||||||
|
this->set_timeout(20, [this] {
|
||||||
|
this->state_ = RESET_DONE;
|
||||||
|
this->enable_loop();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperBase::update() {
|
||||||
|
if (this->state_ != IDLE) {
|
||||||
|
ESP_LOGE(TAG, "Display update already in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->do_update_(); // Calls ESPHome (current page) lambda
|
||||||
|
this->state_ = UPDATING;
|
||||||
|
this->enable_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperBase::loop() {
|
||||||
|
switch (this->state_) {
|
||||||
|
case IDLE:
|
||||||
|
this->disable_loop();
|
||||||
|
break;
|
||||||
|
case UPDATING:
|
||||||
|
this->reset_();
|
||||||
|
this->state_ = RESETTING;
|
||||||
|
break;
|
||||||
|
case RESETTING:
|
||||||
|
// Nothing to do here, next state change is handled by timeouts
|
||||||
|
break;
|
||||||
|
case RESET_DONE:
|
||||||
|
if (this->is_idle_()) {
|
||||||
|
this->initialize();
|
||||||
|
this->state_ = INITIALIZING;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INITIALIZING:
|
||||||
|
if (this->is_idle_()) {
|
||||||
|
ESP_LOGI(TAG, "Display initialized successfully");
|
||||||
|
this->state_ = TRANSFERING_DATA;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TRANSFERING_DATA:
|
||||||
|
this->transfer_data();
|
||||||
|
break;
|
||||||
|
case TRANSFER_DONE:
|
||||||
|
this->power_on();
|
||||||
|
this->state_ = POWERING_ON;
|
||||||
|
break;
|
||||||
|
case POWERING_ON:
|
||||||
|
if (this->is_idle_()) {
|
||||||
|
this->refresh_screen();
|
||||||
|
this->state_ = REFRESHING_SCREEN;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REFRESHING_SCREEN:
|
||||||
|
if (this->is_idle_()) {
|
||||||
|
this->power_off();
|
||||||
|
this->state_ = POWERING_OFF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case POWERING_OFF:
|
||||||
|
if (this->is_idle_()) {
|
||||||
|
this->deep_sleep();
|
||||||
|
}
|
||||||
|
this->state_ = IDLE;
|
||||||
|
this->disable_loop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaper6Color::setup() {
|
||||||
|
if (!this->init_internal_6c_(this->get_buffer_length())) {
|
||||||
|
this->mark_failed("Failed to initialize buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->setup_pins_();
|
||||||
|
this->spi_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EPaper6Color::init_internal_6c_(uint32_t buffer_length) {
|
||||||
|
if (!this->buffer_.init(buffer_length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t EPaper6Color::color_to_hex(Color color) {
|
||||||
|
uint8_t hex_code;
|
||||||
|
if (color.red > 127) {
|
||||||
|
if (color.green > 170) {
|
||||||
|
if (color.blue > 127) {
|
||||||
|
hex_code = 0x1; // White
|
||||||
|
} else {
|
||||||
|
hex_code = 0x2; // Yellow
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hex_code = 0x3; // Red (or Magenta)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (color.green > 127) {
|
||||||
|
if (color.blue > 127) {
|
||||||
|
hex_code = 0x5; // Cyan -> Blue
|
||||||
|
} else {
|
||||||
|
hex_code = 0x6; // Green
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (color.blue > 127) {
|
||||||
|
hex_code = 0x5; // Blue
|
||||||
|
} else {
|
||||||
|
hex_code = 0x0; // Black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex_code;
|
||||||
|
}
|
||||||
|
void EPaper6Color::fill(Color color) {
|
||||||
|
uint8_t pixel_color;
|
||||||
|
if (color.is_on()) {
|
||||||
|
pixel_color = this->color_to_hex(color);
|
||||||
|
} else {
|
||||||
|
pixel_color = 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We store 8 bitset<3> in 3 bytes
|
||||||
|
// | byte 1 | byte 2 | byte 3 |
|
||||||
|
// |aaabbbaa|abbbaaab|bbaaabbb|
|
||||||
|
uint8_t byte_1 = pixel_color << 5 | pixel_color << 2 | pixel_color >> 1;
|
||||||
|
uint8_t byte_2 = pixel_color << 7 | pixel_color << 4 | pixel_color << 1 | pixel_color >> 2;
|
||||||
|
uint8_t byte_3 = pixel_color << 6 | pixel_color << 3 | pixel_color << 0;
|
||||||
|
|
||||||
|
const size_t buffer_length = this->get_buffer_length();
|
||||||
|
for (size_t i = 0; i < buffer_length; i += 3) {
|
||||||
|
this->buffer_[i + 0] = byte_1;
|
||||||
|
this->buffer_[i + 1] = byte_2;
|
||||||
|
this->buffer_[i + 2] = byte_3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EPaper6Color::get_buffer_length() {
|
||||||
|
// 6 colors buffer, 1 pixel = 3 bits, we will store 8 pixels in 24 bits = 3 bytes
|
||||||
|
return this->get_width_controller() * this->get_height_internal() / 8u * 3u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT EPaper6Color::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||||
|
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint8_t pixel_bits = this->color_to_hex(color);
|
||||||
|
uint32_t pixel_position = x + y * this->get_width_controller();
|
||||||
|
uint32_t first_bit_position = pixel_position * 3;
|
||||||
|
uint32_t byte_position = first_bit_position / 8u;
|
||||||
|
uint32_t byte_subposition = first_bit_position % 8u;
|
||||||
|
|
||||||
|
if (byte_subposition <= 5) {
|
||||||
|
this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 << (5 - byte_subposition)))) |
|
||||||
|
(pixel_bits << (5 - byte_subposition));
|
||||||
|
} else {
|
||||||
|
this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 >> (byte_subposition - 5)))) |
|
||||||
|
(pixel_bits >> (byte_subposition - 5));
|
||||||
|
|
||||||
|
this->buffer_[byte_position + 1] =
|
||||||
|
(this->buffer_[byte_position + 1] & (0xFF ^ (0xFF & (0b111 << (13 - byte_subposition))))) |
|
||||||
|
(pixel_bits << (13 - byte_subposition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperBase::start_command_() {
|
||||||
|
this->dc_pin_->digital_write(false);
|
||||||
|
this->enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperBase::end_command_() { this->disable(); }
|
||||||
|
|
||||||
|
void EPaperBase::start_data_() {
|
||||||
|
this->dc_pin_->digital_write(true);
|
||||||
|
this->enable();
|
||||||
|
}
|
||||||
|
void EPaperBase::end_data_() { this->disable(); }
|
||||||
|
|
||||||
|
void EPaperBase::on_safe_shutdown() { this->deep_sleep(); }
|
||||||
|
|
||||||
|
void EPaper7p3InE::initialize() {
|
||||||
|
static const uint8_t cmdh_data[] = {0xAA, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18};
|
||||||
|
this->cmd_data(cmdh_data, sizeof(cmdh_data));
|
||||||
|
|
||||||
|
static const uint8_t data_01[] = {0x01, 0x3F};
|
||||||
|
this->cmd_data(data_01, sizeof(data_01));
|
||||||
|
|
||||||
|
static const uint8_t data_02[] = {0x00, 0x5F, 0x69};
|
||||||
|
this->cmd_data(data_02, sizeof(data_02));
|
||||||
|
|
||||||
|
static const uint8_t data_03[] = {0x03, 0x00, 0x54, 0x00, 0x44};
|
||||||
|
this->cmd_data(data_03, sizeof(data_03));
|
||||||
|
|
||||||
|
static const uint8_t data_04[] = {0x05, 0x40, 0x1F, 0x1F, 0x2C};
|
||||||
|
this->cmd_data(data_04, sizeof(data_04));
|
||||||
|
|
||||||
|
static const uint8_t data_05[] = {0x06, 0x6F, 0x1F, 0x17, 0x49};
|
||||||
|
this->cmd_data(data_05, sizeof(data_05));
|
||||||
|
|
||||||
|
static const uint8_t data_06[] = {0x08, 0x6F, 0x1F, 0x1F, 0x22};
|
||||||
|
this->cmd_data(data_06, sizeof(data_06));
|
||||||
|
|
||||||
|
static const uint8_t data_07[] = {0x30, 0x03};
|
||||||
|
this->cmd_data(data_07, sizeof(data_07));
|
||||||
|
|
||||||
|
static const uint8_t data_08[] = {0x50, 0x3F};
|
||||||
|
this->cmd_data(data_08, sizeof(data_08));
|
||||||
|
|
||||||
|
static const uint8_t data_09[] = {0x60, 0x02, 0x00};
|
||||||
|
this->cmd_data(data_09, sizeof(data_09));
|
||||||
|
|
||||||
|
static const uint8_t data_10[] = {0x61, 0x03, 0x20, 0x01, 0xE0};
|
||||||
|
this->cmd_data(data_10, sizeof(data_10));
|
||||||
|
|
||||||
|
static const uint8_t data_11[] = {0x84, 0x01};
|
||||||
|
this->cmd_data(data_11, sizeof(data_11));
|
||||||
|
|
||||||
|
static const uint8_t data_12[] = {0xE3, 0x2F};
|
||||||
|
this->cmd_data(data_12, sizeof(data_12));
|
||||||
|
|
||||||
|
this->power_on();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT EPaper7p3InE::transfer_data() {
|
||||||
|
const uint32_t start_time = App.get_loop_component_start_time();
|
||||||
|
if (this->current_data_index_ == 0) {
|
||||||
|
ESP_LOGI(TAG, "Sending data to the display");
|
||||||
|
this->command(0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t bytes_to_send[4]{0};
|
||||||
|
const size_t buffer_length = this->get_buffer_length();
|
||||||
|
for (size_t i = this->current_data_index_; i < buffer_length; i += 3) {
|
||||||
|
std::bitset<24> triplet = encode_uint24(this->buffer_[i + 0], this->buffer_[i + 1], this->buffer_[i + 2]);
|
||||||
|
// 8 bitset<3> are stored in 3 bytes
|
||||||
|
// |aaabbbaa|abbbaaab|bbaaabbb|
|
||||||
|
// | byte 1 | byte 2 | byte 3 |
|
||||||
|
bytes_to_send[0] = ((triplet >> 17).to_ulong() & 0b01110000) | ((triplet >> 18).to_ulong() & 0b00000111);
|
||||||
|
bytes_to_send[1] = ((triplet >> 11).to_ulong() & 0b01110000) | ((triplet >> 12).to_ulong() & 0b00000111);
|
||||||
|
bytes_to_send[2] = ((triplet >> 5).to_ulong() & 0b01110000) | ((triplet >> 6).to_ulong() & 0b00000111);
|
||||||
|
bytes_to_send[3] = ((triplet << 1).to_ulong() & 0b01110000) | ((triplet << 0).to_ulong() & 0b00000111);
|
||||||
|
|
||||||
|
this->start_data_();
|
||||||
|
this->write_array(bytes_to_send, sizeof(bytes_to_send));
|
||||||
|
this->end_data_();
|
||||||
|
|
||||||
|
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||||
|
// Let the main loop run and come back next loop
|
||||||
|
this->current_data_index_ = i + 3;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finished the entire dataset
|
||||||
|
this->current_data_index_ = 0;
|
||||||
|
this->state_ = TRANSFER_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaper7p3InE::power_on() {
|
||||||
|
ESP_LOGI(TAG, "Power on the display");
|
||||||
|
this->command(0x04);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaper7p3InE::power_off() {
|
||||||
|
ESP_LOGI(TAG, "Power off the display");
|
||||||
|
this->command(0x02);
|
||||||
|
this->data(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaper7p3InE::refresh_screen() {
|
||||||
|
this->command(0x12);
|
||||||
|
this->data(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaper7p3InE::deep_sleep() {
|
||||||
|
ESP_LOGI(TAG, "Set the display to deep sleep");
|
||||||
|
this->command(0x07);
|
||||||
|
this->data(0xA5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaper7p3InE::dump_config() {
|
||||||
|
LOG_DISPLAY("", "E-Paper SPI", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Model: 7.3in-E");
|
||||||
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::epaper_spi
|
109
esphome/components/epaper_spi/epaper_spi.h
Normal file
109
esphome/components/epaper_spi/epaper_spi.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/display/display_buffer.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
#include "esphome/components/split_buffer/split_buffer.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome::epaper_spi {
|
||||||
|
|
||||||
|
enum EPaperState : uint8_t {
|
||||||
|
IDLE,
|
||||||
|
UPDATING,
|
||||||
|
RESETTING,
|
||||||
|
RESET_DONE,
|
||||||
|
INITIALIZING,
|
||||||
|
TRANSFERING_DATA,
|
||||||
|
TRANSFER_DONE,
|
||||||
|
POWERING_ON,
|
||||||
|
REFRESHING_SCREEN,
|
||||||
|
POWERING_OFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
class EPaperBase : public display::DisplayBuffer,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||||
|
spi::DATA_RATE_2MHZ> {
|
||||||
|
public:
|
||||||
|
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||||
|
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
|
||||||
|
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
|
||||||
|
|
||||||
|
void command(uint8_t value);
|
||||||
|
void data(uint8_t value);
|
||||||
|
void cmd_data(const uint8_t *data, size_t length);
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
void on_safe_shutdown() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool is_idle_();
|
||||||
|
void setup_pins_();
|
||||||
|
void reset_();
|
||||||
|
|
||||||
|
virtual int get_width_controller() { return this->get_width_internal(); };
|
||||||
|
virtual void initialize() = 0;
|
||||||
|
virtual void deep_sleep() = 0;
|
||||||
|
virtual void transfer_data() = 0;
|
||||||
|
virtual void refresh_screen() = 0;
|
||||||
|
|
||||||
|
virtual void power_on() = 0;
|
||||||
|
virtual void power_off() = 0;
|
||||||
|
virtual uint32_t get_buffer_length() = 0;
|
||||||
|
|
||||||
|
void start_command_();
|
||||||
|
void end_command_();
|
||||||
|
void start_data_();
|
||||||
|
void end_data_();
|
||||||
|
|
||||||
|
GPIOPin *dc_pin_;
|
||||||
|
GPIOPin *busy_pin_{nullptr};
|
||||||
|
GPIOPin *reset_pin_{nullptr};
|
||||||
|
|
||||||
|
uint32_t reset_duration_{200};
|
||||||
|
|
||||||
|
EPaperState state_{IDLE};
|
||||||
|
|
||||||
|
split_buffer::SplitBuffer buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EPaper6Color : public EPaperBase {
|
||||||
|
public:
|
||||||
|
uint8_t color_to_hex(Color color);
|
||||||
|
void fill(Color color) override;
|
||||||
|
|
||||||
|
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||||
|
uint32_t get_buffer_length() override;
|
||||||
|
void setup() override;
|
||||||
|
bool init_internal_6c_(uint32_t buffer_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
class EPaper7p3InE : public EPaper6Color {
|
||||||
|
public:
|
||||||
|
void initialize() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int get_width_internal() override { return 800; };
|
||||||
|
int get_height_internal() override { return 480; };
|
||||||
|
void transfer_data() override;
|
||||||
|
void refresh_screen() override;
|
||||||
|
|
||||||
|
void power_on() override;
|
||||||
|
void power_off() override;
|
||||||
|
|
||||||
|
void deep_sleep() override;
|
||||||
|
|
||||||
|
size_t current_data_index_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::epaper_spi
|
5
esphome/components/split_buffer/__init__.py
Normal file
5
esphome/components/split_buffer/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
|
||||||
|
# Allows split_buffer to be configured in yaml, to allow use of the C++ api.
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = {}
|
133
esphome/components/split_buffer/split_buffer.cpp
Normal file
133
esphome/components/split_buffer/split_buffer.cpp
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#include "split_buffer.h"
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome::split_buffer {
|
||||||
|
|
||||||
|
static constexpr const char *const TAG = "split_buffer";
|
||||||
|
|
||||||
|
SplitBuffer::~SplitBuffer() { this->free(); }
|
||||||
|
|
||||||
|
bool SplitBuffer::init(size_t total_length) {
|
||||||
|
this->free(); // Clean up any existing allocation
|
||||||
|
|
||||||
|
if (total_length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->total_length_ = total_length;
|
||||||
|
size_t current_buffer_size = total_length;
|
||||||
|
|
||||||
|
RAMAllocator<uint8_t *> ptr_allocator;
|
||||||
|
RAMAllocator<uint8_t> allocator;
|
||||||
|
|
||||||
|
// Try to allocate the entire buffer first
|
||||||
|
while (current_buffer_size > 0) {
|
||||||
|
// Calculate how many buffers we need of this size
|
||||||
|
size_t needed_buffers = (total_length + current_buffer_size - 1) / current_buffer_size;
|
||||||
|
|
||||||
|
// Try to allocate array of buffer pointers
|
||||||
|
uint8_t **temp_buffers = ptr_allocator.allocate(needed_buffers);
|
||||||
|
if (temp_buffers == nullptr) {
|
||||||
|
// If we can't even allocate the pointer array, halve the buffer size
|
||||||
|
current_buffer_size = current_buffer_size / 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize all pointers to null
|
||||||
|
for (size_t i = 0; i < needed_buffers; i++) {
|
||||||
|
temp_buffers[i] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to allocate all the buffers
|
||||||
|
bool allocation_success = true;
|
||||||
|
for (size_t i = 0; i < needed_buffers; i++) {
|
||||||
|
size_t this_buffer_size = current_buffer_size;
|
||||||
|
// Last buffer might be smaller if total_length is not divisible by current_buffer_size
|
||||||
|
if (i == needed_buffers - 1 && total_length % current_buffer_size != 0) {
|
||||||
|
this_buffer_size = total_length % current_buffer_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_buffers[i] = allocator.allocate(this_buffer_size);
|
||||||
|
if (temp_buffers[i] == nullptr) {
|
||||||
|
allocation_success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize buffer to zero
|
||||||
|
memset(temp_buffers[i], 0, this_buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocation_success) {
|
||||||
|
// Success! Store the result
|
||||||
|
this->buffers_ = temp_buffers;
|
||||||
|
this->buffer_count_ = needed_buffers;
|
||||||
|
this->buffer_size_ = current_buffer_size;
|
||||||
|
ESP_LOGD(TAG, "SplitBuffer allocated %zu buffers of %zu bytes each (total: %zu bytes)", this->buffer_count_,
|
||||||
|
this->buffer_size_, this->total_length_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocation failed, clean up and try smaller buffers
|
||||||
|
for (size_t i = 0; i < needed_buffers; i++) {
|
||||||
|
if (temp_buffers[i] != nullptr) {
|
||||||
|
allocator.deallocate(temp_buffers[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr_allocator.deallocate(temp_buffers, 0);
|
||||||
|
|
||||||
|
// Halve the buffer size and try again
|
||||||
|
current_buffer_size = current_buffer_size / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGE(TAG, "SplitBuffer failed to allocate %zu bytes", total_length);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SplitBuffer::free() {
|
||||||
|
if (this->buffers_ != nullptr) {
|
||||||
|
RAMAllocator<uint8_t> allocator;
|
||||||
|
for (size_t i = 0; i < this->buffer_count_; i++) {
|
||||||
|
if (this->buffers_[i] != nullptr) {
|
||||||
|
allocator.deallocate(this->buffers_[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RAMAllocator<uint8_t *> ptr_allocator;
|
||||||
|
ptr_allocator.deallocate(this->buffers_, 0);
|
||||||
|
this->buffers_ = nullptr;
|
||||||
|
}
|
||||||
|
this->buffer_count_ = 0;
|
||||||
|
this->buffer_size_ = 0;
|
||||||
|
this->total_length_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t &SplitBuffer::operator[](size_t index) {
|
||||||
|
if (index >= this->total_length_) {
|
||||||
|
ESP_LOGE(TAG, "SplitBuffer index %zu out of bounds (size: %zu)", index, this->total_length_);
|
||||||
|
// Return reference to a static dummy byte to avoid crash
|
||||||
|
static uint8_t dummy = 0;
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t buffer_index = index / this->buffer_size_;
|
||||||
|
size_t offset_in_buffer = index % this->buffer_size_;
|
||||||
|
|
||||||
|
return this->buffers_[buffer_index][offset_in_buffer];
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t &SplitBuffer::operator[](size_t index) const {
|
||||||
|
if (index >= this->total_length_) {
|
||||||
|
ESP_LOGE(TAG, "SplitBuffer index %zu out of bounds (size: %zu)", index, this->total_length_);
|
||||||
|
// Return reference to a static dummy byte to avoid crash
|
||||||
|
static const uint8_t dummy = 0;
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t buffer_index = index / this->buffer_size_;
|
||||||
|
size_t offset_in_buffer = index % this->buffer_size_;
|
||||||
|
|
||||||
|
return this->buffers_[buffer_index][offset_in_buffer];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::split_buffer
|
40
esphome/components/split_buffer/split_buffer.h
Normal file
40
esphome/components/split_buffer/split_buffer.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace esphome::split_buffer {
|
||||||
|
|
||||||
|
class SplitBuffer {
|
||||||
|
public:
|
||||||
|
SplitBuffer() = default;
|
||||||
|
~SplitBuffer();
|
||||||
|
|
||||||
|
// Initialize the buffer with the desired total length
|
||||||
|
bool init(size_t total_length);
|
||||||
|
|
||||||
|
// Free all allocated buffers
|
||||||
|
void free();
|
||||||
|
|
||||||
|
// Access operators
|
||||||
|
uint8_t &operator[](size_t index);
|
||||||
|
const uint8_t &operator[](size_t index) const;
|
||||||
|
|
||||||
|
// Get the total length
|
||||||
|
size_t size() const { return total_length_; }
|
||||||
|
|
||||||
|
// Get buffer information
|
||||||
|
size_t get_buffer_count() const { return buffer_count_; }
|
||||||
|
size_t get_buffer_size() const { return buffer_size_; }
|
||||||
|
|
||||||
|
// Check if successfully initialized
|
||||||
|
bool is_valid() const { return buffers_ != nullptr && buffer_count_ > 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t **buffers_{nullptr};
|
||||||
|
size_t buffer_count_{0};
|
||||||
|
size_t buffer_size_{0};
|
||||||
|
size_t total_length_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::split_buffer
|
Reference in New Issue
Block a user