ESPHome 2026.5.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 this->state_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
125}
126
128 ESP_LOGCONFIG(TAG, "MQTT Fan '%s': ", this->state_->get_name().c_str());
129 LOG_MQTT_COMPONENT(true, true);
130 if (this->state_->get_traits().supports_direction()) {
131 ESP_LOGCONFIG(TAG,
132 " Direction State Topic: '%s'\n"
133 " Direction Command Topic: '%s'",
134 this->get_direction_state_topic().c_str(), this->get_direction_command_topic().c_str());
135 }
136 if (this->state_->get_traits().supports_oscillation()) {
137 ESP_LOGCONFIG(TAG,
138 " Oscillation State Topic: '%s'\n"
139 " Oscillation Command Topic: '%s'",
140 this->get_oscillation_state_topic().c_str(), this->get_oscillation_command_topic().c_str());
141 }
142 if (this->state_->get_traits().supports_speed()) {
143 ESP_LOGCONFIG(TAG,
144 " Speed Level State Topic: '%s'\n"
145 " Speed Level Command Topic: '%s'",
146 this->get_speed_level_state_topic().c_str(), this->get_speed_level_command_topic().c_str());
147 }
148}
149
151
153 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
154 if (this->state_->get_traits().supports_direction()) {
155 root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic();
156 root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic();
157 }
158 if (this->state_->get_traits().supports_oscillation()) {
159 root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic();
160 root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic();
161 }
162 if (this->state_->get_traits().supports_speed()) {
163 root[MQTT_PERCENTAGE_COMMAND_TOPIC] = this->get_speed_level_command_topic();
164 root[MQTT_PERCENTAGE_STATE_TOPIC] = this->get_speed_level_state_topic();
165 root[MQTT_SPEED_RANGE_MAX] = this->state_->get_traits().supported_speed_count();
166 }
167}
169 char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
170 const char *state_s = this->state_->state ? "ON" : "OFF";
171 ESP_LOGD(TAG, "'%s' Sending state %s.", this->state_->get_name().c_str(), state_s);
172 this->publish(this->get_state_topic_to_(topic_buf), state_s);
173 bool failed = false;
174 if (this->state_->get_traits().supports_direction()) {
175 bool success = this->publish(this->get_direction_state_topic_to(topic_buf),
176 fan_direction_to_mqtt_str(this->state_->direction));
177 failed = failed || !success;
178 }
179 if (this->state_->get_traits().supports_oscillation()) {
180 bool success = this->publish(this->get_oscillation_state_topic_to(topic_buf),
181 fan_oscillation_to_mqtt_str(this->state_->oscillating));
182 failed = failed || !success;
183 }
184 auto traits = this->state_->get_traits();
185 if (traits.supports_speed()) {
186 char buf[12];
187 size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
188 bool success = this->publish(this->get_speed_level_state_topic_to(topic_buf), buf, len);
189 failed = failed || !success;
190 }
191 return !failed;
192}
193
194} // namespace esphome::mqtt
195
196#endif
197#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:560
void status_momentary_warning(const char *name, uint32_t length=5000)
Set warning status flag and automatically clear it after a timeout.
const StringRef & get_name() const
Definition entity_base.h:71
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
void add_on_state_callback(F &&callback)
Register a callback that will be called each time the state changes.
Definition fan.h:125
FanCall turn_on()
Definition fan.cpp:157
FanCall turn_off()
Definition fan.cpp:158
FanCall make_call()
Definition fan.cpp:160
virtual FanTraits get_traits()=0
FanCall toggle()
Definition fan.cpp:159
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:31
bool supports_direction() const
Return if this fan supports changing direction.
Definition fan_traits.h:35
bool supports_speed() const
Return if this fan supports speed modes.
Definition fan_traits.h:27
bool supports_oscillation() const
Return if this fan supports oscillation.
Definition fan_traits.h:23
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:152
bool send_initial_state() override
Send the full current state to MQTT.
Definition mqtt_fan.cpp:150
void setup() override
Setup the fan subscriptions and discovery.
MQTTFanComponent(fan::Fan *state)
Definition mqtt_fan.cpp:24
FanDirection direction
Definition fan.h:5
bool oscillating
Definition fan.h:4
bool state
Definition fan.h:2
mopeka_std_values val[3]
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:510
std::string size_t len
Definition helpers.h:1045
optional< T > parse_number(const char *str)
Parse an unsigned decimal number from a null-terminated string.
Definition helpers.h:1160
@ PARSE_ON
Definition helpers.h:1701
@ PARSE_TOGGLE
Definition helpers.h:1703
@ PARSE_OFF
Definition helpers.h:1702
@ PARSE_NONE
Definition helpers.h:1700
const __FlashStringHelper * ProgmemStr
Definition progmem.h:27
Simple Helper struct used for Home Assistant MQTT send_discovery().