mirror of
https://github.com/esphome/esphome.git
synced 2026-06-01 01:19:45 +08:00
[bthome] Refactor to use FixedVector and add features
Major refactoring to address memory efficiency, advertisement cycling,
and immediate advertising support:
**Use FixedVector instead of std::vector:**
- Replace std::vector with FixedVector for measurements storage
- Initialize with exact sizes determined from configuration
- Eliminates STL reallocation overhead and reduces flash usage
- Uses runtime-sized FixedVector allocated once in setup()
**Config key changes:**
- Use `sensors` and `binary_sensors` (plurals) for consistency
- Matches ESPHome conventions for sensor arrays
**Advertisement size management and cycling:**
- Calculate max advertisement size (31 bytes total, minus overhead)
- Split measurements across multiple packets if they don't fit
- Automatically cycle through packets on each advertising interval
- Ensures all sensors get advertised even with many measurements
- Overhead: 8 bytes unencrypted, 16 bytes encrypted
**Immediate advertising support:**
- Add `advertise_immediately` option for sensors/binary_sensors
- When enabled, triggers immediate advertisement on state change
- Interrupts normal advertising cycle to send only that sensor
- Resumes normal cycle after immediate advertisement
- Perfect for motion sensors, door sensors, or critical alerts
**Implementation details:**
- Refactored encode functions to use raw pointers and calculate sizes
- Build multiple advertisement packets as needed
- Track current packet index for cycling
- Handle immediate advertising with separate packet building path
- Proper encryption handling with per-packet counters
Example configuration:
```yaml
bthome:
sensors:
- type: temperature
id: room_temp
- type: humidity
id: room_humidity
binary_sensors:
- type: motion
id: pir_sensor
advertise_immediately: true # Instant notification
- type: door
id: front_door
advertise_immediately: true
```
This commit is contained in:
@@ -4,8 +4,9 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components.esp32_ble import CONF_BLE_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BINARY_SENSOR,
|
||||
CONF_BINARY_SENSORS,
|
||||
CONF_ID,
|
||||
CONF_SENSORS,
|
||||
CONF_TX_POWER,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_BATTERY_CHARGING,
|
||||
@@ -52,11 +53,11 @@ BTHome = bthome_ns.class_(
|
||||
)
|
||||
|
||||
# Configuration constants
|
||||
CONF_MEASUREMENT = "measurement"
|
||||
CONF_ENCRYPTION_KEY = "encryption_key"
|
||||
CONF_MIN_INTERVAL = "min_interval"
|
||||
CONF_MAX_INTERVAL = "max_interval"
|
||||
CONF_SENSOR_TYPE = "type"
|
||||
CONF_ADVERTISE_IMMEDIATELY = "advertise_immediately"
|
||||
|
||||
# BTHome object IDs for sensors (mapping from device class to BTHome object ID)
|
||||
SENSOR_DEVICE_CLASS_TO_OBJECT_ID = {
|
||||
@@ -140,23 +141,25 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.decibel, cv.enum(esp32_ble.TX_POWER_LEVELS, int=True)
|
||||
),
|
||||
cv.Optional(CONF_ENCRYPTION_KEY): validate_encryption_key,
|
||||
cv.Optional(CONF_MEASUREMENT): cv.ensure_list(
|
||||
cv.Optional(CONF_SENSORS): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SENSOR_TYPE): cv.one_of(
|
||||
*SENSOR_DEVICE_CLASS_TO_OBJECT_ID.keys(), lower=True
|
||||
),
|
||||
cv.Required(CONF_ID): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_ADVERTISE_IMMEDIATELY, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_BINARY_SENSOR): cv.ensure_list(
|
||||
cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SENSOR_TYPE): cv.one_of(
|
||||
*BINARY_SENSOR_DEVICE_CLASS_TO_OBJECT_ID.keys(), lower=True
|
||||
),
|
||||
cv.Required(CONF_ID): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ADVERTISE_IMMEDIATELY, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
),
|
||||
@@ -179,26 +182,40 @@ async def to_code(config):
|
||||
cg.add(var.set_max_interval(config[CONF_MAX_INTERVAL]))
|
||||
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
|
||||
|
||||
# Initialize FixedVectors with proper sizes
|
||||
num_sensors = len(config.get(CONF_SENSORS, []))
|
||||
num_binary_sensors = len(config.get(CONF_BINARY_SENSORS, []))
|
||||
max_packets = max(1, num_sensors + num_binary_sensors)
|
||||
|
||||
# Initialize the measurements and binary_measurements FixedVectors
|
||||
cg.add(cg.RawExpression(f"{var}->measurements_.init({num_sensors})"))
|
||||
cg.add(cg.RawExpression(f"{var}->binary_measurements_.init({num_binary_sensors})"))
|
||||
cg.add(cg.RawExpression(f"{var}->adv_packets_.init({max_packets})"))
|
||||
cg.add(cg.RawExpression(f"{var}->adv_packet_sizes_.init({max_packets})"))
|
||||
|
||||
if CONF_ENCRYPTION_KEY in config:
|
||||
key = config[CONF_ENCRYPTION_KEY]
|
||||
key_bytes = [int(key[i : i + 2], 16) for i in range(0, len(key), 2)]
|
||||
cg.add(var.set_encryption_key(key_bytes))
|
||||
key_bytes = [cg.RawExpression(f"0x{key[i:i+2]}") for i in range(0, len(key), 2)]
|
||||
key_array = cg.RawExpression(f"std::array<uint8_t, 16>{{{', '.join(str(b) for b in key_bytes)}}}")
|
||||
cg.add(var.set_encryption_key(key_array))
|
||||
|
||||
# Add sensor measurements
|
||||
if CONF_MEASUREMENT in config:
|
||||
for measurement in config[CONF_MEASUREMENT]:
|
||||
if CONF_SENSORS in config:
|
||||
for measurement in config[CONF_SENSORS]:
|
||||
sensor_type = measurement[CONF_SENSOR_TYPE]
|
||||
object_id = SENSOR_DEVICE_CLASS_TO_OBJECT_ID[sensor_type]
|
||||
sens = await cg.get_variable(measurement[CONF_ID])
|
||||
cg.add(var.add_measurement(sens, object_id))
|
||||
advertise_immediately = measurement[CONF_ADVERTISE_IMMEDIATELY]
|
||||
cg.add(var.add_measurement(sens, object_id, advertise_immediately))
|
||||
|
||||
# Add binary sensor measurements
|
||||
if CONF_BINARY_SENSOR in config:
|
||||
for measurement in config[CONF_BINARY_SENSOR]:
|
||||
if CONF_BINARY_SENSORS in config:
|
||||
for measurement in config[CONF_BINARY_SENSORS]:
|
||||
sensor_type = measurement[CONF_SENSOR_TYPE]
|
||||
object_id = BINARY_SENSOR_DEVICE_CLASS_TO_OBJECT_ID[sensor_type]
|
||||
sens = await cg.get_variable(measurement[CONF_ID])
|
||||
cg.add(var.add_binary_measurement(sens, object_id))
|
||||
advertise_immediately = measurement[CONF_ADVERTISE_IMMEDIATELY]
|
||||
cg.add(var.add_binary_measurement(sens, object_id, advertise_immediately))
|
||||
|
||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
#include "esphome/components/esp32_ble/ble.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -13,7 +14,6 @@
|
||||
#include <esp_gap_ble_api.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace bthome {
|
||||
@@ -23,11 +23,13 @@ using namespace esp32_ble;
|
||||
struct SensorMeasurement {
|
||||
sensor::Sensor *sensor;
|
||||
uint8_t object_id;
|
||||
bool advertise_immediately;
|
||||
};
|
||||
|
||||
struct BinarySensorMeasurement {
|
||||
binary_sensor::BinarySensor *sensor;
|
||||
uint8_t object_id;
|
||||
bool advertise_immediately;
|
||||
};
|
||||
|
||||
class BTHome : public Component, public GAPEventHandler, public Parented<ESP32BLE> {
|
||||
@@ -41,21 +43,22 @@ class BTHome : public Component, public GAPEventHandler, public Parented<ESP32BL
|
||||
void set_max_interval(uint16_t val) { this->max_interval_ = val; }
|
||||
void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; }
|
||||
|
||||
void set_encryption_key(const std::vector<uint8_t> &key);
|
||||
void add_measurement(sensor::Sensor *sensor, uint8_t object_id);
|
||||
void add_binary_measurement(binary_sensor::BinarySensor *sensor, uint8_t object_id);
|
||||
void set_encryption_key(const std::array<uint8_t, 16> &key);
|
||||
void add_measurement(sensor::Sensor *sensor, uint8_t object_id, bool advertise_immediately);
|
||||
void add_binary_measurement(binary_sensor::BinarySensor *sensor, uint8_t object_id, bool advertise_immediately);
|
||||
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
|
||||
protected:
|
||||
void on_advertise_();
|
||||
void build_advertisement_data_();
|
||||
void encode_measurement_(std::vector<uint8_t> &data, uint8_t object_id, float value);
|
||||
void encode_binary_measurement_(std::vector<uint8_t> &data, uint8_t object_id, bool value);
|
||||
bool encrypt_payload_(const std::vector<uint8_t> &plaintext, std::vector<uint8_t> &ciphertext);
|
||||
void build_advertisement_packets_();
|
||||
size_t encode_measurement_(uint8_t *data, size_t max_len, uint8_t object_id, float value);
|
||||
size_t encode_binary_measurement_(uint8_t *data, size_t max_len, uint8_t object_id, bool value);
|
||||
bool encrypt_payload_(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t *ciphertext_len);
|
||||
void trigger_immediate_advertising_(uint8_t measurement_index, bool is_binary);
|
||||
|
||||
std::vector<SensorMeasurement> measurements_;
|
||||
std::vector<BinarySensorMeasurement> binary_measurements_;
|
||||
FixedVector<SensorMeasurement> measurements_;
|
||||
FixedVector<BinarySensorMeasurement> binary_measurements_;
|
||||
|
||||
uint16_t min_interval_{};
|
||||
uint16_t max_interval_{};
|
||||
@@ -68,9 +71,14 @@ class BTHome : public Component, public GAPEventHandler, public Parented<ESP32BL
|
||||
std::array<uint8_t, 16> encryption_key_{};
|
||||
uint32_t counter_{0};
|
||||
|
||||
// Cached advertisement data
|
||||
std::vector<uint8_t> adv_data_;
|
||||
// Advertisement cycling support
|
||||
FixedVector<std::unique_ptr<uint8_t[]>> adv_packets_; // Multiple advertisement packets
|
||||
FixedVector<uint16_t> adv_packet_sizes_; // Size of each packet
|
||||
uint8_t current_packet_index_{0};
|
||||
bool data_changed_{true};
|
||||
bool immediate_advertising_pending_{false};
|
||||
uint8_t immediate_adv_measurement_index_{0};
|
||||
bool immediate_adv_is_binary_{false};
|
||||
};
|
||||
|
||||
} // namespace bthome
|
||||
|
||||
@@ -18,15 +18,16 @@ binary_sensor:
|
||||
name: "Test Door"
|
||||
|
||||
bthome:
|
||||
measurement:
|
||||
sensors:
|
||||
- type: temperature
|
||||
id: test_temperature
|
||||
- type: humidity
|
||||
id: test_humidity
|
||||
- type: battery
|
||||
id: test_battery
|
||||
binary_sensor:
|
||||
binary_sensors:
|
||||
- type: motion
|
||||
id: test_motion
|
||||
advertise_immediately: true
|
||||
- type: door
|
||||
id: test_door
|
||||
|
||||
@@ -10,9 +10,10 @@ binary_sensor:
|
||||
|
||||
bthome:
|
||||
encryption_key: "231d39c1d7cc1ab1aee224cd096db932"
|
||||
measurement:
|
||||
sensors:
|
||||
- type: temperature
|
||||
id: test_temperature
|
||||
binary_sensor:
|
||||
advertise_immediately: true
|
||||
binary_sensors:
|
||||
- type: motion
|
||||
id: test_motion
|
||||
|
||||
Reference in New Issue
Block a user