1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-05 11:23:47 +01:00

Merge branch 'ble_pool' into integration

This commit is contained in:
J. Nick Koston
2025-06-16 15:43:59 +02:00
17 changed files with 336 additions and 183 deletions

View File

@@ -1,4 +1,5 @@
import re import re
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv

View File

@@ -1,10 +1,10 @@
"""CM1106 Sensor component for ESPHome.""" """CM1106 Sensor component for ESPHome."""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import sensor, uart from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CO2, CONF_CO2,
CONF_ID, CONF_ID,

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "ble.h" #include "ble.h"
#include "ble_event_pool.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -23,9 +24,6 @@ namespace esp32_ble {
static const char *const TAG = "esp32_ble"; static const char *const TAG = "esp32_ble";
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
void ESP32BLE::setup() { void ESP32BLE::setup() {
global_ble = this; global_ble = this;
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Running setup");
@@ -349,9 +347,8 @@ void ESP32BLE::loop() {
default: default:
break; break;
} }
// Destructor will clean up external allocations for GATTC/GATTS // Return the event to the pool
ble_event->~BLEEvent(); this->ble_event_pool_.release(ble_event);
EVENT_ALLOCATOR.deallocate(ble_event, 1);
ble_event = this->ble_events_.pop(); ble_event = this->ble_events_.pop();
} }
if (this->advertising_ != nullptr) { if (this->advertising_ != nullptr) {
@@ -359,37 +356,41 @@ void ESP32BLE::loop() {
} }
// Log dropped events periodically // Log dropped events periodically
size_t dropped = this->ble_events_.get_and_reset_dropped_count(); uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
if (dropped > 0) { if (dropped > 0) {
ESP_LOGW(TAG, "Dropped %zu BLE events due to buffer overflow", dropped); ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped);
} }
} }
// Helper function to load new event data based on type
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
event->load_gap_event(e, p);
}
void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
event->load_gattc_event(e, i, p);
}
void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
event->load_gatts_event(e, i, p);
}
template<typename... Args> void enqueue_ble_event(Args... args) { template<typename... Args> void enqueue_ble_event(Args... args) {
// Check if queue is full before allocating // Allocate an event from the pool
if (global_ble->ble_events_.full()) { BLEEvent *event = global_ble->ble_event_pool_.allocate();
// Queue is full, drop the event if (event == nullptr) {
// No events available - queue is full or we're out of memory
global_ble->ble_events_.increment_dropped_count(); global_ble->ble_events_.increment_dropped_count();
return; return;
} }
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); // Load new event data (replaces previous event)
if (new_event == nullptr) { load_ble_event(event, args...);
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
global_ble->ble_events_.increment_dropped_count();
return;
}
new (new_event) BLEEvent(args...);
// Push the event - since we're the only producer and we checked full() above, // Push the event to the queue
// this should always succeed unless we have a bug global_ble->ble_events_.push(event);
if (!global_ble->ble_events_.push(new_event)) { // Push always succeeds: we checked full() above and we're the only producer
// This should not happen in SPSC queue with single producer }
ESP_LOGE(TAG, "BLE queue push failed unexpectedly");
new_event->~BLEEvent();
EVENT_ALLOCATOR.deallocate(new_event, 1);
}
} // NOLINT(clang-analyzer-unix.Malloc)
// Explicit template instantiations for the friend function // Explicit template instantiations for the friend function
template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *); template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);

View File

@@ -12,6 +12,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "ble_event.h" #include "ble_event.h"
#include "ble_event_pool.h"
#include "queue.h" #include "queue.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -148,6 +149,7 @@ class ESP32BLE : public Component {
BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_; LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
BLEEventPool<MAX_BLE_QUEUE_SIZE> ble_event_pool_;
BLEAdvertising *advertising_{}; BLEAdvertising *advertising_{};
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_{}; uint32_t advertising_cycle_time_{};

View File

@@ -63,123 +63,66 @@ class BLEEvent {
// Constructor for GAP events - no external allocations needed // Constructor for GAP events - no external allocations needed
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->type_ = GAP; this->type_ = GAP;
this->event_.gap.gap_event = e; this->init_gap_data_(e, p);
if (p == nullptr) {
return; // Invalid event, but we can't log in header file
}
// Only copy the data we actually use for each GAP event type
switch (e) {
case ESP_GAP_BLE_SCAN_RESULT_EVT:
// Copy only the fields we use from scan results
memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t));
this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type;
this->event_.gap.scan_result.rssi = p->scan_rst.rssi;
this->event_.gap.scan_result.adv_data_len = p->scan_rst.adv_data_len;
this->event_.gap.scan_result.scan_rsp_len = p->scan_rst.scan_rsp_len;
this->event_.gap.scan_result.search_evt = p->scan_rst.search_evt;
memcpy(this->event_.gap.scan_result.ble_adv, p->scan_rst.ble_adv,
ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX);
break;
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
this->event_.gap.scan_complete.status = p->scan_param_cmpl.status;
break;
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
this->event_.gap.scan_complete.status = p->scan_start_cmpl.status;
break;
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status;
break;
default:
// We only handle 4 GAP event types, others are dropped
break;
}
} }
// Constructor for GATTC events - uses heap allocation // Constructor for GATTC events - uses heap allocation
// Creates a copy of the param struct since the original is only valid during the callback // Creates a copy of the param struct since the original is only valid during the callback
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
this->type_ = GATTC; this->type_ = GATTC;
this->event_.gattc.gattc_event = e; this->init_gattc_data_(e, i, p);
this->event_.gattc.gattc_if = i;
if (p == nullptr) {
this->event_.gattc.gattc_param = nullptr;
this->event_.gattc.data = nullptr;
return; // Invalid event, but we can't log in header file
}
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
// Copy data for events that need it
switch (e) {
case ESP_GATTC_NOTIFY_EVT:
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
break;
case ESP_GATTC_READ_CHAR_EVT:
case ESP_GATTC_READ_DESCR_EVT:
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
break;
default:
this->event_.gattc.data = nullptr;
break;
}
} }
// Constructor for GATTS events - uses heap allocation // Constructor for GATTS events - uses heap allocation
// Creates a copy of the param struct since the original is only valid during the callback // Creates a copy of the param struct since the original is only valid during the callback
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
this->type_ = GATTS; this->type_ = GATTS;
this->event_.gatts.gatts_event = e; this->init_gatts_data_(e, i, p);
this->event_.gatts.gatts_if = i;
if (p == nullptr) {
this->event_.gatts.gatts_param = nullptr;
this->event_.gatts.data = nullptr;
return; // Invalid event, but we can't log in header file
}
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
// Copy data for events that need it
switch (e) {
case ESP_GATTS_WRITE_EVT:
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
break;
default:
this->event_.gatts.data = nullptr;
break;
}
} }
// Destructor to clean up heap allocations // Destructor to clean up heap allocations
~BLEEvent() { ~BLEEvent() { this->cleanup_heap_data(); }
switch (this->type_) {
case GATTC: // Default constructor for pre-allocation in pool
delete this->event_.gattc.gattc_param; BLEEvent() : type_(GAP) {}
delete this->event_.gattc.data;
break; // Clean up any heap-allocated data
case GATTS: void cleanup_heap_data() {
delete this->event_.gatts.gatts_param; if (this->type_ == GAP) {
delete this->event_.gatts.data; return;
break;
default:
break;
} }
if (this->type_ == GATTC) {
delete this->event_.gattc.gattc_param;
delete this->event_.gattc.data;
this->event_.gattc.gattc_param = nullptr;
this->event_.gattc.data = nullptr;
return;
}
if (this->type_ == GATTS) {
delete this->event_.gatts.gatts_param;
delete this->event_.gatts.data;
this->event_.gatts.gatts_param = nullptr;
this->event_.gatts.data = nullptr;
}
}
// Load new event data for reuse (replaces previous event data)
void load_gap_event(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->cleanup_heap_data();
this->type_ = GAP;
this->init_gap_data_(e, p);
}
void load_gattc_event(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
this->cleanup_heap_data();
this->type_ = GATTC;
this->init_gattc_data_(e, i, p);
}
void load_gatts_event(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
this->cleanup_heap_data();
this->type_ = GATTS;
this->init_gatts_data_(e, i, p);
} }
// Disable copy to prevent double-delete // Disable copy to prevent double-delete
@@ -224,6 +167,107 @@ class BLEEvent {
esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; } esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; }
const BLEScanResult &scan_result() const { return event_.gap.scan_result; } const BLEScanResult &scan_result() const { return event_.gap.scan_result; }
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; } esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
private:
// Initialize GAP event data
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->event_.gap.gap_event = e;
if (p == nullptr) {
return; // Invalid event, but we can't log in header file
}
// Copy data based on event type
switch (e) {
case ESP_GAP_BLE_SCAN_RESULT_EVT:
memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t));
this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type;
this->event_.gap.scan_result.rssi = p->scan_rst.rssi;
this->event_.gap.scan_result.adv_data_len = p->scan_rst.adv_data_len;
this->event_.gap.scan_result.scan_rsp_len = p->scan_rst.scan_rsp_len;
this->event_.gap.scan_result.search_evt = p->scan_rst.search_evt;
memcpy(this->event_.gap.scan_result.ble_adv, p->scan_rst.ble_adv,
ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX);
break;
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
this->event_.gap.scan_complete.status = p->scan_param_cmpl.status;
break;
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
this->event_.gap.scan_complete.status = p->scan_start_cmpl.status;
break;
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status;
break;
default:
// We only handle 4 GAP event types, others are dropped
break;
}
}
// Initialize GATTC event data
void init_gattc_data_(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
this->event_.gattc.gattc_event = e;
this->event_.gattc.gattc_if = i;
if (p == nullptr) {
this->event_.gattc.gattc_param = nullptr;
this->event_.gattc.data = nullptr;
return; // Invalid event, but we can't log in header file
}
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
// Copy data for events that need it
switch (e) {
case ESP_GATTC_NOTIFY_EVT:
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
break;
case ESP_GATTC_READ_CHAR_EVT:
case ESP_GATTC_READ_DESCR_EVT:
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
break;
default:
this->event_.gattc.data = nullptr;
break;
}
}
// Initialize GATTS event data
void init_gatts_data_(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
this->event_.gatts.gatts_event = e;
this->event_.gatts.gatts_if = i;
if (p == nullptr) {
this->event_.gatts.gatts_param = nullptr;
this->event_.gatts.data = nullptr;
return; // Invalid event, but we can't log in header file
}
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
// Copy data for events that need it
switch (e) {
case ESP_GATTS_WRITE_EVT:
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
break;
default:
this->event_.gatts.data = nullptr;
break;
}
}
}; };
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding) // BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)

View File

@@ -0,0 +1,72 @@
#pragma once
#ifdef USE_ESP32
#include <atomic>
#include <cstddef>
#include "ble_event.h"
#include "queue.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace esp32_ble {
// BLE Event Pool - On-demand pool of BLEEvent objects to avoid heap fragmentation
// Events are allocated on first use and reused thereafter, growing to peak usage
template<uint8_t SIZE> class BLEEventPool {
public:
BLEEventPool() : total_created_(0) {}
~BLEEventPool() {
// Clean up any remaining events in the free list
BLEEvent *event;
while ((event = this->free_list_.pop()) != nullptr) {
delete event;
}
}
// Allocate an event from the pool
// Returns nullptr if pool is full
BLEEvent *allocate() {
// Try to get from free list first
BLEEvent *event = this->free_list_.pop();
if (event != nullptr)
return event;
// Need to create a new event
if (this->total_created_ >= SIZE) {
// Pool is at capacity
return nullptr;
}
// Use internal RAM for better performance
RAMAllocator<BLEEvent> allocator(RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
event = allocator.allocate(1);
if (event == nullptr) {
// Memory allocation failed
return nullptr;
}
// Placement new to construct the object
new (event) BLEEvent();
this->total_created_++;
return event;
}
// Return an event to the pool for reuse
void release(BLEEvent *event) {
if (event != nullptr) {
this->free_list_.push(event);
}
}
private:
LockFreeQueue<BLEEvent, SIZE> free_list_; // Free events ready for reuse
uint8_t total_created_; // Total events created (high water mark)
};
} // namespace esp32_ble
} // namespace esphome
#endif

View File

@@ -18,7 +18,7 @@
namespace esphome { namespace esphome {
namespace esp32_ble { namespace esp32_ble {
template<class T, size_t SIZE> class LockFreeQueue { template<class T, uint8_t SIZE> class LockFreeQueue {
public: public:
LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {} LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {}
@@ -26,8 +26,8 @@ template<class T, size_t SIZE> class LockFreeQueue {
if (element == nullptr) if (element == nullptr)
return false; return false;
size_t current_tail = tail_.load(std::memory_order_relaxed); uint8_t current_tail = tail_.load(std::memory_order_relaxed);
size_t next_tail = (current_tail + 1) % SIZE; uint8_t next_tail = (current_tail + 1) % SIZE;
if (next_tail == head_.load(std::memory_order_acquire)) { if (next_tail == head_.load(std::memory_order_acquire)) {
// Buffer full // Buffer full
@@ -41,7 +41,7 @@ template<class T, size_t SIZE> class LockFreeQueue {
} }
T *pop() { T *pop() {
size_t current_head = head_.load(std::memory_order_relaxed); uint8_t current_head = head_.load(std::memory_order_relaxed);
if (current_head == tail_.load(std::memory_order_acquire)) { if (current_head == tail_.load(std::memory_order_acquire)) {
return nullptr; // Empty return nullptr; // Empty
@@ -53,27 +53,30 @@ template<class T, size_t SIZE> class LockFreeQueue {
} }
size_t size() const { size_t size() const {
size_t tail = tail_.load(std::memory_order_acquire); uint8_t tail = tail_.load(std::memory_order_acquire);
size_t head = head_.load(std::memory_order_acquire); uint8_t head = head_.load(std::memory_order_acquire);
return (tail - head + SIZE) % SIZE; return (tail - head + SIZE) % SIZE;
} }
size_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); } uint16_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); } void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); }
bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); } bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
bool full() const { bool full() const {
size_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE; uint8_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE;
return next_tail == head_.load(std::memory_order_acquire); return next_tail == head_.load(std::memory_order_acquire);
} }
protected: protected:
T *buffer_[SIZE]; T *buffer_[SIZE];
std::atomic<size_t> head_; // Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset)
std::atomic<size_t> tail_; std::atomic<uint16_t> dropped_count_; // 65535 max - more than enough for drop tracking
std::atomic<size_t> dropped_count_; // Atomic: written by consumer (pop), read by producer (push) to check if full
std::atomic<uint8_t> head_;
// Atomic: written by producer (push), read by consumer (pop) to check if empty
std::atomic<uint8_t> tail_;
}; };
} // namespace esp32_ble } // namespace esp32_ble

View File

@@ -18,7 +18,7 @@ void I2SAudioComponent::setup() {
static i2s_port_t next_port_num = I2S_NUM_0; static i2s_port_t next_port_num = I2S_NUM_0;
if (next_port_num >= I2S_NUM_MAX) { if (next_port_num >= I2S_NUM_MAX) {
ESP_LOGE(TAG, "Too many I2S Audio components"); ESP_LOGE(TAG, "Too many components");
this->mark_failed(); this->mark_failed();
return; return;
} }

View File

@@ -45,7 +45,7 @@ void I2SAudioMicrophone::setup() {
#if SOC_I2S_SUPPORTS_ADC #if SOC_I2S_SUPPORTS_ADC
if (this->adc_) { if (this->adc_) {
if (this->parent_->get_port() != I2S_NUM_0) { if (this->parent_->get_port() != I2S_NUM_0) {
ESP_LOGE(TAG, "Internal ADC only works on I2S0!"); ESP_LOGE(TAG, "Internal ADC only works on I2S0");
this->mark_failed(); this->mark_failed();
return; return;
} }
@@ -55,7 +55,7 @@ void I2SAudioMicrophone::setup() {
{ {
if (this->pdm_) { if (this->pdm_) {
if (this->parent_->get_port() != I2S_NUM_0) { if (this->parent_->get_port() != I2S_NUM_0) {
ESP_LOGE(TAG, "PDM only works on I2S0!"); ESP_LOGE(TAG, "PDM only works on I2S0");
this->mark_failed(); this->mark_failed();
return; return;
} }
@@ -64,14 +64,14 @@ void I2SAudioMicrophone::setup() {
this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS); this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS);
if (this->active_listeners_semaphore_ == nullptr) { if (this->active_listeners_semaphore_ == nullptr) {
ESP_LOGE(TAG, "Failed to create semaphore"); ESP_LOGE(TAG, "Creating semaphore failed");
this->mark_failed(); this->mark_failed();
return; return;
} }
this->event_group_ = xEventGroupCreate(); this->event_group_ = xEventGroupCreate();
if (this->event_group_ == nullptr) { if (this->event_group_ == nullptr) {
ESP_LOGE(TAG, "Failed to create event group"); ESP_LOGE(TAG, "Creating event group failed");
this->mark_failed(); this->mark_failed();
return; return;
} }
@@ -79,6 +79,15 @@ void I2SAudioMicrophone::setup() {
this->configure_stream_settings_(); this->configure_stream_settings_();
} }
void I2SAudioMicrophone::dump_config() {
ESP_LOGCONFIG(TAG,
"Microphone:\n"
" Pin: %d\n"
" PDM: %s\n"
" DC offset correction: %s",
static_cast<int8_t>(this->din_pin_), YESNO(this->pdm_), YESNO(this->correct_dc_offset_));
}
void I2SAudioMicrophone::configure_stream_settings_() { void I2SAudioMicrophone::configure_stream_settings_() {
uint8_t channel_count = 1; uint8_t channel_count = 1;
#ifdef USE_I2S_LEGACY #ifdef USE_I2S_LEGACY
@@ -151,7 +160,7 @@ bool I2SAudioMicrophone::start_driver_() {
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN); config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err));
return false; return false;
} }
@@ -174,7 +183,7 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err));
return false; return false;
} }
@@ -183,7 +192,7 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_set_pin(this->parent_->get_port(), &pin_config); err = i2s_set_pin(this->parent_->get_port(), &pin_config);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Error setting I2S pin: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Error setting pin: %s", esp_err_to_name(err));
return false; return false;
} }
} }
@@ -198,7 +207,7 @@ bool I2SAudioMicrophone::start_driver_() {
/* Allocate a new RX channel and get the handle of this channel */ /* Allocate a new RX channel and get the handle of this channel */
err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_); err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Error creating channel: %s", esp_err_to_name(err));
return false; return false;
} }
@@ -270,14 +279,14 @@ bool I2SAudioMicrophone::start_driver_() {
err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg); err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg);
} }
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Error initializing channel: %s", esp_err_to_name(err));
return false; return false;
} }
/* Before reading data, start the RX channel first */ /* Before reading data, start the RX channel first */
i2s_channel_enable(this->rx_handle_); i2s_channel_enable(this->rx_handle_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Enabling failed: %s", esp_err_to_name(err));
return false; return false;
} }
#endif #endif
@@ -304,29 +313,29 @@ void I2SAudioMicrophone::stop_driver_() {
if (this->adc_) { if (this->adc_) {
err = i2s_adc_disable(this->parent_->get_port()); err = i2s_adc_disable(this->parent_->get_port());
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Error disabling ADC - it may not have started: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Error disabling ADC: %s", esp_err_to_name(err));
} }
} }
#endif #endif
err = i2s_stop(this->parent_->get_port()); err = i2s_stop(this->parent_->get_port());
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Error stopping: %s", esp_err_to_name(err));
} }
err = i2s_driver_uninstall(this->parent_->get_port()); err = i2s_driver_uninstall(this->parent_->get_port());
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Error uninstalling I2S driver - it may not have started: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Error uninstalling driver: %s", esp_err_to_name(err));
} }
#else #else
if (this->rx_handle_ != nullptr) { if (this->rx_handle_ != nullptr) {
/* Have to stop the channel before deleting it */ /* Have to stop the channel before deleting it */
err = i2s_channel_disable(this->rx_handle_); err = i2s_channel_disable(this->rx_handle_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Error stopping: %s", esp_err_to_name(err));
} }
/* If the handle is not needed any more, delete it to release the channel resources */ /* If the handle is not needed any more, delete it to release the channel resources */
err = i2s_del_channel(this->rx_handle_); err = i2s_del_channel(this->rx_handle_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "Error deleting I2S channel - it may not have started: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Error deleting channel: %s", esp_err_to_name(err));
} }
this->rx_handle_ = nullptr; this->rx_handle_ = nullptr;
} }
@@ -403,7 +412,7 @@ size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_w
// Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call // Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call
if (!this->status_has_warning()) { if (!this->status_has_warning()) {
// Avoid spamming the logs with the error message if its repeated // Avoid spamming the logs with the error message if its repeated
ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Read error: %s", esp_err_to_name(err));
} }
this->status_set_warning(); this->status_set_warning();
return 0; return 0;
@@ -431,19 +440,19 @@ void I2SAudioMicrophone::loop() {
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_); uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) { if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) {
ESP_LOGD(TAG, "Task started, attempting to allocate buffer"); ESP_LOGV(TAG, "Task started, attempting to allocate buffer");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING); xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
} }
if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) { if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) {
ESP_LOGD(TAG, "Task is running and reading data"); ESP_LOGV(TAG, "Task is running and reading data");
xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING); xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
this->state_ = microphone::STATE_RUNNING; this->state_ = microphone::STATE_RUNNING;
} }
if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) { if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) {
ESP_LOGD(TAG, "Task finished, freeing resources and uninstalling I2S driver"); ESP_LOGV(TAG, "Task finished, freeing resources and uninstalling driver");
vTaskDelete(this->task_handle_); vTaskDelete(this->task_handle_);
this->task_handle_ = nullptr; this->task_handle_ = nullptr;
@@ -473,7 +482,7 @@ void I2SAudioMicrophone::loop() {
} }
if (!this->start_driver_()) { if (!this->start_driver_()) {
this->status_momentary_error("I2S driver failed to start, unloading it and attempting again in 1 second", 1000); this->status_momentary_error("Driver failed to start; retrying in 1 second", 1000);
this->stop_driver_(); // Stop/frees whatever possibly started this->stop_driver_(); // Stop/frees whatever possibly started
break; break;
} }
@@ -483,7 +492,7 @@ void I2SAudioMicrophone::loop() {
&this->task_handle_); &this->task_handle_);
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {
this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000); this->status_momentary_error("Task failed to start, retrying in 1 second", 1000);
this->stop_driver_(); // Stops the driver to return the lock; will be reloaded in next attempt this->stop_driver_(); // Stops the driver to return the lock; will be reloaded in next attempt
} }
} }

View File

@@ -18,6 +18,7 @@ namespace i2s_audio {
class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, public Component { class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, public Component {
public: public:
void setup() override; void setup() override;
void dump_config() override;
void start() override; void start() override;
void stop() override; void stop() override;

View File

@@ -110,29 +110,48 @@ void I2SAudioSpeaker::setup() {
} }
} }
void I2SAudioSpeaker::dump_config() {
ESP_LOGCONFIG(TAG,
"Speaker:\n"
" Pin: %d\n"
" Buffer duration: %" PRIu32,
static_cast<int8_t>(this->dout_pin_), this->buffer_duration_ms_);
if (this->timeout_.has_value()) {
ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 " ms", this->timeout_.value());
}
#ifdef USE_I2S_LEGACY
#if SOC_I2S_SUPPORTS_DAC
ESP_LOGCONFIG(TAG, " Internal DAC mode: %d", static_cast<int8_t>(this->internal_dac_mode_));
#endif
ESP_LOGCONFIG(TAG, " Communication format: %d", static_cast<int8_t>(this->i2s_comm_fmt_));
#else
ESP_LOGCONFIG(TAG, " Communication format: %s", this->i2s_comm_fmt_.c_str());
#endif
}
void I2SAudioSpeaker::loop() { void I2SAudioSpeaker::loop() {
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_); uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
if (event_group_bits & SpeakerEventGroupBits::STATE_STARTING) { if (event_group_bits & SpeakerEventGroupBits::STATE_STARTING) {
ESP_LOGD(TAG, "Starting Speaker"); ESP_LOGD(TAG, "Starting");
this->state_ = speaker::STATE_STARTING; this->state_ = speaker::STATE_STARTING;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STARTING); xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STARTING);
} }
if (event_group_bits & SpeakerEventGroupBits::STATE_RUNNING) { if (event_group_bits & SpeakerEventGroupBits::STATE_RUNNING) {
ESP_LOGD(TAG, "Started Speaker"); ESP_LOGD(TAG, "Started");
this->state_ = speaker::STATE_RUNNING; this->state_ = speaker::STATE_RUNNING;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_RUNNING); xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
this->status_clear_warning(); this->status_clear_warning();
this->status_clear_error(); this->status_clear_error();
} }
if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPING) { if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPING) {
ESP_LOGD(TAG, "Stopping Speaker"); ESP_LOGD(TAG, "Stopping");
this->state_ = speaker::STATE_STOPPING; this->state_ = speaker::STATE_STOPPING;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPING); xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
} }
if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPED) { if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPED) {
if (!this->task_created_) { if (!this->task_created_) {
ESP_LOGD(TAG, "Stopped Speaker"); ESP_LOGD(TAG, "Stopped");
this->state_ = speaker::STATE_STOPPED; this->state_ = speaker::STATE_STOPPED;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ALL_BITS); xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ALL_BITS);
this->speaker_task_handle_ = nullptr; this->speaker_task_handle_ = nullptr;
@@ -140,20 +159,19 @@ void I2SAudioSpeaker::loop() {
} }
if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) { if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) {
this->status_set_error("Failed to start speaker task"); this->status_set_error("Failed to start task");
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START);
} }
if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); ESP_LOGW(TAG, "Writing failed: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
this->status_set_warning(); this->status_set_warning();
} }
if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) { if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); this->status_set_error("Failed to adjust bus to match incoming audio");
ESP_LOGE(TAG, ESP_LOGE(TAG, "Incompatible audio format: sample rate = %" PRIu32 ", channels = %u, bits per sample = %u",
"Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8,
this->audio_stream_info_.get_sample_rate(), this->audio_stream_info_.get_channels(), this->audio_stream_info_.get_sample_rate(), this->audio_stream_info_.get_channels(),
this->audio_stream_info_.get_bits_per_sample()); this->audio_stream_info_.get_bits_per_sample());
} }
@@ -202,7 +220,7 @@ void I2SAudioSpeaker::set_mute_state(bool mute_state) {
size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) { size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup"); ESP_LOGE(TAG, "Setup failed; cannot play audio");
return 0; return 0;
} }
if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) { if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {

View File

@@ -24,6 +24,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; }
void setup() override; void setup() override;
void dump_config() override;
void loop() override; void loop() override;
void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; } void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID from esphome.const import CONF_ID
CODEOWNERS = ["@p1ngb4ck"] CODEOWNERS = ["@p1ngb4ck"]

View File

@@ -1,8 +1,9 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output from esphome.components import output
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_INITIAL_VALUE from esphome.const import CONF_CHANNEL, CONF_ID, CONF_INITIAL_VALUE
from .. import Mcp4461Component, CONF_MCP4461_ID, mcp4461_ns
from .. import CONF_MCP4461_ID, Mcp4461Component, mcp4461_ns
DEPENDENCIES = ["mcp4461"] DEPENDENCIES = ["mcp4461"]

View File

@@ -3,6 +3,8 @@ import esphome.codegen as cg
from esphome.components import i2c, sensirion_common, sensor from esphome.components import i2c, sensirion_common, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_AMBIENT_PRESSURE_COMPENSATION,
CONF_AUTOMATIC_SELF_CALIBRATION,
CONF_CO2, CONF_CO2,
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_ID, CONF_ID,
@@ -18,8 +20,6 @@ from esphome.const import (
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_MILLION,
UNIT_PERCENT, UNIT_PERCENT,
CONF_AUTOMATIC_SELF_CALIBRATION,
CONF_AMBIENT_PRESSURE_COMPENSATION,
) )
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]

View File

@@ -4,9 +4,13 @@ import esphome.codegen as cg
from esphome.components import i2c, sensirion_common, sensor from esphome.components import i2c, sensirion_common, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_AMBIENT_PRESSURE_COMPENSATION,
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE,
CONF_AUTOMATIC_SELF_CALIBRATION,
CONF_CO2, CONF_CO2,
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_ID, CONF_ID,
CONF_MEASUREMENT_MODE,
CONF_TEMPERATURE, CONF_TEMPERATURE,
CONF_TEMPERATURE_OFFSET, CONF_TEMPERATURE_OFFSET,
CONF_VALUE, CONF_VALUE,
@@ -20,10 +24,6 @@ from esphome.const import (
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_MILLION,
UNIT_PERCENT, UNIT_PERCENT,
CONF_AUTOMATIC_SELF_CALIBRATION,
CONF_AMBIENT_PRESSURE_COMPENSATION,
CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE,
CONF_MEASUREMENT_MODE,
) )
CODEOWNERS = ["@sjtrny", "@martgras"] CODEOWNERS = ["@sjtrny", "@martgras"]

View File

@@ -2,10 +2,10 @@ import esphome.codegen as cg
from esphome.components import i2c, sensirion_common, sensor from esphome.components import i2c, sensirion_common, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_MEASUREMENT_MODE,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_HECTOPASCAL, UNIT_HECTOPASCAL,
CONF_MEASUREMENT_MODE,
) )
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]