esphome 2025.10.0b1__py3-none-any.whl → 2025.10.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/__main__.py +7 -0
- esphome/components/canbus/canbus.h +3 -3
- esphome/components/dashboard_import/dashboard_import.cpp +1 -1
- esphome/components/dashboard_import/dashboard_import.h +1 -1
- esphome/components/esp32/__init__.py +9 -8
- esphome/components/esp32_ble/__init__.py +9 -1
- esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +0 -4
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +0 -4
- esphome/components/esp32_improv/esp32_improv_component.cpp +33 -21
- esphome/components/esp32_improv/esp32_improv_component.h +1 -0
- esphome/components/esphome/ota/ota_esphome.cpp +1 -1
- esphome/components/json/json_util.cpp +8 -2
- esphome/components/mcp23xxx_base/mcp23xxx_base.h +3 -3
- esphome/components/mdns/__init__.py +76 -15
- esphome/components/mdns/mdns_component.cpp +52 -57
- esphome/components/mdns/mdns_component.h +12 -1
- esphome/components/mdns/mdns_esp32.cpp +3 -9
- esphome/components/mdns/mdns_esp8266.cpp +1 -1
- esphome/components/mdns/mdns_libretiny.cpp +1 -2
- esphome/components/mdns/mdns_rp2040.cpp +1 -2
- esphome/components/opentherm/opentherm.cpp +5 -5
- esphome/components/opentherm/opentherm.h +3 -3
- esphome/components/openthread/openthread.cpp +5 -3
- esphome/components/uart/__init__.py +1 -1
- esphome/components/usb_host/__init__.py +10 -1
- esphome/components/usb_host/usb_host.h +24 -18
- esphome/components/usb_host/usb_host_client.cpp +18 -39
- esphome/components/wifi/wifi_component.cpp +3 -2
- esphome/const.py +1 -1
- esphome/core/__init__.py +2 -0
- esphome/core/defines.h +2 -0
- esphome/core/entity_helpers.py +9 -6
- esphome/espota2.py +1 -1
- esphome/pins.py +2 -2
- esphome/platformio_api.py +31 -0
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b2.dist-info}/METADATA +3 -3
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b2.dist-info}/RECORD +41 -41
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b2.dist-info}/WHEEL +0 -0
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b2.dist-info}/entry_points.txt +0 -0
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b2.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b2.dist-info}/top_level.txt +0 -0
esphome/__main__.py
CHANGED
@@ -1002,6 +1002,12 @@ def parse_args(argv):
|
|
1002
1002
|
action="append",
|
1003
1003
|
default=[],
|
1004
1004
|
)
|
1005
|
+
options_parser.add_argument(
|
1006
|
+
"--testing-mode",
|
1007
|
+
help="Enable testing mode (disables validation checks for grouped component testing)",
|
1008
|
+
action="store_true",
|
1009
|
+
default=False,
|
1010
|
+
)
|
1005
1011
|
|
1006
1012
|
parser = argparse.ArgumentParser(
|
1007
1013
|
description=f"ESPHome {const.__version__}", parents=[options_parser]
|
@@ -1260,6 +1266,7 @@ def run_esphome(argv):
|
|
1260
1266
|
|
1261
1267
|
args = parse_args(argv)
|
1262
1268
|
CORE.dashboard = args.dashboard
|
1269
|
+
CORE.testing_mode = args.testing_mode
|
1263
1270
|
|
1264
1271
|
# Create address cache from command-line arguments
|
1265
1272
|
CORE.address_cache = AddressCache.from_cli_args(
|
@@ -105,9 +105,9 @@ class Canbus : public Component {
|
|
105
105
|
CallbackManager<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)>
|
106
106
|
callback_manager_{};
|
107
107
|
|
108
|
-
virtual bool setup_internal();
|
109
|
-
virtual Error send_message(struct CanFrame *frame);
|
110
|
-
virtual Error read_message(struct CanFrame *frame);
|
108
|
+
virtual bool setup_internal() = 0;
|
109
|
+
virtual Error send_message(struct CanFrame *frame) = 0;
|
110
|
+
virtual Error read_message(struct CanFrame *frame) = 0;
|
111
111
|
};
|
112
112
|
|
113
113
|
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
@@ -5,7 +5,7 @@ namespace dashboard_import {
|
|
5
5
|
|
6
6
|
static std::string g_package_import_url; // NOLINT
|
7
7
|
|
8
|
-
std::string get_package_import_url() { return g_package_import_url; }
|
8
|
+
const std::string &get_package_import_url() { return g_package_import_url; }
|
9
9
|
void set_package_import_url(std::string url) { g_package_import_url = std::move(url); }
|
10
10
|
|
11
11
|
} // namespace dashboard_import
|
@@ -314,11 +314,12 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
|
314
314
|
# - https://github.com/espressif/arduino-esp32/releases
|
315
315
|
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
|
316
316
|
"recommended": cv.Version(3, 2, 1),
|
317
|
-
"latest": cv.Version(3, 3,
|
318
|
-
"dev": cv.Version(3, 3,
|
317
|
+
"latest": cv.Version(3, 3, 2),
|
318
|
+
"dev": cv.Version(3, 3, 2),
|
319
319
|
}
|
320
320
|
ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
321
|
-
cv.Version(3, 3,
|
321
|
+
cv.Version(3, 3, 2): cv.Version(55, 3, 31, "1"),
|
322
|
+
cv.Version(3, 3, 1): cv.Version(55, 3, 31, "1"),
|
322
323
|
cv.Version(3, 3, 0): cv.Version(55, 3, 30, "2"),
|
323
324
|
cv.Version(3, 2, 1): cv.Version(54, 3, 21, "2"),
|
324
325
|
cv.Version(3, 2, 0): cv.Version(54, 3, 20),
|
@@ -336,8 +337,8 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
|
336
337
|
"dev": cv.Version(5, 5, 1),
|
337
338
|
}
|
338
339
|
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
339
|
-
cv.Version(5, 5, 1): cv.Version(55, 3, 31),
|
340
|
-
cv.Version(5, 5, 0): cv.Version(55, 3, 31),
|
340
|
+
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
|
341
|
+
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
|
341
342
|
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
|
342
343
|
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
|
343
344
|
cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"),
|
@@ -352,8 +353,8 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
|
352
353
|
# - https://github.com/pioarduino/platform-espressif32/releases
|
353
354
|
PLATFORM_VERSION_LOOKUP = {
|
354
355
|
"recommended": cv.Version(54, 3, 21, "2"),
|
355
|
-
"latest": cv.Version(55, 3, 31),
|
356
|
-
"dev":
|
356
|
+
"latest": cv.Version(55, 3, 31, "1"),
|
357
|
+
"dev": cv.Version(55, 3, 31, "1"),
|
357
358
|
}
|
358
359
|
|
359
360
|
|
@@ -645,6 +646,7 @@ def _show_framework_migration_message(name: str, variant: str) -> None:
|
|
645
646
|
+ "Why change? ESP-IDF offers:\n"
|
646
647
|
+ color(AnsiFore.GREEN, " ✨ Up to 40% smaller binaries\n")
|
647
648
|
+ color(AnsiFore.GREEN, " 🚀 Better performance and optimization\n")
|
649
|
+
+ color(AnsiFore.GREEN, " ⚡ 2-3x faster compile times\n")
|
648
650
|
+ color(AnsiFore.GREEN, " 📦 Custom-built firmware for your exact needs\n")
|
649
651
|
+ color(
|
650
652
|
AnsiFore.GREEN,
|
@@ -652,7 +654,6 @@ def _show_framework_migration_message(name: str, variant: str) -> None:
|
|
652
654
|
)
|
653
655
|
+ "\n"
|
654
656
|
+ "Trade-offs:\n"
|
655
|
-
+ color(AnsiFore.YELLOW, " ⏱️ Compile times are ~25% longer\n")
|
656
657
|
+ color(AnsiFore.YELLOW, " 🔄 Some components need migration\n")
|
657
658
|
+ "\n"
|
658
659
|
+ "What should I do?\n"
|
@@ -285,6 +285,10 @@ def consume_connection_slots(
|
|
285
285
|
|
286
286
|
def validate_connection_slots(max_connections: int) -> None:
|
287
287
|
"""Validate that BLE connection slots don't exceed the configured maximum."""
|
288
|
+
# Skip validation in testing mode to allow component grouping
|
289
|
+
if CORE.testing_mode:
|
290
|
+
return
|
291
|
+
|
288
292
|
ble_data = CORE.data.get(KEY_ESP32_BLE, {})
|
289
293
|
used_slots = ble_data.get(KEY_USED_CONNECTION_SLOTS, [])
|
290
294
|
num_used = len(used_slots)
|
@@ -332,12 +336,16 @@ def final_validation(config):
|
|
332
336
|
|
333
337
|
# Check if BLE Server is needed
|
334
338
|
has_ble_server = "esp32_ble_server" in full_config
|
335
|
-
add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server)
|
336
339
|
|
337
340
|
# Check if BLE Client is needed (via esp32_ble_tracker or esp32_ble_client)
|
338
341
|
has_ble_client = (
|
339
342
|
"esp32_ble_tracker" in full_config or "esp32_ble_client" in full_config
|
340
343
|
)
|
344
|
+
|
345
|
+
# ESP-IDF BLE stack requires GATT Server to be enabled when GATT Client is enabled
|
346
|
+
# This is an internal dependency in the Bluedroid stack (tested ESP-IDF 5.4.2-5.5.1)
|
347
|
+
# See: https://github.com/espressif/esp-idf/issues/17724
|
348
|
+
add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server or has_ble_client)
|
341
349
|
add_idf_sdkconfig_option("CONFIG_BT_GATTC_ENABLE", has_ble_client)
|
342
350
|
|
343
351
|
# Handle max_connections: check for deprecated location in esp32_ble_tracker
|
@@ -143,6 +143,7 @@ void ESP32ImprovComponent::loop() {
|
|
143
143
|
#else
|
144
144
|
this->set_state_(improv::STATE_AUTHORIZED);
|
145
145
|
#endif
|
146
|
+
this->check_wifi_connection_();
|
146
147
|
break;
|
147
148
|
}
|
148
149
|
case improv::STATE_AUTHORIZED: {
|
@@ -156,31 +157,12 @@ void ESP32ImprovComponent::loop() {
|
|
156
157
|
if (!this->check_identify_()) {
|
157
158
|
this->set_status_indicator_state_((now % 1000) < 500);
|
158
159
|
}
|
160
|
+
this->check_wifi_connection_();
|
159
161
|
break;
|
160
162
|
}
|
161
163
|
case improv::STATE_PROVISIONING: {
|
162
164
|
this->set_status_indicator_state_((now % 200) < 100);
|
163
|
-
|
164
|
-
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
|
165
|
-
this->connecting_sta_.get_password());
|
166
|
-
this->connecting_sta_ = {};
|
167
|
-
this->cancel_timeout("wifi-connect-timeout");
|
168
|
-
this->set_state_(improv::STATE_PROVISIONED);
|
169
|
-
|
170
|
-
std::vector<std::string> urls = {ESPHOME_MY_LINK};
|
171
|
-
#ifdef USE_WEBSERVER
|
172
|
-
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
|
173
|
-
if (ip.is_ip4()) {
|
174
|
-
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
175
|
-
urls.push_back(webserver_url);
|
176
|
-
break;
|
177
|
-
}
|
178
|
-
}
|
179
|
-
#endif
|
180
|
-
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
|
181
|
-
this->send_response_(data);
|
182
|
-
this->stop();
|
183
|
-
}
|
165
|
+
this->check_wifi_connection_();
|
184
166
|
break;
|
185
167
|
}
|
186
168
|
case improv::STATE_PROVISIONED: {
|
@@ -392,6 +374,36 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() {
|
|
392
374
|
wifi::global_wifi_component->clear_sta();
|
393
375
|
}
|
394
376
|
|
377
|
+
void ESP32ImprovComponent::check_wifi_connection_() {
|
378
|
+
if (!wifi::global_wifi_component->is_connected()) {
|
379
|
+
return;
|
380
|
+
}
|
381
|
+
|
382
|
+
if (this->state_ == improv::STATE_PROVISIONING) {
|
383
|
+
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(), this->connecting_sta_.get_password());
|
384
|
+
this->connecting_sta_ = {};
|
385
|
+
this->cancel_timeout("wifi-connect-timeout");
|
386
|
+
|
387
|
+
std::vector<std::string> urls = {ESPHOME_MY_LINK};
|
388
|
+
#ifdef USE_WEBSERVER
|
389
|
+
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
|
390
|
+
if (ip.is_ip4()) {
|
391
|
+
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
392
|
+
urls.push_back(webserver_url);
|
393
|
+
break;
|
394
|
+
}
|
395
|
+
}
|
396
|
+
#endif
|
397
|
+
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
|
398
|
+
this->send_response_(data);
|
399
|
+
} else if (this->is_active() && this->state_ != improv::STATE_PROVISIONED) {
|
400
|
+
ESP_LOGD(TAG, "WiFi provisioned externally");
|
401
|
+
}
|
402
|
+
|
403
|
+
this->set_state_(improv::STATE_PROVISIONED);
|
404
|
+
this->stop();
|
405
|
+
}
|
406
|
+
|
395
407
|
void ESP32ImprovComponent::advertise_service_data_() {
|
396
408
|
uint8_t service_data[IMPROV_SERVICE_DATA_SIZE] = {};
|
397
409
|
service_data[0] = IMPROV_PROTOCOL_ID_1; // PR
|
@@ -111,6 +111,7 @@ class ESP32ImprovComponent : public Component {
|
|
111
111
|
void send_response_(std::vector<uint8_t> &response);
|
112
112
|
void process_incoming_data_();
|
113
113
|
void on_wifi_connect_timeout_();
|
114
|
+
void check_wifi_connection_();
|
114
115
|
bool check_identify_();
|
115
116
|
void advertise_service_data_();
|
116
117
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
@@ -29,7 +29,7 @@ namespace esphome {
|
|
29
29
|
static const char *const TAG = "esphome.ota";
|
30
30
|
static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
|
31
31
|
static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size for OTA data transfer
|
32
|
-
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE =
|
32
|
+
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
|
33
33
|
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
|
34
34
|
|
35
35
|
#ifdef USE_OTA_PASSWORD
|
@@ -8,6 +8,13 @@ namespace json {
|
|
8
8
|
|
9
9
|
static const char *const TAG = "json";
|
10
10
|
|
11
|
+
#ifdef USE_PSRAM
|
12
|
+
// Global allocator that outlives all JsonDocuments returned by parse_json()
|
13
|
+
// This prevents dangling pointer issues when JsonDocuments are returned from functions
|
14
|
+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - Must be mutable for ArduinoJson::Allocator
|
15
|
+
static SpiRamAllocator global_json_allocator;
|
16
|
+
#endif
|
17
|
+
|
11
18
|
std::string build_json(const json_build_t &f) {
|
12
19
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
13
20
|
JsonBuilder builder;
|
@@ -33,8 +40,7 @@ JsonDocument parse_json(const uint8_t *data, size_t len) {
|
|
33
40
|
return JsonObject(); // return unbound object
|
34
41
|
}
|
35
42
|
#ifdef USE_PSRAM
|
36
|
-
|
37
|
-
JsonDocument json_document(&doc_allocator);
|
43
|
+
JsonDocument json_document(&global_json_allocator);
|
38
44
|
#else
|
39
45
|
JsonDocument json_document;
|
40
46
|
#endif
|
@@ -21,11 +21,11 @@ template<uint8_t N> class MCP23XXXBase : public Component, public gpio_expander:
|
|
21
21
|
|
22
22
|
protected:
|
23
23
|
// read a given register
|
24
|
-
virtual bool read_reg(uint8_t reg, uint8_t *value);
|
24
|
+
virtual bool read_reg(uint8_t reg, uint8_t *value) = 0;
|
25
25
|
// write a value to a given register
|
26
|
-
virtual bool write_reg(uint8_t reg, uint8_t value);
|
26
|
+
virtual bool write_reg(uint8_t reg, uint8_t value) = 0;
|
27
27
|
// update registers with given pin value.
|
28
|
-
virtual void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a);
|
28
|
+
virtual void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a) = 0;
|
29
29
|
|
30
30
|
bool open_drain_ints_;
|
31
31
|
};
|
@@ -11,7 +11,7 @@ from esphome.const import (
|
|
11
11
|
CONF_SERVICES,
|
12
12
|
PlatformFramework,
|
13
13
|
)
|
14
|
-
from esphome.core import CORE, coroutine_with_priority
|
14
|
+
from esphome.core import CORE, Lambda, coroutine_with_priority
|
15
15
|
from esphome.coroutine import CoroPriority
|
16
16
|
|
17
17
|
CODEOWNERS = ["@esphome/core"]
|
@@ -58,17 +58,64 @@ CONFIG_SCHEMA = cv.All(
|
|
58
58
|
)
|
59
59
|
|
60
60
|
|
61
|
-
def mdns_txt_record(key: str, value: str):
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
def mdns_txt_record(key: str, value: str) -> cg.RawExpression:
|
62
|
+
"""Create a mDNS TXT record.
|
63
|
+
|
64
|
+
Public API for external components. Do not remove.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
key: The TXT record key
|
68
|
+
value: The TXT record value (static string only)
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
A RawExpression representing a MDNSTXTRecord struct
|
72
|
+
"""
|
73
|
+
return cg.RawExpression(
|
74
|
+
f"{{MDNS_STR({cg.safe_exp(key)}), MDNS_STR({cg.safe_exp(value)})}}"
|
66
75
|
)
|
67
76
|
|
68
77
|
|
78
|
+
async def _mdns_txt_record_templated(
|
79
|
+
mdns_comp: cg.Pvariable, key: str, value: Lambda | str
|
80
|
+
) -> cg.RawExpression:
|
81
|
+
"""Create a mDNS TXT record with support for templated values.
|
82
|
+
|
83
|
+
Internal helper function.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
mdns_comp: The MDNSComponent instance (from cg.get_variable())
|
87
|
+
key: The TXT record key
|
88
|
+
value: The TXT record value (can be a static string or a lambda template)
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
A RawExpression representing a MDNSTXTRecord struct
|
92
|
+
"""
|
93
|
+
if not cg.is_template(value):
|
94
|
+
# It's a static string - use directly in flash, no need to store in vector
|
95
|
+
return mdns_txt_record(key, value)
|
96
|
+
# It's a lambda - evaluate and store using helper
|
97
|
+
templated_value = await cg.templatable(value, [], cg.std_string)
|
98
|
+
safe_key = cg.safe_exp(key)
|
99
|
+
dynamic_call = f"{mdns_comp}->add_dynamic_txt_value(({templated_value})())"
|
100
|
+
return cg.RawExpression(f"{{MDNS_STR({safe_key}), MDNS_STR({dynamic_call})}}")
|
101
|
+
|
102
|
+
|
69
103
|
def mdns_service(
|
70
|
-
service: str, proto: str, port: int, txt_records: list[
|
71
|
-
):
|
104
|
+
service: str, proto: str, port: int, txt_records: list[cg.RawExpression]
|
105
|
+
) -> cg.StructInitializer:
|
106
|
+
"""Create a mDNS service.
|
107
|
+
|
108
|
+
Public API for external components. Do not remove.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
service: Service name (e.g., "_http")
|
112
|
+
proto: Protocol (e.g., "_tcp" or "_udp")
|
113
|
+
port: Port number
|
114
|
+
txt_records: List of MDNSTXTRecord expressions
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
A StructInitializer representing a MDNSService struct
|
118
|
+
"""
|
72
119
|
return cg.StructInitializer(
|
73
120
|
MDNSService,
|
74
121
|
("service_type", cg.RawExpression(f"MDNS_STR({cg.safe_exp(service)})")),
|
@@ -107,23 +154,37 @@ async def to_code(config):
|
|
107
154
|
# Ensure at least 1 service (fallback service)
|
108
155
|
cg.add_define("MDNS_SERVICE_COUNT", max(1, service_count))
|
109
156
|
|
157
|
+
# Calculate compile-time dynamic TXT value count
|
158
|
+
# Dynamic values are those that cannot be stored in flash at compile time
|
159
|
+
dynamic_txt_count = 0
|
160
|
+
if "api" in CORE.config:
|
161
|
+
# Always: get_mac_address()
|
162
|
+
dynamic_txt_count += 1
|
163
|
+
# User-provided templatable TXT values (only lambdas, not static strings)
|
164
|
+
dynamic_txt_count += sum(
|
165
|
+
1
|
166
|
+
for service in config[CONF_SERVICES]
|
167
|
+
for txt_value in service[CONF_TXT].values()
|
168
|
+
if cg.is_template(txt_value)
|
169
|
+
)
|
170
|
+
|
171
|
+
# Ensure at least 1 to avoid zero-size array
|
172
|
+
cg.add_define("MDNS_DYNAMIC_TXT_COUNT", max(1, dynamic_txt_count))
|
173
|
+
|
110
174
|
var = cg.new_Pvariable(config[CONF_ID])
|
111
175
|
await cg.register_component(var, config)
|
112
176
|
|
113
177
|
for service in config[CONF_SERVICES]:
|
114
|
-
|
115
|
-
|
116
|
-
MDNSTXTRecord,
|
117
|
-
("key", cg.RawExpression(f"MDNS_STR({cg.safe_exp(txt_key)})")),
|
118
|
-
("value", await cg.templatable(txt_value, [], cg.std_string)),
|
119
|
-
)
|
178
|
+
txt_records = [
|
179
|
+
await _mdns_txt_record_templated(var, txt_key, txt_value)
|
120
180
|
for txt_key, txt_value in service[CONF_TXT].items()
|
121
181
|
]
|
182
|
+
|
122
183
|
exp = mdns_service(
|
123
184
|
service[CONF_SERVICE],
|
124
185
|
service[CONF_PROTOCOL],
|
125
186
|
await cg.templatable(service[CONF_PORT], [], cg.uint16),
|
126
|
-
|
187
|
+
txt_records,
|
127
188
|
)
|
128
189
|
|
129
190
|
cg.add(var.add_extra_service(exp))
|
@@ -9,21 +9,9 @@
|
|
9
9
|
#include <pgmspace.h>
|
10
10
|
// Macro to define strings in PROGMEM on ESP8266, regular memory on other platforms
|
11
11
|
#define MDNS_STATIC_CONST_CHAR(name, value) static const char name[] PROGMEM = value
|
12
|
-
// Helper to convert PROGMEM string to std::string for TemplatableValue
|
13
|
-
// Only define this function if we have services that will use it
|
14
|
-
#if defined(USE_API) || defined(USE_PROMETHEUS) || defined(USE_WEBSERVER) || defined(USE_MDNS_EXTRA_SERVICES)
|
15
|
-
static std::string mdns_str_value(PGM_P str) {
|
16
|
-
char buf[64];
|
17
|
-
strncpy_P(buf, str, sizeof(buf) - 1);
|
18
|
-
buf[sizeof(buf) - 1] = '\0';
|
19
|
-
return std::string(buf);
|
20
|
-
}
|
21
|
-
#define MDNS_STR_VALUE(name) mdns_str_value(name)
|
22
|
-
#endif
|
23
12
|
#else
|
24
13
|
// On non-ESP8266 platforms, use regular const char*
|
25
14
|
#define MDNS_STATIC_CONST_CHAR(name, value) static constexpr const char name[] = value
|
26
|
-
#define MDNS_STR_VALUE(name) std::string(name)
|
27
15
|
#endif
|
28
16
|
|
29
17
|
#ifdef USE_API
|
@@ -43,30 +31,10 @@ static const char *const TAG = "mdns";
|
|
43
31
|
#endif
|
44
32
|
|
45
33
|
// Define all constant strings using the macro
|
46
|
-
MDNS_STATIC_CONST_CHAR(SERVICE_ESPHOMELIB, "_esphomelib");
|
47
34
|
MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
MDNS_STATIC_CONST_CHAR(TXT_FRIENDLY_NAME, "friendly_name");
|
52
|
-
MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
|
53
|
-
MDNS_STATIC_CONST_CHAR(TXT_MAC, "mac");
|
54
|
-
MDNS_STATIC_CONST_CHAR(TXT_PLATFORM, "platform");
|
55
|
-
MDNS_STATIC_CONST_CHAR(TXT_BOARD, "board");
|
56
|
-
MDNS_STATIC_CONST_CHAR(TXT_NETWORK, "network");
|
57
|
-
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION, "api_encryption");
|
58
|
-
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION_SUPPORTED, "api_encryption_supported");
|
59
|
-
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_NAME, "project_name");
|
60
|
-
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_VERSION, "project_version");
|
61
|
-
MDNS_STATIC_CONST_CHAR(TXT_PACKAGE_IMPORT_URL, "package_import_url");
|
62
|
-
|
63
|
-
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP8266, "ESP8266");
|
64
|
-
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP32, "ESP32");
|
65
|
-
MDNS_STATIC_CONST_CHAR(PLATFORM_RP2040, "RP2040");
|
66
|
-
|
67
|
-
MDNS_STATIC_CONST_CHAR(NETWORK_WIFI, "wifi");
|
68
|
-
MDNS_STATIC_CONST_CHAR(NETWORK_ETHERNET, "ethernet");
|
69
|
-
MDNS_STATIC_CONST_CHAR(NETWORK_THREAD, "thread");
|
35
|
+
|
36
|
+
// Wrap build-time defines into flash storage
|
37
|
+
MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
|
70
38
|
|
71
39
|
void MDNSComponent::compile_records_() {
|
72
40
|
this->hostname_ = App.get_name();
|
@@ -75,6 +43,15 @@ void MDNSComponent::compile_records_() {
|
|
75
43
|
// in mdns/__init__.py. If you add a new service here, update both locations.
|
76
44
|
|
77
45
|
#ifdef USE_API
|
46
|
+
MDNS_STATIC_CONST_CHAR(SERVICE_ESPHOMELIB, "_esphomelib");
|
47
|
+
MDNS_STATIC_CONST_CHAR(TXT_FRIENDLY_NAME, "friendly_name");
|
48
|
+
MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
|
49
|
+
MDNS_STATIC_CONST_CHAR(TXT_MAC, "mac");
|
50
|
+
MDNS_STATIC_CONST_CHAR(TXT_PLATFORM, "platform");
|
51
|
+
MDNS_STATIC_CONST_CHAR(TXT_BOARD, "board");
|
52
|
+
MDNS_STATIC_CONST_CHAR(TXT_NETWORK, "network");
|
53
|
+
MDNS_STATIC_CONST_CHAR(VALUE_BOARD, ESPHOME_BOARD);
|
54
|
+
|
78
55
|
if (api::global_api_server != nullptr) {
|
79
56
|
auto &service = this->services_.emplace_next();
|
80
57
|
service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
|
@@ -109,52 +86,66 @@ void MDNSComponent::compile_records_() {
|
|
109
86
|
txt_records.reserve(txt_count);
|
110
87
|
|
111
88
|
if (!friendly_name_empty) {
|
112
|
-
txt_records.push_back({MDNS_STR(TXT_FRIENDLY_NAME), friendly_name});
|
89
|
+
txt_records.push_back({MDNS_STR(TXT_FRIENDLY_NAME), MDNS_STR(friendly_name.c_str())});
|
113
90
|
}
|
114
|
-
txt_records.push_back({MDNS_STR(TXT_VERSION),
|
115
|
-
txt_records.push_back({MDNS_STR(TXT_MAC), get_mac_address()});
|
91
|
+
txt_records.push_back({MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)});
|
92
|
+
txt_records.push_back({MDNS_STR(TXT_MAC), MDNS_STR(this->add_dynamic_txt_value(get_mac_address()))});
|
116
93
|
|
117
94
|
#ifdef USE_ESP8266
|
118
|
-
|
95
|
+
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP8266, "ESP8266");
|
96
|
+
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP8266)});
|
119
97
|
#elif defined(USE_ESP32)
|
120
|
-
|
98
|
+
MDNS_STATIC_CONST_CHAR(PLATFORM_ESP32, "ESP32");
|
99
|
+
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP32)});
|
121
100
|
#elif defined(USE_RP2040)
|
122
|
-
|
101
|
+
MDNS_STATIC_CONST_CHAR(PLATFORM_RP2040, "RP2040");
|
102
|
+
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2040)});
|
123
103
|
#elif defined(USE_LIBRETINY)
|
124
|
-
txt_records.push_back({MDNS_STR(TXT_PLATFORM), lt_cpu_get_model_name()});
|
104
|
+
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(lt_cpu_get_model_name())});
|
125
105
|
#endif
|
126
106
|
|
127
|
-
txt_records.push_back({MDNS_STR(TXT_BOARD),
|
107
|
+
txt_records.push_back({MDNS_STR(TXT_BOARD), MDNS_STR(VALUE_BOARD)});
|
128
108
|
|
129
109
|
#if defined(USE_WIFI)
|
130
|
-
|
110
|
+
MDNS_STATIC_CONST_CHAR(NETWORK_WIFI, "wifi");
|
111
|
+
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_WIFI)});
|
131
112
|
#elif defined(USE_ETHERNET)
|
132
|
-
|
113
|
+
MDNS_STATIC_CONST_CHAR(NETWORK_ETHERNET, "ethernet");
|
114
|
+
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_ETHERNET)});
|
133
115
|
#elif defined(USE_OPENTHREAD)
|
134
|
-
|
116
|
+
MDNS_STATIC_CONST_CHAR(NETWORK_THREAD, "thread");
|
117
|
+
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_THREAD)});
|
135
118
|
#endif
|
136
119
|
|
137
120
|
#ifdef USE_API_NOISE
|
121
|
+
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION, "api_encryption");
|
122
|
+
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION_SUPPORTED, "api_encryption_supported");
|
138
123
|
MDNS_STATIC_CONST_CHAR(NOISE_ENCRYPTION, "Noise_NNpsk0_25519_ChaChaPoly_SHA256");
|
139
|
-
|
140
|
-
|
141
|
-
}
|
142
|
-
txt_records.push_back({MDNS_STR(TXT_API_ENCRYPTION_SUPPORTED), MDNS_STR_VALUE(NOISE_ENCRYPTION)});
|
143
|
-
}
|
124
|
+
bool has_psk = api::global_api_server->get_noise_ctx()->has_psk();
|
125
|
+
const char *encryption_key = has_psk ? TXT_API_ENCRYPTION : TXT_API_ENCRYPTION_SUPPORTED;
|
126
|
+
txt_records.push_back({MDNS_STR(encryption_key), MDNS_STR(NOISE_ENCRYPTION)});
|
144
127
|
#endif
|
145
128
|
|
146
129
|
#ifdef ESPHOME_PROJECT_NAME
|
147
|
-
|
148
|
-
|
130
|
+
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_NAME, "project_name");
|
131
|
+
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_VERSION, "project_version");
|
132
|
+
MDNS_STATIC_CONST_CHAR(VALUE_PROJECT_NAME, ESPHOME_PROJECT_NAME);
|
133
|
+
MDNS_STATIC_CONST_CHAR(VALUE_PROJECT_VERSION, ESPHOME_PROJECT_VERSION);
|
134
|
+
txt_records.push_back({MDNS_STR(TXT_PROJECT_NAME), MDNS_STR(VALUE_PROJECT_NAME)});
|
135
|
+
txt_records.push_back({MDNS_STR(TXT_PROJECT_VERSION), MDNS_STR(VALUE_PROJECT_VERSION)});
|
149
136
|
#endif // ESPHOME_PROJECT_NAME
|
150
137
|
|
151
138
|
#ifdef USE_DASHBOARD_IMPORT
|
152
|
-
|
139
|
+
MDNS_STATIC_CONST_CHAR(TXT_PACKAGE_IMPORT_URL, "package_import_url");
|
140
|
+
txt_records.push_back(
|
141
|
+
{MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url().c_str())});
|
153
142
|
#endif
|
154
143
|
}
|
155
144
|
#endif // USE_API
|
156
145
|
|
157
146
|
#ifdef USE_PROMETHEUS
|
147
|
+
MDNS_STATIC_CONST_CHAR(SERVICE_PROMETHEUS, "_prometheus-http");
|
148
|
+
|
158
149
|
auto &prom_service = this->services_.emplace_next();
|
159
150
|
prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
|
160
151
|
prom_service.proto = MDNS_STR(SERVICE_TCP);
|
@@ -162,6 +153,8 @@ void MDNSComponent::compile_records_() {
|
|
162
153
|
#endif
|
163
154
|
|
164
155
|
#ifdef USE_WEBSERVER
|
156
|
+
MDNS_STATIC_CONST_CHAR(SERVICE_HTTP, "_http");
|
157
|
+
|
165
158
|
auto &web_service = this->services_.emplace_next();
|
166
159
|
web_service.service_type = MDNS_STR(SERVICE_HTTP);
|
167
160
|
web_service.proto = MDNS_STR(SERVICE_TCP);
|
@@ -169,13 +162,16 @@ void MDNSComponent::compile_records_() {
|
|
169
162
|
#endif
|
170
163
|
|
171
164
|
#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES)
|
165
|
+
MDNS_STATIC_CONST_CHAR(SERVICE_HTTP, "_http");
|
166
|
+
MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
|
167
|
+
|
172
168
|
// Publish "http" service if not using native API or any other services
|
173
169
|
// This is just to have *some* mDNS service so that .local resolution works
|
174
170
|
auto &fallback_service = this->services_.emplace_next();
|
175
171
|
fallback_service.service_type = MDNS_STR(SERVICE_HTTP);
|
176
172
|
fallback_service.proto = MDNS_STR(SERVICE_TCP);
|
177
173
|
fallback_service.port = USE_WEBSERVER_PORT;
|
178
|
-
fallback_service.txt_records.push_back({MDNS_STR(TXT_VERSION),
|
174
|
+
fallback_service.txt_records.push_back({MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)});
|
179
175
|
#endif
|
180
176
|
}
|
181
177
|
|
@@ -190,8 +186,7 @@ void MDNSComponent::dump_config() {
|
|
190
186
|
ESP_LOGV(TAG, " - %s, %s, %d", MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto),
|
191
187
|
const_cast<TemplatableValue<uint16_t> &>(service.port).value());
|
192
188
|
for (const auto &record : service.txt_records) {
|
193
|
-
ESP_LOGV(TAG, " TXT: %s = %s", MDNS_STR_ARG(record.key),
|
194
|
-
const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
189
|
+
ESP_LOGV(TAG, " TXT: %s = %s", MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
195
190
|
}
|
196
191
|
}
|
197
192
|
#endif
|
@@ -27,7 +27,7 @@ struct MDNSString;
|
|
27
27
|
|
28
28
|
struct MDNSTXTRecord {
|
29
29
|
const MDNSString *key;
|
30
|
-
|
30
|
+
const MDNSString *value;
|
31
31
|
};
|
32
32
|
|
33
33
|
struct MDNSService {
|
@@ -59,6 +59,17 @@ class MDNSComponent : public Component {
|
|
59
59
|
|
60
60
|
void on_shutdown() override;
|
61
61
|
|
62
|
+
/// Add a dynamic TXT value and return pointer to it for use in MDNSTXTRecord
|
63
|
+
const char *add_dynamic_txt_value(const std::string &value) {
|
64
|
+
this->dynamic_txt_values_.push_back(value);
|
65
|
+
return this->dynamic_txt_values_[this->dynamic_txt_values_.size() - 1].c_str();
|
66
|
+
}
|
67
|
+
|
68
|
+
/// Storage for runtime-generated TXT values (MAC address, user lambdas)
|
69
|
+
/// Pre-sized at compile time via MDNS_DYNAMIC_TXT_COUNT to avoid heap allocations.
|
70
|
+
/// Static/compile-time values (version, board, etc.) are stored directly in flash and don't use this.
|
71
|
+
StaticVector<std::string, MDNS_DYNAMIC_TXT_COUNT> dynamic_txt_values_;
|
72
|
+
|
62
73
|
protected:
|
63
74
|
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
|
64
75
|
std::string hostname_;
|