mirror of
https://github.com/esphome/esphome.git
synced 2025-09-06 05:12:21 +01:00
Merge remote-tracking branch 'upstream/dev' into esp32_ble_tracker_cleanup_code
This commit is contained in:
@@ -56,6 +56,14 @@ class CustomAPIDevice {
|
|||||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename T, typename... Ts>
|
||||||
|
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||||
|
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||||
|
static_assert(
|
||||||
|
sizeof(T) == 0,
|
||||||
|
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Register a custom native API service that will show up in Home Assistant.
|
/** Register a custom native API service that will show up in Home Assistant.
|
||||||
@@ -81,6 +89,12 @@ class CustomAPIDevice {
|
|||||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||||
|
static_assert(
|
||||||
|
sizeof(T) == 0,
|
||||||
|
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
@@ -135,6 +149,22 @@ class CustomAPIDevice {
|
|||||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename T>
|
||||||
|
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||||
|
const std::string &attribute = "") {
|
||||||
|
static_assert(sizeof(T) == 0,
|
||||||
|
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||||
|
"of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||||
|
const std::string &attribute = "") {
|
||||||
|
static_assert(sizeof(T) == 0,
|
||||||
|
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||||
|
"of your YAML configuration");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
@@ -222,6 +252,28 @@ class CustomAPIDevice {
|
|||||||
}
|
}
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename T = void> void call_homeassistant_service(const std::string &service_name) {
|
||||||
|
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = void>
|
||||||
|
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
|
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = void> void fire_homeassistant_event(const std::string &event_name) {
|
||||||
|
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = void>
|
||||||
|
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
|
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,14 +1,20 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||||
from esphome.components.esp32_ble import BTLoggers
|
from esphome.components.esp32_ble import BTLoggers
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ACTIVE, CONF_ID
|
from esphome.const import CONF_ACTIVE, CONF_ID
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome.log import AnsiFore, color
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||||
DEPENDENCIES = ["api", "esp32"]
|
DEPENDENCIES = ["api", "esp32"]
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_CONNECTION_SLOTS = "connection_slots"
|
CONF_CONNECTION_SLOTS = "connection_slots"
|
||||||
CONF_CACHE_SERVICES = "cache_services"
|
CONF_CACHE_SERVICES = "cache_services"
|
||||||
CONF_CONNECTIONS = "connections"
|
CONF_CONNECTIONS = "connections"
|
||||||
@@ -41,6 +47,27 @@ def validate_connections(config):
|
|||||||
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Warn about connection slot waste when using Arduino framework
|
||||||
|
if CORE.using_arduino and connection_slots:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Bluetooth Proxy with active connections on Arduino framework has suboptimal performance.\n"
|
||||||
|
"If BLE connections fail, they can waste connection slots for 10 seconds because\n"
|
||||||
|
"Arduino doesn't allow configuring the BLE connection timeout (fixed at 30s).\n"
|
||||||
|
"ESP-IDF framework allows setting it to 20s to match client timeouts.\n"
|
||||||
|
"\n"
|
||||||
|
"To switch to ESP-IDF, add this to your YAML:\n"
|
||||||
|
" esp32:\n"
|
||||||
|
" framework:\n"
|
||||||
|
" type: esp-idf\n"
|
||||||
|
"\n"
|
||||||
|
"For detailed migration instructions, see:\n"
|
||||||
|
"%s",
|
||||||
|
color(
|
||||||
|
AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf.html"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
**config,
|
**config,
|
||||||
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
||||||
|
@@ -35,8 +35,8 @@ void BluetoothProxy::setup() {
|
|||||||
// Don't pre-allocate pool - let it grow only if needed in busy environments
|
// Don't pre-allocate pool - let it grow only if needed in busy environments
|
||||||
// Many devices in quiet areas will never need the overflow pool
|
// Many devices in quiet areas will never need the overflow pool
|
||||||
|
|
||||||
this->connections_free_response_.limit = this->connections_.size();
|
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||||
this->connections_free_response_.free = this->connections_.size();
|
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||||
|
|
||||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||||
if (this->api_connection_ != nullptr) {
|
if (this->api_connection_ != nullptr) {
|
||||||
@@ -134,12 +134,13 @@ void BluetoothProxy::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" Active: %s\n"
|
" Active: %s\n"
|
||||||
" Connections: %d",
|
" Connections: %d",
|
||||||
YESNO(this->active_), this->connections_.size());
|
YESNO(this->active_), this->connection_count_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::loop() {
|
void BluetoothProxy::loop() {
|
||||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||||
for (auto *connection : this->connections_) {
|
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||||
|
auto *connection = this->connections_[i];
|
||||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||||
connection->disconnect();
|
connection->disconnect();
|
||||||
}
|
}
|
||||||
@@ -162,7 +163,8 @@ esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_par
|
|||||||
}
|
}
|
||||||
|
|
||||||
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
||||||
for (auto *connection : this->connections_) {
|
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||||
|
auto *connection = this->connections_[i];
|
||||||
if (connection->get_address() == address)
|
if (connection->get_address() == address)
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
@@ -170,7 +172,8 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
|
|||||||
if (!reserve)
|
if (!reserve)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
for (auto *connection : this->connections_) {
|
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||||
|
auto *connection = this->connections_[i];
|
||||||
if (connection->get_address() == 0) {
|
if (connection->get_address() == 0) {
|
||||||
connection->send_service_ = DONE_SENDING_SERVICES;
|
connection->send_service_ = DONE_SENDING_SERVICES;
|
||||||
connection->set_address(address);
|
connection->set_address(address);
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -63,8 +64,10 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||||
|
|
||||||
void register_connection(BluetoothConnection *connection) {
|
void register_connection(BluetoothConnection *connection) {
|
||||||
this->connections_.push_back(connection);
|
if (this->connection_count_ < BLUETOOTH_PROXY_MAX_CONNECTIONS) {
|
||||||
connection->proxy_ = this;
|
this->connections_[this->connection_count_++] = connection;
|
||||||
|
connection->proxy_ = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||||
@@ -138,8 +141,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||||
api::APIConnection *api_connection_{nullptr};
|
api::APIConnection *api_connection_{nullptr};
|
||||||
|
|
||||||
// Group 2: Container types (typically 12 bytes on 32-bit)
|
// Group 2: Fixed-size array of connection pointers
|
||||||
std::vector<BluetoothConnection *> connections_{};
|
std::array<BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS> connections_{};
|
||||||
|
|
||||||
// BLE advertisement batching
|
// BLE advertisement batching
|
||||||
std::vector<api::BluetoothLERawAdvertisement> advertisement_pool_;
|
std::vector<api::BluetoothLERawAdvertisement> advertisement_pool_;
|
||||||
@@ -154,7 +157,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
// Group 4: 1-byte types grouped together
|
// Group 4: 1-byte types grouped together
|
||||||
bool active_;
|
bool active_;
|
||||||
uint8_t advertisement_count_{0};
|
uint8_t advertisement_count_{0};
|
||||||
// 2 bytes used, 2 bytes padding
|
uint8_t connection_count_{0};
|
||||||
|
// 3 bytes used, 1 byte padding
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
@@ -23,21 +23,14 @@
|
|||||||
|
|
||||||
namespace esphome::esp32_ble {
|
namespace esphome::esp32_ble {
|
||||||
|
|
||||||
// Maximum number of BLE scan results to buffer
|
// Maximum size of the BLE event queue
|
||||||
// Sized to handle bursts of advertisements while allowing for processing delays
|
// Increased to absorb the ring buffer capacity from esp32_ble_tracker
|
||||||
// With 16 advertisements per batch and some safety margin:
|
|
||||||
// - Without PSRAM: 24 entries (1.5× batch size)
|
|
||||||
// - With PSRAM: 36 entries (2.25× batch size)
|
|
||||||
// The reduced structure size (~80 bytes vs ~400 bytes) allows for larger buffers
|
|
||||||
#ifdef USE_PSRAM
|
#ifdef USE_PSRAM
|
||||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 36;
|
static constexpr uint8_t MAX_BLE_QUEUE_SIZE = 100; // 64 + 36 (ring buffer size with PSRAM)
|
||||||
#else
|
#else
|
||||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 24;
|
static constexpr uint8_t MAX_BLE_QUEUE_SIZE = 88; // 64 + 24 (ring buffer size without PSRAM)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue
|
|
||||||
static constexpr size_t MAX_BLE_QUEUE_SIZE = 64;
|
|
||||||
|
|
||||||
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
|
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
|
||||||
|
|
||||||
// NOLINTNEXTLINE(modernize-use-using)
|
// NOLINTNEXTLINE(modernize-use-using)
|
||||||
|
@@ -145,6 +145,36 @@ void BLEClientBase::connect() {
|
|||||||
this->remote_addr_type_);
|
this->remote_addr_type_);
|
||||||
this->paired_ = false;
|
this->paired_ = false;
|
||||||
|
|
||||||
|
// Set preferred connection parameters before connecting
|
||||||
|
// Use FAST for all V3 connections (better latency and reliability)
|
||||||
|
// Use MEDIUM for V1/legacy connections (balanced performance)
|
||||||
|
uint16_t min_interval, max_interval, timeout;
|
||||||
|
const char *param_type;
|
||||||
|
|
||||||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
||||||
|
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
|
min_interval = FAST_MIN_CONN_INTERVAL;
|
||||||
|
max_interval = FAST_MAX_CONN_INTERVAL;
|
||||||
|
timeout = FAST_CONN_TIMEOUT;
|
||||||
|
param_type = "fast";
|
||||||
|
} else {
|
||||||
|
min_interval = MEDIUM_MIN_CONN_INTERVAL;
|
||||||
|
max_interval = MEDIUM_MAX_CONN_INTERVAL;
|
||||||
|
timeout = MEDIUM_CONN_TIMEOUT;
|
||||||
|
param_type = "medium";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
|
||||||
|
0, // latency: 0
|
||||||
|
timeout);
|
||||||
|
if (param_ret != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param_ret);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "[%d] [%s] Set %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now open the connection
|
||||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
@@ -152,35 +182,6 @@ void BLEClientBase::connect() {
|
|||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
} else {
|
} else {
|
||||||
this->set_state(espbt::ClientState::CONNECTING);
|
this->set_state(espbt::ClientState::CONNECTING);
|
||||||
|
|
||||||
// Always set connection parameters to ensure stable operation
|
|
||||||
// Use FAST for all V3 connections (better latency and reliability)
|
|
||||||
// Use MEDIUM for V1/legacy connections (balanced performance)
|
|
||||||
uint16_t min_interval, max_interval, timeout;
|
|
||||||
const char *param_type;
|
|
||||||
|
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
|
||||||
min_interval = FAST_MIN_CONN_INTERVAL;
|
|
||||||
max_interval = FAST_MAX_CONN_INTERVAL;
|
|
||||||
timeout = FAST_CONN_TIMEOUT;
|
|
||||||
param_type = "fast";
|
|
||||||
} else {
|
|
||||||
min_interval = MEDIUM_MIN_CONN_INTERVAL;
|
|
||||||
max_interval = MEDIUM_MAX_CONN_INTERVAL;
|
|
||||||
timeout = MEDIUM_CONN_TIMEOUT;
|
|
||||||
param_type = "medium";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
|
|
||||||
0, // latency: 0
|
|
||||||
timeout);
|
|
||||||
if (param_ret != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), param_ret);
|
|
||||||
} else {
|
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Set %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +256,19 @@ void BLEClientBase::log_event_(const char *name) {
|
|||||||
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name);
|
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BLEClientBase::restore_medium_conn_params_() {
|
||||||
|
// Restore to medium connection parameters after initial connection phase
|
||||||
|
// This balances performance with bandwidth usage for normal operation
|
||||||
|
esp_ble_conn_update_params_t conn_params = {{0}};
|
||||||
|
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
||||||
|
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
|
||||||
|
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
|
||||||
|
conn_params.latency = 0;
|
||||||
|
conn_params.timeout = MEDIUM_CONN_TIMEOUT;
|
||||||
|
ESP_LOGD(TAG, "[%d] [%s] Restoring medium conn params", this->connection_index_, this->address_str_.c_str());
|
||||||
|
esp_ble_gap_update_conn_params(&conn_params);
|
||||||
|
}
|
||||||
|
|
||||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||||
@@ -283,7 +297,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
if (!this->check_addr(param->open.remote_bda))
|
if (!this->check_addr(param->open.remote_bda))
|
||||||
return false;
|
return false;
|
||||||
this->log_event_("ESP_GATTC_OPEN_EVT");
|
this->log_event_("ESP_GATTC_OPEN_EVT");
|
||||||
this->conn_id_ = param->open.conn_id;
|
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
||||||
this->service_count_ = 0;
|
this->service_count_ = 0;
|
||||||
if (this->state_ != espbt::ClientState::CONNECTING) {
|
if (this->state_ != espbt::ClientState::CONNECTING) {
|
||||||
// This should not happen but lets log it in case it does
|
// This should not happen but lets log it in case it does
|
||||||
@@ -317,15 +331,15 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
this->conn_id_ = UNSET_CONN_ID;
|
this->conn_id_ = UNSET_CONN_ID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
// MTU negotiation already started in ESP_GATTC_CONNECT_EVT
|
||||||
if (ret) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), ret);
|
|
||||||
}
|
|
||||||
this->set_state(espbt::ClientState::CONNECTED);
|
this->set_state(espbt::ClientState::CONNECTED);
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str());
|
||||||
|
|
||||||
|
// Restore to medium connection parameters for cached connections too
|
||||||
|
this->restore_medium_conn_params_();
|
||||||
|
|
||||||
// only set our state, subclients might have more stuff to do yet.
|
// only set our state, subclients might have more stuff to do yet.
|
||||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||||
break;
|
break;
|
||||||
@@ -338,6 +352,16 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
if (!this->check_addr(param->connect.remote_bda))
|
if (!this->check_addr(param->connect.remote_bda))
|
||||||
return false;
|
return false;
|
||||||
this->log_event_("ESP_GATTC_CONNECT_EVT");
|
this->log_event_("ESP_GATTC_CONNECT_EVT");
|
||||||
|
this->conn_id_ = param->connect.conn_id;
|
||||||
|
// Start MTU negotiation immediately as recommended by ESP-IDF examples
|
||||||
|
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
|
||||||
|
// ESP_GATTC_CONNECT_EVT instead of waiting for ESP_GATTC_OPEN_EVT.
|
||||||
|
// This saves ~3ms in the connection process.
|
||||||
|
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
|
||||||
|
if (ret) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), ret);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
@@ -413,15 +437,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
// This balances performance with bandwidth usage after the critical discovery phase
|
// This balances performance with bandwidth usage after the critical discovery phase
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
esp_ble_conn_update_params_t conn_params = {{0}};
|
this->restore_medium_conn_params_();
|
||||||
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
|
||||||
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
|
|
||||||
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
|
|
||||||
conn_params.latency = 0;
|
|
||||||
conn_params.timeout = MEDIUM_CONN_TIMEOUT;
|
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Restored medium conn params after service discovery", this->connection_index_,
|
|
||||||
this->address_str_.c_str());
|
|
||||||
esp_ble_gap_update_conn_params(&conn_params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||||
|
@@ -66,7 +66,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
(uint8_t) (this->address_ >> 0) & 0xff);
|
(uint8_t) (this->address_ >> 0) & 0xff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::string address_str() const { return this->address_str_; }
|
const std::string &address_str() const { return this->address_str_; }
|
||||||
|
|
||||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||||
BLEService *get_service(uint16_t uuid);
|
BLEService *get_service(uint16_t uuid);
|
||||||
@@ -127,6 +127,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
// 6 bytes used, 2 bytes padding
|
// 6 bytes used, 2 bytes padding
|
||||||
|
|
||||||
void log_event_(const char *name);
|
void log_event_(const char *name);
|
||||||
|
void restore_medium_conn_params_();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
@@ -49,7 +49,7 @@ class ESPNowPacket {
|
|||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||||
// Constructor for sent data
|
// Constructor for sent data
|
||||||
ESPNowPacket(const esp_now_send_info_t *info, esp_now_send_status_t status) {
|
ESPNowPacket(const esp_now_send_info_t *info, esp_now_send_status_t status) {
|
||||||
this->init_sent_data(info->src_addr, status);
|
this->init_sent_data_(info->src_addr, status);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Constructor for sent data
|
// Constructor for sent data
|
||||||
|
@@ -20,12 +20,11 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned
|
|||||||
|
|
||||||
#define ERROR_CHECK(err) \
|
#define ERROR_CHECK(err) \
|
||||||
if ((err) != i2c::ERROR_OK) { \
|
if ((err) != i2c::ERROR_OK) { \
|
||||||
this->status_set_warning("Communication failure"); \
|
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); \
|
||||||
return; \
|
return; \
|
||||||
}
|
}
|
||||||
|
|
||||||
void GT911Touchscreen::setup() {
|
void GT911Touchscreen::setup() {
|
||||||
i2c::ErrorCode err;
|
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->setup();
|
this->reset_pin_->setup();
|
||||||
this->reset_pin_->digital_write(false);
|
this->reset_pin_->digital_write(false);
|
||||||
@@ -35,9 +34,14 @@ void GT911Touchscreen::setup() {
|
|||||||
this->interrupt_pin_->digital_write(false);
|
this->interrupt_pin_->digital_write(false);
|
||||||
}
|
}
|
||||||
delay(2);
|
delay(2);
|
||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true); // wait 50ms after reset
|
||||||
delay(50); // NOLINT
|
this->set_timeout(50, [this] { this->setup_internal_(); });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this->setup_internal_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GT911Touchscreen::setup_internal_() {
|
||||||
if (this->interrupt_pin_ != nullptr) {
|
if (this->interrupt_pin_ != nullptr) {
|
||||||
// set pre-configured input mode
|
// set pre-configured input mode
|
||||||
this->interrupt_pin_->setup();
|
this->interrupt_pin_->setup();
|
||||||
@@ -45,7 +49,7 @@ void GT911Touchscreen::setup() {
|
|||||||
|
|
||||||
// check the configuration of the int line.
|
// check the configuration of the int line.
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
i2c::ErrorCode err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
||||||
if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) {
|
if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) {
|
||||||
this->address_ = SECONDARY_ADDRESS;
|
this->address_ = SECONDARY_ADDRESS;
|
||||||
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
||||||
@@ -53,7 +57,7 @@ void GT911Touchscreen::setup() {
|
|||||||
if (err == i2c::ERROR_OK) {
|
if (err == i2c::ERROR_OK) {
|
||||||
err = this->read(data, 1);
|
err = this->read(data, 1);
|
||||||
if (err == i2c::ERROR_OK) {
|
if (err == i2c::ERROR_OK) {
|
||||||
ESP_LOGD(TAG, "Read from switches at address 0x%02X: 0x%02X", this->address_, data[0]);
|
ESP_LOGD(TAG, "Switches ADDR: 0x%02X DATA: 0x%02X", this->address_, data[0]);
|
||||||
if (this->interrupt_pin_ != nullptr) {
|
if (this->interrupt_pin_ != nullptr) {
|
||||||
this->attach_interrupt_(this->interrupt_pin_,
|
this->attach_interrupt_(this->interrupt_pin_,
|
||||||
(data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
(data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
||||||
@@ -75,16 +79,24 @@ void GT911Touchscreen::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
this->mark_failed("Failed to read calibration");
|
this->mark_failed("Calibration error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
this->mark_failed("Failed to communicate");
|
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this->setup_done_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GT911Touchscreen::update_touches() {
|
void GT911Touchscreen::update_touches() {
|
||||||
|
this->skip_update_ = true; // skip send touch events by default, set to false after successful error checks
|
||||||
|
if (!this->setup_done_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
i2c::ErrorCode err;
|
i2c::ErrorCode err;
|
||||||
uint8_t touch_state = 0;
|
uint8_t touch_state = 0;
|
||||||
uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte
|
uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte
|
||||||
@@ -97,7 +109,6 @@ void GT911Touchscreen::update_touches() {
|
|||||||
uint8_t num_of_touches = touch_state & 0x07;
|
uint8_t num_of_touches = touch_state & 0x07;
|
||||||
|
|
||||||
if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) {
|
if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) {
|
||||||
this->skip_update_ = true; // skip send touch events, touchscreen is not ready yet.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +118,7 @@ void GT911Touchscreen::update_touches() {
|
|||||||
err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1);
|
err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1);
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
|
|
||||||
|
this->skip_update_ = false; // All error checks passed, send touch events
|
||||||
for (uint8_t i = 0; i != num_of_touches; i++) {
|
for (uint8_t i = 0; i != num_of_touches; i++) {
|
||||||
uint16_t id = data[i][0];
|
uint16_t id = data[i][0];
|
||||||
uint16_t x = encode_uint16(data[i][2], data[i][1]);
|
uint16_t x = encode_uint16(data[i][2], data[i][1]);
|
||||||
|
@@ -15,8 +15,20 @@ class GT911ButtonListener {
|
|||||||
|
|
||||||
class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
|
/// @brief Initialize the GT911 touchscreen.
|
||||||
|
///
|
||||||
|
/// If @ref reset_pin_ is set, the touchscreen will be hardware reset,
|
||||||
|
/// and the rest of the setup will be scheduled to run 50ms later using @ref set_timeout()
|
||||||
|
/// to allow the device to stabilize after reset.
|
||||||
|
///
|
||||||
|
/// If @ref interrupt_pin_ is set, it will be temporarily configured during reset
|
||||||
|
/// to control I2C address selection.
|
||||||
|
///
|
||||||
|
/// After the timeout, or immediately if no reset is performed, @ref setup_internal_()
|
||||||
|
/// is called to complete the initialization.
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
bool can_proceed() override { return this->setup_done_; }
|
||||||
|
|
||||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||||
@@ -25,8 +37,20 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
|||||||
protected:
|
protected:
|
||||||
void update_touches() override;
|
void update_touches() override;
|
||||||
|
|
||||||
InternalGPIOPin *interrupt_pin_{};
|
/// @brief Perform the internal setup routine for the GT911 touchscreen.
|
||||||
GPIOPin *reset_pin_{};
|
///
|
||||||
|
/// This function checks the I2C address, configures the interrupt pin (if available),
|
||||||
|
/// reads the touchscreen mode from the controller, and attempts to read calibration
|
||||||
|
/// data (maximum X and Y values) if not already set.
|
||||||
|
///
|
||||||
|
/// On success, sets @ref setup_done_ to true.
|
||||||
|
/// On failure, calls @ref mark_failed() with an appropriate error message.
|
||||||
|
void setup_internal_();
|
||||||
|
/// @brief True if the touchscreen setup has completed successfully.
|
||||||
|
bool setup_done_{false};
|
||||||
|
|
||||||
|
InternalGPIOPin *interrupt_pin_{nullptr};
|
||||||
|
GPIOPin *reset_pin_{nullptr};
|
||||||
std::vector<GT911ButtonListener *> button_listeners_;
|
std::vector<GT911ButtonListener *> button_listeners_;
|
||||||
uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update.
|
uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update.
|
||||||
};
|
};
|
||||||
|
@@ -24,9 +24,6 @@ static const uint32_t READ_DURATION_MS = 16;
|
|||||||
static const size_t TASK_STACK_SIZE = 4096;
|
static const size_t TASK_STACK_SIZE = 4096;
|
||||||
static const ssize_t TASK_PRIORITY = 23;
|
static const ssize_t TASK_PRIORITY = 23;
|
||||||
|
|
||||||
// Use an exponential moving average to correct a DC offset with weight factor 1/1000
|
|
||||||
static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
|
|
||||||
|
|
||||||
static const char *const TAG = "i2s_audio.microphone";
|
static const char *const TAG = "i2s_audio.microphone";
|
||||||
|
|
||||||
enum MicrophoneEventGroupBits : uint32_t {
|
enum MicrophoneEventGroupBits : uint32_t {
|
||||||
@@ -381,26 +378,57 @@ void I2SAudioMicrophone::mic_task(void *params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
|
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
|
||||||
|
/**
|
||||||
|
* From https://www.musicdsp.org/en/latest/Filters/135-dc-filter.html:
|
||||||
|
*
|
||||||
|
* y(n) = x(n) - x(n-1) + R * y(n-1)
|
||||||
|
* R = 1 - (pi * 2 * frequency / samplerate)
|
||||||
|
*
|
||||||
|
* From https://en.wikipedia.org/wiki/Hearing_range:
|
||||||
|
* The human range is commonly given as 20Hz up.
|
||||||
|
*
|
||||||
|
* From https://en.wikipedia.org/wiki/High-resolution_audio:
|
||||||
|
* A reasonable upper bound for sample rate seems to be 96kHz.
|
||||||
|
*
|
||||||
|
* Calculate R value for 20Hz on a 96kHz sample rate:
|
||||||
|
* R = 1 - (pi * 2 * 20 / 96000)
|
||||||
|
* R = 0.9986910031
|
||||||
|
*
|
||||||
|
* Transform floating point to bit-shifting approximation:
|
||||||
|
* output = input - prev_input + R * prev_output
|
||||||
|
* output = input - prev_input + (prev_output - (prev_output >> S))
|
||||||
|
*
|
||||||
|
* Approximate bit-shift value S from R:
|
||||||
|
* R = 1 - (1 >> S)
|
||||||
|
* R = 1 - (1 / 2^S)
|
||||||
|
* R = 1 - 2^-S
|
||||||
|
* 0.9986910031 = 1 - 2^-S
|
||||||
|
* S = 9.57732 ~= 10
|
||||||
|
*
|
||||||
|
* Actual R from S:
|
||||||
|
* R = 1 - 2^-10 = 0.9990234375
|
||||||
|
*
|
||||||
|
* Confirm this has effect outside human hearing on 96000kHz sample:
|
||||||
|
* 0.9990234375 = 1 - (pi * 2 * f / 96000)
|
||||||
|
* f = 14.9208Hz
|
||||||
|
*
|
||||||
|
* Confirm this has effect outside human hearing on PDM 16kHz sample:
|
||||||
|
* 0.9990234375 = 1 - (pi * 2 * f / 16000)
|
||||||
|
* f = 2.4868Hz
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const uint8_t dc_filter_shift = 10;
|
||||||
const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
|
const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
|
||||||
const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
|
const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
|
||||||
|
|
||||||
if (total_samples == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t offset_accumulator = 0;
|
|
||||||
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
|
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
|
||||||
const uint32_t byte_index = sample_index * bytes_per_sample;
|
const uint32_t byte_index = sample_index * bytes_per_sample;
|
||||||
int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
|
int32_t input = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
|
||||||
offset_accumulator += sample;
|
int32_t output = input - this->dc_offset_prev_input_ +
|
||||||
sample -= this->dc_offset_;
|
(this->dc_offset_prev_output_ - (this->dc_offset_prev_output_ >> dc_filter_shift));
|
||||||
audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample);
|
this->dc_offset_prev_input_ = input;
|
||||||
|
this->dc_offset_prev_output_ = output;
|
||||||
|
audio::pack_q31_as_audio_sample(output, &data[byte_index], bytes_per_sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int32_t new_offset = offset_accumulator / total_samples;
|
|
||||||
this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR +
|
|
||||||
(DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ /
|
|
||||||
DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
||||||
|
@@ -82,7 +82,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||||||
|
|
||||||
bool correct_dc_offset_;
|
bool correct_dc_offset_;
|
||||||
bool locked_driver_{false};
|
bool locked_driver_{false};
|
||||||
int32_t dc_offset_{0};
|
int32_t dc_offset_prev_input_{0};
|
||||||
|
int32_t dc_offset_prev_output_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace i2s_audio
|
} // namespace i2s_audio
|
||||||
|
@@ -77,6 +77,7 @@ BRIGHTNESS = 0x51
|
|||||||
WRDISBV = 0x51
|
WRDISBV = 0x51
|
||||||
RDDISBV = 0x52
|
RDDISBV = 0x52
|
||||||
WRCTRLD = 0x53
|
WRCTRLD = 0x53
|
||||||
|
WCE = 0x58
|
||||||
SWIRE1 = 0x5A
|
SWIRE1 = 0x5A
|
||||||
SWIRE2 = 0x5B
|
SWIRE2 = 0x5B
|
||||||
IFMODE = 0xB0
|
IFMODE = 0xB0
|
||||||
@@ -91,6 +92,7 @@ PWCTR2 = 0xC1
|
|||||||
PWCTR3 = 0xC2
|
PWCTR3 = 0xC2
|
||||||
PWCTR4 = 0xC3
|
PWCTR4 = 0xC3
|
||||||
PWCTR5 = 0xC4
|
PWCTR5 = 0xC4
|
||||||
|
SPIMODESEL = 0xC4
|
||||||
VMCTR1 = 0xC5
|
VMCTR1 = 0xC5
|
||||||
IFCTR = 0xC6
|
IFCTR = 0xC6
|
||||||
VMCTR2 = 0xC7
|
VMCTR2 = 0xC7
|
||||||
|
@@ -5,10 +5,13 @@ from esphome.components.mipi import (
|
|||||||
PAGESEL,
|
PAGESEL,
|
||||||
PIXFMT,
|
PIXFMT,
|
||||||
SLPOUT,
|
SLPOUT,
|
||||||
|
SPIMODESEL,
|
||||||
SWIRE1,
|
SWIRE1,
|
||||||
SWIRE2,
|
SWIRE2,
|
||||||
TEON,
|
TEON,
|
||||||
|
WCE,
|
||||||
WRAM,
|
WRAM,
|
||||||
|
WRCTRLD,
|
||||||
DriverChip,
|
DriverChip,
|
||||||
delay,
|
delay,
|
||||||
)
|
)
|
||||||
@@ -87,4 +90,19 @@ T4_S3_AMOLED = RM690B0.extend(
|
|||||||
bus_mode=TYPE_QUAD,
|
bus_mode=TYPE_QUAD,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CO5300 = DriverChip(
|
||||||
|
"CO5300",
|
||||||
|
brightness=0xD0,
|
||||||
|
color_order=MODE_RGB,
|
||||||
|
bus_mode=TYPE_QUAD,
|
||||||
|
initsequence=(
|
||||||
|
(SLPOUT,), # Requires early SLPOUT
|
||||||
|
(PAGESEL, 0x00),
|
||||||
|
(SPIMODESEL, 0x80),
|
||||||
|
(WRCTRLD, 0x20),
|
||||||
|
(WCE, 0x00),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
models = {}
|
models = {}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from esphome.components.mipi import DriverChip
|
from esphome.components.mipi import DriverChip
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
|
from .amoled import CO5300
|
||||||
from .ili import ILI9488_A
|
from .ili import ILI9488_A
|
||||||
|
|
||||||
DriverChip(
|
DriverChip(
|
||||||
@@ -140,3 +141,14 @@ ILI9488_A.extend(
|
|||||||
data_rate="20MHz",
|
data_rate="20MHz",
|
||||||
invert_colors=True,
|
invert_colors=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CO5300.extend(
|
||||||
|
"WAVESHARE-ESP32-S3-TOUCH-AMOLED-1.75",
|
||||||
|
width=466,
|
||||||
|
height=466,
|
||||||
|
pixel_mode="16bit",
|
||||||
|
offset_height=0,
|
||||||
|
offset_width=6,
|
||||||
|
cs_pin=12,
|
||||||
|
reset_pin=39,
|
||||||
|
)
|
||||||
|
@@ -116,7 +116,7 @@ esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle regular form data
|
// Handle regular form data
|
||||||
if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) {
|
if (r->content_len > CONFIG_HTTPD_MAX_REQ_HDR_LEN) {
|
||||||
ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len);
|
ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len);
|
||||||
httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
|
httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
|
@@ -373,3 +373,20 @@ button:
|
|||||||
name: "Test Button"
|
name: "Test Button"
|
||||||
on_press:
|
on_press:
|
||||||
- logger.log: "Button pressed"
|
- logger.log: "Button pressed"
|
||||||
|
|
||||||
|
# Date, Time, and DateTime entities
|
||||||
|
datetime:
|
||||||
|
- platform: template
|
||||||
|
type: date
|
||||||
|
name: "Test Date"
|
||||||
|
initial_value: "2023-05-13"
|
||||||
|
optimistic: true
|
||||||
|
- platform: template
|
||||||
|
type: time
|
||||||
|
name: "Test Time"
|
||||||
|
initial_value: "12:30:00"
|
||||||
|
optimistic: true
|
||||||
|
- platform: template
|
||||||
|
type: datetime
|
||||||
|
name: "Test DateTime"
|
||||||
|
optimistic: true
|
||||||
|
@@ -4,7 +4,17 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from aioesphomeapi import ClimateInfo, EntityState, SensorState
|
from aioesphomeapi import (
|
||||||
|
ClimateInfo,
|
||||||
|
DateInfo,
|
||||||
|
DateState,
|
||||||
|
DateTimeInfo,
|
||||||
|
DateTimeState,
|
||||||
|
EntityState,
|
||||||
|
SensorState,
|
||||||
|
TimeInfo,
|
||||||
|
TimeState,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||||
@@ -22,34 +32,56 @@ async def test_host_mode_many_entities(
|
|||||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||||
# Subscribe to state changes
|
# Subscribe to state changes
|
||||||
states: dict[int, EntityState] = {}
|
states: dict[int, EntityState] = {}
|
||||||
sensor_count_future: asyncio.Future[int] = loop.create_future()
|
minimum_states_future: asyncio.Future[None] = loop.create_future()
|
||||||
|
|
||||||
def on_state(state: EntityState) -> None:
|
def on_state(state: EntityState) -> None:
|
||||||
states[state.key] = state
|
states[state.key] = state
|
||||||
# Count sensor states specifically
|
# Check if we have received minimum expected states
|
||||||
sensor_states = [
|
sensor_states = [
|
||||||
s
|
s
|
||||||
for s in states.values()
|
for s in states.values()
|
||||||
if isinstance(s, SensorState) and isinstance(s.state, float)
|
if isinstance(s, SensorState) and isinstance(s.state, float)
|
||||||
]
|
]
|
||||||
# When we have received states from at least 50 sensors, resolve the future
|
date_states = [s for s in states.values() if isinstance(s, DateState)]
|
||||||
if len(sensor_states) >= 50 and not sensor_count_future.done():
|
time_states = [s for s in states.values() if isinstance(s, TimeState)]
|
||||||
sensor_count_future.set_result(len(sensor_states))
|
datetime_states = [
|
||||||
|
s for s in states.values() if isinstance(s, DateTimeState)
|
||||||
|
]
|
||||||
|
|
||||||
|
# We expect at least 50 sensors and 1 of each datetime entity type
|
||||||
|
if (
|
||||||
|
len(sensor_states) >= 50
|
||||||
|
and len(date_states) >= 1
|
||||||
|
and len(time_states) >= 1
|
||||||
|
and len(datetime_states) >= 1
|
||||||
|
and not minimum_states_future.done()
|
||||||
|
):
|
||||||
|
minimum_states_future.set_result(None)
|
||||||
|
|
||||||
client.subscribe_states(on_state)
|
client.subscribe_states(on_state)
|
||||||
|
|
||||||
# Wait for states from at least 50 sensors with timeout
|
# Wait for minimum states with timeout
|
||||||
try:
|
try:
|
||||||
sensor_count = await asyncio.wait_for(sensor_count_future, timeout=10.0)
|
await asyncio.wait_for(minimum_states_future, timeout=10.0)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
sensor_states = [
|
sensor_states = [
|
||||||
s
|
s
|
||||||
for s in states.values()
|
for s in states.values()
|
||||||
if isinstance(s, SensorState) and isinstance(s.state, float)
|
if isinstance(s, SensorState) and isinstance(s.state, float)
|
||||||
]
|
]
|
||||||
|
date_states = [s for s in states.values() if isinstance(s, DateState)]
|
||||||
|
time_states = [s for s in states.values() if isinstance(s, TimeState)]
|
||||||
|
datetime_states = [
|
||||||
|
s for s in states.values() if isinstance(s, DateTimeState)
|
||||||
|
]
|
||||||
|
|
||||||
pytest.fail(
|
pytest.fail(
|
||||||
f"Did not receive states from at least 50 sensors within 10 seconds. "
|
f"Did not receive expected states within 10 seconds. "
|
||||||
f"Received {len(sensor_states)} sensor states out of {len(states)} total states"
|
f"Received: {len(sensor_states)} sensor states (expected >=50), "
|
||||||
|
f"{len(date_states)} date states (expected >=1), "
|
||||||
|
f"{len(time_states)} time states (expected >=1), "
|
||||||
|
f"{len(datetime_states)} datetime states (expected >=1). "
|
||||||
|
f"Total states: {len(states)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify we received a good number of entity states
|
# Verify we received a good number of entity states
|
||||||
@@ -64,13 +96,25 @@ async def test_host_mode_many_entities(
|
|||||||
if isinstance(s, SensorState) and isinstance(s.state, float)
|
if isinstance(s, SensorState) and isinstance(s.state, float)
|
||||||
]
|
]
|
||||||
|
|
||||||
assert sensor_count >= 50, (
|
|
||||||
f"Expected at least 50 sensor states, got {sensor_count}"
|
|
||||||
)
|
|
||||||
assert len(sensor_states) >= 50, (
|
assert len(sensor_states) >= 50, (
|
||||||
f"Expected at least 50 sensor states, got {len(sensor_states)}"
|
f"Expected at least 50 sensor states, got {len(sensor_states)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Verify we received datetime entity states
|
||||||
|
date_states = [s for s in states.values() if isinstance(s, DateState)]
|
||||||
|
time_states = [s for s in states.values() if isinstance(s, TimeState)]
|
||||||
|
datetime_states = [s for s in states.values() if isinstance(s, DateTimeState)]
|
||||||
|
|
||||||
|
assert len(date_states) >= 1, (
|
||||||
|
f"Expected at least 1 date state, got {len(date_states)}"
|
||||||
|
)
|
||||||
|
assert len(time_states) >= 1, (
|
||||||
|
f"Expected at least 1 time state, got {len(time_states)}"
|
||||||
|
)
|
||||||
|
assert len(datetime_states) >= 1, (
|
||||||
|
f"Expected at least 1 datetime state, got {len(datetime_states)}"
|
||||||
|
)
|
||||||
|
|
||||||
# Get entity info to verify climate entity details
|
# Get entity info to verify climate entity details
|
||||||
entities = await client.list_entities_services()
|
entities = await client.list_entities_services()
|
||||||
climate_infos = [e for e in entities[0] if isinstance(e, ClimateInfo)]
|
climate_infos = [e for e in entities[0] if isinstance(e, ClimateInfo)]
|
||||||
@@ -89,3 +133,28 @@ async def test_host_mode_many_entities(
|
|||||||
assert "HOME" in preset_names, f"Expected 'HOME' preset, got {preset_names}"
|
assert "HOME" in preset_names, f"Expected 'HOME' preset, got {preset_names}"
|
||||||
assert "AWAY" in preset_names, f"Expected 'AWAY' preset, got {preset_names}"
|
assert "AWAY" in preset_names, f"Expected 'AWAY' preset, got {preset_names}"
|
||||||
assert "SLEEP" in preset_names, f"Expected 'SLEEP' preset, got {preset_names}"
|
assert "SLEEP" in preset_names, f"Expected 'SLEEP' preset, got {preset_names}"
|
||||||
|
|
||||||
|
# Verify datetime entities exist
|
||||||
|
date_infos = [e for e in entities[0] if isinstance(e, DateInfo)]
|
||||||
|
time_infos = [e for e in entities[0] if isinstance(e, TimeInfo)]
|
||||||
|
datetime_infos = [e for e in entities[0] if isinstance(e, DateTimeInfo)]
|
||||||
|
|
||||||
|
assert len(date_infos) >= 1, "Expected at least 1 date entity"
|
||||||
|
assert len(time_infos) >= 1, "Expected at least 1 time entity"
|
||||||
|
assert len(datetime_infos) >= 1, "Expected at least 1 datetime entity"
|
||||||
|
|
||||||
|
# Verify the entity names
|
||||||
|
date_info = date_infos[0]
|
||||||
|
assert date_info.name == "Test Date", (
|
||||||
|
f"Expected date entity name 'Test Date', got {date_info.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
time_info = time_infos[0]
|
||||||
|
assert time_info.name == "Test Time", (
|
||||||
|
f"Expected time entity name 'Test Time', got {time_info.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
datetime_info = datetime_infos[0]
|
||||||
|
assert datetime_info.name == "Test DateTime", (
|
||||||
|
f"Expected datetime entity name 'Test DateTime', got {datetime_info.name}"
|
||||||
|
)
|
||||||
|
Reference in New Issue
Block a user