mirror of
https://github.com/esphome/esphome.git
synced 2025-10-12 14:53:49 +01:00
store mdns values in flash
This commit is contained in:
@@ -58,11 +58,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def mdns_txt_record(key: str, value: str):
|
||||
def mdns_txt_record_static(key: str, value: str):
|
||||
"""Create a TXT record with a static (compile-time) value stored in flash."""
|
||||
return cg.StructInitializer(
|
||||
MDNSTXTRecord,
|
||||
("key", cg.RawExpression(f"MDNS_STR({cg.safe_exp(key)})")),
|
||||
("value", value),
|
||||
("value", cg.RawExpression(f"MDNS_STR({cg.safe_exp(value)})")),
|
||||
)
|
||||
|
||||
|
||||
def mdns_txt_record_dynamic(key: str, value_expr: str):
|
||||
"""Create a TXT record with a dynamic value (will be evaluated and stored in vector)."""
|
||||
return cg.StructInitializer(
|
||||
MDNSTXTRecord,
|
||||
("key", cg.RawExpression(f"MDNS_STR({cg.safe_exp(key)})")),
|
||||
("value", cg.RawExpression(f"MDNS_STR({value_expr})")),
|
||||
)
|
||||
|
||||
|
||||
@@ -107,23 +117,56 @@ async def to_code(config):
|
||||
# Ensure at least 1 service (fallback service)
|
||||
cg.add_define("MDNS_SERVICE_COUNT", max(1, service_count))
|
||||
|
||||
# Calculate compile-time dynamic TXT value count
|
||||
# Dynamic values are those that cannot be stored in flash at compile time
|
||||
dynamic_txt_count = 0
|
||||
if "api" in CORE.config:
|
||||
# Always: get_mac_address()
|
||||
dynamic_txt_count += 1
|
||||
# Conditional: friendly_name (if not empty, but we conservatively count it)
|
||||
dynamic_txt_count += 1
|
||||
# Conditional: dashboard_import_url
|
||||
if "dashboard_import" in CORE.config:
|
||||
dynamic_txt_count += 1
|
||||
# User-provided templatable TXT values (only lambdas, not static strings)
|
||||
dynamic_txt_count += sum(
|
||||
1
|
||||
for service in config[CONF_SERVICES]
|
||||
for txt_value in service[CONF_TXT].values()
|
||||
if cg.is_template(txt_value)
|
||||
)
|
||||
|
||||
# Ensure at least 1 to avoid zero-size array
|
||||
cg.add_define("MDNS_DYNAMIC_TXT_COUNT", max(1, dynamic_txt_count))
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
for service in config[CONF_SERVICES]:
|
||||
txt = [
|
||||
cg.StructInitializer(
|
||||
MDNSTXTRecord,
|
||||
("key", cg.RawExpression(f"MDNS_STR({cg.safe_exp(txt_key)})")),
|
||||
("value", await cg.templatable(txt_value, [], cg.std_string)),
|
||||
)
|
||||
for txt_key, txt_value in service[CONF_TXT].items()
|
||||
]
|
||||
# Build the txt records list for the service
|
||||
txt_records = []
|
||||
for txt_key, txt_value in service[CONF_TXT].items():
|
||||
if cg.is_template(txt_value):
|
||||
# It's a lambda - evaluate and store using helper
|
||||
templated_value = await cg.templatable(txt_value, [], cg.std_string)
|
||||
txt_records.append(
|
||||
cg.RawExpression(
|
||||
f"{{MDNS_STR({cg.safe_exp(txt_key)}), MDNS_STR({var}->add_dynamic_txt_value(({templated_value})()))}}"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# It's a static string - use directly in flash, no need to store in vector
|
||||
txt_records.append(
|
||||
cg.RawExpression(
|
||||
f"{{MDNS_STR({cg.safe_exp(txt_key)}), MDNS_STR({cg.safe_exp(txt_value)})}}"
|
||||
)
|
||||
)
|
||||
|
||||
exp = mdns_service(
|
||||
service[CONF_SERVICE],
|
||||
service[CONF_PROTOCOL],
|
||||
await cg.templatable(service[CONF_PORT], [], cg.uint16),
|
||||
txt,
|
||||
txt_records,
|
||||
)
|
||||
|
||||
cg.add(var.add_extra_service(exp))
|
||||
|
@@ -9,21 +9,9 @@
|
||||
#include <pgmspace.h>
|
||||
// Macro to define strings in PROGMEM on ESP8266, regular memory on other platforms
|
||||
#define MDNS_STATIC_CONST_CHAR(name, value) static const char name[] PROGMEM = value
|
||||
// Helper to convert PROGMEM string to std::string for TemplatableValue
|
||||
// Only define this function if we have services that will use it
|
||||
#if defined(USE_API) || defined(USE_PROMETHEUS) || defined(USE_WEBSERVER) || defined(USE_MDNS_EXTRA_SERVICES)
|
||||
static std::string mdns_str_value(PGM_P str) {
|
||||
char buf[64];
|
||||
strncpy_P(buf, str, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
return std::string(buf);
|
||||
}
|
||||
#define MDNS_STR_VALUE(name) mdns_str_value(name)
|
||||
#endif
|
||||
#else
|
||||
// On non-ESP8266 platforms, use regular const char*
|
||||
#define MDNS_STATIC_CONST_CHAR(name, value) static constexpr const char name[] = value
|
||||
#define MDNS_STR_VALUE(name) std::string(name)
|
||||
#endif
|
||||
|
||||
#ifdef USE_API
|
||||
@@ -109,47 +97,48 @@ void MDNSComponent::compile_records_() {
|
||||
txt_records.reserve(txt_count);
|
||||
|
||||
if (!friendly_name_empty) {
|
||||
txt_records.push_back({MDNS_STR(TXT_FRIENDLY_NAME), friendly_name});
|
||||
txt_records.push_back({MDNS_STR(TXT_FRIENDLY_NAME), MDNS_STR(this->add_dynamic_txt_value(friendly_name))});
|
||||
}
|
||||
txt_records.push_back({MDNS_STR(TXT_VERSION), ESPHOME_VERSION});
|
||||
txt_records.push_back({MDNS_STR(TXT_MAC), get_mac_address()});
|
||||
txt_records.push_back({MDNS_STR(TXT_VERSION), MDNS_STR(ESPHOME_VERSION)});
|
||||
txt_records.push_back({MDNS_STR(TXT_MAC), MDNS_STR(this->add_dynamic_txt_value(get_mac_address()))});
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR_VALUE(PLATFORM_ESP8266)});
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP8266)});
|
||||
#elif defined(USE_ESP32)
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR_VALUE(PLATFORM_ESP32)});
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP32)});
|
||||
#elif defined(USE_RP2040)
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR_VALUE(PLATFORM_RP2040)});
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2040)});
|
||||
#elif defined(USE_LIBRETINY)
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), lt_cpu_get_model_name()});
|
||||
txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(lt_cpu_get_model_name())});
|
||||
#endif
|
||||
|
||||
txt_records.push_back({MDNS_STR(TXT_BOARD), ESPHOME_BOARD});
|
||||
txt_records.push_back({MDNS_STR(TXT_BOARD), MDNS_STR(ESPHOME_BOARD)});
|
||||
|
||||
#if defined(USE_WIFI)
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR_VALUE(NETWORK_WIFI)});
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_WIFI)});
|
||||
#elif defined(USE_ETHERNET)
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR_VALUE(NETWORK_ETHERNET)});
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_ETHERNET)});
|
||||
#elif defined(USE_OPENTHREAD)
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR_VALUE(NETWORK_THREAD)});
|
||||
txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_THREAD)});
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
MDNS_STATIC_CONST_CHAR(NOISE_ENCRYPTION, "Noise_NNpsk0_25519_ChaChaPoly_SHA256");
|
||||
if (api::global_api_server->get_noise_ctx()->has_psk()) {
|
||||
txt_records.push_back({MDNS_STR(TXT_API_ENCRYPTION), MDNS_STR_VALUE(NOISE_ENCRYPTION)});
|
||||
txt_records.push_back({MDNS_STR(TXT_API_ENCRYPTION), MDNS_STR(NOISE_ENCRYPTION)});
|
||||
} else {
|
||||
txt_records.push_back({MDNS_STR(TXT_API_ENCRYPTION_SUPPORTED), MDNS_STR_VALUE(NOISE_ENCRYPTION)});
|
||||
txt_records.push_back({MDNS_STR(TXT_API_ENCRYPTION_SUPPORTED), MDNS_STR(NOISE_ENCRYPTION)});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
txt_records.push_back({MDNS_STR(TXT_PROJECT_NAME), ESPHOME_PROJECT_NAME});
|
||||
txt_records.push_back({MDNS_STR(TXT_PROJECT_VERSION), ESPHOME_PROJECT_VERSION});
|
||||
txt_records.push_back({MDNS_STR(TXT_PROJECT_NAME), MDNS_STR(ESPHOME_PROJECT_NAME)});
|
||||
txt_records.push_back({MDNS_STR(TXT_PROJECT_VERSION), MDNS_STR(ESPHOME_PROJECT_VERSION)});
|
||||
#endif // ESPHOME_PROJECT_NAME
|
||||
|
||||
#ifdef USE_DASHBOARD_IMPORT
|
||||
txt_records.push_back({MDNS_STR(TXT_PACKAGE_IMPORT_URL), dashboard_import::get_package_import_url()});
|
||||
txt_records.push_back({MDNS_STR(TXT_PACKAGE_IMPORT_URL),
|
||||
MDNS_STR(this->add_dynamic_txt_value(dashboard_import::get_package_import_url()))});
|
||||
#endif
|
||||
}
|
||||
#endif // USE_API
|
||||
@@ -175,7 +164,7 @@ void MDNSComponent::compile_records_() {
|
||||
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), ESPHOME_VERSION});
|
||||
fallback_service.txt_records.push_back({MDNS_STR(TXT_VERSION), MDNS_STR(ESPHOME_VERSION)});
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -190,8 +179,7 @@ void MDNSComponent::dump_config() {
|
||||
ESP_LOGV(TAG, " - %s, %s, %d", MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto),
|
||||
const_cast<TemplatableValue<uint16_t> &>(service.port).value());
|
||||
for (const auto &record : service.txt_records) {
|
||||
ESP_LOGV(TAG, " TXT: %s = %s", MDNS_STR_ARG(record.key),
|
||||
const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
||||
ESP_LOGV(TAG, " TXT: %s = %s", MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@@ -27,7 +27,7 @@ struct MDNSString;
|
||||
|
||||
struct MDNSTXTRecord {
|
||||
const MDNSString *key;
|
||||
TemplatableValue<std::string> value;
|
||||
const MDNSString *value;
|
||||
};
|
||||
|
||||
struct MDNSService {
|
||||
@@ -59,6 +59,14 @@ class MDNSComponent : public Component {
|
||||
|
||||
void on_shutdown() override;
|
||||
|
||||
/// Add a dynamic TXT value and return pointer to it for use in MDNSTXTRecord
|
||||
const char *add_dynamic_txt_value(const std::string &value) {
|
||||
this->dynamic_txt_values_.push_back(value);
|
||||
return this->dynamic_txt_values_.back().c_str();
|
||||
}
|
||||
|
||||
StaticVector<std::string, MDNS_DYNAMIC_TXT_COUNT> dynamic_txt_values_;
|
||||
|
||||
protected:
|
||||
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
|
||||
std::string hostname_;
|
||||
|
@@ -29,10 +29,10 @@ void MDNSComponent::setup() {
|
||||
std::vector<mdns_txt_item_t> txt_records;
|
||||
for (const auto &record : service.txt_records) {
|
||||
mdns_txt_item_t it{};
|
||||
// key is a compile-time string literal in flash, no need to strdup
|
||||
// key and value are either compile-time string literals in flash or pointers to dynamic_txt_values_
|
||||
// ESP-IDF requires strdup for both to keep them alive during mdns operation
|
||||
it.key = MDNS_STR_ARG(record.key);
|
||||
// value is a temporary from TemplatableValue, must strdup to keep it alive
|
||||
it.value = strdup(const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
||||
it.value = strdup(MDNS_STR_ARG(record.value));
|
||||
txt_records.push_back(it);
|
||||
}
|
||||
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
||||
|
@@ -33,7 +33,7 @@ void MDNSComponent::setup() {
|
||||
MDNS.addService(FPSTR(service_type), FPSTR(proto), port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(FPSTR(service_type), FPSTR(proto), FPSTR(MDNS_STR_ARG(record.key)),
|
||||
const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
||||
FPSTR(MDNS_STR_ARG(record.value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -32,8 +32,7 @@ void MDNSComponent::setup() {
|
||||
uint16_t port_ = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
||||
MDNS.addService(service_type, proto, port_);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key),
|
||||
const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
||||
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -32,8 +32,7 @@ void MDNSComponent::setup() {
|
||||
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
||||
MDNS.addService(service_type, proto, port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key),
|
||||
const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
||||
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -146,6 +146,9 @@ template<typename T, size_t N> class StaticVector {
|
||||
T &operator[](size_t i) { return data_[i]; }
|
||||
const T &operator[](size_t i) const { return data_[i]; }
|
||||
|
||||
T &back() { return data_[count_ - 1]; }
|
||||
const T &back() const { return data_[count_ - 1]; }
|
||||
|
||||
// For range-based for loops
|
||||
iterator begin() { return data_.begin(); }
|
||||
iterator end() { return data_.begin() + count_; }
|
||||
|
@@ -25,6 +25,9 @@ mdns:
|
||||
- service: _http
|
||||
protocol: _tcp
|
||||
port: 80
|
||||
txt:
|
||||
version: "1.0"
|
||||
path: "/"
|
||||
|
||||
# OTA should run at priority 54 (after mdns)
|
||||
ota:
|
||||
|
Reference in New Issue
Block a user