mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
commit
fa34adbf6c
2
Doxyfile
2
Doxyfile
@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.6.0
|
||||
PROJECT_NUMBER = 2025.6.1
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
@ -1,6 +1,7 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "ble.h"
|
||||
#include "ble_event_pool.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
@ -23,9 +24,6 @@ namespace 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() {
|
||||
global_ble = this;
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
@ -326,14 +324,18 @@ void ESP32BLE::loop() {
|
||||
}
|
||||
case BLEEvent::GAP: {
|
||||
esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
|
||||
if (gap_event == ESP_GAP_BLE_SCAN_RESULT_EVT) {
|
||||
switch (gap_event) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
// Use the new scan event handler - no memcpy!
|
||||
for (auto *scan_handler : this->gap_scan_event_handlers_) {
|
||||
scan_handler->gap_scan_event_handler(ble_event->scan_result());
|
||||
}
|
||||
} else if (gap_event == ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT ||
|
||||
gap_event == ESP_GAP_BLE_SCAN_START_COMPLETE_EVT ||
|
||||
gap_event == ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT) {
|
||||
break;
|
||||
|
||||
// Scan complete events
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
// All three scan complete events have the same structure with just status
|
||||
// The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
|
||||
// This is verified at compile-time by static_assert checks in ble_event.h
|
||||
@ -343,15 +345,56 @@ void ESP32BLE::loop() {
|
||||
gap_handler->gap_event_handler(
|
||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete));
|
||||
}
|
||||
break;
|
||||
|
||||
// Advertising complete events
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
// All advertising complete events have the same structure with just status
|
||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(
|
||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete));
|
||||
}
|
||||
break;
|
||||
|
||||
// RSSI complete event
|
||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(
|
||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete));
|
||||
}
|
||||
break;
|
||||
|
||||
// Security events
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT:
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(
|
||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown/unhandled event
|
||||
ESP_LOGW(TAG, "Unhandled GAP event type in loop: %d", gap_event);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Destructor will clean up external allocations for GATTC/GATTS
|
||||
ble_event->~BLEEvent();
|
||||
EVENT_ALLOCATOR.deallocate(ble_event, 1);
|
||||
// Return the event to the pool
|
||||
this->ble_event_pool_.release(ble_event);
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
if (this->advertising_ != nullptr) {
|
||||
@ -359,37 +402,41 @@ void ESP32BLE::loop() {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
// Check if queue is full before allocating
|
||||
if (global_ble->ble_events_.full()) {
|
||||
// Queue is full, drop the event
|
||||
// Allocate an event from the pool
|
||||
BLEEvent *event = global_ble->ble_event_pool_.allocate();
|
||||
if (event == nullptr) {
|
||||
// No events available - queue is full or we're out of memory
|
||||
global_ble->ble_events_.increment_dropped_count();
|
||||
return;
|
||||
}
|
||||
|
||||
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||
if (new_event == nullptr) {
|
||||
// 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...);
|
||||
// Load new event data (replaces previous event)
|
||||
load_ble_event(event, args...);
|
||||
|
||||
// Push the event - since we're the only producer and we checked full() above,
|
||||
// this should always succeed unless we have a bug
|
||||
if (!global_ble->ble_events_.push(new_event)) {
|
||||
// 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)
|
||||
// Push the event to the queue
|
||||
global_ble->ble_events_.push(event);
|
||||
// Push always succeeds because we're the only producer and the pool ensures we never exceed queue size
|
||||
}
|
||||
|
||||
// Explicit template instantiations for the friend function
|
||||
template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *);
|
||||
@ -398,11 +445,26 @@ template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gat
|
||||
|
||||
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
// Only queue the 4 GAP events we actually handle
|
||||
// Queue GAP events that components need to handle
|
||||
// Scanning events - used by esp32_ble_tracker
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
// Advertising events - used by esp32_ble_beacon and esp32_ble server
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
// Connection events - used by ble_client
|
||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||
// Security events - used by ble_client and bluetooth_proxy
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT:
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
enqueue_ble_event(event, param);
|
||||
return;
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "ble_event.h"
|
||||
#include "ble_event_pool.h"
|
||||
#include "queue.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
@ -148,6 +149,7 @@ class ESP32BLE : public Component {
|
||||
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
|
||||
|
||||
LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
|
||||
BLEEventPool<MAX_BLE_QUEUE_SIZE> ble_event_pool_;
|
||||
BLEAdvertising *advertising_{};
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
||||
uint32_t advertising_cycle_time_{};
|
||||
|
@ -24,16 +24,45 @@ static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param) == si
|
||||
"ESP-IDF scan_stop_cmpl structure has unexpected size");
|
||||
|
||||
// Verify the status field is at offset 0 (first member)
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) ==
|
||||
offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl),
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) == 0,
|
||||
"status must be first member of scan_param_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) ==
|
||||
offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl),
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) == 0,
|
||||
"status must be first member of scan_start_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) ==
|
||||
offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl),
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) == 0,
|
||||
"status must be first member of scan_stop_cmpl");
|
||||
|
||||
// Compile-time verification for advertising complete events
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF adv_data_cmpl structure has unexpected size");
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_rsp_data_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF scan_rsp_data_cmpl structure has unexpected size");
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_raw_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF adv_data_raw_cmpl structure has unexpected size");
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_start_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF adv_start_cmpl structure has unexpected size");
|
||||
static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_stop_cmpl_evt_param) == sizeof(esp_bt_status_t),
|
||||
"ESP-IDF adv_stop_cmpl structure has unexpected size");
|
||||
|
||||
// Verify the status field is at offset 0 for advertising events
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_cmpl.status) == 0,
|
||||
"status must be first member of adv_data_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_rsp_data_cmpl.status) == 0,
|
||||
"status must be first member of scan_rsp_data_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_raw_cmpl.status) == 0,
|
||||
"status must be first member of adv_data_raw_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_start_cmpl.status) == 0,
|
||||
"status must be first member of adv_start_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, adv_stop_cmpl.status) == 0,
|
||||
"status must be first member of adv_stop_cmpl");
|
||||
|
||||
// Compile-time verification for RSSI complete event structure
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.status) == 0,
|
||||
"status must be first member of read_rssi_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(esp_bt_status_t),
|
||||
"rssi must immediately follow status in read_rssi_cmpl");
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t),
|
||||
"remote_addr must follow rssi in read_rssi_cmpl");
|
||||
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event with minimal memory usage.
|
||||
// GAP events (99% of traffic) don't have the vector overhead.
|
||||
@ -51,6 +80,13 @@ static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) ==
|
||||
// - GATTC/GATTS events: We heap-allocate and copy the entire param struct, ensuring
|
||||
// the data remains valid even after the BLE callback returns. The original
|
||||
// param pointer from ESP-IDF is only valid during the callback.
|
||||
//
|
||||
// CRITICAL DESIGN NOTE:
|
||||
// The heap allocations for GATTC/GATTS events are REQUIRED for memory safety.
|
||||
// DO NOT attempt to optimize by removing these allocations or storing pointers
|
||||
// to the original ESP-IDF data. The ESP-IDF callback data has a different lifetime
|
||||
// than our event processing, and accessing it after the callback returns would
|
||||
// result in use-after-free bugs and crashes.
|
||||
class BLEEvent {
|
||||
public:
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
@ -60,128 +96,88 @@ class BLEEvent {
|
||||
GATTS,
|
||||
};
|
||||
|
||||
// Type definitions for cleaner method signatures
|
||||
struct StatusOnlyData {
|
||||
esp_bt_status_t status;
|
||||
};
|
||||
|
||||
struct RSSICompleteData {
|
||||
esp_bt_status_t status;
|
||||
int8_t rssi;
|
||||
esp_bd_addr_t remote_addr;
|
||||
};
|
||||
|
||||
// Constructor for GAP events - no external allocations needed
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->type_ = GAP;
|
||||
this->event_.gap.gap_event = e;
|
||||
|
||||
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;
|
||||
}
|
||||
this->init_gap_data_(e, p);
|
||||
}
|
||||
|
||||
// Constructor for GATTC events - uses heap allocation
|
||||
// Creates a copy of the param struct since the original is only valid during the callback
|
||||
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
||||
// The param pointer from ESP-IDF is only valid during the callback execution.
|
||||
// Since BLE events are processed asynchronously in the main loop, we must create
|
||||
// our own copy to ensure the data remains valid until the event is processed.
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->type_ = GATTC;
|
||||
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;
|
||||
}
|
||||
this->init_gattc_data_(e, i, p);
|
||||
}
|
||||
|
||||
// Constructor for GATTS events - uses heap allocation
|
||||
// Creates a copy of the param struct since the original is only valid during the callback
|
||||
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
||||
// The param pointer from ESP-IDF is only valid during the callback execution.
|
||||
// Since BLE events are processed asynchronously in the main loop, we must create
|
||||
// our own copy to ensure the data remains valid until the event is processed.
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->type_ = GATTS;
|
||||
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;
|
||||
}
|
||||
this->init_gatts_data_(e, i, p);
|
||||
}
|
||||
|
||||
// Destructor to clean up heap allocations
|
||||
~BLEEvent() {
|
||||
switch (this->type_) {
|
||||
case GATTC:
|
||||
~BLEEvent() { this->cleanup_heap_data(); }
|
||||
|
||||
// Default constructor for pre-allocation in pool
|
||||
BLEEvent() : type_(GAP) {}
|
||||
|
||||
// Clean up any heap-allocated data
|
||||
void cleanup_heap_data() {
|
||||
if (this->type_ == GAP) {
|
||||
return;
|
||||
}
|
||||
if (this->type_ == GATTC) {
|
||||
delete this->event_.gattc.gattc_param;
|
||||
delete this->event_.gattc.data;
|
||||
break;
|
||||
case GATTS:
|
||||
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;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
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
|
||||
BLEEvent(const BLEEvent &) = delete;
|
||||
BLEEvent &operator=(const BLEEvent &) = delete;
|
||||
@ -191,12 +187,21 @@ class BLEEvent {
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
union {
|
||||
BLEScanResult scan_result; // 73 bytes
|
||||
BLEScanResult scan_result; // 73 bytes - Used by: esp32_ble_tracker
|
||||
// This matches ESP-IDF's scan complete event structures
|
||||
// All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout
|
||||
struct {
|
||||
esp_bt_status_t status;
|
||||
} scan_complete; // 1 byte
|
||||
// Used by: esp32_ble_tracker
|
||||
StatusOnlyData scan_complete; // 1 byte
|
||||
// Advertising complete events all have same structure
|
||||
// Used by: esp32_ble_beacon, esp32_ble server components
|
||||
// ADV_DATA_SET, SCAN_RSP_DATA_SET, ADV_DATA_RAW_SET, ADV_START, ADV_STOP
|
||||
StatusOnlyData adv_complete; // 1 byte
|
||||
// RSSI complete event
|
||||
// Used by: ble_client (ble_rssi_sensor component)
|
||||
RSSICompleteData read_rssi_complete; // 8 bytes
|
||||
// Security events - we store the full security union
|
||||
// Used by: ble_client (automation), bluetooth_proxy, esp32_ble_client
|
||||
esp_ble_sec_t security; // Variable size, but fits within scan_result size
|
||||
};
|
||||
} gap; // 80 bytes total
|
||||
|
||||
@ -224,8 +229,170 @@ class BLEEvent {
|
||||
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; }
|
||||
esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; }
|
||||
esp_bt_status_t adv_complete_status() const { return event_.gap.adv_complete.status; }
|
||||
const RSSICompleteData &read_rssi_complete() const { return event_.gap.read_rssi_complete; }
|
||||
const esp_ble_sec_t &security() const { return event_.gap.security; }
|
||||
|
||||
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;
|
||||
|
||||
// Advertising complete events - all have same structure with just status
|
||||
// Used by: esp32_ble_beacon, esp32_ble server components
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
this->event_.gap.adv_complete.status = p->adv_data_cmpl.status;
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
||||
this->event_.gap.adv_complete.status = p->scan_rsp_data_cmpl.status;
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: // Used by: esp32_ble_beacon
|
||||
this->event_.gap.adv_complete.status = p->adv_data_raw_cmpl.status;
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: // Used by: esp32_ble_beacon
|
||||
this->event_.gap.adv_complete.status = p->adv_start_cmpl.status;
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: // Used by: esp32_ble_beacon
|
||||
this->event_.gap.adv_complete.status = p->adv_stop_cmpl.status;
|
||||
break;
|
||||
|
||||
// RSSI complete event
|
||||
// Used by: ble_client (ble_rssi_sensor)
|
||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||
this->event_.gap.read_rssi_complete.status = p->read_rssi_cmpl.status;
|
||||
this->event_.gap.read_rssi_complete.rssi = p->read_rssi_cmpl.rssi;
|
||||
memcpy(this->event_.gap.read_rssi_complete.remote_addr, p->read_rssi_cmpl.remote_addr, sizeof(esp_bd_addr_t));
|
||||
break;
|
||||
|
||||
// Security events - copy the entire security union
|
||||
// Used by: ble_client, bluetooth_proxy, esp32_ble_client
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT: // Used by: bluetooth_proxy, esp32_ble_client
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT: // Used by: esp32_ble_client
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // Used by: ble_client automation
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT: // Used by: ble_client automation
|
||||
case ESP_GAP_BLE_NC_REQ_EVT: // Used by: ble_client automation
|
||||
memcpy(&this->event_.gap.security, &p->ble_security, sizeof(esp_ble_sec_t));
|
||||
break;
|
||||
|
||||
default:
|
||||
// We only store data for GAP events that components currently use
|
||||
// Unknown events still get queued and logged in ble.cpp:375 as
|
||||
// "Unhandled GAP event type in loop" - this helps identify new events
|
||||
// that components might need in the future
|
||||
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
|
||||
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
||||
// - The BLEEvent owns the allocated memory for its lifetime
|
||||
// - The data remains valid from the BLE callback context until processed in the main loop
|
||||
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
||||
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
// The param struct contains pointers (e.g., notify.value) that point to temporary buffers.
|
||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||
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
|
||||
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
||||
// - The BLEEvent owns the allocated memory for its lifetime
|
||||
// - The data remains valid from the BLE callback context until processed in the main loop
|
||||
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
||||
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
||||
|
||||
// Copy data for events that need it
|
||||
// The param struct contains pointers (e.g., write.value) that point to temporary buffers.
|
||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Verify the gap_event struct hasn't grown beyond expected size
|
||||
// The gap member in the union should be 80 bytes (including the gap_event enum)
|
||||
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes");
|
||||
|
||||
// Verify esp_ble_sec_t fits within our union
|
||||
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");
|
||||
|
||||
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
72
esphome/components/esp32_ble/ble_event_pool.h
Normal file
72
esphome/components/esp32_ble/ble_event_pool.h
Normal 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
|
@ -18,7 +18,7 @@
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
template<class T, size_t SIZE> class LockFreeQueue {
|
||||
template<class T, uint8_t SIZE> class LockFreeQueue {
|
||||
public:
|
||||
LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {}
|
||||
|
||||
@ -26,8 +26,8 @@ template<class T, size_t SIZE> class LockFreeQueue {
|
||||
if (element == nullptr)
|
||||
return false;
|
||||
|
||||
size_t current_tail = tail_.load(std::memory_order_relaxed);
|
||||
size_t next_tail = (current_tail + 1) % SIZE;
|
||||
uint8_t current_tail = tail_.load(std::memory_order_relaxed);
|
||||
uint8_t next_tail = (current_tail + 1) % SIZE;
|
||||
|
||||
if (next_tail == head_.load(std::memory_order_acquire)) {
|
||||
// Buffer full
|
||||
@ -41,7 +41,7 @@ template<class T, size_t SIZE> class LockFreeQueue {
|
||||
}
|
||||
|
||||
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)) {
|
||||
return nullptr; // Empty
|
||||
@ -53,27 +53,30 @@ template<class T, size_t SIZE> class LockFreeQueue {
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
size_t tail = tail_.load(std::memory_order_acquire);
|
||||
size_t head = head_.load(std::memory_order_acquire);
|
||||
uint8_t tail = tail_.load(std::memory_order_acquire);
|
||||
uint8_t head = head_.load(std::memory_order_acquire);
|
||||
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); }
|
||||
|
||||
bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected:
|
||||
T *buffer_[SIZE];
|
||||
std::atomic<size_t> head_;
|
||||
std::atomic<size_t> tail_;
|
||||
std::atomic<size_t> dropped_count_;
|
||||
// Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset)
|
||||
std::atomic<uint16_t> dropped_count_; // 65535 max - more than enough for drop tracking
|
||||
// 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
|
||||
|
@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
|
||||
}
|
||||
|
||||
void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
|
||||
this->scan_result_ = &scan_result;
|
||||
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
|
||||
this->address_[i] = scan_result.bda[i];
|
||||
this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);
|
||||
|
@ -85,6 +85,9 @@ class ESPBTDevice {
|
||||
|
||||
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
|
||||
|
||||
// Exposed through a function for use in lambdas
|
||||
const BLEScanResult &get_scan_result() const { return *scan_result_; }
|
||||
|
||||
bool resolve_irk(const uint8_t *irk) const;
|
||||
|
||||
optional<ESPBLEiBeacon> get_ibeacon() const {
|
||||
@ -111,6 +114,7 @@ class ESPBTDevice {
|
||||
std::vector<ESPBTUUID> service_uuids_{};
|
||||
std::vector<ServiceData> manufacturer_datas_{};
|
||||
std::vector<ServiceData> service_datas_{};
|
||||
const BLEScanResult *scan_result_{nullptr};
|
||||
};
|
||||
|
||||
class ESP32BLETracker;
|
||||
|
@ -33,6 +33,7 @@ bool Nextion::send_command_(const std::string &command) {
|
||||
|
||||
#ifdef USE_NEXTION_COMMAND_SPACING
|
||||
if (!this->ignore_is_setup_ && !this->command_pacer_.can_send()) {
|
||||
ESP_LOGN(TAG, "Command spacing: delaying command '%s'", command.c_str());
|
||||
return false;
|
||||
}
|
||||
#endif // USE_NEXTION_COMMAND_SPACING
|
||||
@ -43,10 +44,6 @@ bool Nextion::send_command_(const std::string &command) {
|
||||
const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
|
||||
this->write_array(to_send, sizeof(to_send));
|
||||
|
||||
#ifdef USE_NEXTION_COMMAND_SPACING
|
||||
this->command_pacer_.mark_sent();
|
||||
#endif // USE_NEXTION_COMMAND_SPACING
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -377,12 +374,6 @@ void Nextion::process_nextion_commands_() {
|
||||
size_t commands_processed = 0;
|
||||
#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
|
||||
|
||||
#ifdef USE_NEXTION_COMMAND_SPACING
|
||||
if (!this->command_pacer_.can_send()) {
|
||||
return; // Will try again in next loop iteration
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t to_process_length = 0;
|
||||
std::string to_process;
|
||||
|
||||
@ -430,6 +421,7 @@ void Nextion::process_nextion_commands_() {
|
||||
}
|
||||
#ifdef USE_NEXTION_COMMAND_SPACING
|
||||
this->command_pacer_.mark_sent(); // Here is where we should mark the command as sent
|
||||
ESP_LOGN(TAG, "Command spacing: marked command sent at %u ms", millis());
|
||||
#endif
|
||||
break;
|
||||
case 0x02: // invalid Component ID or name was used
|
||||
|
@ -46,7 +46,7 @@ def set_sdkconfig_options(config):
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID])
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL])
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}"
|
||||
"CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}".lower()
|
||||
)
|
||||
|
||||
if network_name := config.get(CONF_NETWORK_NAME):
|
||||
@ -54,14 +54,14 @@ def set_sdkconfig_options(config):
|
||||
|
||||
if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}"
|
||||
"CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower()
|
||||
)
|
||||
if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix:X}"
|
||||
"CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower()
|
||||
)
|
||||
if (pskc := config.get(CONF_PSKC)) is not None:
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}")
|
||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower())
|
||||
|
||||
if CONF_FORCE_DATASET in config:
|
||||
if config[CONF_FORCE_DATASET]:
|
||||
@ -98,7 +98,7 @@ _CONNECTION_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
|
||||
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_PSKC): cv.hex_int,
|
||||
cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.hex_int,
|
||||
cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.ipv6network,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -137,7 +137,7 @@ void OpenThreadSrpComponent::setup() {
|
||||
// Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this
|
||||
// component
|
||||
this->mdns_services_ = this->mdns_->get_services();
|
||||
ESP_LOGW(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
|
||||
ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
|
||||
for (const auto &service : this->mdns_services_) {
|
||||
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
|
||||
if (!entry) {
|
||||
@ -185,11 +185,11 @@ void OpenThreadSrpComponent::setup() {
|
||||
if (error != OT_ERROR_NONE) {
|
||||
ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
|
||||
}
|
||||
ESP_LOGW(TAG, "Added service: %s", full_service.c_str());
|
||||
ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
|
||||
}
|
||||
|
||||
otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr);
|
||||
ESP_LOGW(TAG, "Finished SRP setup");
|
||||
ESP_LOGD(TAG, "Finished SRP setup");
|
||||
}
|
||||
|
||||
void *OpenThreadSrpComponent::pool_alloc_(size_t size) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
|
||||
import binascii
|
||||
import ipaddress
|
||||
|
||||
from esphome.const import CONF_CHANNEL
|
||||
|
||||
@ -37,6 +38,12 @@ def parse_tlv(tlv) -> dict:
|
||||
if tag in TLV_TYPES:
|
||||
if tag == 3:
|
||||
output[TLV_TYPES[tag]] = val.decode("utf-8")
|
||||
elif tag == 7:
|
||||
mesh_local_prefix = binascii.hexlify(val).decode("utf-8")
|
||||
mesh_local_prefix_str = f"{mesh_local_prefix}0000000000000000"
|
||||
ipv6_bytes = bytes.fromhex(mesh_local_prefix_str)
|
||||
ipv6_address = ipaddress.IPv6Address(ipv6_bytes)
|
||||
output[TLV_TYPES[tag]] = f"{ipv6_address}/64"
|
||||
else:
|
||||
output[TLV_TYPES[tag]] = int.from_bytes(val)
|
||||
return output
|
||||
|
@ -3,7 +3,15 @@
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from ipaddress import AddressValueError, IPv4Address, ip_address
|
||||
from ipaddress import (
|
||||
AddressValueError,
|
||||
IPv4Address,
|
||||
IPv4Network,
|
||||
IPv6Address,
|
||||
IPv6Network,
|
||||
ip_address,
|
||||
ip_network,
|
||||
)
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@ -1176,6 +1184,14 @@ def ipv4address(value):
|
||||
return address
|
||||
|
||||
|
||||
def ipv6address(value):
|
||||
try:
|
||||
address = IPv6Address(value)
|
||||
except AddressValueError as exc:
|
||||
raise Invalid(f"{value} is not a valid IPv6 address") from exc
|
||||
return address
|
||||
|
||||
|
||||
def ipv4address_multi_broadcast(value):
|
||||
address = ipv4address(value)
|
||||
if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
|
||||
@ -1193,6 +1209,33 @@ def ipaddress(value):
|
||||
return address
|
||||
|
||||
|
||||
def ipv4network(value):
|
||||
"""Validate that the value is a valid IPv4 network."""
|
||||
try:
|
||||
network = IPv4Network(value, strict=False)
|
||||
except ValueError as exc:
|
||||
raise Invalid(f"{value} is not a valid IPv4 network") from exc
|
||||
return network
|
||||
|
||||
|
||||
def ipv6network(value):
|
||||
"""Validate that the value is a valid IPv6 network."""
|
||||
try:
|
||||
network = IPv6Network(value, strict=False)
|
||||
except ValueError as exc:
|
||||
raise Invalid(f"{value} is not a valid IPv6 network") from exc
|
||||
return network
|
||||
|
||||
|
||||
def ipnetwork(value):
|
||||
"""Validate that the value is a valid IP network."""
|
||||
try:
|
||||
network = ip_network(value, strict=False)
|
||||
except ValueError as exc:
|
||||
raise Invalid(f"{value} is not a valid IP network") from exc
|
||||
return network
|
||||
|
||||
|
||||
def _valid_topic(value):
|
||||
"""Validate that this is a valid topic name/filter."""
|
||||
if value is None: # Used to disable publishing and subscribing
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2025.6.0"
|
||||
__version__ = "2025.6.1"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
@ -5,7 +5,7 @@ import fnmatch
|
||||
import functools
|
||||
import inspect
|
||||
from io import BytesIO, TextIOBase, TextIOWrapper
|
||||
from ipaddress import _BaseAddress
|
||||
from ipaddress import _BaseAddress, _BaseNetwork
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
@ -621,6 +621,7 @@ ESPHomeDumper.add_multi_representer(str, ESPHomeDumper.represent_stringify)
|
||||
ESPHomeDumper.add_multi_representer(int, ESPHomeDumper.represent_int)
|
||||
ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float)
|
||||
ESPHomeDumper.add_multi_representer(_BaseAddress, ESPHomeDumper.represent_stringify)
|
||||
ESPHomeDumper.add_multi_representer(_BaseNetwork, ESPHomeDumper.represent_stringify)
|
||||
ESPHomeDumper.add_multi_representer(MACAddress, ESPHomeDumper.represent_stringify)
|
||||
ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify)
|
||||
ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda)
|
||||
|
@ -8,4 +8,6 @@ openthread:
|
||||
pan_id: 0x8f28
|
||||
ext_pan_id: 0xd63e8e3e495ebbc3
|
||||
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
|
||||
mesh_local_prefix: fd53:145f:ed22:ad81::/64
|
||||
force_dataset: true
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user