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

This commit is contained in:
Jonathan Swoboda 2024-12-25 23:02:43 -05:00
commit 2c30d3f7f6
24 changed files with 550 additions and 146 deletions

View File

@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.4.3 uses: actions/upload-artifact@v4.5.0
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests

View File

@ -25,8 +25,7 @@ void BLEClient::loop() {
void BLEClient::dump_config() { void BLEClient::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Client:"); ESP_LOGCONFIG(TAG, "BLE Client:");
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str()); BLEClientBase::dump_config();
ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_));
} }
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {

View File

@ -13,6 +13,11 @@ namespace bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy.connection"; static const char *const TAG = "bluetooth_proxy.connection";
void BluetoothConnection::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Connection:");
BLEClientBase::dump_config();
}
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *param) {
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))

View File

@ -11,6 +11,7 @@ class BluetoothProxy;
class BluetoothConnection : public esp32_ble_client::BLEClientBase { class BluetoothConnection : public esp32_ble_client::BLEClientBase {
public: public:
void dump_config() override;
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;

View File

@ -602,6 +602,9 @@ async def to_code(config):
cg.add_platformio_option( cg.add_platformio_option(
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
) )
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option( add_idf_sdkconfig_option(

View File

@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() {
esp_err_t err; esp_err_t err;
this->advertising_data_.set_scan_rsp = false; this->advertising_data_.set_scan_rsp = false;
this->advertising_data_.include_name = true; this->advertising_data_.include_name = !this->scan_response_;
this->advertising_data_.include_txpower = !this->scan_response_; this->advertising_data_.include_txpower = !this->scan_response_;
err = esp_ble_gap_config_adv_data(&this->advertising_data_); err = esp_ble_gap_config_adv_data(&this->advertising_data_);
if (err != ESP_OK) { if (err != ESP_OK) {

View File

@ -26,10 +26,10 @@ template<class T> class Queue {
void push(T *element) { void push(T *element) {
if (element == nullptr) if (element == nullptr)
return; return;
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { // It is not called from main loop. Thus it won't block main thread.
q_.push(element); xSemaphoreTake(m_, portMAX_DELAY);
xSemaphoreGive(m_); q_.push(element);
} xSemaphoreGive(m_);
} }
T *pop() { T *pop() {

View File

@ -44,6 +44,50 @@ void BLEClientBase::loop() {
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void BLEClientBase::dump_config() {
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str());
ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_));
std::string state_name;
switch (this->state()) {
case espbt::ClientState::INIT:
state_name = "INIT";
break;
case espbt::ClientState::DISCONNECTING:
state_name = "DISCONNECTING";
break;
case espbt::ClientState::IDLE:
state_name = "IDLE";
break;
case espbt::ClientState::SEARCHING:
state_name = "SEARCHING";
break;
case espbt::ClientState::DISCOVERED:
state_name = "DISCOVERED";
break;
case espbt::ClientState::READY_TO_CONNECT:
state_name = "READY_TO_CONNECT";
break;
case espbt::ClientState::CONNECTING:
state_name = "CONNECTING";
break;
case espbt::ClientState::CONNECTED:
state_name = "CONNECTED";
break;
case espbt::ClientState::ESTABLISHED:
state_name = "ESTABLISHED";
break;
default:
state_name = "UNKNOWN_STATE";
break;
}
ESP_LOGCONFIG(TAG, " State: %s", state_name.c_str());
if (this->status_ == ESP_GATT_NO_RESOURCES) {
ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config.");
} else if (this->status_ != ESP_GATT_OK) {
ESP_LOGW(TAG, " Failed due to error code %d", this->status_);
}
}
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
if (!this->auto_connect_) if (!this->auto_connect_)
return false; return false;
@ -129,6 +173,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
} else { } else {
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_, ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
this->address_str_.c_str(), param->reg.app_id, param->reg.status); this->address_str_.c_str(), param->reg.app_id, param->reg.status);
this->status_ = param->reg.status;
this->mark_failed();
} }
break; break;
} }

View File

@ -26,6 +26,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void setup() override; void setup() override;
void loop() override; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void dump_config() override;
void run_later(std::function<void()> &&f); // NOLINT void run_later(std::function<void()> &&f); // NOLINT
bool parse_device(const espbt::ESPBTDevice &device) override; bool parse_device(const espbt::ESPBTDevice &device) override;
@ -103,6 +104,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
bool paired_{false}; bool paired_{false};
espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
std::vector<BLEService *> services_; std::vector<BLEService *> services_;
esp_gatt_status_t status_{ESP_GATT_OK};
void log_event_(const char *name); void log_event_(const char *name);
}; };

View File

@ -58,7 +58,6 @@ void ESP32BLETracker::setup() {
global_esp32_ble_tracker = this; global_esp32_ble_tracker = this;
this->scan_result_lock_ = xSemaphoreCreateMutex(); this->scan_result_lock_ = xSemaphoreCreateMutex();
this->scan_end_lock_ = xSemaphoreCreateMutex(); this->scan_end_lock_ = xSemaphoreCreateMutex();
this->scanner_idle_ = true;
#ifdef USE_OTA #ifdef USE_OTA
ota::get_global_ota_callback()->add_on_state_callback( ota::get_global_ota_callback()->add_on_state_callback(
@ -107,6 +106,15 @@ void ESP32BLETracker::loop() {
break; break;
} }
} }
if (connecting != connecting_ || discovered != discovered_ || searching != searching_ ||
disconnecting != disconnecting_) {
connecting_ = connecting;
discovered_ = discovered;
searching_ = searching;
disconnecting_ = disconnecting;
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
searching_, disconnecting_);
}
bool promote_to_connecting = discovered && !searching && !connecting; bool promote_to_connecting = discovered && !searching && !connecting;
if (!this->scanner_idle_) { if (!this->scanner_idle_) {
@ -183,8 +191,9 @@ void ESP32BLETracker::loop() {
} }
if (this->scan_start_failed_ || this->scan_set_param_failed_) { if (this->scan_start_failed_ || this->scan_set_param_failed_) {
if (this->scan_start_fail_count_ == 255) { if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after 255 attempts, rebooting to restore BLE stack..."); ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
std::numeric_limits<uint8_t>::max());
App.reboot(); App.reboot();
} }
if (xSemaphoreTake(this->scan_end_lock_, 0L)) { if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
@ -282,6 +291,12 @@ void ESP32BLETracker::start_scan_(bool first) {
this->scan_params_.scan_interval = this->scan_interval_; this->scan_params_.scan_interval = this->scan_interval_;
this->scan_params_.scan_window = this->scan_window_; this->scan_params_.scan_window = this->scan_window_;
// Start timeout before scan is started. Otherwise scan never starts if any error.
this->set_timeout("scan", this->scan_duration_ * 2000, []() {
ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack...");
App.reboot();
});
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err);
@ -293,11 +308,6 @@ void ESP32BLETracker::start_scan_(bool first) {
return; return;
} }
this->scanner_idle_ = false; this->scanner_idle_ = false;
this->set_timeout("scan", this->scan_duration_ * 2000, []() {
ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack...");
App.reboot();
});
} }
void ESP32BLETracker::end_of_scan_() { void ESP32BLETracker::end_of_scan_() {
@ -371,6 +381,7 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
} }
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) { void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
if (param.status == ESP_BT_STATUS_DONE) { if (param.status == ESP_BT_STATUS_DONE) {
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
} else { } else {
@ -379,20 +390,25 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t:
} }
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) { void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
this->scan_start_failed_ = param.status; this->scan_start_failed_ = param.status;
if (param.status == ESP_BT_STATUS_SUCCESS) { if (param.status == ESP_BT_STATUS_SUCCESS) {
this->scan_start_fail_count_ = 0; this->scan_start_fail_count_ = 0;
} else { } else {
this->scan_start_fail_count_++; if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
this->scan_start_fail_count_++;
}
xSemaphoreGive(this->scan_end_lock_); xSemaphoreGive(this->scan_end_lock_);
} }
} }
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) { void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
xSemaphoreGive(this->scan_end_lock_); xSemaphoreGive(this->scan_end_lock_);
} }
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) { void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt);
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
if (xSemaphoreTake(this->scan_result_lock_, 0L)) { if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
@ -663,7 +679,14 @@ void ESP32BLETracker::dump_config() {
ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f);
ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f);
ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE");
ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", YESNO(this->scan_continuous_));
ESP_LOGCONFIG(TAG, " Scanner Idle: %s", YESNO(this->scanner_idle_));
ESP_LOGCONFIG(TAG, " Scan End: %s", YESNO(xSemaphoreGetMutexHolder(this->scan_end_lock_) == nullptr));
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
searching_, disconnecting_);
if (this->scan_start_fail_count_) {
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
}
} }
void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) {

View File

@ -178,7 +178,7 @@ class ESPBTClient : public ESPBTDeviceListener {
int app_id; int app_id;
protected: protected:
ClientState state_; ClientState state_{ClientState::INIT};
}; };
class ESP32BLETracker : public Component, class ESP32BLETracker : public Component,
@ -229,7 +229,7 @@ class ESP32BLETracker : public Component,
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received. /// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param); void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
int app_id_; int app_id_{0};
/// Vector of addresses that have already been printed in print_bt_device_info /// Vector of addresses that have already been printed in print_bt_device_info
std::vector<uint64_t> already_discovered_; std::vector<uint64_t> already_discovered_;
@ -242,10 +242,10 @@ class ESP32BLETracker : public Component,
uint32_t scan_duration_; uint32_t scan_duration_;
uint32_t scan_interval_; uint32_t scan_interval_;
uint32_t scan_window_; uint32_t scan_window_;
uint8_t scan_start_fail_count_; uint8_t scan_start_fail_count_{0};
bool scan_continuous_; bool scan_continuous_;
bool scan_active_; bool scan_active_;
bool scanner_idle_; bool scanner_idle_{true};
bool ble_was_disabled_{true}; bool ble_was_disabled_{true};
bool raw_advertisements_{false}; bool raw_advertisements_{false};
bool parse_advertisements_{false}; bool parse_advertisements_{false};
@ -260,6 +260,10 @@ class ESP32BLETracker : public Component,
esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_;
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
int connecting_{0};
int discovered_{0};
int searching_{0};
int disconnecting_{0};
}; };
// NOLINTNEXTLINE // NOLINTNEXTLINE

View File

@ -1,38 +1,20 @@
#include "json_util.h" #include "json_util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
#ifdef USE_RP2040
#include <Arduino.h>
#endif
namespace esphome { namespace esphome {
namespace json { namespace json {
static const char *const TAG = "json"; static const char *const TAG = "json";
static std::vector<char> global_json_build_buffer; // NOLINT static std::vector<char> global_json_build_buffer; // NOLINT
static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL);
std::string build_json(const json_build_t &f) { std::string build_json(const json_build_t &f) {
// Here we are allocating up to 5kb of memory, // Here we are allocating up to 5kb of memory,
// with the heap size minus 2kb to be safe if less than 5kb // with the heap size minus 2kb to be safe if less than 5kb
// as we can not have a true dynamic sized document. // as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()` // The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266 auto free_heap = ALLOCATOR.get_max_free_block_size();
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
size_t request_size = std::min(free_heap, (size_t) 512); size_t request_size = std::min(free_heap, (size_t) 512);
while (true) { while (true) {
ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size);
@ -67,20 +49,12 @@ bool parse_json(const std::string &data, const json_parse_t &f) {
// with the heap size minus 2kb to be safe if less than that // with the heap size minus 2kb to be safe if less than that
// as we can not have a true dynamic sized document. // as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()` // The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266 auto free_heap = ALLOCATOR.get_max_free_block_size();
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
while (true) { while (true) {
DynamicJsonDocument json_document(request_size); DynamicJsonDocument json_document(request_size);
if (json_document.capacity() == 0) { if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %zu bytes, free heap: %zu", request_size,
free_heap); free_heap);
return false; return false;
} }

View File

@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed
from . import defines as df, helpers, lv_validation as lvalid from . import defines as df, helpers, lv_validation as lvalid
from .automation import disp_update, focused_widgets, update_to_code from .automation import disp_update, focused_widgets, update_to_code
from .defines import add_define from .defines import CONF_DRAW_ROUNDING, add_define
from .encoders import ( from .encoders import (
ENCODERS_CONFIG, ENCODERS_CONFIG,
encoders_to_code, encoders_to_code,
@ -205,6 +205,10 @@ def final_validation(configs):
raise cv.Invalid( raise cv.Invalid(
"Using auto_clear_enabled: true in display config not compatible with LVGL" "Using auto_clear_enabled: true in display config not compatible with LVGL"
) )
if draw_rounding := display.get(CONF_DRAW_ROUNDING):
config[CONF_DRAW_ROUNDING] = max(
draw_rounding, config[CONF_DRAW_ROUNDING]
)
buffer_frac = config[CONF_BUFFER_SIZE] buffer_frac = config[CONF_BUFFER_SIZE]
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
LOGGER.warning("buffer_size: may need to be reduced without PSRAM") LOGGER.warning("buffer_size: may need to be reduced without PSRAM")

View File

@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) {
this->width_ = width; this->width_ = width;
ESP_LOGD(TAG, "New size: (%d, %d)", width, height); ESP_LOGD(TAG, "New size: (%d, %d)", width, height);
} else { } else {
#if defined(USE_ESP8266) ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size());
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
int max_block = ESP.getMaxFreeBlockSize();
#elif defined(USE_ESP32)
int max_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
#else
int max_block = -1;
#endif
ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %d Bytes", max_block);
this->end_connection_(); this->end_connection_();
return false; return false;
} }

View File

@ -1 +1,4 @@
CODEOWNERS = ["@clydebarrow"] CODEOWNERS = ["@clydebarrow"]
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
CONF_DRAW_ROUNDING = "draw_rounding"

View File

@ -24,6 +24,7 @@ from esphome.const import (
) )
from esphome.core import TimePeriod from esphome.core import TimePeriod
from . import CONF_DRAW_FROM_ORIGIN, CONF_DRAW_ROUNDING
from .models import DriverChip from .models import DriverChip
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@ -41,7 +42,6 @@ COLOR_ORDERS = {
} }
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
DELAY_FLAG = 0xFF DELAY_FLAG = 0xFF
@ -78,56 +78,81 @@ def _validate(config):
return config return config
CONFIG_SCHEMA = cv.All( def power_of_two(value):
display.FULL_DISPLAY_SCHEMA.extend( value = cv.int_range(1, 128)(value)
cv.Schema( if value & (value - 1) != 0:
{ raise cv.Invalid("value must be a power of two")
cv.GenerateID(): cv.declare_id(QSPI_DBI), return value
cv.Required(CONF_MODEL): cv.one_of(
*DriverChip.chips.keys(), upper=True
), BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), cv.Schema(
cv.Required(CONF_DIMENSIONS): cv.Any( {
cv.dimensions, cv.GenerateID(): cv.declare_id(QSPI_DBI),
cv.Schema( cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence),
{ cv.Required(CONF_DIMENSIONS): cv.Any(
cv.Required(CONF_WIDTH): validate_dimension, cv.dimensions,
cv.Required(CONF_HEIGHT): validate_dimension, cv.Schema(
cv.Optional(
CONF_OFFSET_HEIGHT, default=0
): validate_dimension,
cv.Optional(
CONF_OFFSET_WIDTH, default=0
): validate_dimension,
}
),
),
cv.Optional(CONF_TRANSFORM): cv.Schema(
{ {
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, cv.Required(CONF_WIDTH): validate_dimension,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, cv.Required(CONF_HEIGHT): validate_dimension,
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension,
cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension,
} }
), ),
cv.Optional(CONF_COLOR_ORDER, default="RGB"): cv.enum( ),
COLOR_ORDERS, upper=True cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean,
), cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range(
cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, 0, 0xFF, min_included=True, max_included=True
cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( ),
0, 0xFF, min_included=True, max_included=True }
), ).extend(
cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, spi.spi_device_schema(
} cs_pin_required=False,
).extend( default_mode="MODE0",
spi.spi_device_schema( default_data_rate=10e6,
cs_pin_required=False, quad=True,
default_mode="MODE0",
default_data_rate=10e6,
quad=True,
)
) )
)
)
def model_property(name, defaults, fallback):
return cv.Optional(name, default=defaults.get(name, fallback))
def model_schema(defaults):
transform = cv.Schema(
{
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
}
)
if defaults.get(CONF_SWAP_XY, True):
transform = transform.extend(
{
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
}
)
return BASE_SCHEMA.extend(
{
model_property(CONF_INVERT_COLORS, defaults, False): cv.boolean,
model_property(CONF_COLOR_ORDER, defaults, "RGB"): cv.enum(
COLOR_ORDERS, upper=True
),
model_property(CONF_DRAW_ROUNDING, defaults, 2): power_of_two,
cv.Optional(CONF_TRANSFORM): transform,
}
)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{k.upper(): model_schema(v.defaults) for k, v in DriverChip.chips.items()},
upper=True,
key=CONF_MODEL,
), ),
cv.only_with_esp_idf, cv.only_with_esp_idf,
) )
@ -152,6 +177,7 @@ async def to_code(config):
cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
cg.add(var.set_model(config[CONF_MODEL])) cg.add(var.set_model(config[CONF_MODEL]))
cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN])) cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN]))
cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING]))
if enable_pin := config.get(CONF_ENABLE_PIN): if enable_pin := config.get(CONF_ENABLE_PIN):
enable = await cg.gpio_pin_expression(enable_pin) enable = await cg.gpio_pin_expression(enable_pin)
cg.add(var.set_enable_pin(enable)) cg.add(var.set_enable_pin(enable))
@ -163,7 +189,8 @@ async def to_code(config):
if transform := config.get(CONF_TRANSFORM): if transform := config.get(CONF_TRANSFORM):
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) # swap_xy is not implemented for some chips
cg.add(var.set_swap_xy(transform.get(CONF_SWAP_XY, False)))
if CONF_DIMENSIONS in config: if CONF_DIMENSIONS in config:
dimensions = config[CONF_DIMENSIONS] dimensions = config[CONF_DIMENSIONS]

View File

@ -1,5 +1,10 @@
# Commands # Commands
from esphome.const import CONF_INVERT_COLORS, CONF_SWAP_XY
from . import CONF_DRAW_ROUNDING
SW_RESET_CMD = 0x01 SW_RESET_CMD = 0x01
SLEEP_IN = 0x10
SLEEP_OUT = 0x11 SLEEP_OUT = 0x11
NORON = 0x13 NORON = 0x13
INVERT_OFF = 0x20 INVERT_OFF = 0x20
@ -24,11 +29,12 @@ PAGESEL = 0xFE
class DriverChip: class DriverChip:
chips = {} chips = {}
def __init__(self, name: str): def __init__(self, name: str, defaults=None):
name = name.upper() name = name.upper()
self.name = name self.name = name
self.chips[name] = self self.chips[name] = self
self.initsequence = [] self.initsequence = []
self.defaults = defaults or {}
def cmd(self, c, *args): def cmd(self, c, *args):
""" """
@ -59,9 +65,246 @@ chip.cmd(TEON, 0x00)
chip.cmd(PIXFMT, 0x55) chip.cmd(PIXFMT, 0x55)
chip.cmd(NORON) chip.cmd(NORON)
chip = DriverChip("AXS15231") chip = DriverChip("AXS15231", {CONF_DRAW_ROUNDING: 8, CONF_SWAP_XY: False})
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5)
chip.cmd(0xC1, 0x33) chip.cmd(0xC1, 0x33)
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
chip = DriverChip(
"JC4832W535",
{
CONF_DRAW_ROUNDING: 8,
CONF_SWAP_XY: False,
},
)
chip.cmd(DISPLAY_OFF)
chip.delay(20)
chip.cmd(SLEEP_IN)
chip.delay(80)
chip.cmd(SLEEP_OUT)
chip.cmd(INVERT_OFF)
# A magic sequence to enable the windowed drawing mode
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5)
chip.cmd(0xC1, 0x33)
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
chip = DriverChip("JC3636W518", {CONF_INVERT_COLORS: True})
chip.cmd(0xF0, 0x08)
chip.cmd(0xF2, 0x08)
chip.cmd(0x9B, 0x51)
chip.cmd(0x86, 0x53)
chip.cmd(0xF2, 0x80)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x01)
chip.cmd(0xF1, 0x01)
chip.cmd(0xB0, 0x54)
chip.cmd(0xB1, 0x3F)
chip.cmd(0xB2, 0x2A)
chip.cmd(0xB4, 0x46)
chip.cmd(0xB5, 0x34)
chip.cmd(0xB6, 0xD5)
chip.cmd(0xB7, 0x30)
chip.cmd(0xBA, 0x00)
chip.cmd(0xBB, 0x08)
chip.cmd(0xBC, 0x08)
chip.cmd(0xBD, 0x00)
chip.cmd(0xC0, 0x80)
chip.cmd(0xC1, 0x10)
chip.cmd(0xC2, 0x37)
chip.cmd(0xC3, 0x80)
chip.cmd(0xC4, 0x10)
chip.cmd(0xC5, 0x37)
chip.cmd(0xC6, 0xA9)
chip.cmd(0xC7, 0x41)
chip.cmd(0xC8, 0x51)
chip.cmd(0xC9, 0xA9)
chip.cmd(0xCA, 0x41)
chip.cmd(0xCB, 0x51)
chip.cmd(0xD0, 0x91)
chip.cmd(0xD1, 0x68)
chip.cmd(0xD2, 0x69)
chip.cmd(0xF5, 0x00, 0xA5)
chip.cmd(0xDD, 0x3F)
chip.cmd(0xDE, 0x3F)
chip.cmd(0xF1, 0x10)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x02)
chip.cmd(
0xE0,
0x70,
0x09,
0x12,
0x0C,
0x0B,
0x27,
0x38,
0x54,
0x4E,
0x19,
0x15,
0x15,
0x2C,
0x2F,
)
chip.cmd(
0xE1,
0x70,
0x08,
0x11,
0x0C,
0x0B,
0x27,
0x38,
0x43,
0x4C,
0x18,
0x14,
0x14,
0x2B,
0x2D,
)
chip.cmd(0xF0, 0x10)
chip.cmd(0xF3, 0x10)
chip.cmd(0xE0, 0x08)
chip.cmd(0xE1, 0x00)
chip.cmd(0xE2, 0x00)
chip.cmd(0xE3, 0x00)
chip.cmd(0xE4, 0xE0)
chip.cmd(0xE5, 0x06)
chip.cmd(0xE6, 0x21)
chip.cmd(0xE7, 0x00)
chip.cmd(0xE8, 0x05)
chip.cmd(0xE9, 0x82)
chip.cmd(0xEA, 0xDF)
chip.cmd(0xEB, 0x89)
chip.cmd(0xEC, 0x20)
chip.cmd(0xED, 0x14)
chip.cmd(0xEE, 0xFF)
chip.cmd(0xEF, 0x00)
chip.cmd(0xF8, 0xFF)
chip.cmd(0xF9, 0x00)
chip.cmd(0xFA, 0x00)
chip.cmd(0xFB, 0x30)
chip.cmd(0xFC, 0x00)
chip.cmd(0xFD, 0x00)
chip.cmd(0xFE, 0x00)
chip.cmd(0xFF, 0x00)
chip.cmd(0x60, 0x42)
chip.cmd(0x61, 0xE0)
chip.cmd(0x62, 0x40)
chip.cmd(0x63, 0x40)
chip.cmd(0x64, 0x02)
chip.cmd(0x65, 0x00)
chip.cmd(0x66, 0x40)
chip.cmd(0x67, 0x03)
chip.cmd(0x68, 0x00)
chip.cmd(0x69, 0x00)
chip.cmd(0x6A, 0x00)
chip.cmd(0x6B, 0x00)
chip.cmd(0x70, 0x42)
chip.cmd(0x71, 0xE0)
chip.cmd(0x72, 0x40)
chip.cmd(0x73, 0x40)
chip.cmd(0x74, 0x02)
chip.cmd(0x75, 0x00)
chip.cmd(0x76, 0x40)
chip.cmd(0x77, 0x03)
chip.cmd(0x78, 0x00)
chip.cmd(0x79, 0x00)
chip.cmd(0x7A, 0x00)
chip.cmd(0x7B, 0x00)
chip.cmd(0x80, 0x48)
chip.cmd(0x81, 0x00)
chip.cmd(0x82, 0x05)
chip.cmd(0x83, 0x02)
chip.cmd(0x84, 0xDD)
chip.cmd(0x85, 0x00)
chip.cmd(0x86, 0x00)
chip.cmd(0x87, 0x00)
chip.cmd(0x88, 0x48)
chip.cmd(0x89, 0x00)
chip.cmd(0x8A, 0x07)
chip.cmd(0x8B, 0x02)
chip.cmd(0x8C, 0xDF)
chip.cmd(0x8D, 0x00)
chip.cmd(0x8E, 0x00)
chip.cmd(0x8F, 0x00)
chip.cmd(0x90, 0x48)
chip.cmd(0x91, 0x00)
chip.cmd(0x92, 0x09)
chip.cmd(0x93, 0x02)
chip.cmd(0x94, 0xE1)
chip.cmd(0x95, 0x00)
chip.cmd(0x96, 0x00)
chip.cmd(0x97, 0x00)
chip.cmd(0x98, 0x48)
chip.cmd(0x99, 0x00)
chip.cmd(0x9A, 0x0B)
chip.cmd(0x9B, 0x02)
chip.cmd(0x9C, 0xE3)
chip.cmd(0x9D, 0x00)
chip.cmd(0x9E, 0x00)
chip.cmd(0x9F, 0x00)
chip.cmd(0xA0, 0x48)
chip.cmd(0xA1, 0x00)
chip.cmd(0xA2, 0x04)
chip.cmd(0xA3, 0x02)
chip.cmd(0xA4, 0xDC)
chip.cmd(0xA5, 0x00)
chip.cmd(0xA6, 0x00)
chip.cmd(0xA7, 0x00)
chip.cmd(0xA8, 0x48)
chip.cmd(0xA9, 0x00)
chip.cmd(0xAA, 0x06)
chip.cmd(0xAB, 0x02)
chip.cmd(0xAC, 0xDE)
chip.cmd(0xAD, 0x00)
chip.cmd(0xAE, 0x00)
chip.cmd(0xAF, 0x00)
chip.cmd(0xB0, 0x48)
chip.cmd(0xB1, 0x00)
chip.cmd(0xB2, 0x08)
chip.cmd(0xB3, 0x02)
chip.cmd(0xB4, 0xE0)
chip.cmd(0xB5, 0x00)
chip.cmd(0xB6, 0x00)
chip.cmd(0xB7, 0x00)
chip.cmd(0xB8, 0x48)
chip.cmd(0xB9, 0x00)
chip.cmd(0xBA, 0x0A)
chip.cmd(0xBB, 0x02)
chip.cmd(0xBC, 0xE2)
chip.cmd(0xBD, 0x00)
chip.cmd(0xBE, 0x00)
chip.cmd(0xBF, 0x00)
chip.cmd(0xC0, 0x12)
chip.cmd(0xC1, 0xAA)
chip.cmd(0xC2, 0x65)
chip.cmd(0xC3, 0x74)
chip.cmd(0xC4, 0x47)
chip.cmd(0xC5, 0x56)
chip.cmd(0xC6, 0x00)
chip.cmd(0xC7, 0x88)
chip.cmd(0xC8, 0x99)
chip.cmd(0xC9, 0x33)
chip.cmd(0xD0, 0x21)
chip.cmd(0xD1, 0xAA)
chip.cmd(0xD2, 0x65)
chip.cmd(0xD3, 0x74)
chip.cmd(0xD4, 0x47)
chip.cmd(0xD5, 0x56)
chip.cmd(0xD6, 0x00)
chip.cmd(0xD7, 0x88)
chip.cmd(0xD8, 0x99)
chip.cmd(0xD9, 0x33)
chip.cmd(0xF3, 0x01)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x01)
chip.cmd(0xF1, 0x01)
chip.cmd(0xA0, 0x0B)
chip.cmd(0xA3, 0x2A)
chip.cmd(0xA5, 0xC3)
chip.cmd(PIXFMT, 0x55)
DriverChip("Custom") DriverChip("Custom")

View File

@ -33,19 +33,12 @@ void QspiDbi::update() {
this->do_update_(); this->do_update_();
if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
return; return;
// Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet) // Some chips require that the drawing window be aligned on certain boundaries
if (this->x_low_ % 2 == 1) { auto dr = this->draw_rounding_;
this->x_low_--; this->x_low_ = this->x_low_ / dr * dr;
} this->y_low_ = this->y_low_ / dr * dr;
if (this->x_high_ % 2 == 0) { this->x_high_ = (this->x_high_ + dr) / dr * dr - 1;
this->x_high_++; this->y_high_ = (this->y_high_ + dr) / dr * dr - 1;
}
if (this->y_low_ % 2 == 1) {
this->y_low_--;
}
if (this->y_high_ % 2 == 0) {
this->y_high_++;
}
if (this->draw_from_origin_) { if (this->draw_from_origin_) {
this->x_low_ = 0; this->x_low_ = 0;
this->y_low_ = 0; this->y_low_ = 0;
@ -175,10 +168,9 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui
this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4);
} else { } else {
auto stride = x_offset + w + x_pad; auto stride = x_offset + w + x_pad;
uint16_t cmd = 0x2C00; this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, nullptr, 0, 4);
for (int y = 0; y != h; y++) { for (int y = 0; y != h; y++) {
this->write_cmd_addr_data(8, 0x32, 24, cmd, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4);
cmd = 0x3C00;
} }
} }
this->disable(); this->disable();
@ -220,6 +212,7 @@ void QspiDbi::dump_config() {
ESP_LOGCONFIG("", "Model: %s", this->model_); ESP_LOGCONFIG("", "Model: %s", this->model_);
ESP_LOGCONFIG(TAG, " Height: %u", this->height_); ESP_LOGCONFIG(TAG, " Height: %u", this->height_);
ESP_LOGCONFIG(TAG, " Width: %u", this->width_); ESP_LOGCONFIG(TAG, " Width: %u", this->width_);
ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_);
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));

View File

@ -4,12 +4,10 @@
#pragma once #pragma once
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include "esphome/core/component.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h" #include "esphome/components/display/display.h"
#include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display_buffer.h"
#include "esphome/components/display/display_color_utils.h" #include "esphome/components/display/display_color_utils.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h" #include "esp_lcd_panel_rgb.h"
@ -105,6 +103,7 @@ class QspiDbi : public display::DisplayBuffer,
int get_height_internal() override { return this->height_; } int get_height_internal() override { return this->height_; }
bool can_proceed() override { return this->setup_complete_; } bool can_proceed() override { return this->setup_complete_; }
void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); } void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); }
void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; }
protected: protected:
void check_buffer_() { void check_buffer_() {
@ -161,6 +160,7 @@ class QspiDbi : public display::DisplayBuffer,
bool mirror_x_{}; bool mirror_x_{};
bool mirror_y_{}; bool mirror_y_{};
bool draw_from_origin_{false}; bool draw_from_origin_{false};
unsigned draw_rounding_{2};
uint8_t brightness_{0xD0}; uint8_t brightness_{0xD0};
const char *model_{"Unknown"}; const char *model_{"Unknown"};
std::vector<std::vector<uint8_t>> init_sequences_{}; std::vector<std::vector<uint8_t>> init_sequences_{};

View File

@ -154,16 +154,16 @@ void RemoteReceiverComponent::setup() {
void RemoteReceiverComponent::dump_config() { void RemoteReceiverComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Remote Receiver:"); ESP_LOGCONFIG(TAG, "Remote Receiver:");
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
if (this->pin_->digital_read()) {
ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to "
"invert the signal using 'inverted: True' in the pin schema!");
}
#if ESP_IDF_VERSION_MAJOR >= 5 #if ESP_IDF_VERSION_MAJOR >= 5
ESP_LOGCONFIG(TAG, " Clock resolution: %" PRIu32 " hz", this->clock_resolution_); ESP_LOGCONFIG(TAG, " Clock resolution: %" PRIu32 " hz", this->clock_resolution_);
ESP_LOGCONFIG(TAG, " RMT symbols: %" PRIu32, this->rmt_symbols_); ESP_LOGCONFIG(TAG, " RMT symbols: %" PRIu32, this->rmt_symbols_);
ESP_LOGCONFIG(TAG, " Filter symbols: %" PRIu32, this->filter_symbols_); ESP_LOGCONFIG(TAG, " Filter symbols: %" PRIu32, this->filter_symbols_);
ESP_LOGCONFIG(TAG, " Receive symbols: %" PRIu32, this->receive_symbols_); ESP_LOGCONFIG(TAG, " Receive symbols: %" PRIu32, this->receive_symbols_);
#else #else
if (this->pin_->digital_read()) {
ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to "
"invert the signal using 'inverted: True' in the pin schema!");
}
ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_); ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_);
ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_); ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_);
ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_); ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_);

View File

@ -7,6 +7,7 @@ from esphome.const import (
CONF_CLOCK_DIVIDER, CONF_CLOCK_DIVIDER,
CONF_CLOCK_RESOLUTION, CONF_CLOCK_RESOLUTION,
CONF_ID, CONF_ID,
CONF_INVERTED,
CONF_PIN, CONF_PIN,
CONF_RMT_CHANNEL, CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS, CONF_RMT_SYMBOLS,
@ -16,6 +17,7 @@ from esphome.core import CORE
AUTO_LOAD = ["remote_base"] AUTO_LOAD = ["remote_base"]
CONF_EOT_LEVEL = "eot_level"
CONF_ON_TRANSMIT = "on_transmit" CONF_ON_TRANSMIT = "on_transmit"
CONF_ON_COMPLETE = "on_complete" CONF_ON_COMPLETE = "on_complete"
CONF_ONE_WIRE = "one_wire" CONF_ONE_WIRE = "one_wire"
@ -41,6 +43,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_CLOCK_DIVIDER): cv.All( cv.Optional(CONF_CLOCK_DIVIDER): cv.All(
cv.only_on_esp32, cv.only_with_arduino, cv.int_range(min=1, max=255) cv.only_on_esp32, cv.only_with_arduino, cv.int_range(min=1, max=255)
), ),
cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_with_esp_idf, cv.boolean),
cv.Optional(CONF_ONE_WIRE): cv.All(cv.only_with_esp_idf, cv.boolean), cv.Optional(CONF_ONE_WIRE): cv.All(cv.only_with_esp_idf, cv.boolean),
cv.Optional(CONF_USE_DMA): cv.All(cv.only_with_esp_idf, cv.boolean), cv.Optional(CONF_USE_DMA): cv.All(cv.only_with_esp_idf, cv.boolean),
cv.SplitDefault( cv.SplitDefault(
@ -73,6 +76,12 @@ async def to_code(config):
cg.add(var.set_with_dma(config[CONF_USE_DMA])) cg.add(var.set_with_dma(config[CONF_USE_DMA]))
if CONF_ONE_WIRE in config: if CONF_ONE_WIRE in config:
cg.add(var.set_one_wire(config[CONF_ONE_WIRE])) cg.add(var.set_one_wire(config[CONF_ONE_WIRE]))
if CONF_EOT_LEVEL in config:
cg.add(var.set_eot_level(config[CONF_EOT_LEVEL]))
elif CONF_ONE_WIRE in config and config[CONF_ONE_WIRE]:
cg.add(var.set_eot_level(True))
elif CONF_INVERTED in config[CONF_PIN] and config[CONF_PIN][CONF_INVERTED]:
cg.add(var.set_eot_level(True))
else: else:
if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel)

View File

@ -41,6 +41,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
void set_one_wire(bool one_wire) { this->one_wire_ = one_wire; } void set_one_wire(bool one_wire) { this->one_wire_ = one_wire; }
void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; }
void digital_write(bool value);
#endif #endif
Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; };
@ -68,6 +70,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
std::vector<rmt_symbol_word_t> rmt_temp_; std::vector<rmt_symbol_word_t> rmt_temp_;
bool with_dma_{false}; bool with_dma_{false};
bool one_wire_{false}; bool one_wire_{false};
bool eot_level_{false};
rmt_channel_handle_t channel_{NULL}; rmt_channel_handle_t channel_{NULL};
rmt_encoder_handle_t encoder_{NULL}; rmt_encoder_handle_t encoder_{NULL};
#else #else

View File

@ -11,6 +11,7 @@ static const char *const TAG = "remote_transmitter";
void RemoteTransmitterComponent::setup() { void RemoteTransmitterComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Remote Transmitter..."); ESP_LOGCONFIG(TAG, "Setting up Remote Transmitter...");
this->inverted_ = this->pin_->is_inverted();
this->configure_rmt_(); this->configure_rmt_();
} }
@ -37,6 +38,31 @@ void RemoteTransmitterComponent::dump_config() {
} }
} }
#if ESP_IDF_VERSION_MAJOR >= 5
void RemoteTransmitterComponent::digital_write(bool value) {
rmt_symbol_word_t symbol = {
.duration0 = 1,
.level0 = value,
.duration1 = 0,
.level1 = value,
};
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.loop_count = 0;
config.flags.eot_level = value;
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
this->status_set_warning();
}
error = rmt_tx_wait_all_done(this->channel_, -1);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
this->status_set_warning();
}
}
#endif
void RemoteTransmitterComponent::configure_rmt_() { void RemoteTransmitterComponent::configure_rmt_() {
#if ESP_IDF_VERSION_MAJOR >= 5 #if ESP_IDF_VERSION_MAJOR >= 5
esp_err_t error; esp_err_t error;
@ -83,6 +109,7 @@ void RemoteTransmitterComponent::configure_rmt_() {
this->mark_failed(); this->mark_failed();
return; return;
} }
this->digital_write(this->one_wire_ || this->inverted_);
this->initialized_ = true; this->initialized_ = true;
} }
@ -120,13 +147,12 @@ void RemoteTransmitterComponent::configure_rmt_() {
} }
c.tx_config.idle_output_en = true; c.tx_config.idle_output_en = true;
if (!this->pin_->is_inverted()) { if (!this->inverted_) {
c.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; c.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH;
c.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; c.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
} else { } else {
c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH;
this->inverted_ = true;
} }
esp_err_t error = rmt_config(&c); esp_err_t error = rmt_config(&c);
@ -210,7 +236,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
rmt_transmit_config_t config; rmt_transmit_config_t config;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
config.loop_count = 0; config.loop_count = 0;
config.flags.eot_level = this->inverted_; config.flags.eot_level = this->eot_level_;
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(), esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config); this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
if (error != ESP_OK) { if (error != ESP_OK) {
@ -223,8 +249,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
if (error != ESP_OK) { if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
this->status_set_warning(); this->status_set_warning();
} else {
this->status_clear_warning();
} }
if (i + 1 < send_times) if (i + 1 < send_times)
delayMicroseconds(send_wait); delayMicroseconds(send_wait);

View File

@ -11,6 +11,14 @@
#include "esphome/core/optional.h" #include "esphome/core/optional.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
#ifdef USE_RP2040
#include <Arduino.h>
#endif
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_heap_caps.h> #include <esp_heap_caps.h>
#endif #endif
@ -684,20 +692,23 @@ template<class T> class RAMAllocator {
}; };
RAMAllocator() = default; RAMAllocator() = default;
RAMAllocator(uint8_t flags) : flags_{flags} {} RAMAllocator(uint8_t flags) {
// default is both external and internal
flags &= ALLOC_INTERNAL | ALLOC_EXTERNAL;
if (flags != 0)
this->flags_ = flags;
}
template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {} template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {}
T *allocate(size_t n) { T *allocate(size_t n) {
size_t size = n * sizeof(T); size_t size = n * sizeof(T);
T *ptr = nullptr; T *ptr = nullptr;
#ifdef USE_ESP32 #ifdef USE_ESP32
// External allocation by default or if explicitely requested if (this->flags_ & Flags::ALLOC_EXTERNAL) {
if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) {
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
} }
// Fallback to internal allocation if explicitely requested or no flag is specified if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) {
if (ptr == nullptr && ((this->flags_ & Flags::ALLOC_INTERNAL) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) { ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
} }
#else #else
// Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported // Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported
@ -710,8 +721,46 @@ template<class T> class RAMAllocator {
free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
} }
/**
* Return the total heap space available via this allocator
*/
size_t get_free_heap_size() const {
#ifdef USE_ESP8266
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
auto max_internal =
this->flags_ & ALLOC_INTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0;
auto max_external =
this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0;
return max_internal + max_external;
#elif defined(USE_RP2040)
return ::rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
return lt_heap_get_free();
#else
return 100000;
#endif
}
/**
* Return the maximum size block this allocator could allocate. This may be an approximation on some platforms
*/
size_t get_max_free_block_size() const {
#ifdef USE_ESP8266
return ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
auto max_internal =
this->flags_ & ALLOC_INTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0;
auto max_external =
this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0;
return std::max(max_internal, max_external);
#else
return this->get_free_heap_size();
#endif
}
private: private:
uint8_t flags_{Flags::ALLOW_FAILURE}; uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL};
}; };
template<class T> using ExternalRAMAllocator = RAMAllocator<T>; template<class T> using ExternalRAMAllocator = RAMAllocator<T>;