esphome 2025.10.0b1__py3-none-any.whl → 2025.10.0b3__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.
Potentially problematic release.
This version of esphome might be problematic. Click here for more details.
- esphome/__main__.py +16 -6
- 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/psram/__init__.py +2 -0
- esphome/components/speaker/media_player/__init__.py +16 -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/writer.py +16 -9
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b3.dist-info}/METADATA +3 -3
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b3.dist-info}/RECORD +44 -44
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b3.dist-info}/WHEEL +0 -0
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b3.dist-info}/entry_points.txt +0 -0
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b3.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.10.0b1.dist-info → esphome-2025.10.0b3.dist-info}/top_level.txt +0 -0
esphome/__main__.py
CHANGED
|
@@ -268,8 +268,10 @@ def has_ip_address() -> bool:
|
|
|
268
268
|
|
|
269
269
|
|
|
270
270
|
def has_resolvable_address() -> bool:
|
|
271
|
-
"""Check if CORE.address is resolvable (via mDNS or is an IP address)."""
|
|
272
|
-
|
|
271
|
+
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
|
|
272
|
+
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
|
|
273
|
+
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
|
|
274
|
+
return CORE.address is not None
|
|
273
275
|
|
|
274
276
|
|
|
275
277
|
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
|
|
@@ -578,11 +580,12 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
|
|
|
578
580
|
if has_api():
|
|
579
581
|
addresses_to_use: list[str] | None = None
|
|
580
582
|
|
|
581
|
-
if port_type == "NETWORK"
|
|
583
|
+
if port_type == "NETWORK":
|
|
584
|
+
# Network addresses (IPs, mDNS names, or regular DNS hostnames) can be used
|
|
585
|
+
# The resolve_ip_address() function in helpers.py handles all types
|
|
582
586
|
addresses_to_use = devices
|
|
583
|
-
elif port_type in ("
|
|
584
|
-
#
|
|
585
|
-
# (for MQTT/MQTTIP types, or for NETWORK when mdns/ip check fails)
|
|
587
|
+
elif port_type in ("MQTT", "MQTTIP") and has_mqtt_ip_lookup():
|
|
588
|
+
# Use MQTT IP lookup for MQTT/MQTTIP types
|
|
586
589
|
addresses_to_use = mqtt_get_ip(
|
|
587
590
|
config, args.username, args.password, args.client_id
|
|
588
591
|
)
|
|
@@ -1002,6 +1005,12 @@ def parse_args(argv):
|
|
|
1002
1005
|
action="append",
|
|
1003
1006
|
default=[],
|
|
1004
1007
|
)
|
|
1008
|
+
options_parser.add_argument(
|
|
1009
|
+
"--testing-mode",
|
|
1010
|
+
help="Enable testing mode (disables validation checks for grouped component testing)",
|
|
1011
|
+
action="store_true",
|
|
1012
|
+
default=False,
|
|
1013
|
+
)
|
|
1005
1014
|
|
|
1006
1015
|
parser = argparse.ArgumentParser(
|
|
1007
1016
|
description=f"ESPHome {const.__version__}", parents=[options_parser]
|
|
@@ -1260,6 +1269,7 @@ def run_esphome(argv):
|
|
|
1260
1269
|
|
|
1261
1270
|
args = parse_args(argv)
|
|
1262
1271
|
CORE.dashboard = args.dashboard
|
|
1272
|
+
CORE.testing_mode = args.testing_mode
|
|
1263
1273
|
|
|
1264
1274
|
# Create address cache from command-line arguments
|
|
1265
1275
|
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
|