ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
mqtt_fan.cpp
Go to the documentation of this file.
1#include "mqtt_fan.h"
2#include "esphome/core/log.h"
4
5#include "mqtt_const.h"
6
7#ifdef USE_MQTT
8#ifdef USE_FAN
9
10namespace esphome::mqtt {
11
12static const char *const TAG = "mqtt.fan";
13
14using namespace esphome::fan;
15
16static ProgmemStr fan_direction_to_mqtt_str(FanDirection direction) {
17 return direction == FanDirection::FORWARD ? ESPHOME_F("forward") : ESPHOME_F("reverse");
18}
19
20static ProgmemStr fan_oscillation_to_mqtt_str(bool oscillating) {
21 return oscillating ? ESPHOME_F("oscillate_on") : ESPHOME_F("oscillate_off");
22}
23
25
26Fan *MQTTFanComponent::get_state() const { return this->state_; }
28const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; }
29
31 this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
32 auto val = parse_on_off(payload.c_str());
33 switch (val) {
34 case PARSE_ON:
35 ESP_LOGD(TAG, "'%s' Turning Fan ON.", this->friendly_name_().c_str());
36 this->state_->turn_on().perform();
37 break;
38 case PARSE_OFF:
39 ESP_LOGD(TAG, "'%s' Turning Fan OFF.", this->friendly_name_().c_str());
40 this->state_->turn_off().perform();
41 break;
42 case PARSE_TOGGLE:
43 ESP_LOGD(TAG, "'%s' Toggling Fan.", this->friendly_name_().c_str());
44 this->state_->toggle().perform();
45 break;
46 case PARSE_NONE:
47 default:
48 ESP_LOGW(TAG, "Unknown state payload %s", payload.c_str());
49 this->status_momentary_warning("state", 5000);
50 break;
51 }
52 });
53
54 if (this->state_->get_traits().supports_direction()) {
55 this->subscribe(this->get_direction_command_topic(), [this](const std::string &topic, const std::string &payload) {
56 auto val = parse_on_off(payload.c_str(), "forward", "reverse");
57 switch (val) {
58 case PARSE_ON:
59 ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name_().c_str());
61 break;
62 case PARSE_OFF:
63 ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name_().c_str());
65 break;
66 case PARSE_TOGGLE:
67 this->state_->make_call()
70 .perform();
71 break;
72 case PARSE_NONE:
73 ESP_LOGW(TAG, "Unknown direction Payload %s", payload.c_str());
74 this->status_momentary_warning("direction", 5000);
75 break;
76 }
77 });
78 }
79
80 if (this->state_->get_traits().supports_oscillation()) {
81 this->subscribe(this->get_oscillation_command_topic(),
82 [this](const std::string &topic, const std::string &payload) {
83 auto val = parse_on_off(payload.c_str(), "oscillate_on", "oscillate_off");
84 switch (val) {
85 case PARSE_ON:
86 ESP_LOGD(TAG, "'%s': Setting oscillating ON", this->friendly_name_().c_str());
87 this->state_->make_call().set_oscillating(true).perform();
88 break;
89 case PARSE_OFF:
90 ESP_LOGD(TAG, "'%s': Setting oscillating OFF", this->friendly_name_().c_str());
91 this->state_->make_call().set_oscillating(false).perform();
92 break;
93 case PARSE_TOGGLE:
95 break;
96 case PARSE_NONE:
97 ESP_LOGW(TAG, "Unknown Oscillation Payload %s", payload.c_str());
98 this->status_momentary_warning("oscillation", 5000);
99 break;
100 }
101 });
102 }
103
104 if (this->state_->get_traits().supports_speed()) {
105 this->subscribe(this->get_speed_level_command_topic(),
106 [this](const std::string &topic, const std::string &payload) {
107 optional<int> speed_level_opt = parse_number<int>(payload);
108 if (speed_level_opt.has_value()) {
109 const int speed_level = speed_level_opt.value();
110 if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) {
111 ESP_LOGD(TAG, "New speed level %d", speed_level);
112 this->state_->make_call().set_speed(speed_level).perform();
113 } else {
114 ESP_LOGW(TAG, "Invalid speed level %d", speed_level);
115 this->status_momentary_warning("speed", 5000);
116 }
117 } else {
118 ESP_LOGW(TAG, "Invalid speed level %s (int expected)", payload.c_str());
119 this->status_momentary_warning("speed", 5000);
120 }
121 });
122 }
123
124 auto f = std::bind(&MQTTFanComponent::publish_state, this);
125 this->state_->add_on_state_callback([this, f]() { this->defer("send", f); });
126}
127
129 ESP_LOGCONFIG(TAG, "MQTT Fan '%s': ", this->state_->get_name().c_str());
130 LOG_MQTT_COMPONENT(true, true);
131 if (this->state_->get_traits().supports_direction()) {
132 ESP_LOGCONFIG(TAG,
133 " Direction State Topic: '%s'\n"
134 " Direction Command Topic: '%s'",
135 this->get_direction_state_topic().c_str(), this->get_direction_command_topic().c_str());
136 }
137 if (this->state_->get_traits().supports_oscillation()) {
138 ESP_LOGCONFIG(TAG,
139 " Oscillation State Topic: '%s'\n"
140 " Oscillation Command Topic: '%s'",
141 this->get_oscillation_state_topic().c_str(), this->get_oscillation_command_topic().c_str());
142 }
143 if (this->state_->get_traits().supports_speed()) {
144 ESP_LOGCONFIG(TAG,
145 " Speed Level State Topic: '%s'\n"
146 " Speed Level Command Topic: '%s'",
147 this->get_speed_level_state_topic().c_str(), this->get_speed_level_command_topic().c_str());
148 }
149}
150
152
154 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
155 if (this->state_->get_traits().supports_direction()) {
156 root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic();
157 root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic();
158 }
159 if (this->state_->get_traits().supports_oscillation()) {
160 root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic();
161 root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic();
162 }
163 if (this->state_->get_traits().supports_speed()) {
164 root[MQTT_PERCENTAGE_COMMAND_TOPIC] = this->get_speed_level_command_topic();
165 root[MQTT_PERCENTAGE_STATE_TOPIC] = this->get_speed_level_state_topic();
166 root[MQTT_SPEED_RANGE_MAX] = this->state_->get_traits().supported_speed_count();
167 }
168}
170 char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
171 const char *state_s = this->state_->state ? "ON" : "OFF";
172 ESP_LOGD(TAG, "'%s' Sending state %s.", this->state_->get_name().c_str(), state_s);
173 this->publish(this->get_state_topic_to_(topic_buf), state_s);
174 bool failed = false;
175 if (this->state_->get_traits().supports_direction()) {
176 bool success = this->publish(this->get_direction_state_topic_to(topic_buf),
177 fan_direction_to_mqtt_str(this->state_->direction));
178 failed = failed || !success;
179 }
180 if (this->state_->get_traits().supports_oscillation()) {
181 bool success = this->publish(this->get_oscillation_state_topic_to(topic_buf),
182 fan_oscillation_to_mqtt_str(this->state_->oscillating));
183 failed = failed || !success;
184 }
185 auto traits = this->state_->get_traits();
186 if (traits.supports_speed()) {
187 char buf[12];
188 size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
189 bool success = this->publish(this->get_speed_level_state_topic_to(topic_buf), buf, len);
190 failed = failed || !success;
191 }
192 return !failed;
193}
194
195} // namespace esphome::mqtt
196
197#endif
198#endif // USE_MQTT
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition component.h:493
void status_momentary_warning(const char *name, uint32_t length=5000)
Set warning status flag and automatically clear it after a timeout.
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std uint32_t uint8_t std::function< RetryResult(uint8_t)> && f
Definition component.h:387
const StringRef & get_name() const
constexpr const char * c_str() const
Definition string_ref.h:73
FanCall & set_oscillating(bool oscillating)
Definition fan.h:51
FanCall & set_direction(FanDirection direction)
Definition fan.h:65
FanCall & set_speed(int speed)
Definition fan.h:60
FanCall turn_on()
Definition fan.cpp:141
FanCall turn_off()
Definition fan.cpp:142
FanCall make_call()
Definition fan.cpp:144
virtual FanTraits get_traits()=0
FanCall toggle()
Definition fan.cpp:143
void add_on_state_callback(std::function< void()> &&callback)
Register a callback that will be called each time the state changes.
Definition fan.cpp:196
FanDirection direction
The current direction of the fan.
Definition fan.h:117
bool oscillating
The current oscillation state of the fan.
Definition fan.h:113
bool state
The current on/off state of the fan.
Definition fan.h:111
int speed
The current fan speed level.
Definition fan.h:115
int supported_speed_count() const
Return how many speed levels the fan has.
Definition fan_traits.h:26
bool supports_direction() const
Return if this fan supports changing direction.
Definition fan_traits.h:30
bool supports_speed() const
Return if this fan supports speed modes.
Definition fan_traits.h:22
bool supports_oscillation() const
Return if this fan supports oscillation.
Definition fan_traits.h:18
bool publish(const std::string &topic, const std::string &payload)
Send a MQTT message.
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).
void subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos=0)
Subscribe to a MQTT topic.
fan::Fan * get_state() const
Definition mqtt_fan.cpp:26
state state state state void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override
Definition mqtt_fan.cpp:153
bool send_initial_state() override
Send the full current state to MQTT.
Definition mqtt_fan.cpp:151
void setup() override
Setup the fan subscriptions and discovery.
MQTTFanComponent(fan::Fan *state)
Definition mqtt_fan.cpp:24
bool has_value() const
Definition optional.h:92
value_type const & value() const
Definition optional.h:94
FanDirection direction
Definition fan.h:5
bool oscillating
Definition fan.h:4
bool state
Definition fan.h:2
mopeka_std_values val[4]
FanDirection
Simple enum to represent the direction of a fan.
Definition fan.h:21
MQTT_COMPONENT_TYPE(MQTTAlarmControlPanelComponent, "alarm_control_panel") const EntityBase *MQTTAlarmControlPanelComponent
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off)
Parse a string that contains either on, off or toggle.
Definition helpers.cpp:454
std::string size_t len
Definition helpers.h:817
optional< T > parse_number(const char *str)
Parse an unsigned decimal number from a null-terminated string.
Definition helpers.h:910
@ PARSE_ON
Definition helpers.h:1436
@ PARSE_TOGGLE
Definition helpers.h:1438
@ PARSE_OFF
Definition helpers.h:1437
@ PARSE_NONE
Definition helpers.h:1435
const __FlashStringHelper * ProgmemStr
Definition progmem.h:26
Simple Helper struct used for Home Assistant MQTT send_discovery().