Merge branch 'add-graphical-layout-system' of github.com:MrMDavidson/esphome into add-graphical-layout-system

This commit is contained in:
Michael Davidson 2024-01-28 16:38:50 +11:00
commit bae83021ed
No known key found for this signature in database
GPG key ID: B8D1A99712B8B0EB
8 changed files with 147 additions and 79 deletions

View file

@ -66,6 +66,7 @@ MODELS = {
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
"WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay),
} }
COLOR_ORDERS = { COLOR_ORDERS = {

View file

@ -7,7 +7,6 @@
namespace esphome { namespace esphome {
namespace ili9xxx { namespace ili9xxx {
static const char *const TAG = "ili9xxx";
static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer
@ -17,13 +16,7 @@ static inline void put16_be(uint8_t *buf, uint16_t value) {
buf[1] = value; buf[1] = value;
} }
void ILI9XXXDisplay::setup() { void ILI9XXXDisplay::set_madctl() {
ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_();
this->init_lcd_();
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
// custom x/y transform and color order // custom x/y transform and color order
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
if (this->swap_xy_) if (this->swap_xy_)
@ -32,8 +25,19 @@ void ILI9XXXDisplay::setup() {
mad |= MADCTL_MX; mad |= MADCTL_MX;
if (this->mirror_y_) if (this->mirror_y_)
mad |= MADCTL_MY; mad |= MADCTL_MY;
this->send_command(ILI9XXX_MADCTL, &mad, 1); this->command(ILI9XXX_MADCTL);
this->data(mad);
esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad);
}
void ILI9XXXDisplay::setup() {
ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_();
this->init_lcd_();
this->set_madctl();
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
this->x_low_ = this->width_; this->x_low_ = this->width_;
this->y_low_ = this->height_; this->y_low_ = this->height_;
this->x_high_ = 0; this->x_high_ = 0;
@ -89,6 +93,7 @@ void ILI9XXXDisplay::dump_config() {
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_);
ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB");
ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_)); ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_)); ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_)); ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
@ -196,7 +201,6 @@ void ILI9XXXDisplay::display_() {
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE]; uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
// check if something was displayed // check if something was displayed
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
ESP_LOGV(TAG, "Nothing to display");
return; return;
} }
@ -211,14 +215,13 @@ void ILI9XXXDisplay::display_() {
size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US; size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
ESP_LOGV(TAG, ESP_LOGV(TAG,
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, " "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
"height:%d, mode=%d, 18bit=%d, sw_time=%dus, mw_time=%dus)", "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_, this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
this->is_18bitdisplay_, sw_time, mw_time); this->is_18bitdisplay_, sw_time, mw_time);
auto now = millis(); auto now = millis();
this->enable();
if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) { if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
// 16 bit mode maps directly to display format // 16 bit mode maps directly to display format
ESP_LOGV(TAG, "Doing single write of %d bytes", this->width_ * h * 2); ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_); set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2); this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
} else { } else {
@ -267,7 +270,7 @@ void ILI9XXXDisplay::display_() {
this->write_array(transfer_buffer, idx); this->write_array(transfer_buffer, idx);
} }
} }
this->disable(); this->end_data_();
ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now)); ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
// invalidate watermarks // invalidate watermarks
this->x_low_ = this->width_; this->x_low_ = this->width_;
@ -290,7 +293,6 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
x_pad); x_pad);
} }
this->enable();
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
if (x_offset == 0 && x_pad == 0 && y_offset == 0) { if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
@ -302,7 +304,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
} }
} }
this->disable(); this->end_data_();
} }
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
@ -328,20 +330,6 @@ void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_byte
this->end_data_(); this->end_data_();
} }
uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) {
uint8_t data = 0x10 + index;
this->send_command(0xD9, &data, 1); // Set Index Register
uint8_t result;
this->start_command_();
this->write_byte(command_byte);
this->start_data_();
do {
result = this->read_byte();
} while (index--);
this->end_data_();
return result;
}
void ILI9XXXDisplay::start_command_() { void ILI9XXXDisplay::start_command_() {
this->dc_pin_->digital_write(false); this->dc_pin_->digital_write(false);
this->enable(); this->enable();
@ -357,9 +345,9 @@ void ILI9XXXDisplay::end_data_() { this->disable(); }
void ILI9XXXDisplay::reset_() { void ILI9XXXDisplay::reset_() {
if (this->reset_pin_ != nullptr) { if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false); this->reset_pin_->digital_write(false);
delay(10); delay(20);
this->reset_pin_->digital_write(true); this->reset_pin_->digital_write(true);
delay(10); delay(20);
} }
} }
@ -369,7 +357,7 @@ void ILI9XXXDisplay::init_lcd_() {
while ((cmd = *addr++) > 0) { while ((cmd = *addr++) > 0) {
x = *addr++; x = *addr++;
num_args = x & 0x7F; num_args = x & 0x7F;
send_command(cmd, addr, num_args); this->send_command(cmd, addr, num_args);
addr += num_args; addr += num_args;
if (x & 0x80) if (x & 0x80)
delay(150); // NOLINT delay(150); // NOLINT
@ -377,24 +365,23 @@ void ILI9XXXDisplay::init_lcd_() {
} }
// Tell the display controller where we want to draw pixels. // Tell the display controller where we want to draw pixels.
// when called, the SPI should have already been enabled, only the D/C pin will be toggled here.
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
uint8_t buf[4]; x1 += this->offset_x_;
this->dc_pin_->digital_write(false); x2 += this->offset_x_;
this->write_byte(ILI9XXX_CASET); // Column address set y1 += this->offset_y_;
put16_be(buf, x1 + this->offset_x_); y2 += this->offset_y_;
put16_be(buf + 2, x2 + this->offset_x_); this->command(ILI9XXX_CASET);
this->dc_pin_->digital_write(true); this->data(x1 >> 8);
this->write_array(buf, sizeof buf); this->data(x1 & 0xFF);
this->dc_pin_->digital_write(false); this->data(x2 >> 8);
this->write_byte(ILI9XXX_PASET); // Row address set this->data(x2 & 0xFF);
put16_be(buf, y1 + this->offset_y_); this->command(ILI9XXX_PASET); // Page address set
put16_be(buf + 2, y2 + this->offset_y_); this->data(y1 >> 8);
this->dc_pin_->digital_write(true); this->data(y1 & 0xFF);
this->write_array(buf, sizeof buf); this->data(y2 >> 8);
this->dc_pin_->digital_write(false); this->data(y2 & 0xFF);
this->write_byte(ILI9XXX_RAMWR); // Write to RAM this->command(ILI9XXX_RAMWR); // Write to RAM
this->dc_pin_->digital_write(true); this->start_data_();
} }
void ILI9XXXDisplay::invert_colors(bool invert) { void ILI9XXXDisplay::invert_colors(bool invert) {

View file

@ -8,6 +8,7 @@
namespace esphome { namespace esphome {
namespace ili9xxx { namespace ili9xxx {
static const char *const TAG = "ili9xxx";
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6 const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6
enum ILI9XXXColorMode { enum ILI9XXXColorMode {
@ -32,6 +33,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
while ((cmd = *addr++) != 0) { while ((cmd = *addr++) != 0) {
num_args = *addr++ & 0x7F; num_args = *addr++ & 0x7F;
bits = *addr; bits = *addr;
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
switch (cmd) { switch (cmd) {
case ILI9XXX_MADCTL: { case ILI9XXX_MADCTL: {
this->swap_xy_ = (bits & MADCTL_MV) != 0; this->swap_xy_ = (bits & MADCTL_MV) != 0;
@ -68,10 +70,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
this->offset_y_ = offset_y; this->offset_y_ = offset_y;
} }
void invert_colors(bool invert); void invert_colors(bool invert);
void command(uint8_t value); virtual void command(uint8_t value);
void data(uint8_t value); virtual void data(uint8_t value);
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
uint8_t read_command(uint8_t command_byte, uint8_t index);
void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; } void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; }
void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; } void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
@ -92,6 +93,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
void draw_absolute_pixel_internal(int x, int y, Color color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;
void setup_pins_(); void setup_pins_();
virtual void set_madctl();
void display_(); void display_();
void init_lcd_(); void init_lcd_();
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
@ -127,7 +129,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
bool need_update_ = false; bool need_update_ = false;
bool is_18bitdisplay_ = false; bool is_18bitdisplay_ = false;
bool pre_invertcolors_ = false; bool pre_invertcolors_ = false;
display::ColorOrder color_order_{}; display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
bool swap_xy_{}; bool swap_xy_{};
bool mirror_x_{}; bool mirror_x_{};
bool mirror_y_{}; bool mirror_y_{};
@ -181,10 +183,48 @@ class ILI9XXXILI9486 : public ILI9XXXDisplay {
ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {} ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
}; };
//----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXILI9488 : public ILI9XXXDisplay { class ILI9XXXILI9488 : public ILI9XXXDisplay {
public: public:
ILI9XXXILI9488() : ILI9XXXDisplay(INITCMD_ILI9488, 480, 320, true) {} ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {}
protected:
void set_madctl() override {
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
uint8_t dfun = 0x22;
this->width_ = 320;
this->height_ = 480;
if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) {
// no transforms
} else if (this->mirror_y_ && this->mirror_x_) {
// rotate 180
dfun = 0x42;
} else if (this->swap_xy_) {
this->width_ = 480;
this->height_ = 320;
mad |= 0x20;
if (this->mirror_x_) {
dfun = 0x02;
} else {
dfun = 0x62;
}
}
this->command(ILI9XXX_DFUNCTR);
this->data(0);
this->data(dfun);
this->command(ILI9XXX_MADCTL);
this->data(mad);
}
};
//----------- Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */
class WAVESHARERES35 : public ILI9XXXILI9488 {
public:
WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {}
void data(uint8_t value) override {
this->start_data_();
this->write_byte(0);
this->write_byte(value);
this->end_data_();
}
}; };
//----------- ILI9XXX_35_TFT origin colors rotated display -------------- //----------- ILI9XXX_35_TFT origin colors rotated display --------------

View file

@ -141,7 +141,8 @@ static const uint8_t PROGMEM INITCMD_ILI9486[] = {
0x00 // End of list 0x00 // End of list
}; };
static const uint8_t PROGMEM INITCMD_ILI9488[] = {
static const uint8_t INITCMD_ILI9488[] = {
ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00, ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00,
ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00, ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00,
@ -153,28 +154,27 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = {
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data 0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3 ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
ILI9XXX_MADCTL, 1, 0x28,
//ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
// 5 frames
//ILI9XXX_ETMOD, 1, 0xC6, //
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
//ILI9XXX_INVON , 0,
ILI9XXX_DISPON, 0x80, // Set display on ILI9XXX_DISPON, 0x80, // Set display on
0x00 // end 0x00 // end
}; };
static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = {
ILI9XXX_PWCTR3, 1, 0x33,
ILI9XXX_VMCTR1, 3, 0x00, 0x1e, 0x80,
ILI9XXX_FRMCTR1, 1, 0xA0,
ILI9XXX_GMCTRP1, 15, 0x0, 0x13, 0x18, 0x04, 0x0F, 0x06, 0x3a, 0x56, 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f,
ILI9XXX_GMCTRN1, 15, 0x0, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f,
ILI9XXX_PIXFMT, 1, 0x55,
ILI9XXX_SLPOUT, 0x80, // slpout, delay
ILI9XXX_DISPON, 0,
0x00 // End of list
};
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = { static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F, ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F, ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,

View file

@ -135,7 +135,7 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True), cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t, cv.Optional(CONF_FULL_UPDATE_EVERY): cv.int_range(min=1, max=4294967295),
cv.Optional(CONF_RESET_DURATION): cv.All( cv.Optional(CONF_RESET_DURATION): cv.All(
cv.positive_time_period_milliseconds, cv.positive_time_period_milliseconds,
cv.Range(max=core.TimePeriod(milliseconds=500)), cv.Range(max=core.TimePeriod(milliseconds=500)),

View file

@ -7,6 +7,7 @@ import logging
import math import math
import os import os
import uuid import uuid
from io import TextIOWrapper
from typing import Any from typing import Any
import yaml import yaml
@ -19,7 +20,7 @@ except ImportError:
FastestAvailableSafeLoader = PurePythonLoader FastestAvailableSafeLoader = PurePythonLoader
from esphome import core from esphome import core
from esphome.config_helpers import Extend, Remove, read_config_file from esphome.config_helpers import Extend, Remove
from esphome.core import ( from esphome.core import (
CORE, CORE,
DocumentRange, DocumentRange,
@ -418,19 +419,26 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
def _load_yaml_internal(fname: str) -> Any: def _load_yaml_internal(fname: str) -> Any:
"""Load a YAML file.""" """Load a YAML file."""
content = read_config_file(fname)
try: try:
return _load_yaml_internal_with_type(ESPHomeLoader, fname, content) with open(fname, encoding="utf-8") as f_handle:
except EsphomeError: try:
# Loading failed, so we now load with the Python loader which has more return _load_yaml_internal_with_type(ESPHomeLoader, fname, f_handle)
# readable exceptions except EsphomeError:
return _load_yaml_internal_with_type(ESPHomePurePythonLoader, fname, content) # Loading failed, so we now load with the Python loader which has more
# readable exceptions
# Rewind the stream so we can try again
f_handle.seek(0, 0)
return _load_yaml_internal_with_type(
ESPHomePurePythonLoader, fname, f_handle
)
except (UnicodeDecodeError, OSError) as err:
raise EsphomeError(f"Error reading file {fname}: {err}") from err
def _load_yaml_internal_with_type( def _load_yaml_internal_with_type(
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader], loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
fname: str, fname: str,
content: str, content: TextIOWrapper,
) -> Any: ) -> Any:
"""Load a YAML file.""" """Load a YAML file."""
loader = loader_type(content) loader = loader_type(content)

View file

@ -0,0 +1,12 @@
esphome:
name: test
esp32:
board: esp32dev
wifi:
ap: ~
image:
- id: its_a_bug
file: "mdi:bug"

View file

@ -22,3 +22,23 @@ def test_loading_a_broken_yaml_file(fixture_path):
yaml_util.load_yaml(yaml_file) yaml_util.load_yaml(yaml_file)
except EsphomeError as err: except EsphomeError as err:
assert "broken_included.yaml" in str(err) assert "broken_included.yaml" in str(err)
def test_loading_a_yaml_file_with_a_missing_component(fixture_path):
"""Ensure we show the filename for a yaml file with a missing component."""
yaml_file = fixture_path / "yaml_util" / "missing_comp.yaml"
try:
yaml_util.load_yaml(yaml_file)
except EsphomeError as err:
assert "missing_comp.yaml" in str(err)
def test_loading_a_missing_file(fixture_path):
"""We throw EsphomeError when loading a missing file."""
yaml_file = fixture_path / "yaml_util" / "missing.yaml"
try:
yaml_util.load_yaml(yaml_file)
except EsphomeError as err:
assert "missing.yaml" in str(err)