23#ifdef USE_DASHBOARD_IMPORT
29static const char *
const TAG =
"mqtt";
33static constexpr uint8_t MAX_RESENDS_PER_LOOP = 8;
36PROGMEM_STRING_TABLE(MQTTDisconnectReasonStrings,
"TCP disconnected",
"Unacceptable Protocol Version",
37 "Identifier Rejected",
"Server Unavailable",
"Malformed Credentials",
"Not Authorized",
38 "Not Enough Space",
"TLS Bad Fingerprint",
"DNS Resolve Error",
"Unknown");
42 char mac_addr[MAC_ADDRESS_BUFFER_SIZE];
45 char client_id[MAX_NAME_WITH_SUFFIX_SIZE];
47 MAC_ADDRESS_BUFFER_SIZE - 1);
54 [
this](
const char *topic,
const char *payload,
size_t len,
size_t index,
size_t total) {
64 if (
len + index == total) {
78 this, [](
void *self, uint8_t level,
const char *tag,
const char *
message,
size_t message_len) {
86 "esphome/discover", [
this](
const std::string &topic,
const std::string &payload) { this->
send_device_info_(); },
91 constexpr size_t ping_topic_buffer_size = 13 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
92 char ping_topic[ping_topic_buffer_size];
93 buf_append_printf(ping_topic,
sizeof(ping_topic), 0,
"esphome/ping/%s",
App.
get_name().
c_str());
95 ping_topic, [
this](
const std::string &topic,
const std::string &payload) { this->
send_device_info_(); }, 2);
109 constexpr size_t topic_buffer_size = 17 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
110 char topic[topic_buffer_size];
111 buf_append_printf(topic,
sizeof(topic), 0,
"esphome/discover/%s",
App.
get_name().
c_str());
116 [](JsonObject root) {
121 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
127 buf_append_printf(key,
sizeof(key), 0,
"ip%u", index);
141 root[ESPHOME_F(
"version")] = ESPHOME_VERSION;
142 char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
144 root[ESPHOME_F(
"mac")] = mac_buf;
147 root[ESPHOME_F(
"platform")] = ESPHOME_F(
"ESP8266");
150 root[ESPHOME_F(
"platform")] = ESPHOME_F(
"ESP32");
153 root[ESPHOME_F(
"platform")] = lt_cpu_get_model_name();
156 root[ESPHOME_F(
"board")] = ESPHOME_BOARD;
158 root[ESPHOME_F(
"network")] = ESPHOME_F(
"wifi");
159#elif defined(USE_ETHERNET)
160 root[ESPHOME_F(
"network")] = ESPHOME_F(
"ethernet");
163#ifdef ESPHOME_PROJECT_NAME
164 root[ESPHOME_F(
"project_name")] = ESPHOME_PROJECT_NAME;
165 root[ESPHOME_F(
"project_version")] = ESPHOME_PROJECT_VERSION;
168#ifdef USE_DASHBOARD_IMPORT
174 : ESPHOME_F(
"api_encryption_supported")] =
175 ESPHOME_F(
"Noise_NNpsk0_25519_ChaChaPoly_SHA256");
187 this->log_message_.retain);
193 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
197 " Server Address: %s:%u (%s)\n"
198 " Username: " LOG_SECRET(
"'%s'")
"\n"
199 " Client ID: " LOG_SECRET(
"'%s'")
"\n"
200 " Clean Session: %s",
202 this->credentials_.username.c_str(), this->credentials_.client_id.c_str(),
206 ESP_LOGCONFIG(TAG,
" Discovery IP enabled");
210 " Discovery prefix: '%s'\n"
211 " Discovery retain: %s",
214 ESP_LOGCONFIG(TAG,
" Topic Prefix: '%s'", this->
topic_prefix_.c_str());
229 subscription.subscribed =
false;
230 subscription.resubscribe_timeout = 0;
242 this, LWIP_DNS_ADDRTYPE_IPV6_IPV4);
245 this, LWIP_DNS_ADDRTYPE_IPV4);
252 this->
ip_ = network::IPAddress(&addr);
256 case ERR_INPROGRESS: {
258 ESP_LOGD(TAG,
"Resolving broker IP address");
264 ESP_LOGW(TAG,
"Error resolving broker IP address: %d", err);
289 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
290 ESP_LOGD(TAG,
"Resolved broker IP address to %s", this->
ip_.
str_to(ip_buf));
293#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1
299 if (ipaddr ==
nullptr) {
300 a_this->dns_resolve_error_ =
true;
302 a_this->ip_ = network::IPAddress(ipaddr);
303 a_this->dns_resolved_ =
true;
311 ESP_LOGI(TAG,
"Connecting");
317 const char *username =
nullptr;
320 const char *password =
nullptr;
329 this->last_will_.payload.c_str());
352 ESP_LOGI(TAG,
"Connected");
368 const LogString *reason_s = MQTTDisconnectReasonStrings::get_log_str(
369 static_cast<uint8_t
>(*this->
disconnect_reason_), MQTTDisconnectReasonStrings::LAST_INDEX);
371 reason_s = LOG_STR(
"WiFi disconnected");
373 ESP_LOGW(TAG,
"Disconnected: %s", LOG_STR_ARG(reason_s));
396 ESP_LOGW(TAG,
"Lost client connection");
409 uint8_t resend_count = 0;
410 for (MQTTComponent *
component : this->children_) {
413 if (++resend_count >= MAX_RESENDS_PER_LOOP)
423 ESP_LOGE(TAG,
"Can't connect; restarting");
438 ESP_LOGV(TAG,
"subscribe(topic='%s')", topic);
441 ESP_LOGV(TAG,
"Subscribe failed for topic='%s'. Will retry", topic);
451 bool do_resub = sub->resubscribe_timeout == 0 || now - sub->resubscribe_timeout > 1000;
454 sub->subscribed = this->
subscribe_(sub->topic.c_str(), sub->qos);
455 sub->resubscribe_timeout = now;
459 for (
auto &subscription : this->subscriptions_) {
465 MQTTSubscription subscription{
468 .callback = std::move(callback),
470 .resubscribe_timeout = 0,
473 this->subscriptions_.push_back(subscription);
477 auto f = [callback](
const std::string &topic,
const std::string &payload) {
479 callback(topic, root);
483 MQTTSubscription subscription{
488 .resubscribe_timeout = 0,
491 this->subscriptions_.push_back(subscription);
498 ESP_LOGV(TAG,
"unsubscribe(topic='%s')", topic.c_str());
501 ESP_LOGV(TAG,
"Unsubscribe failed for topic='%s'.", topic.c_str());
507 if (it->topic == topic) {
517 return this->
publish(topic, payload.data(), payload.size(), qos, retain);
522 return this->
publish(topic.c_str(), payload, payload_length, qos, retain);
531 return this->
publish_json(topic.c_str(), f, qos, retain);
539 size_t topic_len = strlen(topic);
550 if (!logging_topic) {
552 ESP_LOGV(TAG,
"Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos);
553 ESP_LOGVV(TAG,
"Publish payload (len=%u): '%.*s'", payload_length,
static_cast<int>(payload_length), payload);
555 ESP_LOGV(TAG,
"Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length);
570 ESP_LOGD(TAG,
"Enabling");
579 ESP_LOGD(TAG,
"Disabling");
595static bool topic_match(
const char *
message,
const char *subscription,
bool is_normal,
bool past_separator) {
597 if (*
message ==
'\0' && *subscription ==
'\0')
601 if (*
message ==
'\0' || *subscription ==
'\0')
604 bool do_wildcards = is_normal || past_separator;
606 if (*subscription ==
'+' && do_wildcards) {
616 return topic_match(
message, subscription, is_normal,
true);
619 if (*subscription ==
'#' && do_wildcards) {
628 past_separator = past_separator || *subscription ==
'/';
634 return topic_match(
message, subscription, is_normal, past_separator);
637static bool topic_match(
const char *
message,
const char *subscription) {
661 this->
defer([
this, topic, payload]() {
663 for (
auto &subscription : this->subscriptions_) {
664 if (topic_match(topic.c_str(), subscription.topic.c_str()))
665 subscription.callback(topic, payload);
681 char buf[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
700 if (this->
birth_message_.
topic.empty() || this->birth_message_.topic != this->last_will_.topic) {
723 bool discover_ip,
bool clean) {
738 .discover_ip =
false,
758 auto callback_copy = callback;
767void MQTTMessageTrigger::setup() {
770 [
this](
const std::string &topic,
const std::string &payload) {
771 if (this->payload_.has_value() && payload != *this->payload_) {
775 this->trigger(payload);
779void MQTTMessageTrigger::dump_config() {
781 "MQTT Message Trigger:\n"
784 this->topic_.c_str(), this->qos_);
786float MQTTMessageTrigger::get_setup_priority()
const {
return setup_priority::AFTER_CONNECTION; }
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().
bool is_name_add_mac_suffix_enabled() const
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(const char *name, uint32_t length=5000)
Set warning status flag and automatically clear it after a timeout.
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
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
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 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 subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos=0)
Subscribe to a MQTT topic and automatically parse JSON payload.
void register_mqtt_component(MQTTComponent *component)
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.
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 subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos=0)
Subscribe to an MQTT topic and call callback when a message is received.
void on_shutdown() override
MQTTMessage last_will_
The last will message.
void set_topic_prefix(const std::string &topic_prefix, const std::string &check_topic_prefix)
Set the topic prefix that will be prepended to all topics together with "/".
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 set_on_connect(mqtt_on_connect_callback_t &&callback)
optional< MQTTClientDisconnectReason > disconnect_reason_
void resubscribe_subscriptions_()
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_
Availability availability_
Caches availability.
MQTTMessageTrigger(std::string topic)
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")
APIServer * global_api_server
ProgmemStr get_package_import_url()
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
bool parse_json(const std::string &data, const json_parse_t &f)
Parse a JSON string and run the provided json parse function if it's valid.
SerializationBuffer build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
std::function< MQTTBackend::on_disconnect_callback_t > mqtt_on_disconnect_callback_t
MQTTDiscoveryObjectIdGenerator
available discovery object_id generators
@ MQTT_NONE_OBJECT_ID_GENERATOR
MQTTDiscoveryUniqueIdGenerator
available discovery unique_id generators
@ MQTT_LEGACY_UNIQUE_ID_GENERATOR
std::function< void(const std::string &, JsonObject)> mqtt_json_callback_t
std::function< void(const std::string &, const std::string &)> mqtt_callback_t
Callback for MQTT subscriptions.
MQTTClientDisconnectReason
MQTTClientComponent * global_mqtt_client
@ MQTT_CLIENT_DISCONNECTED
@ MQTT_CLIENT_RESOLVING_ADDRESS
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 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.
char * str_sanitize_to(char *buffer, size_t buffer_size, const char *str)
Sanitize a string to buffer, keeping only alphanumerics, dashes, and underscores.
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.
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