Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Daniël Koek 2024-05-27 09:26:00 +01:00
commit ad5c3c37a7
23 changed files with 4356 additions and 4219 deletions

View file

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:

View file

@ -40,7 +40,7 @@ jobs:
arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"]
steps:
- uses: actions/checkout@v4.1.5
- uses: actions/checkout@v4.1.6
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:

View file

@ -34,7 +34,7 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
@ -66,7 +66,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -87,7 +87,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -108,7 +108,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -129,7 +129,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -150,7 +150,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -199,7 +199,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -229,7 +229,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -254,7 +254,7 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Find all YAML test files
id: set-matrix
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
@ -271,7 +271,7 @@ jobs:
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -303,7 +303,7 @@ jobs:
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -358,7 +358,7 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -410,7 +410,7 @@ jobs:
count: ${{ steps.list-components.outputs.count }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500
@ -458,7 +458,7 @@ jobs:
run: sudo apt-get install libsodium-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -484,7 +484,7 @@ jobs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Split components into 20 groups
id: split
run: |
@ -512,7 +512,7 @@ jobs:
run: sudo apt-get install libsodium-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Restore Python
uses: ./.github/actions/restore-python
with:

View file

@ -19,7 +19,7 @@ jobs:
tag: ${{ steps.tag.outputs.tag }}
branch_build: ${{ steps.tag.outputs.branch_build }}
steps:
- uses: actions/checkout@v4.1.5
- uses: actions/checkout@v4.1.6
- name: Get tag
id: tag
# yamllint disable rule:line-length
@ -51,7 +51,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4.1.5
- uses: actions/checkout@v4.1.6
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:
@ -83,7 +83,7 @@ jobs:
- linux/arm/v7
- linux/arm64
steps:
- uses: actions/checkout@v4.1.5
- uses: actions/checkout@v4.1.6
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:
@ -174,7 +174,7 @@ jobs:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@v4.1.5
- uses: actions/checkout@v4.1.6
- name: Download digests
uses: actions/download-artifact@v4.1.7

View file

@ -13,10 +13,10 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Checkout Home Assistant
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
with:
repository: home-assistant/core
path: lib/home-assistant

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.5
uses: actions/checkout@v4.1.6
- name: Run yamllint
uses: frenck/action-yamllint@v1.5.0
with:

View file

@ -351,7 +351,7 @@ def upload_program(config, args, host):
not is_ip_address(CORE.address) # pylint: disable=too-many-boolean-expressions
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config
and (not args.device or args.device == "MQTT")
and (not args.device or args.device in ("MQTT", "OTA"))
):
from esphome import mqtt

View file

@ -1,12 +1,7 @@
#include "deep_sleep_component.h"
#include <cinttypes>
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
namespace esphome {
namespace deep_sleep {
@ -14,25 +9,6 @@ static const char *const TAG = "deep_sleep";
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
#ifdef USE_ESP32
if (this->wakeup_cause_to_run_duration_.has_value()) {
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
switch (wakeup_cause) {
case ESP_SLEEP_WAKEUP_EXT0:
case ESP_SLEEP_WAKEUP_EXT1:
case ESP_SLEEP_WAKEUP_GPIO:
return this->wakeup_cause_to_run_duration_->gpio_cause;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
return this->wakeup_cause_to_run_duration_->touch_cause;
default:
return this->wakeup_cause_to_run_duration_->default_cause;
}
}
#endif
return this->run_duration_;
}
void DeepSleepComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
global_has_deep_sleep = true;
@ -45,6 +21,7 @@ void DeepSleepComponent::setup() {
ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured.");
}
}
void DeepSleepComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
if (this->sleep_duration_.has_value()) {
@ -54,65 +31,31 @@ void DeepSleepComponent::dump_config() {
if (this->run_duration_.has_value()) {
ESP_LOGCONFIG(TAG, " Run Duration: %" PRIu32 " ms", *this->run_duration_);
}
#ifdef USE_ESP32
if (wakeup_pin_ != nullptr) {
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
}
if (this->wakeup_cause_to_run_duration_.has_value()) {
ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %" PRIu32 " ms",
this->wakeup_cause_to_run_duration_->default_cause);
ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %" PRIu32 " ms", this->wakeup_cause_to_run_duration_->touch_cause);
ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %" PRIu32 " ms", this->wakeup_cause_to_run_duration_->gpio_cause);
}
#endif
this->dump_config_platform_();
}
void DeepSleepComponent::loop() {
if (this->next_enter_deep_sleep_)
this->begin_sleep();
}
float DeepSleepComponent::get_loop_priority() const {
return -100.0f; // run after everything else is ready
}
void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; }
#if defined(USE_ESP32)
void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
this->wakeup_pin_mode_ = wakeup_pin_mode;
}
#endif
#if defined(USE_ESP32)
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
#endif
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
}
#endif
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
void DeepSleepComponent::begin_sleep(bool manual) {
if (this->prevent_ && !manual) {
this->next_enter_deep_sleep_ = true;
return;
}
#ifdef USE_ESP32
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr &&
!this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
ESP_LOGW(TAG, "Waiting for pin_ to switch state to enter deep sleep...");
}
this->next_enter_deep_sleep_ = true;
if (!this->prepare_to_sleep_()) {
return;
}
#endif
ESP_LOGI(TAG, "Beginning Deep Sleep");
if (this->sleep_duration_.has_value()) {
@ -120,47 +63,13 @@ void DeepSleepComponent::begin_sleep(bool manual) {
}
App.run_safe_shutdown_hooks();
#if defined(USE_ESP32)
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) {
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
}
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
}
if (this->ext1_wakeup_.has_value()) {
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
this->deep_sleep_();
}
if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) {
esp_sleep_enable_touchpad_wakeup();
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
}
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) {
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
}
esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
}
#endif
esp_deep_sleep_start();
#endif
#ifdef USE_ESP8266
ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance)
#endif
}
float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; }
void DeepSleepComponent::prevent_deep_sleep() { this->prevent_ = true; }
void DeepSleepComponent::allow_deep_sleep() { this->prevent_ = false; }
} // namespace deep_sleep

View file

@ -106,6 +106,10 @@ class DeepSleepComponent : public Component {
// duration before entering deep sleep.
optional<uint32_t> get_run_duration_() const;
void dump_config_platform_();
bool prepare_to_sleep_();
void deep_sleep_();
optional<uint64_t> sleep_duration_;
#ifdef USE_ESP32
InternalGPIOPin *wakeup_pin_;

View file

@ -0,0 +1,104 @@
#ifdef USE_ESP32
#include "deep_sleep_component.h"
#include "esphome/core/log.h"
namespace esphome {
namespace deep_sleep {
static const char *const TAG = "deep_sleep";
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
if (this->wakeup_cause_to_run_duration_.has_value()) {
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
switch (wakeup_cause) {
case ESP_SLEEP_WAKEUP_EXT0:
case ESP_SLEEP_WAKEUP_EXT1:
case ESP_SLEEP_WAKEUP_GPIO:
return this->wakeup_cause_to_run_duration_->gpio_cause;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
return this->wakeup_cause_to_run_duration_->touch_cause;
default:
return this->wakeup_cause_to_run_duration_->default_cause;
}
}
return this->run_duration_;
}
void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
this->wakeup_pin_mode_ = wakeup_pin_mode;
}
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
#endif
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
}
void DeepSleepComponent::dump_config_platform_() {
if (wakeup_pin_ != nullptr) {
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
}
if (this->wakeup_cause_to_run_duration_.has_value()) {
ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %" PRIu32 " ms",
this->wakeup_cause_to_run_duration_->default_cause);
ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %" PRIu32 " ms", this->wakeup_cause_to_run_duration_->touch_cause);
ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %" PRIu32 " ms", this->wakeup_cause_to_run_duration_->gpio_cause);
}
}
bool DeepSleepComponent::prepare_to_sleep_() {
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr &&
!this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
ESP_LOGW(TAG, "Waiting for pin_ to switch state to enter deep sleep...");
}
this->next_enter_deep_sleep_ = true;
return false;
}
return true;
}
void DeepSleepComponent::deep_sleep_() {
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) {
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
}
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
}
if (this->ext1_wakeup_.has_value()) {
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
}
if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) {
esp_sleep_enable_touchpad_wakeup();
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
}
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) {
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
}
esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
}
#endif
esp_deep_sleep_start();
}
} // namespace deep_sleep
} // namespace esphome
#endif

View file

@ -0,0 +1,23 @@
#ifdef USE_ESP8266
#include "deep_sleep_component.h"
#include <Esp.h>
namespace esphome {
namespace deep_sleep {
static const char *const TAG = "deep_sleep";
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {}
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
void DeepSleepComponent::deep_sleep_() {
ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance)
}
} // namespace deep_sleep
} // namespace esphome
#endif

View file

@ -47,6 +47,12 @@ ILI9XXXDisplay = ili9xxx_ns.class_(
display.DisplayBuffer,
)
PixelMode = ili9xxx_ns.enum("PixelMode")
PIXEL_MODES = {
"16bit": PixelMode.PIXEL_MODE_16,
"18bit": PixelMode.PIXEL_MODE_18,
}
ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode")
ColorOrder = display.display_ns.enum("ColorMode")
@ -68,6 +74,7 @@ MODELS = {
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
"WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay),
"CUSTOM": ILI9XXXDisplay,
}
COLOR_ORDERS = {
@ -80,14 +87,37 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
CONF_LED_PIN = "led_pin"
CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
CONF_INVERT_DISPLAY = "invert_display"
CONF_PIXEL_MODE = "pixel_mode"
CONF_INIT_SEQUENCE = "init_sequence"
def cmd(c, *args):
"""
Create a command sequence
:param c: The command (8 bit)
:param args: zero or more arguments (8 bit values)
:return: a list with the command, the argument count and the arguments
"""
return [c, len(args)] + list(args)
def map_sequence(value):
"""
An initialisation sequence is a literal array of data bytes.
The format is a repeated sequence of [CMD, <data>]
"""
if len(value) == 0:
raise cv.Invalid("Empty sequence")
return cmd(*value)
def _validate(config):
if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get(
CONF_COLOR_PALETTE_IMAGES
if (
config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE"
and CONF_COLOR_PALETTE_IMAGES not in config
):
raise cv.Invalid(
"Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette"
"IMAGE_ADAPTIVE palette requires at least one 'color_palette_images' entry"
)
if (
config.get(CONF_COLOR_PALETTE_IMAGES)
@ -96,7 +126,8 @@ def _validate(config):
raise cv.Invalid(
"Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'"
)
if CORE.is_esp8266 and config.get(CONF_MODEL) not in [
model = config[CONF_MODEL]
if CORE.is_esp8266 and model not in [
"M5STACK",
"TFT_2.4",
"TFT_2.4R",
@ -104,9 +135,12 @@ def _validate(config):
"ILI9342",
"ST7789V",
]:
raise cv.Invalid(
"Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard"
)
raise cv.Invalid("Selected model can't run on ESP8266.")
if model == "CUSTOM":
if CONF_INIT_SEQUENCE not in config or CONF_DIMENSIONS not in config:
raise cv.Invalid("CUSTOM model requires init_sequence and dimensions")
return config
@ -116,6 +150,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),
cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"),
cv.Optional(CONF_PIXEL_MODE): cv.enum(PIXEL_MODES),
cv.Optional(CONF_DIMENSIONS): cv.Any(
cv.dimensions,
cv.Schema(
@ -150,6 +185,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
}
),
cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence),
}
)
.extend(cv.polling_component_schema("1s"))
@ -167,6 +203,14 @@ async def to_code(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 init_sequences := config.get(CONF_INIT_SEQUENCE):
sequence = []
for seq in init_sequences:
sequence.extend(seq)
cg.add(var.add_init_sequence(sequence))
if pixel_mode := config.get(CONF_PIXEL_MODE):
cg.add(var.set_pixel_mode(pixel_mode))
if CONF_COLOR_ORDER in config:
cg.add(var.set_color_order(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
if CONF_TRANSFORM in config:

View file

@ -34,7 +34,26 @@ void ILI9XXXDisplay::setup() {
ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_();
this->init_lcd_();
this->init_lcd_(this->init_sequence_);
this->init_lcd_(this->extra_init_sequence_.data());
switch (this->pixel_mode_) {
case PIXEL_MODE_16:
if (this->is_18bitdisplay_) {
this->command(ILI9XXX_PIXFMT);
this->data(0x55);
this->is_18bitdisplay_ = false;
}
break;
case PIXEL_MODE_18:
if (!this->is_18bitdisplay_) {
this->command(ILI9XXX_PIXFMT);
this->data(0x66);
this->is_18bitdisplay_ = true;
}
break;
default:
break;
}
this->set_madctl();
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
@ -203,7 +222,6 @@ void ILI9XXXDisplay::update() {
}
void ILI9XXXDisplay::display_() {
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
// check if something was displayed
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
return;
@ -231,6 +249,7 @@ void ILI9XXXDisplay::display_() {
this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
} else {
ESP_LOGV(TAG, "Doing multiple write");
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
size_t rem = h * w; // remaining number of pixels to write
set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_);
size_t idx = 0; // index into transfer_buffer
@ -247,7 +266,7 @@ void ILI9XXXDisplay::display_() {
display::ColorUtil::index8_to_color_palette888(this->buffer_[pos++], this->palette_));
break;
default: // case BITS_16:
color_val = (buffer_[pos * 2] << 8) + buffer_[pos * 2 + 1];
color_val = (this->buffer_[pos * 2] << 8) + this->buffer_[pos * 2 + 1];
pos++;
break;
}
@ -259,7 +278,7 @@ void ILI9XXXDisplay::display_() {
put16_be(transfer_buffer + idx, color_val);
idx += 2;
}
if (idx == ILI9XXX_TRANSFER_BUFFER_SIZE) {
if (idx == sizeof(transfer_buffer)) {
this->write_array(transfer_buffer, idx);
idx = 0;
App.feed_wdt();
@ -293,22 +312,52 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
// if color mapping or software rotation is required, hand this off to the parent implementation. This will
// do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
// configured the renderer well.
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian ||
this->is_18bitdisplay_) {
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) {
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
x_pad);
}
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.
auto stride = x_offset + w + x_pad;
if (!this->is_18bitdisplay_) {
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
// we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother
this->write_array(ptr, w * h * 2);
} else {
auto stride = x_offset + w + x_pad;
for (size_t y = 0; y != h; y++) {
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
}
}
} else {
// 18 bit mode
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE * 4];
ESP_LOGV(TAG, "Doing multiple write");
size_t rem = h * w; // remaining number of pixels to write
size_t idx = 0; // index into transfer_buffer
size_t pixel = 0; // pixel number offset
ptr += (y_offset * stride + x_offset) * 2;
while (rem-- != 0) {
uint8_t hi_byte = *ptr++;
uint8_t lo_byte = *ptr++;
transfer_buffer[idx++] = hi_byte & 0xF8; // Blue
transfer_buffer[idx++] = ((hi_byte << 5) | (lo_byte) >> 5); // Green
transfer_buffer[idx++] = lo_byte << 3; // Red
if (idx == sizeof(transfer_buffer)) {
this->write_array(transfer_buffer, idx);
idx = 0;
App.feed_wdt();
}
// end of line? Skip to the next.
if (++pixel == w) {
pixel = 0;
ptr += (x_pad + x_offset) * 2;
}
}
// flush any balance.
if (idx != 0) {
this->write_array(transfer_buffer, idx);
}
}
this->end_data_();
}
@ -356,10 +405,11 @@ void ILI9XXXDisplay::reset_() {
}
}
void ILI9XXXDisplay::init_lcd_() {
void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
if (addr == nullptr)
return;
uint8_t cmd, x, num_args;
const uint8_t *addr = this->init_sequence_;
while ((cmd = *addr++) > 0) {
while ((cmd = *addr++) != 0) {
x = *addr++;
num_args = x & 0x7F;
this->send_command(cmd, addr, num_args);

View file

@ -17,6 +17,12 @@ enum ILI9XXXColorMode {
BITS_16 = 0x10,
};
enum PixelMode {
PIXEL_MODE_UNSPECIFIED,
PIXEL_MODE_16,
PIXEL_MODE_18,
};
class ILI9XXXDisplay : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
@ -52,6 +58,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
}
}
void add_init_sequence(const std::vector<uint8_t> &sequence) { this->extra_init_sequence_ = sequence; }
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; }
@ -73,6 +80,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
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_y(bool mirror_y) { this->mirror_y_ = mirror_y; }
void set_pixel_mode(PixelMode mode) { this->pixel_mode_ = mode; }
void update() override;
@ -99,11 +107,12 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
virtual void set_madctl();
void display_();
void init_lcd_();
void init_lcd_(const uint8_t *addr);
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
void reset_();
uint8_t const *init_sequence_{};
std::vector<uint8_t> extra_init_sequence_;
int16_t width_{0}; ///< Display width as modified by current rotation
int16_t height_{0}; ///< Display height as modified by current rotation
int16_t offset_x_{0};
@ -112,7 +121,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
uint16_t y_low_{0};
uint16_t x_high_{0};
uint16_t y_high_{0};
const uint8_t *palette_;
const uint8_t *palette_{};
ILI9XXXColorMode buffer_color_mode_{BITS_16};
@ -133,6 +142,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
bool prossing_update_ = false;
bool need_update_ = false;
bool is_18bitdisplay_ = false;
PixelMode pixel_mode_{};
bool pre_invertcolors_ = false;
display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
bool swap_xy_{};

View file

@ -63,7 +63,13 @@ def validate_tolerance(value):
if "%" in str(value):
type_ = TYPE_PERCENTAGE
else:
try:
cv.positive_time_period_microseconds(value)
type_ = TYPE_TIME
except cv.Invalid as exc:
raise cv.Invalid(
"Tolerance must be a percentage or time. Configurations made before 2024.5.0 treated the value as a percentage."
) from exc
return TOLERANCE_SCHEMA(
{

File diff suppressed because it is too large Load diff

View file

@ -233,7 +233,7 @@ template<typename... Ts> class Automation {
public:
explicit Automation(Trigger<Ts...> *trigger) : trigger_(trigger) { this->trigger_->set_automation_parent(this); }
Action<Ts...> *add_action(Action<Ts...> *action) { this->actions_.add_action(action); }
void add_action(Action<Ts...> *action) { this->actions_.add_action(action); }
void add_actions(const std::vector<Action<Ts...> *> &actions) { this->actions_.add_actions(actions); }
void stop() { this->actions_.stop(); }

View file

@ -103,7 +103,7 @@ class DashboardEntries:
def all(self) -> list[DashboardEntry]:
"""Return all entries."""
return asyncio.run_coroutine_threadsafe(self._async_all, self._loop).result()
return asyncio.run_coroutine_threadsafe(self._async_all(), self._loop).result()
def async_all(self) -> list[DashboardEntry]:
"""Return all entries."""

View file

@ -277,6 +277,7 @@ def wizard(path):
from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.rtl87xx import boards as rtl87xx_boards
from esphome.components.rp2040 import boards as rp2040_boards
if not path.endswith(".yaml") and not path.endswith(".yml"):
safe_print(
@ -343,7 +344,7 @@ def wizard(path):
"firmwares for it."
)
wizard_platforms = ["ESP32", "ESP8266", "BK72XX", "RTL87XX"]
wizard_platforms = ["ESP32", "ESP8266", "BK72XX", "RTL87XX", "RP2040"]
safe_print(
"Please choose one of the supported microcontrollers "
"(Use ESP8266 for Sonoff devices)."
@ -373,6 +374,10 @@ def wizard(path):
board_link = (
"http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards"
)
elif platform == "RP2040":
board_link = (
"https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html"
)
elif platform in ["BK72XX", "RTL87XX"]:
board_link = "https://docs.libretiny.eu/docs/status/supported/"
else:
@ -397,6 +402,10 @@ def wizard(path):
elif platform == "RTL87XX":
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'wr3')}\".")
boards_list = rtl87xx_boards.BOARDS.items()
elif platform == "RP2040":
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'rpipicow')}\".")
boards_list = rp2040_boards.BOARDS.items()
else:
raise NotImplementedError("Unknown platform!")
@ -423,6 +432,8 @@ def wizard(path):
safe_print()
sleep(1)
# Do not create wifi if the board does not support it
if board not in ["rpipico"]:
safe_print_step(3, WIFI_BIG)
safe_print("In this step, I'm going to create the configuration for WiFi.")
safe_print()
@ -477,6 +488,8 @@ def wizard(path):
sleep(0.25)
safe_print("Press ENTER for no password")
password = safe_input(color(Fore.BOLD_WHITE, "(password): "))
else:
ssid, password, psk = "", "", ""
if not wizard_write(
path=path,

View file

@ -20,7 +20,7 @@ fi
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt -r requirements_dev.txt
pip3 install setuptools wheel
pip3 install -e --config-settings editable_mode=compat ".[dev,test,displays]"
pip3 install -e ".[dev,test,displays]" --config-settings editable_mode=compat
pre-commit install

View file

@ -37,9 +37,10 @@ start_esphome() {
# Start esphome process
echo "> [$target_component] [$test_name] [$target_platform]"
echo "esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file"
set -x
# TODO: Validate escape of Command line substitution value
esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
python -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
{ set +x; } 2>/dev/null
}
# Find all test yaml files.

View file

@ -12,24 +12,12 @@ display:
swap_xy: true
mirror_x: true
mirror_y: false
model: TFT 2.4
color_palette: GRAYSCALE
model: custom
cs_pin: 12
dc_pin: 13
reset_pin: 14
init_sequence:
- [0xFF, 0x77, 0x01, 0x00, 0x00, 0x10]
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: ili9xxx
dimensions:
width: 320
height: 240
offset_width: 20
offset_height: 10
model: TFT 2.4
cs_pin: 25
dc_pin: 26
reset_pin: 27
auto_clear_enabled: false
rotation: 90
lambda: |-
it.fill(Color::WHITE);

View file

@ -3,6 +3,7 @@ remote_receiver:
pin: ${pin}
rmt_channel: ${rmt_channel}
dump: all
tolerance: 25%
on_abbwelcome:
then:
- logger.log: