mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 16:38:16 +01:00
Add support for ST7789V display module (as on TTGO T-Display) (#1050)
* TFT-LCD ST7789V of ESP32 TTGO. This patch allows you to use TFT-LCD ST7789V of ESP32 TTGO * Lots of polish and a few tweaks * Add test * Add color to core, take 1 * Where did those tabs come from? * Fix lines too long * Added color component * Linted * Rebase, SPI fix, test * Shuffle bits * One more thing...oops * Image type fix...oops * Make display_buffer use Color * Fix BGR/RGB, remove predefined colors * Fix all the things * renamed colors to color * migrate max7219 Co-authored-by: musk95 <musk95@naver.com> Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
parent
bfb9cb6732
commit
491f7e96f0
21 changed files with 843 additions and 85 deletions
23
esphome/components/color/__init__.py
Normal file
23
esphome/components/color/__init__.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from esphome import config_validation as cv
|
||||
from esphome import codegen as cg
|
||||
from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE
|
||||
|
||||
ColorStruct = cg.esphome_ns.struct('Color')
|
||||
|
||||
MULTI_CONF = True
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
|
||||
cv.Optional(CONF_RED, default=0.0): cv.percentage,
|
||||
cv.Optional(CONF_GREEN, default=0.0): cv.percentage,
|
||||
cv.Optional(CONF_BLUE, default=0.0): cv.percentage,
|
||||
cv.Optional(CONF_WHITE, default=0.0): cv.percentage,
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
cg.variable(config[CONF_ID], cg.StructInitializer(
|
||||
ColorStruct,
|
||||
('r', config[CONF_RED]),
|
||||
('g', config[CONF_GREEN]),
|
||||
('b', config[CONF_BLUE]),
|
||||
('w', config[CONF_WHITE])))
|
|
@ -7,8 +7,8 @@ namespace display {
|
|||
|
||||
static const char *TAG = "display";
|
||||
|
||||
const uint8_t COLOR_OFF = 0;
|
||||
const uint8_t COLOR_ON = 1;
|
||||
const Color COLOR_OFF = 0;
|
||||
const Color COLOR_ON = 1;
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
this->buffer_ = new uint8_t[buffer_length];
|
||||
|
@ -18,7 +18,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
|||
}
|
||||
this->clear();
|
||||
}
|
||||
void DisplayBuffer::fill(int color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
|
||||
int DisplayBuffer::get_width() {
|
||||
switch (this->rotation_) {
|
||||
|
@ -43,7 +43,7 @@ int DisplayBuffer::get_height() {
|
|||
}
|
||||
}
|
||||
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) {
|
||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
|
||||
switch (this->rotation_) {
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
|
@ -63,7 +63,7 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) {
|
|||
this->draw_absolute_pixel_internal(x, y, color);
|
||||
App.feed_wdt();
|
||||
}
|
||||
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) {
|
||||
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
|
||||
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
|
||||
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
||||
int32_t err = dx + dy;
|
||||
|
@ -83,29 +83,29 @@ void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) {
|
|||
}
|
||||
}
|
||||
}
|
||||
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, int color) {
|
||||
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
|
||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||
for (int i = x; i < x + width; i++)
|
||||
this->draw_pixel_at(i, y, color);
|
||||
}
|
||||
void HOT DisplayBuffer::vertical_line(int x, int y, int height, int color) {
|
||||
void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
|
||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||
for (int i = y; i < y + height; i++)
|
||||
this->draw_pixel_at(x, i, color);
|
||||
}
|
||||
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, int color) {
|
||||
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
|
||||
this->horizontal_line(x1, y1, width, color);
|
||||
this->horizontal_line(x1, y1 + height - 1, width, color);
|
||||
this->vertical_line(x1, y1, height, color);
|
||||
this->vertical_line(x1 + width - 1, y1, height, color);
|
||||
}
|
||||
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, int color) {
|
||||
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
|
||||
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
|
||||
for (int i = y1; i < y1 + height; i++) {
|
||||
this->horizontal_line(x1, i, width, color);
|
||||
}
|
||||
}
|
||||
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int color) {
|
||||
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
|
||||
int dx = -radius;
|
||||
int dy = 0;
|
||||
int err = 2 - 2 * radius;
|
||||
|
@ -128,7 +128,7 @@ void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int colo
|
|||
}
|
||||
} while (dx <= 0);
|
||||
}
|
||||
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int color) {
|
||||
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
|
||||
int dx = -int32_t(radius);
|
||||
int dy = 0;
|
||||
int err = 2 - 2 * radius;
|
||||
|
@ -155,7 +155,7 @@ void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int co
|
|||
} while (dx <= 0);
|
||||
}
|
||||
|
||||
void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align, const char *text) {
|
||||
void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align, const char *text) {
|
||||
int x_start, y_start;
|
||||
int width, height;
|
||||
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
|
||||
|
@ -197,16 +197,34 @@ void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align,
|
|||
i += match_length;
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg) {
|
||||
void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg) {
|
||||
char buffer[256];
|
||||
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
||||
if (ret > 0)
|
||||
this->print(x, y, font, color, align, buffer);
|
||||
}
|
||||
void DisplayBuffer::image(int x, int y, Image *image) {
|
||||
void DisplayBuffer::image(int x, int y, Image *image) { this->image(x, y, COLOR_ON, image); }
|
||||
void DisplayBuffer::image(int x, int y, Color color, Image *image, bool invert) {
|
||||
if (image->get_type() == BINARY) {
|
||||
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
||||
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
||||
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_ON : COLOR_OFF);
|
||||
if (invert)
|
||||
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_OFF : color);
|
||||
else
|
||||
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color : COLOR_OFF);
|
||||
}
|
||||
}
|
||||
} else if (image->get_type() == GRAYSCALE4) {
|
||||
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
||||
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
||||
this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale4_pixel(img_x, img_y));
|
||||
}
|
||||
}
|
||||
} else if (image->get_type() == RGB565) {
|
||||
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
||||
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
||||
this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +266,7 @@ void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font,
|
|||
break;
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::print(int x, int y, Font *font, int color, const char *text) {
|
||||
void DisplayBuffer::print(int x, int y, Font *font, Color color, const char *text) {
|
||||
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
|
||||
}
|
||||
void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
|
||||
|
@ -257,13 +275,13 @@ void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char
|
|||
void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
|
||||
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
|
||||
}
|
||||
void DisplayBuffer::printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...) {
|
||||
void DisplayBuffer::printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, align, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void DisplayBuffer::printf(int x, int y, Font *font, int color, const char *format, ...) {
|
||||
void DisplayBuffer::printf(int x, int y, Font *font, Color color, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
|
||||
|
@ -306,14 +324,14 @@ void DisplayBuffer::do_update_() {
|
|||
}
|
||||
}
|
||||
#ifdef USE_TIME
|
||||
void DisplayBuffer::strftime(int x, int y, Font *font, int color, TextAlign align, const char *format,
|
||||
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
|
||||
time::ESPTime time) {
|
||||
char buffer[64];
|
||||
size_t ret = time.strftime(buffer, sizeof(buffer), format);
|
||||
if (ret > 0)
|
||||
this->print(x, y, font, color, align, buffer);
|
||||
}
|
||||
void DisplayBuffer::strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time) {
|
||||
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) {
|
||||
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
|
||||
}
|
||||
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
|
||||
|
@ -431,10 +449,30 @@ bool Image::get_pixel(int x, int y) const {
|
|||
const uint32_t pos = x + y * width_8;
|
||||
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
}
|
||||
int Image::get_color_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return 0;
|
||||
|
||||
const uint32_t pos = (x + y * this->width_) * 2;
|
||||
int color = (pgm_read_byte(this->data_start_ + pos) << 8) + (pgm_read_byte(this->data_start_ + pos + 1));
|
||||
return color;
|
||||
}
|
||||
int Image::get_grayscale4_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return 0;
|
||||
const uint32_t pos = (x + y * this->width_) / 2;
|
||||
// 2 = number of pixels per byte, 4 = pixel shift
|
||||
uint8_t shift = (x % 2) * 4;
|
||||
int color = (pgm_read_byte(this->data_start_ + pos) >> shift) & 0x0f;
|
||||
return color;
|
||||
}
|
||||
int Image::get_width() const { return this->width_; }
|
||||
int Image::get_height() const { return this->height_; }
|
||||
ImageType Image::get_type() const { return this->type_; }
|
||||
Image::Image(const uint8_t *data_start, int width, int height)
|
||||
: width_(width), height_(height), data_start_(data_start) {}
|
||||
Image::Image(const uint8_t *data_start, int width, int height, int type)
|
||||
: width_(width), height_(height), type_((ImageType) type), data_start_(data_start) {}
|
||||
|
||||
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
|
||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/color.h"
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
@ -63,9 +64,11 @@ enum class TextAlign {
|
|||
};
|
||||
|
||||
/// Turn the pixel OFF.
|
||||
extern const uint8_t COLOR_OFF;
|
||||
extern const Color COLOR_OFF;
|
||||
/// Turn the pixel ON.
|
||||
extern const uint8_t COLOR_ON;
|
||||
extern const Color COLOR_ON;
|
||||
|
||||
enum ImageType { BINARY = 0, GRAYSCALE4 = 1, RGB565 = 2 };
|
||||
|
||||
enum DisplayRotation {
|
||||
DISPLAY_ROTATION_0_DEGREES = 0,
|
||||
|
@ -91,7 +94,7 @@ using display_writer_t = std::function<void(DisplayBuffer &)>;
|
|||
class DisplayBuffer {
|
||||
public:
|
||||
/// Fill the entire screen with the given color.
|
||||
virtual void fill(int color);
|
||||
virtual void fill(Color color);
|
||||
/// Clear the entire screen by filling it with OFF pixels.
|
||||
void clear();
|
||||
|
||||
|
@ -100,29 +103,29 @@ class DisplayBuffer {
|
|||
/// Get the height of the image in pixels with rotation applied.
|
||||
int get_height();
|
||||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
void draw_pixel_at(int x, int y, int color = COLOR_ON);
|
||||
void draw_pixel_at(int x, int y, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
|
||||
void line(int x1, int y1, int x2, int y2, int color = COLOR_ON);
|
||||
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
|
||||
void horizontal_line(int x, int y, int width, int color = COLOR_ON);
|
||||
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
|
||||
void vertical_line(int x, int y, int height, int color = COLOR_ON);
|
||||
void vertical_line(int x, int y, int height, Color color = COLOR_ON);
|
||||
|
||||
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
|
||||
/// [x1+width,y1+height].
|
||||
void rectangle(int x1, int y1, int width, int height, int color = COLOR_ON);
|
||||
void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
|
||||
|
||||
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
|
||||
void filled_rectangle(int x1, int y1, int width, int height, int color = COLOR_ON);
|
||||
void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
|
||||
|
||||
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
|
||||
void circle(int center_x, int center_xy, int radius, int color = COLOR_ON);
|
||||
void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
|
||||
|
||||
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
|
||||
void filled_circle(int center_x, int center_y, int radius, int color = COLOR_ON);
|
||||
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
|
||||
|
||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
|
@ -133,7 +136,7 @@ class DisplayBuffer {
|
|||
* @param align The alignment of the text.
|
||||
* @param text The text to draw.
|
||||
*/
|
||||
void print(int x, int y, Font *font, int color, TextAlign align, const char *text);
|
||||
void print(int x, int y, Font *font, Color color, TextAlign align, const char *text);
|
||||
|
||||
/** Print `text` with the top left at [x,y] with `font`.
|
||||
*
|
||||
|
@ -143,7 +146,7 @@ class DisplayBuffer {
|
|||
* @param color The color to draw the text with.
|
||||
* @param text The text to draw.
|
||||
*/
|
||||
void print(int x, int y, Font *font, int color, const char *text);
|
||||
void print(int x, int y, Font *font, Color color, const char *text);
|
||||
|
||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
|
@ -174,7 +177,7 @@ class DisplayBuffer {
|
|||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...)
|
||||
void printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...)
|
||||
__attribute__((format(printf, 7, 8)));
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
|
||||
|
@ -186,7 +189,7 @@ class DisplayBuffer {
|
|||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, Font *font, int color, const char *format, ...) __attribute__((format(printf, 6, 7)));
|
||||
void printf(int x, int y, Font *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
|
@ -220,7 +223,7 @@ class DisplayBuffer {
|
|||
* @param format The strftime format to use.
|
||||
* @param time The time to format.
|
||||
*/
|
||||
void strftime(int x, int y, Font *font, int color, TextAlign align, const char *format, time::ESPTime time)
|
||||
void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time)
|
||||
__attribute__((format(strftime, 7, 0)));
|
||||
|
||||
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
|
||||
|
@ -232,7 +235,7 @@ class DisplayBuffer {
|
|||
* @param format The strftime format to use.
|
||||
* @param time The time to format.
|
||||
*/
|
||||
void strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time)
|
||||
void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time)
|
||||
__attribute__((format(strftime, 6, 0)));
|
||||
|
||||
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
|
@ -261,6 +264,7 @@ class DisplayBuffer {
|
|||
|
||||
/// Draw the `image` with the top-left corner at [x,y] to the screen.
|
||||
void image(int x, int y, Image *image);
|
||||
void image(int x, int y, Color color, Image *image, bool invert = false);
|
||||
|
||||
/** Get the text bounds of the given string.
|
||||
*
|
||||
|
@ -290,9 +294,9 @@ class DisplayBuffer {
|
|||
void set_rotation(DisplayRotation rotation);
|
||||
|
||||
protected:
|
||||
void vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg);
|
||||
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||
|
||||
virtual void draw_absolute_pixel_internal(int x, int y, int color) = 0;
|
||||
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
|
||||
|
||||
virtual int get_height_internal() = 0;
|
||||
|
||||
|
@ -378,13 +382,18 @@ class Font {
|
|||
class Image {
|
||||
public:
|
||||
Image(const uint8_t *data_start, int width, int height);
|
||||
Image(const uint8_t *data_start, int width, int height, int type);
|
||||
bool get_pixel(int x, int y) const;
|
||||
int get_color_pixel(int x, int y) const;
|
||||
int get_grayscale4_pixel(int x, int y) const;
|
||||
int get_width() const;
|
||||
int get_height() const;
|
||||
ImageType get_type() const;
|
||||
|
||||
protected:
|
||||
int width_;
|
||||
int height_;
|
||||
ImageType type_{BINARY};
|
||||
const uint8_t *data_start_;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,13 +4,15 @@ from esphome import core
|
|||
from esphome.components import display, font
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE, CONF_TYPE
|
||||
from esphome.core import CORE, HexInt
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
MULTI_CONF = True
|
||||
|
||||
ImageType = {'binary': 0, 'grayscale4': 1, 'rgb565': 2}
|
||||
|
||||
Image_ = display.display_ns.class_('Image')
|
||||
|
||||
CONF_RAW_DATA_ID = 'raw_data_id'
|
||||
|
@ -19,6 +21,7 @@ IMAGE_SCHEMA = cv.Schema({
|
|||
cv.Required(CONF_ID): cv.declare_id(Image_),
|
||||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_TYPE): cv.string,
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
})
|
||||
|
||||
|
@ -37,11 +40,45 @@ def to_code(config):
|
|||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
|
||||
if CONF_TYPE in config:
|
||||
if config[CONF_TYPE].startswith('GRAYSCALE4'):
|
||||
width, height = image.size
|
||||
image = image.convert('L', dither=Image.NONE)
|
||||
pixels = list(image.getdata())
|
||||
data = [0 for _ in range(height * width // 2)]
|
||||
pos = 0
|
||||
for pixnum, pix in enumerate(pixels):
|
||||
pixshift = (pixnum % 2) * 4
|
||||
data[pos] |= (pix >> 4) << pixshift
|
||||
if pixshift != 0:
|
||||
pos += 1
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['grayscale4'])
|
||||
elif config[CONF_TYPE].startswith('RGB565'):
|
||||
width, height = image.size
|
||||
image = image.convert('RGB')
|
||||
pixels = list(image.getdata())
|
||||
data = [0 for _ in range(height * width * 2)]
|
||||
pos = 0
|
||||
for pix in pixels:
|
||||
r = (pix[0] >> 3) & 0x1F
|
||||
g = (pix[1] >> 2) & 0x3F
|
||||
b = (pix[2] >> 3) & 0x1F
|
||||
p = (r << 11) + (g << 5) + b
|
||||
data[pos] = (p >> 8) & 0xFF
|
||||
pos += 1
|
||||
data[pos] = p & 0xFF
|
||||
pos += 1
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['rgb565'])
|
||||
else:
|
||||
image = image.convert('1', dither=Image.NONE)
|
||||
width, height = image.size
|
||||
if width > 500 or height > 500:
|
||||
_LOGGER.warning("The image you requested is very big. Please consider using the resize "
|
||||
"parameter")
|
||||
_LOGGER.warning("The image you requested is very big. Please consider using"
|
||||
" the resize parameter.")
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range(height * width8 // 8)]
|
||||
for y in range(height):
|
||||
|
|
|
@ -123,7 +123,7 @@ int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; }
|
|||
|
||||
size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; }
|
||||
|
||||
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, int color) {
|
||||
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required
|
||||
this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, int color)
|
|||
uint16_t pos = x; // X is starting at 0 top left
|
||||
uint8_t subpos = y; // Y is starting at 0 top left
|
||||
|
||||
if (color == 1) {
|
||||
if (color.is_on()) {
|
||||
this->max_displaybuffer_[pos] |= (1 << subpos);
|
||||
} else {
|
||||
this->max_displaybuffer_[pos] &= ~(1 << subpos);
|
||||
|
|
|
@ -40,7 +40,7 @@ class MAX7219Component : public PollingComponent,
|
|||
|
||||
void turn_on_off(bool on_off);
|
||||
|
||||
void draw_absolute_pixel_internal(int x, int y, int color) override;
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
int get_height_internal() override;
|
||||
int get_width_internal() override;
|
||||
|
||||
|
|
|
@ -85,14 +85,14 @@ void HOT PCD8544::display() {
|
|||
this->command(this->PCD8544_SETYADDR);
|
||||
}
|
||||
|
||||
void HOT PCD8544::draw_absolute_pixel_internal(int x, int y, int color) {
|
||||
void HOT PCD8544::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;
|
||||
}
|
||||
|
||||
uint16_t pos = x + (y / 8) * this->get_width_internal();
|
||||
uint8_t subpos = y % 8;
|
||||
if (color) {
|
||||
if (color.is_on()) {
|
||||
this->buffer_[pos] |= (1 << subpos);
|
||||
} else {
|
||||
this->buffer_[pos] &= ~(1 << subpos);
|
||||
|
@ -117,8 +117,8 @@ void PCD8544::update() {
|
|||
this->display();
|
||||
}
|
||||
|
||||
void PCD8544::fill(int color) {
|
||||
uint8_t fill = color ? 0xFF : 0x00;
|
||||
void PCD8544::fill(Color color) {
|
||||
uint8_t fill = color.is_on() ? 0xFF : 0x00;
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class PCD8544 : public PollingComponent,
|
|||
|
||||
void update() override;
|
||||
|
||||
void fill(int color) override;
|
||||
void fill(Color color) override;
|
||||
|
||||
void setup() override {
|
||||
this->setup_pins_();
|
||||
|
@ -51,7 +51,7 @@ class PCD8544 : public PollingComponent,
|
|||
}
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, int color) override;
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
|
||||
void setup_pins_();
|
||||
|
||||
|
|
|
@ -179,20 +179,20 @@ size_t SSD1306::get_buffer_length_() {
|
|||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
|
||||
}
|
||||
|
||||
void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, int color) {
|
||||
void HOT SSD1306::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;
|
||||
|
||||
uint16_t pos = x + (y / 8) * this->get_width_internal();
|
||||
uint8_t subpos = y & 0x07;
|
||||
if (color) {
|
||||
if (color.is_on()) {
|
||||
this->buffer_[pos] |= (1 << subpos);
|
||||
} else {
|
||||
this->buffer_[pos] &= ~(1 << subpos);
|
||||
}
|
||||
}
|
||||
void SSD1306::fill(int color) {
|
||||
uint8_t fill = color ? 0xFF : 0x00;
|
||||
void SSD1306::fill(Color color) {
|
||||
uint8_t fill = color.is_on() ? 0xFF : 0x00;
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
|
|||
void set_brightness(float brightness) { this->brightness_ = brightness; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
void fill(int color) override;
|
||||
void fill(Color color) override;
|
||||
|
||||
protected:
|
||||
virtual void command(uint8_t value) = 0;
|
||||
|
@ -41,7 +41,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
|
|||
|
||||
bool is_sh1106_() const;
|
||||
|
||||
void draw_absolute_pixel_internal(int x, int y, int color) override;
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
|
||||
int get_height_internal() override;
|
||||
int get_width_internal() override;
|
||||
|
|
|
@ -144,20 +144,20 @@ size_t SSD1325::get_buffer_length_() {
|
|||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
|
||||
}
|
||||
|
||||
void HOT SSD1325::draw_absolute_pixel_internal(int x, int y, int color) {
|
||||
void HOT SSD1325::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;
|
||||
|
||||
uint16_t pos = x + (y / 8) * this->get_width_internal();
|
||||
uint8_t subpos = y % 8;
|
||||
if (color) {
|
||||
if (color.is_on()) {
|
||||
this->buffer_[pos] |= (1 << subpos);
|
||||
} else {
|
||||
this->buffer_[pos] &= ~(1 << subpos);
|
||||
}
|
||||
}
|
||||
void SSD1325::fill(int color) {
|
||||
uint8_t fill = color ? 0xFF : 0x00;
|
||||
void SSD1325::fill(Color color) {
|
||||
uint8_t fill = color.is_on() ? 0xFF : 0x00;
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
|
|
|
@ -28,14 +28,14 @@ class SSD1325 : public PollingComponent, public display::DisplayBuffer {
|
|||
void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
void fill(int color) override;
|
||||
void fill(Color color) override;
|
||||
|
||||
protected:
|
||||
virtual void command(uint8_t value) = 0;
|
||||
virtual void write_display_data() = 0;
|
||||
void init_reset_();
|
||||
|
||||
void draw_absolute_pixel_internal(int x, int y, int color) override;
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
|
||||
int get_height_internal() override;
|
||||
int get_width_internal() override;
|
||||
|
|
3
esphome/components/st7789v/__init__.py
Normal file
3
esphome/components/st7789v/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
st7789v_ns = cg.esphome_ns.namespace('st7789v')
|
44
esphome/components/st7789v/display.py
Normal file
44
esphome/components/st7789v/display.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.const import CONF_BACKLIGHT_PIN, CONF_BRIGHTNESS, CONF_CS_PIN, CONF_DC_PIN, CONF_ID, \
|
||||
CONF_LAMBDA, CONF_RESET_PIN
|
||||
from . import st7789v_ns
|
||||
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
ST7789V = st7789v_ns.class_('ST7789V', cg.PollingComponent, spi.SPIDevice,
|
||||
display.DisplayBuffer)
|
||||
ST7789VRef = ST7789V.operator('ref')
|
||||
|
||||
CONFIG_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(ST7789V),
|
||||
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema())
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
|
||||
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||
cg.add(var.set_dc_pin(dc))
|
||||
|
||||
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(reset))
|
||||
|
||||
bl = yield cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN])
|
||||
cg.add(var.set_backlight_pin(bl))
|
||||
|
||||
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_))
|
||||
|
||||
yield display.register_display(var, config)
|
274
esphome/components/st7789v/st7789v.cpp
Normal file
274
esphome/components/st7789v/st7789v.cpp
Normal file
|
@ -0,0 +1,274 @@
|
|||
#include "st7789v.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace st7789v {
|
||||
|
||||
static const char *TAG = "st7789v";
|
||||
|
||||
void ST7789V::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SPI ST7789V...");
|
||||
this->spi_setup();
|
||||
this->dc_pin_->setup(); // OUTPUT
|
||||
|
||||
this->init_reset_();
|
||||
|
||||
this->write_command_(ST7789_SLPOUT); // Sleep out
|
||||
delay(120); // NOLINT
|
||||
|
||||
this->write_command_(ST7789_NORON); // Normal display mode on
|
||||
|
||||
// *** display and color format setting ***
|
||||
this->write_command_(ST7789_MADCTL);
|
||||
this->write_data_(ST7789_MADCTL_COLOR_ORDER);
|
||||
|
||||
// JLX240 display datasheet
|
||||
this->write_command_(0xB6);
|
||||
this->write_data_(0x0A);
|
||||
this->write_data_(0x82);
|
||||
|
||||
this->write_command_(ST7789_COLMOD);
|
||||
this->write_data_(0x55);
|
||||
delay(10);
|
||||
|
||||
// *** ST7789V Frame rate setting ***
|
||||
this->write_command_(ST7789_PORCTRL);
|
||||
this->write_data_(0x0c);
|
||||
this->write_data_(0x0c);
|
||||
this->write_data_(0x00);
|
||||
this->write_data_(0x33);
|
||||
this->write_data_(0x33);
|
||||
|
||||
this->write_command_(ST7789_GCTRL); // Voltages: VGH / VGL
|
||||
this->write_data_(0x35);
|
||||
|
||||
// *** ST7789V Power setting ***
|
||||
this->write_command_(ST7789_VCOMS);
|
||||
this->write_data_(0x28); // JLX240 display datasheet
|
||||
|
||||
this->write_command_(ST7789_LCMCTRL);
|
||||
this->write_data_(0x0C);
|
||||
|
||||
this->write_command_(ST7789_VDVVRHEN);
|
||||
this->write_data_(0x01);
|
||||
this->write_data_(0xFF);
|
||||
|
||||
this->write_command_(ST7789_VRHS); // voltage VRHS
|
||||
this->write_data_(0x10);
|
||||
|
||||
this->write_command_(ST7789_VDVS);
|
||||
this->write_data_(0x20);
|
||||
|
||||
this->write_command_(ST7789_FRCTRL2);
|
||||
this->write_data_(0x0f);
|
||||
|
||||
this->write_command_(ST7789_PWCTRL1);
|
||||
this->write_data_(0xa4);
|
||||
this->write_data_(0xa1);
|
||||
|
||||
// *** ST7789V gamma setting ***
|
||||
this->write_command_(ST7789_PVGAMCTRL);
|
||||
this->write_data_(0xd0);
|
||||
this->write_data_(0x00);
|
||||
this->write_data_(0x02);
|
||||
this->write_data_(0x07);
|
||||
this->write_data_(0x0a);
|
||||
this->write_data_(0x28);
|
||||
this->write_data_(0x32);
|
||||
this->write_data_(0x44);
|
||||
this->write_data_(0x42);
|
||||
this->write_data_(0x06);
|
||||
this->write_data_(0x0e);
|
||||
this->write_data_(0x12);
|
||||
this->write_data_(0x14);
|
||||
this->write_data_(0x17);
|
||||
|
||||
this->write_command_(ST7789_NVGAMCTRL);
|
||||
this->write_data_(0xd0);
|
||||
this->write_data_(0x00);
|
||||
this->write_data_(0x02);
|
||||
this->write_data_(0x07);
|
||||
this->write_data_(0x0a);
|
||||
this->write_data_(0x28);
|
||||
this->write_data_(0x31);
|
||||
this->write_data_(0x54);
|
||||
this->write_data_(0x47);
|
||||
this->write_data_(0x0e);
|
||||
this->write_data_(0x1c);
|
||||
this->write_data_(0x17);
|
||||
this->write_data_(0x1b);
|
||||
this->write_data_(0x1e);
|
||||
|
||||
this->write_command_(ST7789_INVON);
|
||||
|
||||
// Clear display - ensures we do not see garbage at power-on
|
||||
this->draw_filled_rect_(0, 0, 239, 319, 0x0000);
|
||||
|
||||
delay(120); // NOLINT
|
||||
|
||||
this->write_command_(ST7789_DISPON); // Display on
|
||||
delay(120); // NOLINT
|
||||
|
||||
backlight_(true);
|
||||
|
||||
this->init_internal_(this->get_buffer_length_());
|
||||
memset(this->buffer_, 0x00, this->get_buffer_length_());
|
||||
}
|
||||
|
||||
void ST7789V::dump_config() {
|
||||
LOG_DISPLAY("", "SPI ST7789V", this);
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" B/L Pin: ", this->backlight_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
float ST7789V::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
|
||||
void ST7789V::update() {
|
||||
this->do_update_();
|
||||
this->write_display_data();
|
||||
}
|
||||
|
||||
void ST7789V::loop() {}
|
||||
|
||||
void ST7789V::write_display_data() {
|
||||
uint16_t x1 = 52; // _offsetx
|
||||
uint16_t x2 = 186; // _offsetx
|
||||
uint16_t y1 = 40; // _offsety
|
||||
uint16_t y2 = 279; // _offsety
|
||||
|
||||
this->enable();
|
||||
|
||||
// set column(x) address
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ST7789_CASET);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_addr_(x1, x2);
|
||||
// set page(y) address
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ST7789_RASET);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_addr_(y1, y2);
|
||||
// write display memory
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ST7789_RAMWR);
|
||||
this->dc_pin_->digital_write(true);
|
||||
|
||||
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void ST7789V::init_reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(1);
|
||||
// Trigger Reset
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(10);
|
||||
// Wake up
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ST7789V::backlight_(bool onoff) {
|
||||
if (this->backlight_pin_ != nullptr) {
|
||||
this->backlight_pin_->setup();
|
||||
this->backlight_pin_->digital_write(onoff);
|
||||
}
|
||||
}
|
||||
|
||||
void ST7789V::write_command_(uint8_t value) {
|
||||
this->enable();
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(value);
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void ST7789V::write_data_(uint8_t value) {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->enable();
|
||||
this->write_byte(value);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void ST7789V::write_addr_(uint16_t addr1, uint16_t addr2) {
|
||||
static uint8_t BYTE[4];
|
||||
BYTE[0] = (addr1 >> 8) & 0xFF;
|
||||
BYTE[1] = addr1 & 0xFF;
|
||||
BYTE[2] = (addr2 >> 8) & 0xFF;
|
||||
BYTE[3] = addr2 & 0xFF;
|
||||
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_array(BYTE, 4);
|
||||
}
|
||||
|
||||
void ST7789V::write_color_(uint16_t color, uint16_t size) {
|
||||
static uint8_t BYTE[1024];
|
||||
int index = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
BYTE[index++] = (color >> 8) & 0xFF;
|
||||
BYTE[index++] = color & 0xFF;
|
||||
}
|
||||
|
||||
this->dc_pin_->digital_write(true);
|
||||
return write_array(BYTE, size * 2);
|
||||
}
|
||||
|
||||
int ST7789V::get_height_internal() {
|
||||
return 240; // 320;
|
||||
}
|
||||
|
||||
int ST7789V::get_width_internal() {
|
||||
return 135; // 240;
|
||||
}
|
||||
|
||||
size_t ST7789V::get_buffer_length_() {
|
||||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) * 2;
|
||||
}
|
||||
|
||||
// Draw a filled rectangle
|
||||
// x1: Start X coordinate
|
||||
// y1: Start Y coordinate
|
||||
// x2: End X coordinate
|
||||
// y2: End Y coordinate
|
||||
// color: color
|
||||
void ST7789V::draw_filled_rect_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
|
||||
// ESP_LOGD(TAG,"offset(x)=%d offset(y)=%d",dev->_offsetx,dev->_offsety);
|
||||
this->enable();
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ST7789_CASET); // set column(x) address
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_addr_(x1, x2);
|
||||
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ST7789_RASET); // set Page(y) address
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_addr_(y1, y2);
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(ST7789_RAMWR); // begin a write to memory
|
||||
this->dc_pin_->digital_write(true);
|
||||
for (int i = x1; i <= x2; i++) {
|
||||
uint16_t size = y2 - y1 + 1;
|
||||
this->write_color_(color, size);
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
void HOT ST7789V::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;
|
||||
|
||||
auto color565 = color.to_rgb_565();
|
||||
|
||||
uint16_t pos = (x + y * this->get_width_internal()) * 2;
|
||||
this->buffer_[pos++] = (color565 >> 8) & 0xff;
|
||||
this->buffer_[pos] = color565 & 0xff;
|
||||
}
|
||||
|
||||
} // namespace st7789v
|
||||
} // namespace esphome
|
151
esphome/components/st7789v/st7789v.h
Normal file
151
esphome/components/st7789v/st7789v.h
Normal file
|
@ -0,0 +1,151 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace st7789v {
|
||||
|
||||
static const uint8_t BLACK = 0;
|
||||
static const uint8_t WHITE = 1;
|
||||
|
||||
static const uint8_t ST7789_NOP = 0x00; // No Operation
|
||||
static const uint8_t ST7789_SWRESET = 0x01; // Software Reset
|
||||
static const uint8_t ST7789_RDDID = 0x04; // Read Display ID
|
||||
static const uint8_t ST7789_RDDST = 0x09; // Read Display Status
|
||||
static const uint8_t ST7789_RDDPM = 0x0A; // Read Display Power Mode
|
||||
static const uint8_t ST7789_RDDMADCTL = 0x0B; // Read Display MADCTL
|
||||
static const uint8_t ST7789_RDDCOLMOD = 0x0C; // Read Display Pixel Format
|
||||
static const uint8_t ST7789_RDDIM = 0x0D; // Read Display Image Mode
|
||||
static const uint8_t ST7789_RDDSM = 0x0E; // Read Display Signal Mod
|
||||
static const uint8_t ST7789_RDDSDR = 0x0F; // Read Display Self-Diagnostic Resul
|
||||
static const uint8_t ST7789_SLPIN = 0x10; // Sleep in
|
||||
static const uint8_t ST7789_SLPOUT = 0x11; // Sleep Out
|
||||
static const uint8_t ST7789_PTLON = 0x12; // Partial Display Mode O
|
||||
static const uint8_t ST7789_NORON = 0x13; // Normal Display Mode O
|
||||
static const uint8_t ST7789_INVOFF = 0x20; // Display Inversion Off
|
||||
static const uint8_t ST7789_INVON = 0x21; // Display Inversion O
|
||||
static const uint8_t ST7789_GAMSET = 0x26; // Gamma Set
|
||||
static const uint8_t ST7789_DISPOFF = 0x28; // Display Off
|
||||
static const uint8_t ST7789_DISPON = 0x29; // Display On
|
||||
static const uint8_t ST7789_CASET = 0x2A; // Column Address Set
|
||||
static const uint8_t ST7789_RASET = 0x2B; // Row Address Set
|
||||
static const uint8_t ST7789_RAMWR = 0x2C; // Memory Write
|
||||
static const uint8_t ST7789_RAMRD = 0x2E; // Memory Read
|
||||
static const uint8_t ST7789_PTLAR = 0x30; // Partial Area
|
||||
static const uint8_t ST7789_VSCRDEF = 0x33; // Vertical Scrolling Definitio
|
||||
static const uint8_t ST7789_TEOFF = 0x34; // Tearing Effect Line OFF
|
||||
static const uint8_t ST7789_TEON = 0x35; // Tearing Effect Line On
|
||||
static const uint8_t ST7789_MADCTL = 0x36; // Memory Data Access Control
|
||||
static const uint8_t ST7789_VSCSAD = 0x37; // Vertical Scroll Start Address of RAM
|
||||
static const uint8_t ST7789_IDMOFF = 0x38; // Idle Mode Off
|
||||
static const uint8_t ST7789_IDMON = 0x39; // Idle mode on
|
||||
static const uint8_t ST7789_COLMOD = 0x3A; // Interface Pixel Format
|
||||
static const uint8_t ST7789_WRMEMC = 0x3C; // Write Memory Continue
|
||||
static const uint8_t ST7789_RDMEMC = 0x3E; // Read Memory Continue
|
||||
static const uint8_t ST7789_STE = 0x44; // Set Tear Scanline
|
||||
static const uint8_t ST7789_GSCAN = 0x45; // Get Scanlin
|
||||
static const uint8_t ST7789_WRDISBV = 0x51; // Write Display Brightness
|
||||
static const uint8_t ST7789_RDDISBV = 0x52; // Read Display Brightness Value
|
||||
static const uint8_t ST7789_WRCTRLD = 0x53; // Write CTRL Display
|
||||
static const uint8_t ST7789_RDCTRLD = 0x54; // Read CTRL Value Display
|
||||
static const uint8_t ST7789_WRCACE = 0x55; // Write Content Adaptive Brightness Control and Color Enhancement
|
||||
static const uint8_t ST7789_RDCABC = 0x56; // Read Content Adaptive Brightness Control
|
||||
static const uint8_t ST7789_WRCABCMB = 0x5E; // Write CABC Minimum Brightnes
|
||||
static const uint8_t ST7789_RDCABCMB = 0x5F; // Read CABC Minimum Brightnes
|
||||
static const uint8_t ST7789_RDABCSDR = 0x68; // Read Automatic Brightness Control Self-Diagnostic Result
|
||||
static const uint8_t ST7789_RDID1 = 0xDA; // Read ID1
|
||||
static const uint8_t ST7789_RDID2 = 0xDB; // Read ID2
|
||||
static const uint8_t ST7789_RDID3 = 0xDC; // Read ID3
|
||||
static const uint8_t ST7789_RAMCTRL = 0xB0; // RAM Control
|
||||
static const uint8_t ST7789_RGBCTRL = 0xB1; // RGB Interface Contro
|
||||
static const uint8_t ST7789_PORCTRL = 0xB2; // Porch Setting
|
||||
static const uint8_t ST7789_FRCTRL1 = 0xB3; // Frame Rate Control 1 (In partial mode/ idle colors)
|
||||
static const uint8_t ST7789_PARCTRL = 0xB5; // Partial mode Contro
|
||||
static const uint8_t ST7789_GCTRL = 0xB7; // Gate Contro
|
||||
static const uint8_t ST7789_GTADJ = 0xB8; // Gate On Timing Adjustmen
|
||||
static const uint8_t ST7789_DGMEN = 0xBA; // Digital Gamma Enable
|
||||
static const uint8_t ST7789_VCOMS = 0xBB; // VCOMS Setting
|
||||
static const uint8_t ST7789_LCMCTRL = 0xC0; // LCM Control
|
||||
static const uint8_t ST7789_IDSET = 0xC1; // ID Code Settin
|
||||
static const uint8_t ST7789_VDVVRHEN = 0xC2; // VDV and VRH Command Enabl
|
||||
static const uint8_t ST7789_VRHS = 0xC3; // VRH Set
|
||||
static const uint8_t ST7789_VDVS = 0xC4; // VDV Set
|
||||
static const uint8_t ST7789_VCMOFSET = 0xC5; // VCOMS Offset Set
|
||||
static const uint8_t ST7789_FRCTRL2 = 0xC6; // Frame Rate Control in Normal Mode
|
||||
static const uint8_t ST7789_CABCCTRL = 0xC7; // CABC Control
|
||||
static const uint8_t ST7789_REGSEL1 = 0xC8; // Register Value Selection 1
|
||||
static const uint8_t ST7789_REGSEL2 = 0xCA; // Register Value Selection
|
||||
static const uint8_t ST7789_PWMFRSEL = 0xCC; // PWM Frequency Selection
|
||||
static const uint8_t ST7789_PWCTRL1 = 0xD0; // Power Control 1
|
||||
static const uint8_t ST7789_VAPVANEN = 0xD2; // Enable VAP/VAN signal output
|
||||
static const uint8_t ST7789_CMD2EN = 0xDF; // Command 2 Enable
|
||||
static const uint8_t ST7789_PVGAMCTRL = 0xE0; // Positive Voltage Gamma Control
|
||||
static const uint8_t ST7789_NVGAMCTRL = 0xE1; // Negative Voltage Gamma Control
|
||||
static const uint8_t ST7789_DGMLUTR = 0xE2; // Digital Gamma Look-up Table for Red
|
||||
static const uint8_t ST7789_DGMLUTB = 0xE3; // Digital Gamma Look-up Table for Blue
|
||||
static const uint8_t ST7789_GATECTRL = 0xE4; // Gate Control
|
||||
static const uint8_t ST7789_SPI2EN = 0xE7; // SPI2 Enable
|
||||
static const uint8_t ST7789_PWCTRL2 = 0xE8; // Power Control 2
|
||||
static const uint8_t ST7789_EQCTRL = 0xE9; // Equalize time control
|
||||
static const uint8_t ST7789_PROMCTRL = 0xEC; // Program Mode Contro
|
||||
static const uint8_t ST7789_PROMEN = 0xFA; // Program Mode Enabl
|
||||
static const uint8_t ST7789_NVMSET = 0xFC; // NVM Setting
|
||||
static const uint8_t ST7789_PROMACT = 0xFE; // Program action
|
||||
|
||||
// Flags for ST7789_MADCTL
|
||||
static const uint8_t ST7789_MADCTL_MY = 0x80;
|
||||
static const uint8_t ST7789_MADCTL_MX = 0x40;
|
||||
static const uint8_t ST7789_MADCTL_MV = 0x20;
|
||||
static const uint8_t ST7789_MADCTL_ML = 0x10;
|
||||
static const uint8_t ST7789_MADCTL_RGB = 0x00;
|
||||
static const uint8_t ST7789_MADCTL_BGR = 0x08;
|
||||
static const uint8_t ST7789_MADCTL_MH = 0x04;
|
||||
static const uint8_t ST7789_MADCTL_SS = 0x02;
|
||||
static const uint8_t ST7789_MADCTL_GS = 0x01;
|
||||
|
||||
static const uint8_t ST7789_MADCTL_COLOR_ORDER = ST7789_MADCTL_BGR;
|
||||
|
||||
class ST7789V : public PollingComponent,
|
||||
public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
||||
spi::DATA_RATE_8MHZ> {
|
||||
public:
|
||||
void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_backlight_pin(GPIOPin *backlight_pin) { this->backlight_pin_ = backlight_pin; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
void write_display_data();
|
||||
|
||||
protected:
|
||||
GPIOPin *dc_pin_;
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
GPIOPin *backlight_pin_{nullptr};
|
||||
|
||||
void init_reset_();
|
||||
void backlight_(bool onoff);
|
||||
void write_command_(uint8_t value);
|
||||
void write_data_(uint8_t value);
|
||||
void write_addr_(uint16_t addr1, uint16_t addr2);
|
||||
void write_color_(uint16_t color, uint16_t size);
|
||||
|
||||
int get_height_internal() override;
|
||||
int get_width_internal() override;
|
||||
size_t get_buffer_length_();
|
||||
|
||||
void draw_filled_rect_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
|
||||
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
};
|
||||
|
||||
} // namespace st7789v
|
||||
} // namespace esphome
|
|
@ -115,20 +115,20 @@ void WaveshareEPaper::update() {
|
|||
this->do_update_();
|
||||
this->display();
|
||||
}
|
||||
void WaveshareEPaper::fill(int color) {
|
||||
void WaveshareEPaper::fill(Color color) {
|
||||
// flip logic
|
||||
const uint8_t fill = color ? 0x00 : 0xFF;
|
||||
const uint8_t fill = color.is_on() ? 0x00 : 0xFF;
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
|
||||
this->buffer_[i] = fill;
|
||||
}
|
||||
void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, int color) {
|
||||
void HOT WaveshareEPaper::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;
|
||||
|
||||
const uint32_t pos = (x + y * this->get_width_internal()) / 8u;
|
||||
const uint8_t subpos = x & 0x07;
|
||||
// flip logic
|
||||
if (!color)
|
||||
if (!color.is_on())
|
||||
this->buffer_[pos] |= 0x80 >> subpos;
|
||||
else
|
||||
this->buffer_[pos] &= ~(0x80 >> subpos);
|
||||
|
|
|
@ -26,7 +26,7 @@ class WaveshareEPaper : public PollingComponent,
|
|||
|
||||
void update() override;
|
||||
|
||||
void fill(int color) override;
|
||||
void fill(Color color) override;
|
||||
|
||||
void setup() override {
|
||||
this->setup_pins_();
|
||||
|
@ -36,7 +36,7 @@ class WaveshareEPaper : public PollingComponent,
|
|||
void on_safe_shutdown() override;
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, int color) override;
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
|
||||
bool wait_until_idle_();
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ CONF_AUTOMATION_ID = 'automation_id'
|
|||
CONF_AVAILABILITY = 'availability'
|
||||
CONF_AWAY = 'away'
|
||||
CONF_AWAY_CONFIG = 'away_config'
|
||||
CONF_BACKLIGHT_PIN = 'backlight_pin'
|
||||
CONF_BATTERY_LEVEL = 'battery_level'
|
||||
CONF_BATTERY_VOLTAGE = 'battery_voltage'
|
||||
CONF_BAUD_RATE = 'baud_rate'
|
||||
|
|
161
esphome/core/color.h
Normal file
161
esphome/core/color.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
#pragma once
|
||||
|
||||
#include "component.h"
|
||||
#include "helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; }
|
||||
|
||||
struct Color {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
uint8_t r;
|
||||
uint8_t red;
|
||||
};
|
||||
union {
|
||||
uint8_t g;
|
||||
uint8_t green;
|
||||
};
|
||||
union {
|
||||
uint8_t b;
|
||||
uint8_t blue;
|
||||
};
|
||||
union {
|
||||
uint8_t w;
|
||||
uint8_t white;
|
||||
};
|
||||
};
|
||||
uint8_t raw[4];
|
||||
uint32_t raw_32;
|
||||
};
|
||||
inline Color() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT
|
||||
inline Color(float red, float green, float blue) ALWAYS_INLINE : r(uint8_t(red * 255)),
|
||||
g(uint8_t(green * 255)),
|
||||
b(uint8_t(blue * 255)),
|
||||
w(0) {}
|
||||
inline Color(float red, float green, float blue, float white) ALWAYS_INLINE : r(uint8_t(red * 255)),
|
||||
g(uint8_t(green * 255)),
|
||||
b(uint8_t(blue * 255)),
|
||||
w(uint8_t(white * 255)) {}
|
||||
inline Color(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF),
|
||||
g((colorcode >> 8) & 0xFF),
|
||||
b((colorcode >> 0) & 0xFF),
|
||||
w((colorcode >> 24) & 0xFF) {}
|
||||
inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; }
|
||||
inline Color &operator=(const Color &rhs) ALWAYS_INLINE {
|
||||
this->r = rhs.r;
|
||||
this->g = rhs.g;
|
||||
this->b = rhs.b;
|
||||
this->w = rhs.w;
|
||||
return *this;
|
||||
}
|
||||
inline Color &operator=(uint32_t colorcode) ALWAYS_INLINE {
|
||||
this->w = (colorcode >> 24) & 0xFF;
|
||||
this->r = (colorcode >> 16) & 0xFF;
|
||||
this->g = (colorcode >> 8) & 0xFF;
|
||||
this->b = (colorcode >> 0) & 0xFF;
|
||||
return *this;
|
||||
}
|
||||
inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; }
|
||||
inline Color operator*(uint8_t scale) const ALWAYS_INLINE {
|
||||
return Color(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale),
|
||||
esp_scale8(this->white, scale));
|
||||
}
|
||||
inline Color &operator*=(uint8_t scale) ALWAYS_INLINE {
|
||||
this->red = esp_scale8(this->red, scale);
|
||||
this->green = esp_scale8(this->green, scale);
|
||||
this->blue = esp_scale8(this->blue, scale);
|
||||
this->white = esp_scale8(this->white, scale);
|
||||
return *this;
|
||||
}
|
||||
inline Color operator*(const Color &scale) const ALWAYS_INLINE {
|
||||
return Color(esp_scale8(this->red, scale.red), esp_scale8(this->green, scale.green),
|
||||
esp_scale8(this->blue, scale.blue), esp_scale8(this->white, scale.white));
|
||||
}
|
||||
inline Color &operator*=(const Color &scale) ALWAYS_INLINE {
|
||||
this->red = esp_scale8(this->red, scale.red);
|
||||
this->green = esp_scale8(this->green, scale.green);
|
||||
this->blue = esp_scale8(this->blue, scale.blue);
|
||||
this->white = esp_scale8(this->white, scale.white);
|
||||
return *this;
|
||||
}
|
||||
inline Color operator+(const Color &add) const ALWAYS_INLINE {
|
||||
Color ret;
|
||||
if (uint8_t(add.r + this->r) < this->r)
|
||||
ret.r = 255;
|
||||
else
|
||||
ret.r = this->r + add.r;
|
||||
if (uint8_t(add.g + this->g) < this->g)
|
||||
ret.g = 255;
|
||||
else
|
||||
ret.g = this->g + add.g;
|
||||
if (uint8_t(add.b + this->b) < this->b)
|
||||
ret.b = 255;
|
||||
else
|
||||
ret.b = this->b + add.b;
|
||||
if (uint8_t(add.w + this->w) < this->w)
|
||||
ret.w = 255;
|
||||
else
|
||||
ret.w = this->w + add.w;
|
||||
return ret;
|
||||
}
|
||||
inline Color &operator+=(const Color &add) ALWAYS_INLINE { return *this = (*this) + add; }
|
||||
inline Color operator+(uint8_t add) const ALWAYS_INLINE { return (*this) + Color(add, add, add, add); }
|
||||
inline Color &operator+=(uint8_t add) ALWAYS_INLINE { return *this = (*this) + add; }
|
||||
inline Color operator-(const Color &subtract) const ALWAYS_INLINE {
|
||||
Color ret;
|
||||
if (subtract.r > this->r)
|
||||
ret.r = 0;
|
||||
else
|
||||
ret.r = this->r - subtract.r;
|
||||
if (subtract.g > this->g)
|
||||
ret.g = 0;
|
||||
else
|
||||
ret.g = this->g - subtract.g;
|
||||
if (subtract.b > this->b)
|
||||
ret.b = 0;
|
||||
else
|
||||
ret.b = this->b - subtract.b;
|
||||
if (subtract.w > this->w)
|
||||
ret.w = 0;
|
||||
else
|
||||
ret.w = this->w - subtract.w;
|
||||
return ret;
|
||||
}
|
||||
inline Color &operator-=(const Color &subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
|
||||
inline Color operator-(uint8_t subtract) const ALWAYS_INLINE {
|
||||
return (*this) - Color(subtract, subtract, subtract, subtract);
|
||||
}
|
||||
inline Color &operator-=(uint8_t subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
|
||||
static Color random_color() {
|
||||
float r = float(random_uint32()) / float(UINT32_MAX);
|
||||
float g = float(random_uint32()) / float(UINT32_MAX);
|
||||
float b = float(random_uint32()) / float(UINT32_MAX);
|
||||
float w = float(random_uint32()) / float(UINT32_MAX);
|
||||
return Color(r, g, b, w);
|
||||
}
|
||||
Color fade_to_white(uint8_t amnt) { return Color(1, 1, 1, 1) - (*this * amnt); }
|
||||
Color fade_to_black(uint8_t amnt) { return *this * amnt; }
|
||||
Color lighten(uint8_t delta) { return *this + delta; }
|
||||
Color darken(uint8_t delta) { return *this - delta; }
|
||||
|
||||
uint32_t to_rgb_565() const {
|
||||
uint32_t color565 =
|
||||
(esp_scale8(this->red, 31) << 11) | (esp_scale8(this->green, 63) << 5) | (esp_scale8(this->blue, 31) << 0);
|
||||
return color565;
|
||||
}
|
||||
uint32_t to_bgr_565() const {
|
||||
uint32_t color565 =
|
||||
(esp_scale8(this->blue, 31) << 11) | (esp_scale8(this->green, 63) << 5) | (esp_scale8(this->red, 31) << 0);
|
||||
return color565;
|
||||
}
|
||||
uint32_t to_grayscale4() const {
|
||||
uint32_t gs4 = esp_scale8(this->white, 15);
|
||||
return gs4;
|
||||
}
|
||||
};
|
||||
static const Color COLOR_BLACK(0, 0, 0);
|
||||
static const Color COLOR_WHITE(1, 1, 1);
|
||||
}; // namespace esphome
|
|
@ -1507,6 +1507,16 @@ interval:
|
|||
|
||||
id(btn_left)->set_threshold(btn_left_state * 0.9);
|
||||
|
||||
color:
|
||||
- id: kbx_red
|
||||
red: 100%
|
||||
green: 1%
|
||||
blue: 2%
|
||||
- id: kbx_blue
|
||||
red: 0%
|
||||
green: 1%
|
||||
blue: 100%
|
||||
|
||||
display:
|
||||
- platform: lcd_gpio
|
||||
dimensions: 18x4
|
||||
|
@ -1591,6 +1601,13 @@ display:
|
|||
full_update_every: 30
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
- platform: st7789v
|
||||
cs_pin: GPIO5
|
||||
dc_pin: GPIO16
|
||||
reset_pin: GPIO23
|
||||
backlight_pin: GPIO4
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
tm1651:
|
||||
id: tm1651_battery
|
||||
|
|
Loading…
Reference in a new issue