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
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# 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
|
# 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
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
@ -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");
|
||||||
@ -326,32 +324,77 @@ void ESP32BLE::loop() {
|
|||||||
}
|
}
|
||||||
case BLEEvent::GAP: {
|
case BLEEvent::GAP: {
|
||||||
esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event;
|
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) {
|
||||||
// Use the new scan event handler - no memcpy!
|
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||||
for (auto *scan_handler : this->gap_scan_event_handlers_) {
|
// Use the new scan event handler - no memcpy!
|
||||||
scan_handler->gap_scan_event_handler(ble_event->scan_result());
|
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 ||
|
break;
|
||||||
gap_event == ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT) {
|
|
||||||
// All three scan complete events have the same structure with just status
|
// Scan complete events
|
||||||
// The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
|
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||||
// This is verified at compile-time by static_assert checks in ble_event.h
|
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||||
// The struct already contains our copy of the status (copied in BLEEvent constructor)
|
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
// All three scan complete events have the same structure with just status
|
||||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
// The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
|
||||||
gap_handler->gap_event_handler(
|
// This is verified at compile-time by static_assert checks in ble_event.h
|
||||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete));
|
// The struct already contains our copy of the status (copied in BLEEvent constructor)
|
||||||
}
|
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.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;
|
break;
|
||||||
}
|
}
|
||||||
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 +402,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 because we're the only producer and the pool ensures we never exceed queue size
|
||||||
// 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 *);
|
||||||
@ -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) {
|
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||||
switch (event) {
|
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_RESULT_EVT:
|
||||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||||
case ESP_GAP_BLE_SCAN_STOP_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);
|
enqueue_ble_event(event, param);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -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_{};
|
||||||
|
@ -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");
|
"ESP-IDF scan_stop_cmpl structure has unexpected size");
|
||||||
|
|
||||||
// Verify the status field is at offset 0 (first member)
|
// Verify the status field is at offset 0 (first member)
|
||||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) ==
|
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) == 0,
|
||||||
offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl),
|
|
||||||
"status must be first member of scan_param_cmpl");
|
"status must be first member of scan_param_cmpl");
|
||||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) ==
|
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) == 0,
|
||||||
offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl),
|
|
||||||
"status must be first member of scan_start_cmpl");
|
"status must be first member of scan_start_cmpl");
|
||||||
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) ==
|
static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) == 0,
|
||||||
offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl),
|
|
||||||
"status must be first member of scan_stop_cmpl");
|
"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().
|
// 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.
|
// This class stores each event with minimal memory usage.
|
||||||
// GAP events (99% of traffic) don't have the vector overhead.
|
// 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
|
// - 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
|
// the data remains valid even after the BLE callback returns. The original
|
||||||
// param pointer from ESP-IDF is only valid during the callback.
|
// 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 {
|
class BLEEvent {
|
||||||
public:
|
public:
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
@ -60,126 +96,86 @@ class BLEEvent {
|
|||||||
GATTS,
|
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
|
// 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
|
// 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) {
|
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
|
// 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) {
|
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
|
||||||
@ -191,12 +187,21 @@ class BLEEvent {
|
|||||||
struct gap_event {
|
struct gap_event {
|
||||||
esp_gap_ble_cb_event_t gap_event;
|
esp_gap_ble_cb_event_t gap_event;
|
||||||
union {
|
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
|
// This matches ESP-IDF's scan complete event structures
|
||||||
// All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout
|
// All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout
|
||||||
struct {
|
// Used by: esp32_ble_tracker
|
||||||
esp_bt_status_t status;
|
StatusOnlyData scan_complete; // 1 byte
|
||||||
} 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
|
} 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; }
|
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; }
|
||||||
|
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)
|
// BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding)
|
||||||
|
|
||||||
} // namespace esp32_ble
|
} // 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 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
|
||||||
|
@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
|
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++)
|
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
|
||||||
this->address_[i] = scan_result.bda[i];
|
this->address_[i] = scan_result.bda[i];
|
||||||
this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);
|
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_; }
|
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;
|
bool resolve_irk(const uint8_t *irk) const;
|
||||||
|
|
||||||
optional<ESPBLEiBeacon> get_ibeacon() const {
|
optional<ESPBLEiBeacon> get_ibeacon() const {
|
||||||
@ -111,6 +114,7 @@ class ESPBTDevice {
|
|||||||
std::vector<ESPBTUUID> service_uuids_{};
|
std::vector<ESPBTUUID> service_uuids_{};
|
||||||
std::vector<ServiceData> manufacturer_datas_{};
|
std::vector<ServiceData> manufacturer_datas_{};
|
||||||
std::vector<ServiceData> service_datas_{};
|
std::vector<ServiceData> service_datas_{};
|
||||||
|
const BLEScanResult *scan_result_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ESP32BLETracker;
|
class ESP32BLETracker;
|
||||||
|
@ -33,6 +33,7 @@ bool Nextion::send_command_(const std::string &command) {
|
|||||||
|
|
||||||
#ifdef USE_NEXTION_COMMAND_SPACING
|
#ifdef USE_NEXTION_COMMAND_SPACING
|
||||||
if (!this->ignore_is_setup_ && !this->command_pacer_.can_send()) {
|
if (!this->ignore_is_setup_ && !this->command_pacer_.can_send()) {
|
||||||
|
ESP_LOGN(TAG, "Command spacing: delaying command '%s'", command.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif // USE_NEXTION_COMMAND_SPACING
|
#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};
|
const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
|
||||||
this->write_array(to_send, sizeof(to_send));
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,12 +374,6 @@ void Nextion::process_nextion_commands_() {
|
|||||||
size_t commands_processed = 0;
|
size_t commands_processed = 0;
|
||||||
#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
|
#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;
|
size_t to_process_length = 0;
|
||||||
std::string to_process;
|
std::string to_process;
|
||||||
|
|
||||||
@ -430,6 +421,7 @@ void Nextion::process_nextion_commands_() {
|
|||||||
}
|
}
|
||||||
#ifdef USE_NEXTION_COMMAND_SPACING
|
#ifdef USE_NEXTION_COMMAND_SPACING
|
||||||
this->command_pacer_.mark_sent(); // Here is where we should mark the command as sent
|
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
|
#endif
|
||||||
break;
|
break;
|
||||||
case 0x02: // invalid Component ID or name was used
|
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_PANID", config[CONF_PAN_ID])
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL])
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL])
|
||||||
add_idf_sdkconfig_option(
|
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):
|
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:
|
if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
|
||||||
add_idf_sdkconfig_option(
|
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:
|
if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None:
|
||||||
add_idf_sdkconfig_option(
|
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:
|
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 CONF_FORCE_DATASET in config:
|
||||||
if config[CONF_FORCE_DATASET]:
|
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_EXT_PAN_ID): cv.hex_int,
|
||||||
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
|
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
|
||||||
cv.Optional(CONF_PSKC): cv.hex_int,
|
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
|
// Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this
|
||||||
// component
|
// component
|
||||||
this->mdns_services_ = this->mdns_->get_services();
|
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_) {
|
for (const auto &service : this->mdns_services_) {
|
||||||
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
|
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
@ -185,11 +185,11 @@ void OpenThreadSrpComponent::setup() {
|
|||||||
if (error != OT_ERROR_NONE) {
|
if (error != OT_ERROR_NONE) {
|
||||||
ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
|
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);
|
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) {
|
void *OpenThreadSrpComponent::pool_alloc_(size_t size) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
|
# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
|
||||||
import binascii
|
import binascii
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
from esphome.const import CONF_CHANNEL
|
from esphome.const import CONF_CHANNEL
|
||||||
|
|
||||||
@ -37,6 +38,12 @@ def parse_tlv(tlv) -> dict:
|
|||||||
if tag in TLV_TYPES:
|
if tag in TLV_TYPES:
|
||||||
if tag == 3:
|
if tag == 3:
|
||||||
output[TLV_TYPES[tag]] = val.decode("utf-8")
|
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:
|
else:
|
||||||
output[TLV_TYPES[tag]] = int.from_bytes(val)
|
output[TLV_TYPES[tag]] = int.from_bytes(val)
|
||||||
return output
|
return output
|
||||||
|
@ -3,7 +3,15 @@
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
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 logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -1176,6 +1184,14 @@ def ipv4address(value):
|
|||||||
return address
|
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):
|
def ipv4address_multi_broadcast(value):
|
||||||
address = ipv4address(value)
|
address = ipv4address(value)
|
||||||
if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
|
if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
|
||||||
@ -1193,6 +1209,33 @@ def ipaddress(value):
|
|||||||
return address
|
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):
|
def _valid_topic(value):
|
||||||
"""Validate that this is a valid topic name/filter."""
|
"""Validate that this is a valid topic name/filter."""
|
||||||
if value is None: # Used to disable publishing and subscribing
|
if value is None: # Used to disable publishing and subscribing
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2025.6.0"
|
__version__ = "2025.6.1"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -5,7 +5,7 @@ import fnmatch
|
|||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
from io import BytesIO, TextIOBase, TextIOWrapper
|
from io import BytesIO, TextIOBase, TextIOWrapper
|
||||||
from ipaddress import _BaseAddress
|
from ipaddress import _BaseAddress, _BaseNetwork
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import os
|
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(int, ESPHomeDumper.represent_int)
|
||||||
ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float)
|
ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float)
|
||||||
ESPHomeDumper.add_multi_representer(_BaseAddress, ESPHomeDumper.represent_stringify)
|
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(MACAddress, ESPHomeDumper.represent_stringify)
|
||||||
ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify)
|
ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify)
|
||||||
ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda)
|
ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda)
|
||||||
|
@ -8,4 +8,6 @@ openthread:
|
|||||||
pan_id: 0x8f28
|
pan_id: 0x8f28
|
||||||
ext_pan_id: 0xd63e8e3e495ebbc3
|
ext_pan_id: 0xd63e8e3e495ebbc3
|
||||||
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
|
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
|
||||||
|
mesh_local_prefix: fd53:145f:ed22:ad81::/64
|
||||||
force_dataset: true
|
force_dataset: true
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user