24#ifdef USE_DASHBOARD_IMPORT
34static constexpr uint8_t MAX_RESENDS_PER_LOOP = 8;
37PROGMEM_STRING_TABLE(MQTTDisconnectReasonStrings,
"TCP disconnected",
"Unacceptable Protocol Version",
38 "Identifier Rejected",
"Server Unavailable",
"Malformed Credentials",
"Not Authorized",
39 "Not Enough Space",
"TLS Bad Fingerprint",
"DNS Resolve Error",
"Unknown");
43 char mac_addr[MAC_ADDRESS_BUFFER_SIZE];
46 char client_id[MAX_NAME_WITH_SUFFIX_SIZE];
48 MAC_ADDRESS_BUFFER_SIZE - 1);
54static constexpr size_t ESPHOME_TOPIC_BUFFER_SIZE =
sizeof(
"esphome/discover/") + ESPHOME_DEVICE_NAME_MAX_LEN;
56static void format_esphome_topic(
char (&topic)[ESPHOME_TOPIC_BUFFER_SIZE],
const char *kind) {
57 buf_append_printf(topic,
sizeof(topic), 0,
"esphome/%s/%s", kind,
App.
get_name().
c_str());
62 [
this](
const char *topic,
const char *payload,
size_t len,
size_t index,
size_t total) {
72 if (
len + index == total) {
86 this, [](
void *self, uint8_t level,
const char *tag,
const char *
message,
size_t message_len) {
89#ifdef USE_MQTT_CRASH_LOG
102 [
this](
const std::string &topic,
const std::string &payload) { this->
send_device_info_(); }, 2);
104 static_assert(
sizeof(
"esphome/ping/") + ESPHOME_DEVICE_NAME_MAX_LEN <= MQTT_DEFAULT_TOPIC_MAX_LEN);
105 MQTTTopicSource ping_topic{[](
const void *, std::span<char, MQTT_DEFAULT_TOPIC_MAX_LEN> buf) {
106 size_t len = buf_append_printf(buf.data(), buf.size(), 0,
"esphome/ping/%s",
108 return StringRef(buf.data(),
len);
112 ping_topic, [
this](
const std::string &topic,
const std::string &payload) { this->
send_device_info_(); }, 2);
125 char topic[ESPHOME_TOPIC_BUFFER_SIZE];
126 format_esphome_topic(topic,
"discover");
131 [](JsonObject root) {
136 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
142 buf_append_printf(
key,
sizeof(
key), 0,
"ip%u", index);
156 root[ESPHOME_F(
"version")] = ESPHOME_VERSION;
157 char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
159 root[ESPHOME_F(
"mac")] = mac_buf;
162 root[ESPHOME_F(
"platform")] = ESPHOME_F(
"ESP8266");
165 root[ESPHOME_F(
"platform")] = ESPHOME_F(
"ESP32");
168 root[ESPHOME_F(
"platform")] = lt_cpu_get_model_name();
171 root[ESPHOME_F(
"board")] = ESPHOME_BOARD;
173 root[ESPHOME_F(
"network")] = ESPHOME_F(
"wifi");
174#elif defined(USE_ETHERNET)
175 root[ESPHOME_F(
"network")] = ESPHOME_F(
"ethernet");
178#ifdef ESPHOME_PROJECT_NAME
179 root[ESPHOME_F(
"project_name")] = ESPHOME_PROJECT_NAME;
180 root[ESPHOME_F(
"project_version")] = ESPHOME_PROJECT_VERSION;
183#ifdef USE_DASHBOARD_IMPORT
189 : ESPHOME_F(
"api_encryption_supported")] =
190 ESPHOME_F(
"Noise_NNpsk0_25519_ChaChaPoly_SHA256");
202 this->log_message_.retain);
208 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
212 " Server Address: %s:%u (%s)\n"
213 " Username: " LOG_SECRET(
"'%s'")
"\n"
214 " Client ID: " LOG_SECRET(
"'%s'")
"\n"
215 " Clean Session: %s",
217 this->credentials_.username.c_str(), this->credentials_.client_id.c_str(),
225 " WebSocket Path: '%s'",
230#if defined(USE_MQTT_CERT_BUNDLE)
231 ESP_LOGCONFIG(TAG,
" TLS: certificate bundle");
232#elif defined(USE_MQTT_TLS_INSECURE)
233 ESP_LOGCONFIG(TAG,
" TLS: no verification");
234#elif defined(USE_MQTT_TLS)
235 ESP_LOGCONFIG(TAG,
" TLS: CA certificate");
238 ESP_LOGCONFIG(TAG,
" Discovery IP enabled");
242 " Discovery prefix: '%s'\n"
243 " Discovery retain: %s",
246 ESP_LOGCONFIG(TAG,
" Topic Prefix: '%s'", this->
topic_prefix_.c_str());
261 subscription.subscribed =
false;
262 subscription.resubscribe_timeout = 0;
274 this, LWIP_DNS_ADDRTYPE_IPV6_IPV4);
277 this, LWIP_DNS_ADDRTYPE_IPV4);
284 this->
ip_ = network::IPAddress(&addr);
288 case ERR_INPROGRESS: {
290 ESP_LOGD(TAG,
"Resolving broker IP address");
296 ESP_LOGW(TAG,
"Error resolving broker IP address: %d", err);
321 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
322 ESP_LOGD(TAG,
"Resolved broker IP address to %s", this->
ip_.
str_to(ip_buf));
325#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1
331 if (ipaddr ==
nullptr) {
332 a_this->dns_resolve_error_ =
true;
334 a_this->ip_ = network::IPAddress(ipaddr);
335 a_this->dns_resolved_ =
true;
343 ESP_LOGI(TAG,
"Connecting");
349 const char *username =
nullptr;
352 const char *password =
nullptr;
361 this->last_will_.payload.c_str());
384 ESP_LOGI(TAG,
"Connected");
389#ifdef USE_MQTT_CRASH_LOG
392 char topic[ESPHOME_TOPIC_BUFFER_SIZE];
393 format_esphome_topic(topic,
"logs");
408 const LogString *reason_s = MQTTDisconnectReasonStrings::get_log_str(
409 static_cast<uint8_t
>(*this->
disconnect_reason_), MQTTDisconnectReasonStrings::LAST_INDEX);
411 reason_s = LOG_STR(
"WiFi disconnected");
413 ESP_LOGW(TAG,
"Disconnected: %s", LOG_STR_ARG(reason_s));
436 ESP_LOGW(TAG,
"Lost client connection");
449 uint8_t resend_count = 0;
453 if (++resend_count >= MAX_RESENDS_PER_LOOP)
463 ESP_LOGE(TAG,
"Can't connect; restarting");
478 ESP_LOGV(TAG,
"subscribe(topic='%s')", topic);
481 ESP_LOGV(TAG,
"Subscribe failed for topic='%s'. Will retry", topic);
488 char buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
489 return this->
subscribe_(topic.build(buf).c_str(), qos);
497 bool do_resub = sub->resubscribe_timeout == 0 || now - sub->resubscribe_timeout > 1000;
501 sub->resubscribe_timeout = now;
505 for (
auto &subscription : this->subscriptions_) {
510#ifdef USE_MQTT_CRASH_LOG
516 char logs_topic[ESPHOME_TOPIC_BUFFER_SIZE];
517 format_esphome_topic(logs_topic,
"logs");
518 if (topic == logs_topic)
523static StringRef static_topic_builder(
const void *context, std::span<char, MQTT_DEFAULT_TOPIC_MAX_LEN>) {
524 return StringRef(
static_cast<const char *
>(context));
527static StringRef copied_topic_builder(
const void *context, std::span<char, MQTT_DEFAULT_TOPIC_MAX_LEN>) {
528 return StringRef(
static_cast<const char *
>(context));
531MQTTTopicSource
static_topic(
const char *topic) {
return {static_topic_builder, topic}; }
533static_assert(MQTT_DEFAULT_TOPIC_MAX_LEN == 248,
"update MQTT_DEFAULT_TOPIC_MAX_LEN in mqtt/__init__.py");
537static StringRef progmem_topic_builder(
const void *context, std::span<char, MQTT_DEFAULT_TOPIC_MAX_LEN> buf) {
538 size_t len =
buf_append_str_p(buf.data(), buf.size(), 0,
static_cast<const char *
>(context));
539 return StringRef(buf.data(),
len);
543 return {progmem_topic_builder,
reinterpret_cast<const char *
>(topic)};
548 char *copy =
new char[topic.size() + 1];
549 memcpy(copy, topic.c_str(), topic.size());
550 copy[topic.size()] =
'\0';
551 return {copied_topic_builder, copy};
556 auto &subscription = this->subscriptions_.emplace_back(MQTTSubscription{
558 .callback = callback,
559 .resubscribe_timeout = 0,
569__attribute__((noinline))
static bool topic_equals(
const MQTTTopicSource &source,
const std::string &topic) {
570 char buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
571 return source.build(buf) == topic;
578 ESP_LOGV(TAG,
"unsubscribe(topic='%s')", topic.c_str());
581 ESP_LOGV(TAG,
"Unsubscribe failed for topic='%s'.", topic.c_str());
585 for (
auto &subscription : this->subscriptions_) {
586 if (topic_equals(subscription.topic, topic))
587 subscription.removed =
true;
598 auto it = this->subscriptions_.begin();
599 while (it != this->subscriptions_.end()) {
602 it->callback.free_boxed();
603 if (it->topic.builder == copied_topic_builder)
604 delete[]
static_cast<const char *
>(it->topic.context);
605 it = this->subscriptions_.erase(it);
614 return this->
publish(topic, payload.data(), payload.size(), qos, retain);
619 return this->
publish(topic.c_str(), payload, payload_length, qos, retain);
628 return this->
publish_json(topic.c_str(), f, qos, retain);
636 size_t topic_len = strlen(topic);
647 if (!logging_topic) {
649 ESP_LOGV(TAG,
"Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos);
650 ESP_LOGVV(TAG,
"Publish payload (len=%u): '%.*s'", payload_length,
static_cast<int>(payload_length), payload);
652 ESP_LOGV(TAG,
"Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length);
667 ESP_LOGD(TAG,
"Enabling");
676 ESP_LOGD(TAG,
"Disabling");
682__attribute__((noinline))
static bool topic_matches(
const char *
message,
const MQTTTopicSource &topic) {
683 char buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
707 this->
defer([
this, topic, payload]() {
709#ifdef USE_MQTT_CRASH_LOG
717 const size_t count = this->subscriptions_.size();
718 for (
size_t i = 0; i <
count; i++) {
719 const auto &subscription = this->subscriptions_[i];
720 if (!subscription.removed && topic_matches(topic.c_str(), subscription.topic))
721 subscription.callback.call(topic, payload);
754 if (this->
birth_message_.
topic.empty() || this->birth_message_.topic != this->last_will_.topic) {
777 bool discover_ip,
bool clean) {
792 .discover_ip =
false,
812 auto callback_copy = callback;
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
const StringRef & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void status_set_warning()
void status_momentary_warning(uint32_t length=5000)
Set warning status flag and clear it after length ms.
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
void status_clear_warning()
constexpr const char * c_str() const
constexpr bool empty() const
uint16_t get_port() const
noise::NoiseContext & get_noise_ctx()
void add_log_callback(void *instance, void(*fn)(void *, uint8_t, const char *, const char *, size_t))
Register a log callback to receive log messages.
void set_keep_alive(uint16_t keep_alive) final
void set_on_message(std::function< on_message_callback_t > &&callback) final
MQTTTransport get_transport() const
void set_client_id(const char *client_id) final
void set_on_connect(std::function< on_connect_callback_t > &&callback) final
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final
bool subscribe(const char *topic, uint8_t qos) final
void set_server(network::IPAddress ip, uint16_t port) final
const char * get_ws_path() const
bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final
void set_clean_session(bool clean_session) final
bool unsubscribe(const char *topic) final
bool connected() const final
void set_on_disconnect(std::function< on_disconnect_callback_t > &&callback) final
void set_credentials(const char *username, const char *password) final
void set_birth_message(MQTTMessage &&message)
Set the birth message.
MQTTMessage shutdown_message_
bool is_log_message_enabled() const
void start_connect_()
Reconnect to the MQTT broker if not already connected.
void setup() override
Setup the MQTT client, registering a bunch of callbacks and attempting to connect.
void disable_discovery()
Globally disable Home Assistant discovery.
void recalculate_availability_()
Re-calculate the availability property.
void erase_removed_subscriptions_()
void subscribe(MQTTTopicSource topic, F &&callback, uint8_t qos=0)
Subscribe to an MQTT topic and call callback when a message is received.
void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool discover_ip, bool clean=false)
Set the Home Assistant discovery info.
bool can_proceed() override
float get_setup_priority() const override
MQTT client setup priority.
void disable_log_message()
Get the topic used for logging. Defaults to "<topic_prefix>/debug" and the value is cached for speed.
const std::string & get_topic_prefix() const
Get the topic prefix of this device, using default if necessary.
void register_mqtt_component(MQTTComponent *component)
bool removed_while_dispatching_
bool is_discovery_ip_enabled() const
void set_last_will(MQTTMessage &&message)
Set the last will testament message.
bool publish(const MQTTMessage &message)
Publish a MQTTMessage.
void set_topic_prefix(std::string topic_prefix)
Set the topic prefix that will be prepended to all topics together with "/".
const MQTTDiscoveryInfo & get_discovery_info() const
Get Home Assistant discovery info.
void disable_birth_message()
Remove the birth message.
static void dns_found_callback(const char *name, ip_addr_t *ipaddr, void *callback_arg)
void on_shutdown() override
MQTTMessage last_will_
The last will message.
void set_shutdown_message(MQTTMessage &&message)
MQTTMessage birth_message_
The birth message (e.g.
MQTTCredentials credentials_
std::vector< MQTTComponent * > children_
void dump_config() override
void add_subscription_(MQTTTopicSource topic, mqtt_subscription_callback_t callback, bool boxed, uint8_t qos)
void set_on_connect(mqtt_on_connect_callback_t &&callback)
optional< MQTTClientDisconnectReason > disconnect_reason_
void resubscribe_subscriptions_()
static MQTTTopicSource copy_topic(StringRef topic)
A heap copy of topic, owned by the one subscription it is passed to and freed when that is erased.
bool subscribe_topic_(const MQTTTopicSource &topic, uint8_t qos)
void disable_shutdown_message()
void unsubscribe(const std::string &topic)
Unsubscribe from an MQTT topic.
void on_message(const std::string &topic, const std::string &payload)
void set_log_message_template(MQTTMessage &&message)
Manually set the topic used for logging.
void resubscribe_subscription_(MQTTSubscription *sub)
bool wait_for_connection_
void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback)
bool subscribe_(const char *topic, uint8_t qos)
const Availability & get_availability()
std::string topic_prefix_
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len)
std::vector< MQTTSubscription > subscriptions_
bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos=0, bool retain=false)
Construct and send a JSON MQTT message.
bool is_discovery_enabled() const
MQTTBackendESP32 mqtt_backend_
void disable_last_will()
Remove the last will testament message.
void set_keep_alive(uint16_t keep_alive_s)
Set the keep alive time in seconds, every 0.7*keep_alive a ping will be sent.
void loop() override
Reconnect if required.
std::string payload_buffer_
MQTTDiscoveryInfo discovery_info_
The discovery info options for Home Assistant.
CallbackManager< MQTTBackend::on_disconnect_callback_t > on_disconnect_
void handle_crash_log_request_(const std::string &topic, const std::string &payload)
friend class MQTTComponent
Availability availability_
Caches availability.
const Component * component
const LogString * message
PROGMEM_STRING_TABLE(AlarmControlPanelStateStrings, "DISARMED", "ARMED_HOME", "ARMED_AWAY", "ARMED_NIGHT", "ARMED_VACATION", "ARMED_CUSTOM_BYPASS", "PENDING", "ARMING", "DISARMING", "TRIGGERED", "UNKNOWN")
ESPHOME_LOG_TAG(TAG, "alarm_control_panel")
APIServer * global_api_server
esphome::climate::Climate __attribute__
ProgmemStr get_package_import_url()
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
SerializationBuffer build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
bool crash_log_pending()
Whether a crash record from before this boot is waiting to be reported.
void log_crash_to_client()
Log the previous crash for a client that just subscribed to the logs.
std::function< MQTTBackend::on_disconnect_callback_t > mqtt_on_disconnect_callback_t
MQTTDiscoveryObjectIdGenerator
available discovery object_id generators
@ MQTT_NONE_OBJECT_ID_GENERATOR
MQTTTopicSource progmem_topic(ProgmemStr topic)
A topic in flash, copied into the buffer when needed, so at most MQTT_DEFAULT_TOPIC_MAX_LEN - 1 chara...
bool topic_match(const char *message, const char *subscription)
True if the message topic matches the subscription, with + and # wildcards.
MQTTDiscoveryUniqueIdGenerator
available discovery unique_id generators
@ MQTT_LEGACY_UNIQUE_ID_GENERATOR
MQTTTransport
Transport for the broker connection; only the ESP-IDF backend supports WebSocket.
Callback< void(const std::string &, const std::string &)> mqtt_subscription_callback_t
Stored callback of an MQTT subscription: the topic, then the payload.
MQTTClientDisconnectReason
MQTTClientComponent * global_mqtt_client
@ MQTT_CLIENT_DISCONNECTED
@ MQTT_CLIENT_RESOLVING_ADDRESS
MQTTTopicSource static_topic(const char *topic)
A topic string that outlives the subscription, such as a literal; it is referenced,...
std::function< MQTTBackend::on_connect_callback_t > mqtt_on_connect_callback_t
Callback for MQTT events.
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
network::IPAddresses get_ip_addresses()
bool is_disabled()
Return whether the network is disabled: every configured interface with a disable() lifecycle (modem,...
constexpr float AFTER_WIFI
For components that should be initialized after WiFi is connected.
size_t buf_append_str_p(char *buf, size_t size, size_t pos, PGM_P str)
Safely append a PROGMEM string to buffer, returning new position (capped at size).
size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, const char *suffix_ptr, size_t suffix_len)
Format name + separator + suffix directly into buffer without heap allocation.
void get_mac_address_into_buffer(std::span< char, MAC_ADDRESS_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in lowercase hex notation.
void HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
const __FlashStringHelper * ProgmemStr
std::string payload_not_available
std::string topic
Empty means disabled.
std::string payload_available
std::string address
The address of the server without port number.
bool clean_session
Whether the session will be cleaned or remembered between connects.
std::string client_id
The client ID. Will automatically be truncated to 23 characters.
MQTTDiscoveryUniqueIdGenerator unique_id_generator
bool discover_ip
Enable the Home Assistant device discovery.
std::string prefix
The Home Assistant discovery prefix. Empty means disabled.
MQTTDiscoveryObjectIdGenerator object_id_generator
bool retain
Whether to retain discovery messages.
char * str_to(char *buf) const