mirror of
https://github.com/esphome/esphome.git
synced 2025-10-15 14:48:44 +00:00
Compare commits
54 Commits
release
...
esp32_ble_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5bdd6dac97 | ||
![]() |
fe07c34246 | ||
![]() |
c652aa375a | ||
![]() |
9fb254fdc2 | ||
![]() |
3df4dbd3a6 | ||
![]() |
6372099df3 | ||
![]() |
8d8fcfeda2 | ||
![]() |
0f356fcc79 | ||
![]() |
aec60d122b | ||
![]() |
c10f68ef0c | ||
![]() |
bcc424afed | ||
![]() |
be2c859df3 | ||
![]() |
59f728488e | ||
![]() |
04a0de556d | ||
![]() |
1f13d44c1b | ||
![]() |
9ebfa9aaa8 | ||
![]() |
6bc9ed0810 | ||
![]() |
9b6e8b4b41 | ||
![]() |
cad747c672 | ||
![]() |
660adccda3 | ||
![]() |
51fbc4f7a3 | ||
![]() |
2cc5e24b38 | ||
![]() |
3afa73b449 | ||
![]() |
dcf2697a2a | ||
![]() |
6a11700a6b | ||
![]() |
9bd9b043c8 | ||
![]() |
cb602c9b1a | ||
![]() |
b54beb357a | ||
![]() |
6abc2efd96 | ||
![]() |
be51093a7e | ||
![]() |
52219c4dcc | ||
![]() |
590cae13c0 | ||
![]() |
e15429b0f5 | ||
![]() |
b5cc668a45 | ||
![]() |
a1b0ae78e0 | ||
![]() |
fcc8a809e6 | ||
![]() |
48474c0f8c | ||
![]() |
9f9c95dd09 | ||
![]() |
a74fcbc8b6 | ||
![]() |
c8b898f9c5 | ||
![]() |
81bf2688b4 | ||
![]() |
87d2c9868f | ||
![]() |
5ca407e27c | ||
![]() |
5bbc2ab482 | ||
![]() |
309e8b4c92 | ||
![]() |
eee2987c99 | ||
![]() |
061e55f8c5 | ||
![]() |
56334b7832 | ||
![]() |
a4b7e0c700 | ||
![]() |
84ad7ee0e4 | ||
![]() |
d006008539 | ||
![]() |
6bb1e4c9c0 | ||
![]() |
82bdb08884 | ||
![]() |
b709ff84c3 |
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7
|
||||
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7
|
||||
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
|
||||
remove-stale-when-updated: true
|
||||
operations-per-run: 150
|
||||
operations-per-run: 400
|
||||
|
||||
# The 90 day stale policy for PRs
|
||||
# - PRs
|
||||
|
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.10.0
|
||||
PROJECT_NUMBER = 2025.11.0-dev
|
||||
|
||||
# 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
|
||||
|
@@ -304,6 +304,17 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
|
||||
|
||||
|
||||
def _is_framework_url(source: str) -> str:
|
||||
# platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink
|
||||
import urllib.parse
|
||||
|
||||
try:
|
||||
parsed = urllib.parse.urlparse(source)
|
||||
except ValueError:
|
||||
return False
|
||||
return bool(parsed.scheme)
|
||||
|
||||
|
||||
# NOTE: Keep this in mind when updating the recommended version:
|
||||
# * New framework historically have had some regressions, especially for WiFi.
|
||||
# The new version needs to be thoroughly validated before changing the
|
||||
@@ -387,7 +398,7 @@ def _check_versions(value):
|
||||
value[CONF_SOURCE] = value.get(
|
||||
CONF_SOURCE, _format_framework_arduino_version(version)
|
||||
)
|
||||
if value[CONF_SOURCE].startswith("http"):
|
||||
if _is_framework_url(value[CONF_SOURCE]):
|
||||
value[CONF_SOURCE] = (
|
||||
f"pioarduino/framework-arduinoespressif32@{value[CONF_SOURCE]}"
|
||||
)
|
||||
@@ -400,7 +411,7 @@ def _check_versions(value):
|
||||
CONF_SOURCE,
|
||||
_format_framework_espidf_version(version, value.get(CONF_RELEASE, None)),
|
||||
)
|
||||
if value[CONF_SOURCE].startswith("http"):
|
||||
if _is_framework_url(value[CONF_SOURCE]):
|
||||
value[CONF_SOURCE] = f"pioarduino/framework-espidf@{value[CONF_SOURCE]}"
|
||||
|
||||
if CONF_PLATFORM_VERSION not in value:
|
||||
|
@@ -213,8 +213,11 @@ bool ESP32BLE::ble_setup_() {
|
||||
if (this->name_.has_value()) {
|
||||
name = this->name_.value();
|
||||
if (App.is_name_add_mac_suffix_enabled()) {
|
||||
name += "-";
|
||||
name += get_mac_address().substr(6);
|
||||
// MAC address suffix length (last 6 characters of 12-char MAC address string)
|
||||
constexpr size_t mac_address_suffix_len = 6;
|
||||
const std::string mac_addr = get_mac_address();
|
||||
const char *mac_suffix_ptr = mac_addr.c_str() + mac_address_suffix_len;
|
||||
name = make_name_with_suffix(name, '-', mac_suffix_ptr, mac_address_suffix_len);
|
||||
}
|
||||
} else {
|
||||
name = App.get_name();
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from esphome import automation
|
||||
@@ -52,8 +53,28 @@ class BLEFeatures(StrEnum):
|
||||
ESP_BT_DEVICE = "ESP_BT_DEVICE"
|
||||
|
||||
|
||||
# Set to track which features are needed by components
|
||||
_required_features: set[BLEFeatures] = set()
|
||||
# Dataclass for registration counts
|
||||
@dataclass
|
||||
class RegistrationCounts:
|
||||
listeners: int = 0
|
||||
clients: int = 0
|
||||
|
||||
|
||||
# CORE.data keys for state management
|
||||
ESP32_BLE_TRACKER_REQUIRED_FEATURES_KEY = "esp32_ble_tracker_required_features"
|
||||
ESP32_BLE_TRACKER_REGISTRATION_COUNTS_KEY = "esp32_ble_tracker_registration_counts"
|
||||
|
||||
|
||||
def _get_required_features() -> set[BLEFeatures]:
|
||||
"""Get the set of required BLE features from CORE.data."""
|
||||
return CORE.data.setdefault(ESP32_BLE_TRACKER_REQUIRED_FEATURES_KEY, set())
|
||||
|
||||
|
||||
def _get_registration_counts() -> RegistrationCounts:
|
||||
"""Get the registration counts from CORE.data."""
|
||||
return CORE.data.setdefault(
|
||||
ESP32_BLE_TRACKER_REGISTRATION_COUNTS_KEY, RegistrationCounts()
|
||||
)
|
||||
|
||||
|
||||
def register_ble_features(features: set[BLEFeatures]) -> None:
|
||||
@@ -62,7 +83,7 @@ def register_ble_features(features: set[BLEFeatures]) -> None:
|
||||
Args:
|
||||
features: Set of BLEFeatures enum members
|
||||
"""
|
||||
_required_features.update(features)
|
||||
_get_required_features().update(features)
|
||||
|
||||
|
||||
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||
@@ -256,13 +277,17 @@ async def to_code(config):
|
||||
):
|
||||
register_ble_features({BLEFeatures.ESP_BT_DEVICE})
|
||||
|
||||
registration_counts = _get_registration_counts()
|
||||
|
||||
for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
|
||||
registration_counts.listeners += 1
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if CONF_MAC_ADDRESS in conf:
|
||||
addr_list = [it.as_hex for it in conf[CONF_MAC_ADDRESS]]
|
||||
cg.add(trigger.set_addresses(addr_list))
|
||||
await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf)
|
||||
for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []):
|
||||
registration_counts.listeners += 1
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if len(conf[CONF_SERVICE_UUID]) == len(bt_uuid16_format):
|
||||
cg.add(trigger.set_service_uuid16(as_hex(conf[CONF_SERVICE_UUID])))
|
||||
@@ -275,6 +300,7 @@ async def to_code(config):
|
||||
cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex))
|
||||
await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf)
|
||||
for conf in config.get(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, []):
|
||||
registration_counts.listeners += 1
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid16_format):
|
||||
cg.add(trigger.set_manufacturer_uuid16(as_hex(conf[CONF_MANUFACTURER_ID])))
|
||||
@@ -287,6 +313,7 @@ async def to_code(config):
|
||||
cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex))
|
||||
await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf)
|
||||
for conf in config.get(CONF_ON_SCAN_END, []):
|
||||
registration_counts.listeners += 1
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
@@ -316,10 +343,23 @@ async def to_code(config):
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def _add_ble_features():
|
||||
# Add feature-specific defines based on what's needed
|
||||
if BLEFeatures.ESP_BT_DEVICE in _required_features:
|
||||
required_features = _get_required_features()
|
||||
if BLEFeatures.ESP_BT_DEVICE in required_features:
|
||||
cg.add_define("USE_ESP32_BLE_DEVICE")
|
||||
cg.add_define("USE_ESP32_BLE_UUID")
|
||||
|
||||
# Add defines for StaticVector sizing based on registration counts
|
||||
# Only define if count > 0 to avoid allocating unnecessary memory
|
||||
registration_counts = _get_registration_counts()
|
||||
if registration_counts.listeners > 0:
|
||||
cg.add_define(
|
||||
"ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT", registration_counts.listeners
|
||||
)
|
||||
if registration_counts.clients > 0:
|
||||
cg.add_define(
|
||||
"ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT", registration_counts.clients
|
||||
)
|
||||
|
||||
|
||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
@@ -369,6 +409,7 @@ async def register_ble_device(
|
||||
var: cg.SafeExpType, config: ConfigType
|
||||
) -> cg.SafeExpType:
|
||||
register_ble_features({BLEFeatures.ESP_BT_DEVICE})
|
||||
_get_registration_counts().listeners += 1
|
||||
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
cg.add(paren.register_listener(var))
|
||||
return var
|
||||
@@ -376,6 +417,7 @@ async def register_ble_device(
|
||||
|
||||
async def register_client(var: cg.SafeExpType, config: ConfigType) -> cg.SafeExpType:
|
||||
register_ble_features({BLEFeatures.ESP_BT_DEVICE})
|
||||
_get_registration_counts().clients += 1
|
||||
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
cg.add(paren.register_client(var))
|
||||
return var
|
||||
@@ -389,6 +431,7 @@ async def register_raw_ble_device(
|
||||
This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice
|
||||
will not be compiled in if this is the only registration method used.
|
||||
"""
|
||||
_get_registration_counts().listeners += 1
|
||||
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
cg.add(paren.register_listener(var))
|
||||
return var
|
||||
@@ -402,6 +445,7 @@ async def register_raw_client(
|
||||
This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice
|
||||
will not be compiled in if this is the only registration method used.
|
||||
"""
|
||||
_get_registration_counts().clients += 1
|
||||
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
cg.add(paren.register_client(var))
|
||||
return var
|
||||
|
@@ -74,9 +74,11 @@ void ESP32BLETracker::setup() {
|
||||
[this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
|
||||
if (state == ota::OTA_STARTED) {
|
||||
this->stop_scan();
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
client->disconnect();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
});
|
||||
#endif
|
||||
@@ -206,8 +208,10 @@ void ESP32BLETracker::start_scan_(bool first) {
|
||||
this->set_scanner_state_(ScannerState::STARTING);
|
||||
ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
|
||||
if (!first) {
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
for (auto *listener : this->listeners_)
|
||||
listener->on_scan_end();
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
this->already_discovered_.clear();
|
||||
@@ -236,20 +240,25 @@ void ESP32BLETracker::start_scan_(bool first) {
|
||||
}
|
||||
|
||||
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
client->app_id = ++this->app_id_;
|
||||
this->clients_.push_back(client);
|
||||
this->recalculate_advertisement_parser_types();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32BLETracker::register_listener(ESPBTDeviceListener *listener) {
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
listener->set_parent(this);
|
||||
this->listeners_.push_back(listener);
|
||||
this->recalculate_advertisement_parser_types();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32BLETracker::recalculate_advertisement_parser_types() {
|
||||
this->raw_advertisements_ = false;
|
||||
this->parse_advertisements_ = false;
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
|
||||
this->parse_advertisements_ = true;
|
||||
@@ -257,6 +266,8 @@ void ESP32BLETracker::recalculate_advertisement_parser_types() {
|
||||
this->raw_advertisements_ = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
|
||||
this->parse_advertisements_ = true;
|
||||
@@ -264,6 +275,7 @@ void ESP32BLETracker::recalculate_advertisement_parser_types() {
|
||||
this->raw_advertisements_ = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
@@ -282,10 +294,12 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Forward all events to clients (scan results are handled separately via gap_scan_event_handler)
|
||||
// Forward all events to clients (scan results are handled separately via gap_scan_event_handler)
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
client->gap_event_handler(event, param);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
|
||||
@@ -348,9 +362,11 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_
|
||||
|
||||
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
client->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
|
||||
@@ -704,12 +720,16 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
|
||||
void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
||||
// Process raw advertisements
|
||||
if (this->raw_advertisements_) {
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->parse_devices(&scan_result, 1);
|
||||
}
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
client->parse_devices(&scan_result, 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Process parsed advertisements
|
||||
@@ -719,16 +739,20 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
||||
device.parse_scan_rst(scan_result);
|
||||
|
||||
bool found = false;
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->parse_device(device))
|
||||
found = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!found && !this->scan_continuous_) {
|
||||
this->print_bt_device_info(device);
|
||||
@@ -745,8 +769,10 @@ void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
|
||||
// Reset timeout state machine instead of cancelling scheduler timeout
|
||||
this->scan_timeout_state_ = ScanTimeoutState::INACTIVE;
|
||||
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
for (auto *listener : this->listeners_)
|
||||
listener->on_scan_end();
|
||||
#endif
|
||||
|
||||
this->set_scanner_state_(ScannerState::IDLE);
|
||||
}
|
||||
@@ -770,6 +796,7 @@ void ESP32BLETracker::handle_scanner_failure_() {
|
||||
|
||||
void ESP32BLETracker::try_promote_discovered_clients_() {
|
||||
// Only promote the first discovered client to avoid multiple simultaneous connections
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->state() != ClientState::DISCOVERED) {
|
||||
continue;
|
||||
@@ -791,6 +818,7 @@ void ESP32BLETracker::try_promote_discovered_clients_() {
|
||||
client->connect();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *ESP32BLETracker::scanner_state_to_string_(ScannerState state) const {
|
||||
|
@@ -302,6 +302,7 @@ class ESP32BLETracker : public Component,
|
||||
/// Count clients in each state
|
||||
ClientStateCounts count_client_states_() const {
|
||||
ClientStateCounts counts;
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
for (auto *client : this->clients_) {
|
||||
switch (client->state()) {
|
||||
case ClientState::DISCONNECTING:
|
||||
@@ -317,12 +318,17 @@ class ESP32BLETracker : public Component,
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return counts;
|
||||
}
|
||||
|
||||
// Group 1: Large objects (12+ bytes) - vectors and callback manager
|
||||
std::vector<ESPBTDeviceListener *> listeners_;
|
||||
std::vector<ESPBTClient *> clients_;
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT
|
||||
StaticVector<ESPBTDeviceListener *, ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT> listeners_;
|
||||
#endif
|
||||
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
|
||||
StaticVector<ESPBTClient *, ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT> clients_;
|
||||
#endif
|
||||
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
/// Vector of addresses that have already been printed in print_bt_device_info
|
||||
|
@@ -691,7 +691,9 @@ void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_
|
||||
|
||||
std::string EthernetComponent::get_use_address() const {
|
||||
if (this->use_address_.empty()) {
|
||||
return App.get_name() + ".local";
|
||||
// ".local" suffix length for mDNS hostnames
|
||||
constexpr size_t mdns_local_suffix_len = 5;
|
||||
return make_name_with_suffix(App.get_name(), '.', "local", mdns_local_suffix_len);
|
||||
}
|
||||
return this->use_address_;
|
||||
}
|
||||
|
@@ -167,8 +167,8 @@ class HttpRequestComponent : public Component {
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
|
||||
const std::string &body, const std::list<Header> &request_headers,
|
||||
std::set<std::string> collect_headers) = 0;
|
||||
const char *useragent_{nullptr};
|
||||
bool follow_redirects_{};
|
||||
|
@@ -14,8 +14,9 @@ namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.arduino";
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
|
@@ -31,8 +31,8 @@ class HttpContainerArduino : public HttpContainer {
|
||||
|
||||
class HttpRequestArduino : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
std::set<std::string> collect_headers) override;
|
||||
};
|
||||
|
||||
|
@@ -17,8 +17,9 @@ namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.host";
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestHost::perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
std::set<std::string> response_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
|
@@ -18,8 +18,8 @@ class HttpContainerHost : public HttpContainer {
|
||||
|
||||
class HttpRequestHost : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
std::set<std::string> response_headers) override;
|
||||
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
|
||||
|
||||
|
@@ -52,8 +52,9 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
|
@@ -37,8 +37,8 @@ class HttpRequestIDF : public HttpRequestComponent {
|
||||
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
std::set<std::string> collect_headers) override;
|
||||
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
||||
uint16_t buffer_size_rx_{};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
from esphome.config_helpers import filter_source_files_from_platform, get_logger_level
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DISABLED,
|
||||
@@ -125,6 +125,17 @@ def mdns_service(
|
||||
)
|
||||
|
||||
|
||||
def enable_mdns_storage():
|
||||
"""Enable persistent storage of mDNS services in the MDNSComponent.
|
||||
|
||||
Called by external components (like OpenThread) that need access to
|
||||
services after setup() completes via get_services().
|
||||
|
||||
Public API for external components. Do not remove.
|
||||
"""
|
||||
cg.add_define("USE_MDNS_STORE_SERVICES")
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.NETWORK_SERVICES)
|
||||
async def to_code(config):
|
||||
if config[CONF_DISABLED] is True:
|
||||
@@ -150,6 +161,8 @@ async def to_code(config):
|
||||
|
||||
if config[CONF_SERVICES]:
|
||||
cg.add_define("USE_MDNS_EXTRA_SERVICES")
|
||||
# Extra services need to be stored persistently
|
||||
enable_mdns_storage()
|
||||
|
||||
# Ensure at least 1 service (fallback service)
|
||||
cg.add_define("MDNS_SERVICE_COUNT", max(1, service_count))
|
||||
@@ -171,6 +184,10 @@ async def to_code(config):
|
||||
# Ensure at least 1 to avoid zero-size array
|
||||
cg.add_define("MDNS_DYNAMIC_TXT_COUNT", max(1, dynamic_txt_count))
|
||||
|
||||
# Enable storage if verbose logging is enabled (for dump_config)
|
||||
if get_logger_level() in ("VERBOSE", "VERY_VERBOSE"):
|
||||
enable_mdns_storage()
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
@@ -36,7 +36,7 @@ MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
|
||||
// Wrap build-time defines into flash storage
|
||||
MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
|
||||
|
||||
void MDNSComponent::compile_records_() {
|
||||
void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
||||
this->hostname_ = App.get_name();
|
||||
|
||||
// IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES
|
||||
@@ -53,7 +53,7 @@ void MDNSComponent::compile_records_() {
|
||||
MDNS_STATIC_CONST_CHAR(VALUE_BOARD, ESPHOME_BOARD);
|
||||
|
||||
if (api::global_api_server != nullptr) {
|
||||
auto &service = this->services_.emplace_next();
|
||||
auto &service = services.emplace_next();
|
||||
service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
|
||||
service.proto = MDNS_STR(SERVICE_TCP);
|
||||
service.port = api::global_api_server->get_port();
|
||||
@@ -146,7 +146,7 @@ void MDNSComponent::compile_records_() {
|
||||
#ifdef USE_PROMETHEUS
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_PROMETHEUS, "_prometheus-http");
|
||||
|
||||
auto &prom_service = this->services_.emplace_next();
|
||||
auto &prom_service = services.emplace_next();
|
||||
prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
|
||||
prom_service.proto = MDNS_STR(SERVICE_TCP);
|
||||
prom_service.port = USE_WEBSERVER_PORT;
|
||||
@@ -155,7 +155,7 @@ void MDNSComponent::compile_records_() {
|
||||
#ifdef USE_WEBSERVER
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_HTTP, "_http");
|
||||
|
||||
auto &web_service = this->services_.emplace_next();
|
||||
auto &web_service = services.emplace_next();
|
||||
web_service.service_type = MDNS_STR(SERVICE_HTTP);
|
||||
web_service.proto = MDNS_STR(SERVICE_TCP);
|
||||
web_service.port = USE_WEBSERVER_PORT;
|
||||
@@ -167,12 +167,17 @@ void MDNSComponent::compile_records_() {
|
||||
|
||||
// Publish "http" service if not using native API or any other services
|
||||
// This is just to have *some* mDNS service so that .local resolution works
|
||||
auto &fallback_service = this->services_.emplace_next();
|
||||
auto &fallback_service = services.emplace_next();
|
||||
fallback_service.service_type = MDNS_STR(SERVICE_HTTP);
|
||||
fallback_service.proto = MDNS_STR(SERVICE_TCP);
|
||||
fallback_service.port = USE_WEBSERVER_PORT;
|
||||
fallback_service.txt_records.push_back({MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)});
|
||||
#endif
|
||||
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
// Copy to member variable if storage is enabled (verbose logging, OpenThread, or extra services)
|
||||
this->services_ = services;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MDNSComponent::dump_config() {
|
||||
@@ -180,7 +185,7 @@ void MDNSComponent::dump_config() {
|
||||
"mDNS:\n"
|
||||
" Hostname: %s",
|
||||
this->hostname_.c_str());
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
ESP_LOGV(TAG, " Services:");
|
||||
for (const auto &service : this->services_) {
|
||||
ESP_LOGV(TAG, " - %s, %s, %d", MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto),
|
||||
|
@@ -55,7 +55,9 @@ class MDNSComponent : public Component {
|
||||
void add_extra_service(MDNSService service) { this->services_.emplace_next() = std::move(service); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
const StaticVector<MDNSService, MDNS_SERVICE_COUNT> &get_services() const { return this->services_; }
|
||||
#endif
|
||||
|
||||
void on_shutdown() override;
|
||||
|
||||
@@ -71,9 +73,11 @@ class MDNSComponent : public Component {
|
||||
StaticVector<std::string, MDNS_DYNAMIC_TXT_COUNT> dynamic_txt_values_;
|
||||
|
||||
protected:
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
|
||||
#endif
|
||||
std::string hostname_;
|
||||
void compile_records_();
|
||||
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services);
|
||||
};
|
||||
|
||||
} // namespace mdns
|
||||
|
@@ -12,7 +12,8 @@ namespace mdns {
|
||||
static const char *const TAG = "mdns";
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
this->compile_records_();
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services;
|
||||
this->compile_records_(services);
|
||||
|
||||
esp_err_t err = mdns_init();
|
||||
if (err != ESP_OK) {
|
||||
@@ -24,7 +25,7 @@ void MDNSComponent::setup() {
|
||||
mdns_hostname_set(this->hostname_.c_str());
|
||||
mdns_instance_name_set(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
for (const auto &service : services) {
|
||||
std::vector<mdns_txt_item_t> txt_records;
|
||||
for (const auto &record : service.txt_records) {
|
||||
mdns_txt_item_t it{};
|
||||
|
@@ -12,11 +12,12 @@ namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
this->compile_records_();
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services;
|
||||
this->compile_records_(services);
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
// part of the wire protocol to have an underscore, and for example ESP-IDF
|
||||
// expects the underscore to be there, the ESP8266 implementation always adds
|
||||
|
@@ -9,7 +9,9 @@
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() { this->compile_records_(); }
|
||||
void MDNSComponent::setup() {
|
||||
// Host platform doesn't have actual mDNS implementation
|
||||
}
|
||||
|
||||
void MDNSComponent::on_shutdown() {}
|
||||
|
||||
|
@@ -12,11 +12,12 @@ namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
this->compile_records_();
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services;
|
||||
this->compile_records_(services);
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
// part of the wire protocol to have an underscore, and for example ESP-IDF
|
||||
// expects the underscore to be there, the ESP8266 implementation always adds
|
||||
|
@@ -12,11 +12,12 @@ namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
this->compile_records_();
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services;
|
||||
this->compile_records_(services);
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
// part of the wire protocol to have an underscore, and for example ESP-IDF
|
||||
// expects the underscore to be there, the ESP8266 implementation always adds
|
||||
|
@@ -56,41 +56,50 @@ DriverChip(
|
||||
"WAVESHARE-P4-86-PANEL",
|
||||
height=720,
|
||||
width=720,
|
||||
hsync_back_porch=50,
|
||||
hsync_back_porch=80,
|
||||
hsync_pulse_width=20,
|
||||
hsync_front_porch=50,
|
||||
vsync_back_porch=20,
|
||||
hsync_front_porch=80,
|
||||
vsync_back_porch=12,
|
||||
vsync_pulse_width=4,
|
||||
vsync_front_porch=20,
|
||||
pclk_frequency="38MHz",
|
||||
lane_bit_rate="480Mbps",
|
||||
vsync_front_porch=30,
|
||||
pclk_frequency="46MHz",
|
||||
lane_bit_rate="1Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
(0xB9, 0xF1, 0x12, 0x83),
|
||||
(0xB1, 0x00, 0x00, 0x00, 0xDA, 0x80),
|
||||
(0xB2, 0x3C, 0x12, 0x30),
|
||||
(0xB3, 0x10, 0x10, 0x28, 0x28, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xB4, 0x80),
|
||||
(0xB5, 0x0A, 0x0A),
|
||||
(0xB6, 0x97, 0x97),
|
||||
(0xB8, 0x26, 0x22, 0xF0, 0x13),
|
||||
(0xBA, 0x31, 0x81, 0x0F, 0xF9, 0x0E, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25, 0x00, 0x90, 0x0A, 0x00, 0x00, 0x01, 0x4F, 0x01, 0x00, 0x00, 0x37),
|
||||
(0xBC, 0x47),
|
||||
(
|
||||
0xBA, 0x31, 0x81, 0x05, 0xF9, 0x0E, 0x0E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25, 0x00,
|
||||
0x90, 0x0A, 0x00, 0x00, 0x01, 0x4F, 0x01, 0x00, 0x00, 0x37,
|
||||
),
|
||||
(0xB8, 0x25, 0x22, 0xF0, 0x63),
|
||||
(0xBF, 0x02, 0x11, 0x00),
|
||||
(0xB3, 0x10, 0x10, 0x28, 0x28, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xC0, 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x12, 0x70, 0x00),
|
||||
(0xC1, 0x25, 0x00, 0x32, 0x32, 0x77, 0xE4, 0xFF, 0xFF, 0xCC, 0xCC, 0x77, 0x77),
|
||||
(0xC6, 0x82, 0x00, 0xBF, 0xFF, 0x00, 0xFF),
|
||||
(0xC7, 0xB8, 0x00, 0x0A, 0x10, 0x01, 0x09),
|
||||
(0xC8, 0x10, 0x40, 0x1E, 0x02),
|
||||
(0xCC, 0x0B),
|
||||
(0xE0, 0x00, 0x0B, 0x10, 0x2C, 0x3D, 0x3F, 0x42, 0x3A, 0x07, 0x0D, 0x0F, 0x13, 0x15, 0x13, 0x14, 0x0F, 0x16, 0x00, 0x0B, 0x10, 0x2C, 0x3D, 0x3F, 0x42, 0x3A, 0x07, 0x0D, 0x0F, 0x13, 0x15, 0x13, 0x14, 0x0F, 0x16),
|
||||
(0xE3, 0x07, 0x07, 0x0B, 0x0B, 0x0B, 0x0B, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10),
|
||||
(0xE9, 0xC8, 0x10, 0x0A, 0x00, 0x00, 0x80, 0x81, 0x12, 0x31, 0x23, 0x4F, 0x86, 0xA0, 0x00, 0x47, 0x08, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x98, 0x02, 0x8B, 0xAF, 0x46, 0x02, 0x88, 0x88, 0x88, 0x88, 0x88, 0x98, 0x13, 0x8B, 0xAF, 0x57, 0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xEA, 0x97, 0x0C, 0x09, 0x09, 0x09, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0x31, 0x8B, 0xA8, 0x31, 0x75, 0x88, 0x88, 0x88, 0x88, 0x88, 0x9F, 0x20, 0x8B, 0xA8, 0x20, 0x64, 0x88, 0x88, 0x88, 0x88, 0x88, 0x23, 0x00, 0x00, 0x02, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x81, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xEF, 0xFF, 0xFF, 0x01),
|
||||
(0x11, 0x00),
|
||||
(0x29, 0x00),
|
||||
(0xBC, 0x46), (0xCC, 0x0B), (0xB4, 0x80), (0xB2, 0x3C, 0x12, 0x30),
|
||||
(0xE3, 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10,),
|
||||
(0xC1, 0x36, 0x00, 0x32, 0x32, 0x77, 0xF1, 0xCC, 0xCC, 0x77, 0x77, 0x33, 0x33),
|
||||
(0xB5, 0x0A, 0x0A),
|
||||
(0xB6, 0xB2, 0xB2),
|
||||
(
|
||||
0xE9, 0xC8, 0x10, 0x0A, 0x10, 0x0F, 0xA1, 0x80, 0x12, 0x31, 0x23, 0x47, 0x86, 0xA1, 0x80,
|
||||
0x47, 0x08, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x48,
|
||||
0x02, 0x8B, 0xAF, 0x46, 0x02, 0x88, 0x88, 0x88, 0x88, 0x88, 0x48, 0x13, 0x8B, 0xAF, 0x57,
|
||||
0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
),
|
||||
(
|
||||
0xEA, 0x96, 0x12, 0x01, 0x01, 0x01, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x31,
|
||||
0x8B, 0xA8, 0x31, 0x75, 0x88, 0x88, 0x88, 0x88, 0x88, 0x4F, 0x20, 0x8B, 0xA8, 0x20, 0x64,
|
||||
0x88, 0x88, 0x88, 0x88, 0x88, 0x23, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xA1, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
),
|
||||
(
|
||||
0xE0, 0x00, 0x0A, 0x0F, 0x29, 0x3B, 0x3F, 0x42, 0x39, 0x06, 0x0D, 0x10, 0x13, 0x15, 0x14,
|
||||
0x15, 0x10, 0x17, 0x00, 0x0A, 0x0F, 0x29, 0x3B, 0x3F, 0x42, 0x39, 0x06, 0x0D, 0x10, 0x13,
|
||||
0x15, 0x14, 0x15, 0x10, 0x17,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
@@ -29,7 +29,8 @@ static const char *const TAG = "mqtt";
|
||||
|
||||
MQTTClientComponent::MQTTClientComponent() {
|
||||
global_mqtt_client = this;
|
||||
this->credentials_.client_id = App.get_name() + "-" + get_mac_address();
|
||||
const std::string mac_addr = get_mac_address();
|
||||
this->credentials_.client_id = make_name_with_suffix(App.get_name(), '-', mac_addr.c_str(), mac_addr.size());
|
||||
}
|
||||
|
||||
// Connection
|
||||
|
@@ -5,7 +5,7 @@ from esphome.components.esp32 import (
|
||||
add_idf_sdkconfig_option,
|
||||
only_on_variant,
|
||||
)
|
||||
from esphome.components.mdns import MDNSComponent
|
||||
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID
|
||||
import esphome.final_validate as fv
|
||||
@@ -141,6 +141,9 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_OPENTHREAD")
|
||||
|
||||
# OpenThread SRP needs access to mDNS services after setup
|
||||
enable_mdns_storage()
|
||||
|
||||
ot = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(ot, config)
|
||||
|
||||
|
@@ -19,72 +19,54 @@ ListEntitiesIterator::~ListEntitiesIterator() {}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::binary_sensor_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::cover_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::Fan *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::fan_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::light_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::sensor_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::switch_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool ListEntitiesIterator::on_button(button::Button *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::button_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::text_sensor_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::lock_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -92,8 +74,6 @@ bool ListEntitiesIterator::on_lock(lock::Lock *obj) {
|
||||
|
||||
#ifdef USE_VALVE
|
||||
bool ListEntitiesIterator::on_valve(valve::Valve *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::valve_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -101,8 +81,6 @@ bool ListEntitiesIterator::on_valve(valve::Valve *obj) {
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::climate_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -110,8 +88,6 @@ bool ListEntitiesIterator::on_climate(climate::Climate *obj) {
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool ListEntitiesIterator::on_number(number::Number *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::number_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -119,8 +95,6 @@ bool ListEntitiesIterator::on_number(number::Number *obj) {
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool ListEntitiesIterator::on_date(datetime::DateEntity *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::date_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -128,8 +102,6 @@ bool ListEntitiesIterator::on_date(datetime::DateEntity *obj) {
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool ListEntitiesIterator::on_time(datetime::TimeEntity *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::time_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -137,8 +109,6 @@ bool ListEntitiesIterator::on_time(datetime::TimeEntity *obj) {
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::datetime_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -146,8 +116,6 @@ bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *obj) {
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool ListEntitiesIterator::on_text(text::Text *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::text_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -155,8 +123,6 @@ bool ListEntitiesIterator::on_text(text::Text *obj) {
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool ListEntitiesIterator::on_select(select::Select *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::select_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -164,8 +130,6 @@ bool ListEntitiesIterator::on_select(select::Select *obj) {
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::alarm_control_panel_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
@@ -173,8 +137,6 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
|
||||
|
||||
#ifdef USE_EVENT
|
||||
bool ListEntitiesIterator::on_event(event::Event *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
// Null event type, since we are just iterating over entities
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::event_all_json_generator);
|
||||
return true;
|
||||
@@ -183,8 +145,6 @@ bool ListEntitiesIterator::on_event(event::Event *obj) {
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *obj) {
|
||||
if (this->events_->count() == 0)
|
||||
return true;
|
||||
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::update_all_json_generator);
|
||||
return true;
|
||||
}
|
||||
|
@@ -152,6 +152,10 @@ void DeferredUpdateEventSource::loop() {
|
||||
|
||||
void DeferredUpdateEventSource::deferrable_send_state(void *source, const char *event_type,
|
||||
message_generator_t *message_generator) {
|
||||
// Skip if no connected clients to avoid unnecessary deferred queue processing
|
||||
if (this->count() == 0)
|
||||
return;
|
||||
|
||||
// allow all json "details_all" to go through before publishing bare state events, this avoids unnamed entries showing
|
||||
// up in the web GUI and reduces event load during initial connect
|
||||
if (!entities_iterator_.completed() && 0 != strcmp(event_type, "state_detail_all"))
|
||||
@@ -197,6 +201,9 @@ void DeferredUpdateEventSourceList::loop() {
|
||||
|
||||
void DeferredUpdateEventSourceList::deferrable_send_state(void *source, const char *event_type,
|
||||
message_generator_t *message_generator) {
|
||||
// Skip if no event sources (no connected clients) to avoid unnecessary iteration
|
||||
if (this->empty())
|
||||
return;
|
||||
for (DeferredUpdateEventSource *dues : *this) {
|
||||
dues->deferrable_send_state(source, event_type, message_generator);
|
||||
}
|
||||
@@ -424,8 +431,6 @@ static JsonDetail get_request_detail(AsyncWebServerRequest *request) {
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", sensor_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -453,13 +458,8 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
|
||||
|
||||
const auto uom_ref = obj->get_unit_of_measurement_ref();
|
||||
|
||||
// Build JSON directly inline
|
||||
std::string state;
|
||||
if (std::isnan(value)) {
|
||||
state = "NA";
|
||||
} else {
|
||||
state = value_accuracy_with_uom_to_string(value, obj->get_accuracy_decimals(), uom_ref);
|
||||
}
|
||||
std::string state =
|
||||
std::isnan(value) ? "NA" : value_accuracy_with_uom_to_string(value, obj->get_accuracy_decimals(), uom_ref);
|
||||
set_json_icon_state_value(root, obj, "sensor", state, value, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
this->add_sorting_info_(root, obj);
|
||||
@@ -473,8 +473,6 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", text_sensor_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -514,8 +512,6 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std:
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", switch_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -627,8 +623,6 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config)
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -667,8 +661,6 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool
|
||||
|
||||
#ifdef USE_FAN
|
||||
void WebServer::on_fan_update(fan::Fan *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", fan_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -743,8 +735,6 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void WebServer::on_light_update(light::LightState *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", light_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -800,8 +790,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
set_json_id(root, obj, "light", start_config);
|
||||
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
|
||||
set_json_value(root, obj, "light", obj->remote_values.is_on() ? "ON" : "OFF", start_config);
|
||||
|
||||
light::LightJSONSchema::dump_json(*obj, root);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
@@ -819,8 +808,6 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
|
||||
|
||||
#ifdef USE_COVER
|
||||
void WebServer::on_cover_update(cover::Cover *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", cover_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -906,8 +893,6 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void WebServer::on_number_update(number::Number *obj, float state) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", number_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -948,7 +933,13 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
|
||||
|
||||
const auto uom_ref = obj->traits.get_unit_of_measurement_ref();
|
||||
|
||||
set_json_id(root, obj, "number", start_config);
|
||||
std::string val_str = std::isnan(value)
|
||||
? "\"NaN\""
|
||||
: value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step()));
|
||||
std::string state_str = std::isnan(value) ? "NA"
|
||||
: value_accuracy_with_uom_to_string(
|
||||
value, step_to_accuracy_decimals(obj->traits.get_step()), uom_ref);
|
||||
set_json_icon_state_value(root, obj, "number", state_str, val_str, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root["min_value"] =
|
||||
value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step()));
|
||||
@@ -960,14 +951,6 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
|
||||
root["uom"] = uom_ref;
|
||||
this->add_sorting_info_(root, obj);
|
||||
}
|
||||
if (std::isnan(value)) {
|
||||
root["value"] = "\"NaN\"";
|
||||
root["state"] = "NA";
|
||||
} else {
|
||||
root["value"] = value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step()));
|
||||
root["state"] =
|
||||
value_accuracy_with_uom_to_string(value, step_to_accuracy_decimals(obj->traits.get_step()), uom_ref);
|
||||
}
|
||||
|
||||
return builder.serialize();
|
||||
}
|
||||
@@ -975,8 +958,6 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void WebServer::on_date_update(datetime::DateEntity *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", date_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1020,10 +1001,8 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
set_json_id(root, obj, "date", start_config);
|
||||
std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day);
|
||||
root["value"] = value;
|
||||
root["state"] = value;
|
||||
set_json_icon_state_value(root, obj, "date", value, value, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
this->add_sorting_info_(root, obj);
|
||||
}
|
||||
@@ -1034,8 +1013,6 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void WebServer::on_time_update(datetime::TimeEntity *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", time_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1078,10 +1055,8 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
set_json_id(root, obj, "time", start_config);
|
||||
std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
|
||||
root["value"] = value;
|
||||
root["state"] = value;
|
||||
set_json_icon_state_value(root, obj, "time", value, value, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
this->add_sorting_info_(root, obj);
|
||||
}
|
||||
@@ -1092,8 +1067,6 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", datetime_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1136,11 +1109,9 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
set_json_id(root, obj, "datetime", start_config);
|
||||
std::string value =
|
||||
str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second);
|
||||
root["value"] = value;
|
||||
root["state"] = value;
|
||||
set_json_icon_state_value(root, obj, "datetime", value, value, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
this->add_sorting_info_(root, obj);
|
||||
}
|
||||
@@ -1151,8 +1122,6 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void WebServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", text_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1191,16 +1160,11 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
set_json_id(root, obj, "text", start_config);
|
||||
std::string state = obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD ? "********" : value;
|
||||
set_json_icon_state_value(root, obj, "text", state, value, start_config);
|
||||
root["min_length"] = obj->traits.get_min_length();
|
||||
root["max_length"] = obj->traits.get_max_length();
|
||||
root["pattern"] = obj->traits.get_pattern();
|
||||
if (obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD) {
|
||||
root["state"] = "********";
|
||||
} else {
|
||||
root["state"] = value;
|
||||
}
|
||||
root["value"] = value;
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root["mode"] = (int) obj->traits.get_mode();
|
||||
this->add_sorting_info_(root, obj);
|
||||
@@ -1212,8 +1176,6 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", select_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1270,8 +1232,6 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void WebServer::on_climate_update(climate::Climate *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", climate_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1412,8 +1372,6 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", lock_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1485,8 +1443,6 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet
|
||||
|
||||
#ifdef USE_VALVE
|
||||
void WebServer::on_valve_update(valve::Valve *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", valve_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1568,8 +1524,6 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", alarm_control_panel_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1714,8 +1668,6 @@ static const char *update_state_to_string(update::UpdateState state) {
|
||||
}
|
||||
|
||||
void WebServer::on_update(update::UpdateEntity *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", update_state_json_generator);
|
||||
}
|
||||
void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -1754,9 +1706,8 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
|
||||
json::JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
|
||||
set_json_id(root, obj, "update", start_config);
|
||||
root["value"] = obj->update_info.latest_version;
|
||||
root["state"] = update_state_to_string(obj->state);
|
||||
set_json_icon_state_value(root, obj, "update", update_state_to_string(obj->state), obj->update_info.latest_version,
|
||||
start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root["current_version"] = obj->update_info.current_version;
|
||||
root["title"] = obj->update_info.title;
|
||||
|
@@ -412,6 +412,9 @@ void AsyncEventSource::try_send_nodefer(const char *message, const char *event,
|
||||
|
||||
void AsyncEventSource::deferrable_send_state(void *source, const char *event_type,
|
||||
message_generator_t *message_generator) {
|
||||
// Skip if no connected clients to avoid unnecessary processing
|
||||
if (this->empty())
|
||||
return;
|
||||
for (auto *ses : this->sessions_) {
|
||||
if (ses->fd_.load() != 0) { // Skip dead sessions
|
||||
ses->deferrable_send_state(source, event_type, message_generator);
|
||||
|
@@ -267,7 +267,9 @@ network::IPAddress WiFiComponent::get_dns_address(int num) {
|
||||
}
|
||||
std::string WiFiComponent::get_use_address() const {
|
||||
if (this->use_address_.empty()) {
|
||||
return App.get_name() + ".local";
|
||||
// ".local" suffix length for mDNS hostnames
|
||||
constexpr size_t mdns_local_suffix_len = 5;
|
||||
return make_name_with_suffix(App.get_name(), '.', "local", mdns_local_suffix_len);
|
||||
}
|
||||
return this->use_address_;
|
||||
}
|
||||
|
@@ -1195,6 +1195,13 @@ def validate_bytes(value):
|
||||
|
||||
|
||||
def hostname(value):
|
||||
"""Validate that the value is a valid hostname.
|
||||
|
||||
Maximum length is 63 characters per RFC 1035.
|
||||
|
||||
Note: If this limit is changed, update MAX_NAME_WITH_SUFFIX_SIZE in
|
||||
esphome/core/helpers.cpp to accommodate the new maximum length.
|
||||
"""
|
||||
value = string(value)
|
||||
if re.match(r"^[a-z0-9-]{1,63}$", value, re.IGNORECASE) is not None:
|
||||
return value
|
||||
|
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.10.0"
|
||||
__version__ = "2025.11.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
@@ -340,8 +340,8 @@ void Application::calculate_looping_components_() {
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-reserve vector to avoid reallocations
|
||||
this->looping_components_.reserve(total_looping);
|
||||
// Initialize FixedVector with exact size - no reallocation possible
|
||||
this->looping_components_.init(total_looping);
|
||||
|
||||
// Add all components with loop override that aren't already LOOP_DONE
|
||||
// Some components (like logger) may call disable_loop() during initialization
|
||||
|
@@ -102,9 +102,15 @@ class Application {
|
||||
arch_init();
|
||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
||||
if (name_add_mac_suffix) {
|
||||
const std::string mac_suffix = get_mac_address().substr(6);
|
||||
this->name_ = name + "-" + mac_suffix;
|
||||
this->friendly_name_ = friendly_name.empty() ? "" : friendly_name + " " + mac_suffix;
|
||||
// MAC address suffix length (last 6 characters of 12-char MAC address string)
|
||||
constexpr size_t mac_address_suffix_len = 6;
|
||||
const std::string mac_addr = get_mac_address();
|
||||
// Use pointer + offset to avoid substr() allocation
|
||||
const char *mac_suffix_ptr = mac_addr.c_str() + mac_address_suffix_len;
|
||||
this->name_ = make_name_with_suffix(name, '-', mac_suffix_ptr, mac_address_suffix_len);
|
||||
if (!friendly_name.empty()) {
|
||||
this->friendly_name_ = make_name_with_suffix(friendly_name, ' ', mac_suffix_ptr, mac_address_suffix_len);
|
||||
}
|
||||
} else {
|
||||
this->name_ = name;
|
||||
this->friendly_name_ = friendly_name;
|
||||
@@ -472,7 +478,7 @@ class Application {
|
||||
// - When a component is enabled, it's swapped with the first inactive component
|
||||
// and active_end_ is incremented
|
||||
// - This eliminates branch mispredictions from flag checking in the hot loop
|
||||
std::vector<Component *> looping_components_{};
|
||||
FixedVector<Component *> looping_components_{};
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
|
||||
#endif
|
||||
|
@@ -200,7 +200,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string,
|
||||
cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All(cv.string, cv.Length(max=120)),
|
||||
cv.Optional(CONF_AREA): validate_area_config,
|
||||
cv.Optional(CONF_COMMENT): cv.string,
|
||||
cv.Required(CONF_BUILD_PATH): cv.string,
|
||||
|
@@ -83,6 +83,7 @@
|
||||
#define USE_LVGL_TILEVIEW
|
||||
#define USE_LVGL_TOUCHSCREEN
|
||||
#define USE_MDNS
|
||||
#define USE_MDNS_STORE_SERVICES
|
||||
#define MDNS_SERVICE_COUNT 3
|
||||
#define MDNS_DYNAMIC_TXT_COUNT 3
|
||||
#define USE_MEDIA_PLAYER
|
||||
@@ -175,6 +176,8 @@
|
||||
#define USE_ESP32_BLE_SERVER_DESCRIPTOR_ON_WRITE
|
||||
#define USE_ESP32_BLE_SERVER_ON_CONNECT
|
||||
#define USE_ESP32_BLE_SERVER_ON_DISCONNECT
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 1
|
||||
#define ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT 1
|
||||
#define USE_ESP32_CAMERA_JPEG_ENCODER
|
||||
#define USE_I2C
|
||||
#define USE_IMPROV
|
||||
|
@@ -235,6 +235,30 @@ std::string str_sprintf(const char *fmt, ...) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// Maximum size for name with suffix: 120 (max friendly name) + 1 (separator) + 6 (MAC suffix) + 1 (null term)
|
||||
static constexpr size_t MAX_NAME_WITH_SUFFIX_SIZE = 128;
|
||||
|
||||
std::string make_name_with_suffix(const std::string &name, char sep, const char *suffix_ptr, size_t suffix_len) {
|
||||
char buffer[MAX_NAME_WITH_SUFFIX_SIZE];
|
||||
size_t name_len = name.size();
|
||||
size_t total_len = name_len + 1 + suffix_len;
|
||||
|
||||
// Silently truncate if needed: prioritize keeping the full suffix
|
||||
if (total_len >= MAX_NAME_WITH_SUFFIX_SIZE) {
|
||||
// NOTE: This calculation could underflow if suffix_len >= MAX_NAME_WITH_SUFFIX_SIZE - 2,
|
||||
// but this is safe because this helper is only called with small suffixes:
|
||||
// MAC suffixes (6-12 bytes), ".local" (5 bytes), etc.
|
||||
name_len = MAX_NAME_WITH_SUFFIX_SIZE - suffix_len - 2; // -2 for separator and null terminator
|
||||
total_len = name_len + 1 + suffix_len;
|
||||
}
|
||||
|
||||
memcpy(buffer, name.c_str(), name_len);
|
||||
buffer[name_len] = sep;
|
||||
memcpy(buffer + name_len + 1, suffix_ptr, suffix_len);
|
||||
buffer[total_len] = '\0';
|
||||
return std::string(buffer, total_len);
|
||||
}
|
||||
|
||||
// Parsing & formatting
|
||||
|
||||
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
|
||||
|
@@ -159,6 +159,54 @@ template<typename T, size_t N> class StaticVector {
|
||||
const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
|
||||
};
|
||||
|
||||
/// Fixed-capacity vector - allocates once at runtime, never reallocates
|
||||
/// This avoids std::vector template overhead (_M_realloc_insert, _M_default_append)
|
||||
/// when size is known at initialization but not at compile time
|
||||
template<typename T> class FixedVector {
|
||||
private:
|
||||
T *data_{nullptr};
|
||||
size_t size_{0};
|
||||
size_t capacity_{0};
|
||||
|
||||
public:
|
||||
FixedVector() = default;
|
||||
|
||||
~FixedVector() {
|
||||
if (data_ != nullptr) {
|
||||
delete[] data_;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable copy to avoid accidental copies
|
||||
FixedVector(const FixedVector &) = delete;
|
||||
FixedVector &operator=(const FixedVector &) = delete;
|
||||
|
||||
// Allocate capacity - can only be called once on empty vector
|
||||
void init(size_t n) {
|
||||
if (data_ == nullptr && n > 0) {
|
||||
data_ = new T[n];
|
||||
capacity_ = n;
|
||||
size_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Add element without bounds checking
|
||||
/// Caller must ensure sufficient capacity was allocated via init()
|
||||
/// Silently ignores pushes beyond capacity (no exception or assertion)
|
||||
void push_back(const T &value) {
|
||||
if (size_ < capacity_) {
|
||||
data_[size_++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
|
||||
/// Access element without bounds checking (matches std::vector behavior)
|
||||
/// Caller must ensure index is valid (i < size())
|
||||
T &operator[](size_t i) { return data_[i]; }
|
||||
const T &operator[](size_t i) const { return data_[i]; }
|
||||
};
|
||||
|
||||
///@}
|
||||
|
||||
/// @name Mathematics
|
||||
@@ -306,6 +354,16 @@ std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt,
|
||||
/// sprintf-like function returning std::string.
|
||||
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
||||
|
||||
/// Concatenate a name with a separator and suffix using an efficient stack-based approach.
|
||||
/// This avoids multiple heap allocations during string construction.
|
||||
/// Maximum name length supported is 120 characters for friendly names.
|
||||
/// @param name The base name string
|
||||
/// @param sep The separator character (e.g., '-', ' ', or '.')
|
||||
/// @param suffix_ptr Pointer to the suffix characters
|
||||
/// @param suffix_len Length of the suffix
|
||||
/// @return The concatenated string: name + sep + suffix
|
||||
std::string make_name_with_suffix(const std::string &name, char sep, const char *suffix_ptr, size_t suffix_len);
|
||||
|
||||
///@}
|
||||
|
||||
/// @name Parsing & formatting
|
||||
|
@@ -1,4 +1,3 @@
|
||||
[build]
|
||||
command = "script/build-api-docs"
|
||||
publish = "api-docs"
|
||||
environment = { PYTHON_VERSION = "3.13" }
|
||||
|
@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||
esptool==5.1.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20251013.0
|
||||
aioesphomeapi==41.16.1
|
||||
aioesphomeapi==41.16.0
|
||||
zeroconf==0.148.0
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.18.15 # dashboard_import
|
||||
|
@@ -1,7 +1,7 @@
|
||||
pylint==3.3.9
|
||||
pylint==4.0.0
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.0 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.0 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
# Unit tests
|
||||
|
@@ -1,7 +1,6 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
flow_control_pin: GPIO13
|
||||
|
||||
packages:
|
||||
modbus: !include ../../test_build_components/common/modbus/esp32-idf.yaml
|
||||
|
@@ -1,7 +1,6 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
flow_control_pin: GPIO13
|
||||
|
||||
packages:
|
||||
modbus: !include ../../test_build_components/common/modbus/esp32-idf.yaml
|
||||
|
@@ -1,7 +1,6 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
flow_control_pin: GPIO13
|
||||
|
||||
packages:
|
||||
modbus: !include ../../test_build_components/common/modbus/esp32-idf.yaml
|
||||
|
Reference in New Issue
Block a user