15static const char *
const TAG =
"mqtt.component";
41 char buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
58 char sanitized_name[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
61 char object_id_buf[OBJECT_ID_MAX_LEN];
68 p =
append_str(p, comp_type, strlen(comp_type));
70 p =
append_str(p, sanitized_name, strlen(sanitized_name));
76 return StringRef(buf.data(), p - buf.data());
80 size_t suffix_len)
const {
82 if (topic_prefix.empty()) {
87 char object_id_buf[OBJECT_ID_MAX_LEN];
92 p =
append_str(p, topic_prefix.data(), topic_prefix.size());
94 p =
append_str(p, comp_type, strlen(comp_type));
101 return StringRef(buf.data(), p - buf.data());
105 char buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
107 return std::string(ref.
c_str(), ref.
size());
127 char buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
129 return std::string(ref.
c_str(), ref.
size());
133 char buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
135 return std::string(ref.
c_str(), ref.
size());
139 return this->
publish(topic.c_str(), payload.data(), payload.size());
143 return this->
publish(topic.c_str(), payload, payload_length);
147 if (topic[0] ==
'\0')
153 return this->
publish(topic, payload, strlen(payload));
158 return this->
publish(topic.c_str(), payload);
162 if (topic[0] ==
'\0')
166 strncpy_P(buf,
reinterpret_cast<const char *
>(payload),
sizeof(buf) - 1);
167 buf[
sizeof(buf) - 1] =
'\0';
177 if (topic[0] ==
'\0')
185 char discovery_topic_buf[MQTT_DISCOVERY_TOPIC_MAX_LEN];
188 if (discovery_info.
clean) {
189 ESP_LOGV(TAG,
"'%s': Cleaning discovery", this->
friendly_name_().c_str());
193 ESP_LOGV(TAG,
"'%s': Sending discovery", this->
friendly_name_().c_str());
197 discovery_topic.
c_str(),
198 [
this](JsonObject root) {
199 SendDiscoveryConfig config;
200 config.state_topic = true;
201 config.command_topic = true;
203 this->send_discovery(root, config);
205 if (this->subscribe_qos_ != 0) {
206 root[MQTT_QOS] = this->subscribe_qos_;
210 root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name_() :
StringRef();
213 root[MQTT_ENABLED_BY_DEFAULT] =
false;
214 char icon_buf[MAX_ICON_LENGTH];
216 if (icon[0] !=
'\0') {
217 root[MQTT_ICON] = icon;
219 char dc_buf[MAX_DEVICE_CLASS_LENGTH];
222 root[MQTT_DEVICE_CLASS] = dc;
227 root[MQTT_ENTITY_CATEGORY] = EntityCategoryMqttStrings::get_progmem_str(
231 if (config.state_topic) {
232 char state_topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
235 if (config.command_topic) {
236 char command_topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
240 root[MQTT_COMMAND_RETAIN] =
true;
244 if (!avail.
topic.empty()) {
245 root[MQTT_AVAILABILITY_TOPIC] = avail.
topic;
253 char object_id_buf[OBJECT_ID_MAX_LEN];
256 char friendly_name_hash[9];
257 buf_append_printf(friendly_name_hash,
sizeof(friendly_name_hash), 0,
"%08" PRIx32,
261 char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
262 char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
264 buf_append_printf(unique_id,
sizeof(unique_id), 0,
"%s-%s-%s", mac_buf, this->
component_type(),
266 root[MQTT_UNIQUE_ID] = unique_id;
271 char unique_id_buf[3 + MQTT_COMPONENT_TYPE_MAX_LEN + OBJECT_ID_MAX_LEN + 1];
272 buf_append_printf(unique_id_buf,
sizeof(unique_id_buf), 0,
"ESP%s%s", this->
component_type(),
274 root[MQTT_UNIQUE_ID] = unique_id_buf;
280 char object_id_full[ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1];
281 buf_append_printf(object_id_full,
sizeof(object_id_full), 0,
"%s_%s", node_name.c_str(), object_id.
c_str());
282 root[MQTT_OBJECT_ID] = object_id_full;
286 const auto &node_friendly_name = friendly_name_ref.
empty() ? node_name : friendly_name_ref;
289 JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
290 char mac[MAC_ADDRESS_BUFFER_SIZE];
292 device_info[MQTT_DEVICE_IDENTIFIERS] = mac;
293 device_info[MQTT_DEVICE_NAME] = node_friendly_name;
294#ifdef ESPHOME_PROJECT_NAME
295 device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION
" (ESPHome " ESPHOME_VERSION
")";
296 const char *model = std::strchr(ESPHOME_PROJECT_NAME,
'.');
297 device_info[MQTT_DEVICE_MODEL] = model ==
nullptr ? ESPHOME_BOARD : model + 1;
298 if (model ==
nullptr) {
299 device_info[MQTT_DEVICE_MANUFACTURER] = ESPHOME_PROJECT_NAME;
304 char manufacturer[
sizeof(ESPHOME_PROJECT_NAME)];
305 size_t len = model - ESPHOME_PROJECT_NAME;
306 memcpy(manufacturer, ESPHOME_PROJECT_NAME,
len);
307 manufacturer[
len] =
'\0';
308 device_info[MQTT_DEVICE_MANUFACTURER] = manufacturer;
311 static const char ver_fmt[] PROGMEM = ESPHOME_VERSION
" (config hash 0x%08" PRIx32
")";
314 char version_buf[
sizeof(ver_fmt) + 8];
320 device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
321 device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
322#if defined(USE_ESP8266) || defined(USE_ESP32)
323 device_info[MQTT_DEVICE_MANUFACTURER] =
"Espressif";
324#elif defined(USE_RP2)
325 device_info[MQTT_DEVICE_MANUFACTURER] =
"Raspberry Pi";
326#elif defined(USE_BK72XX)
327 device_info[MQTT_DEVICE_MANUFACTURER] =
"Beken";
328#elif defined(USE_RTL87XX)
329 device_info[MQTT_DEVICE_MANUFACTURER] =
"Realtek";
330#elif defined(USE_HOST)
331 device_info[MQTT_DEVICE_MANUFACTURER] =
"Host";
334 if (node_area[0] !=
'\0') {
335 device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
338 device_info[MQTT_DEVICE_CONNECTIONS][0][0] =
"mac";
339 device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac;
341 this->qos_, discovery_info.retain);
345bool MQTTComponent::is_discovery_enabled()
const {
349void MQTTComponent::subscribe(
const std::string &topic,
mqtt_callback_t callback, uint8_t qos) {
353void MQTTComponent::subscribe_json(
const std::string &topic,
const mqtt_json_callback_t &callback, uint8_t qos) {
357MQTTComponent::MQTTComponent() =
default;
360void MQTTComponent::disable_discovery() { this->discovery_enabled_ =
false; }
361void MQTTComponent::set_command_retain(
bool command_retain) { this->command_retain_ = command_retain; }
363void MQTTComponent::set_availability(std::string topic, std::string payload_available,
364 std::string payload_not_available) {
365 this->availability_ = make_unique<Availability>();
366 this->availability_->topic = std::move(topic);
367 this->availability_->payload_available = std::move(payload_available);
368 this->availability_->payload_not_available = std::move(payload_not_available);
370void MQTTComponent::disable_availability() { this->set_availability(
"",
"",
""); }
371void MQTTComponent::call_setup() {
373 this->is_internal_ = this->compute_is_internal_();
374 if (this->is_internal_)
381 if (!this->is_connected_())
384 if (this->is_discovery_enabled()) {
385 if (!this->send_discovery_()) {
386 this->schedule_resend_state();
389 if (!this->send_initial_state()) {
390 this->schedule_resend_state();
394void MQTTComponent::process_resend() {
397 if (!this->resend_state_)
400 this->resend_state_ =
false;
401 if (this->is_discovery_enabled()) {
402 if (!this->send_discovery_()) {
403 this->schedule_resend_state();
406 if (!this->send_initial_state()) {
407 this->schedule_resend_state();
410void MQTTComponent::schedule_resend_state() { this->resend_state_ =
true; }
414const StringRef &MQTTComponent::friendly_name_()
const {
return this->get_entity()->get_name(); }
415StringRef MQTTComponent::get_default_object_id_to_(std::span<char, OBJECT_ID_MAX_LEN> buf)
const {
416 return this->get_entity()->get_object_id_to(buf);
418bool MQTTComponent::is_disabled_by_default_()
const {
return this->get_entity()->is_disabled_by_default(); }
419bool MQTTComponent::compute_is_internal_() {
420 if (this->custom_state_topic_.has_value()) {
424 return this->custom_state_topic_.is_empty();
427 if (this->custom_command_topic_.has_value()) {
431 return this->custom_command_topic_.is_empty();
441 return this->get_entity()->is_internal();
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
uint32_t get_config_hash()
Get the config hash as a 32-bit integer.
const StringRef & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
const char * get_area() const
Get the area of this Application set by pre_setup().
const char * get_device_class_to(std::span< char, MAX_DEVICE_CLASS_LENGTH > buffer) const
EntityCategory get_entity_category() const
StringRef is a reference to a string owned by something else.
constexpr const char * c_str() const
constexpr bool empty() const
constexpr size_type size() const
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 publish(const MQTTMessage &message)
Publish a MQTTMessage.
const MQTTDiscoveryInfo & get_discovery_info() const
Get Home Assistant discovery info.
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.
const Availability & get_availability()
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
MQTTComponent is the base class for all components that interact with MQTT to expose certain function...
StringRef get_command_topic_to_(std::span< char, MQTT_DEFAULT_TOPIC_MAX_LEN > buf) const
Get the MQTT command topic into a buffer (no heap allocation for non-lambda custom topics).
TemplatableValue< std::string > custom_state_topic_
TemplatableValue< std::string > custom_command_topic_
std::unique_ptr< Availability > availability_
StringRef get_discovery_topic_to_(std::span< char, MQTT_DISCOVERY_TOPIC_MAX_LEN > buf, const MQTTDiscoveryInfo &discovery_info) const
Helper method to get the discovery topic for this component into a buffer.
bool is_disabled_by_default_() const
Get whether the underlying Entity is disabled by default.
const char * get_icon_to_(std::span< char, MAX_ICON_LENGTH > buf) const
Get the icon field of this component into a stack buffer.
void set_qos(uint8_t qos)
Set QOS for state messages.
StringRef get_default_topic_for_to_(std::span< char, MQTT_DEFAULT_TOPIC_MAX_LEN > buf, const char *suffix, size_t suffix_len) const
Get this components state/command/... topic into a buffer.
bool publish(const std::string &topic, const std::string &payload)
Send a MQTT message.
bool send_discovery_()
Internal method to start sending discovery info, this will call send_discovery().
void set_subscribe_qos(uint8_t qos)
Set the QOS for subscribe messages (used in discovery).
bool publish_json(const std::string &topic, const json::json_build_t &f)
Construct and send a JSON MQTT message.
std::string get_default_topic_for_(const std::string &suffix) const
Get this components state/command/... topic (allocates std::string).
StringRef get_default_object_id_to_(std::span< char, OBJECT_ID_MAX_LEN > buf) const
Get the object ID for this MQTT component, writing to the provided buffer.
void set_retain(bool retain)
Set whether state message should be retained.
virtual const EntityBase * get_entity() const =0
Gets the Entity served by this MQTT component.
std::string get_state_topic_() const
Get the MQTT topic that new states will be shared to (allocates std::string).
StringRef get_state_topic_to_(std::span< char, MQTT_DEFAULT_TOPIC_MAX_LEN > buf) const
Get the MQTT state topic into a buffer (no heap allocation for non-lambda custom topics).
const StringRef & friendly_name_() const
Get the friendly name of this MQTT component.
std::string get_command_topic_() const
Get the MQTT topic for listening to commands (allocates std::string).
virtual const char * component_type() const =0
Override this method to return the component type (e.g. "light", "sensor", ...)
PROGMEM_STRING_TABLE(AlarmControlPanelStateStrings, "DISARMED", "ARMED_HOME", "ARMED_AWAY", "ARMED_NIGHT", "ARMED_VACATION", "ARMED_CUSTOM_BYPASS", "PENDING", "ARMING", "DISARMING", "TRIGGERED", "UNKNOWN")
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
@ MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR
@ MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR
std::function< void(const std::string &, JsonObject)> mqtt_json_callback_t
char * append_char(char *p, char c)
char * append_str(char *p, const char *s, size_t len)
std::function< void(const std::string &, const std::string &)> mqtt_callback_t
Callback for MQTT subscriptions.
MQTTClientComponent * global_mqtt_client
void log_mqtt_component(const char *tag, MQTTComponent *obj, bool state_topic, bool command_topic)
constexpr float AFTER_CONNECTION
For components that should be initialized after a data connection (API/MQTT) is connected.
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
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.
Application App
Global storage of Application pointer - only one Application can exist.
const __FlashStringHelper * ProgmemStr
Simple data struct for Home Assistant component availability.
std::string payload_not_available
std::string topic
Empty means disabled.
std::string payload_available
Internal struct for MQTT Home Assistant discovery.
MQTTDiscoveryUniqueIdGenerator unique_id_generator
std::string prefix
The Home Assistant discovery prefix. Empty means disabled.
MQTTDiscoveryObjectIdGenerator object_id_generator