ESPHome 2025.12.0-dev
Loading...
Searching...
No Matches
fan.cpp
Go to the documentation of this file.
1#include "fan.h"
4#include "esphome/core/log.h"
5
6namespace esphome {
7namespace fan {
8
9static const char *const TAG = "fan";
10
12 switch (direction) {
14 return LOG_STR("FORWARD");
16 return LOG_STR("REVERSE");
17 default:
18 return LOG_STR("UNKNOWN");
19 }
20}
21
22FanCall &FanCall::set_preset_mode(const std::string &preset_mode) { return this->set_preset_mode(preset_mode.c_str()); }
23
25 if (preset_mode == nullptr || strlen(preset_mode) == 0) {
26 this->preset_mode_ = nullptr;
27 return *this;
28 }
29
30 // Find and validate pointer from traits immediately
31 auto traits = this->parent_.get_traits();
32 const char *validated_mode = traits.find_preset_mode(preset_mode);
33 if (validated_mode != nullptr) {
34 this->preset_mode_ = validated_mode; // Store pointer from traits
35 } else {
36 // Preset mode not found in traits - log warning and don't set
37 ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), preset_mode);
38 this->preset_mode_ = nullptr;
39 }
40 return *this;
41}
42
44 ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str());
45 this->validate_();
46 if (this->binary_state_.has_value()) {
47 ESP_LOGD(TAG, " State: %s", ONOFF(*this->binary_state_));
48 }
49 if (this->oscillating_.has_value()) {
50 ESP_LOGD(TAG, " Oscillating: %s", YESNO(*this->oscillating_));
51 }
52 if (this->speed_.has_value()) {
53 ESP_LOGD(TAG, " Speed: %d", *this->speed_);
54 }
55 if (this->direction_.has_value()) {
56 ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
57 }
58 if (this->has_preset_mode()) {
59 ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
60 }
61 this->parent_.control(*this);
62}
63
65 auto traits = this->parent_.get_traits();
66
67 if (this->speed_.has_value()) {
68 this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
69
70 // https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
71 // "Manually setting a speed must disable any set preset mode"
72 this->preset_mode_ = nullptr;
73 }
74
75 // when turning on...
76 if (!this->parent_.state && this->binary_state_.has_value() &&
77 *this->binary_state_
78 // ..,and no preset mode will be active...
79 && !this->has_preset_mode() &&
80 this->parent_.get_preset_mode() == nullptr
81 // ...and neither current nor new speed is available...
82 && traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) {
83 // ...set speed to 100%
84 this->speed_ = traits.supported_speed_count();
85 }
86
87 if (this->oscillating_.has_value() && !traits.supports_oscillation()) {
88 ESP_LOGW(TAG, "%s: Oscillation not supported", this->parent_.get_name().c_str());
89 this->oscillating_.reset();
90 }
91
92 if (this->speed_.has_value() && !traits.supports_speed()) {
93 ESP_LOGW(TAG, "%s: Speed control not supported", this->parent_.get_name().c_str());
94 this->speed_.reset();
95 }
96
97 if (this->direction_.has_value() && !traits.supports_direction()) {
98 ESP_LOGW(TAG, "%s: Direction control not supported", this->parent_.get_name().c_str());
99 this->direction_.reset();
100 }
101}
102
104 auto call = fan.make_call();
105 call.set_state(this->state);
106 call.set_oscillating(this->oscillating);
107 call.set_speed(this->speed);
108 call.set_direction(this->direction);
109
110 auto traits = fan.get_traits();
111 if (traits.supports_preset_modes()) {
112 // Use stored preset index to get preset name
113 const auto &preset_modes = traits.supported_preset_modes();
114 if (this->preset_mode < preset_modes.size()) {
115 call.set_preset_mode(preset_modes[this->preset_mode]);
116 }
117 }
118 return call;
119}
121 fan.state = this->state;
122 fan.oscillating = this->oscillating;
123 fan.speed = this->speed;
124 fan.direction = this->direction;
125
126 auto traits = fan.get_traits();
127 if (traits.supports_preset_modes()) {
128 // Use stored preset index to get preset name from traits
129 const auto &preset_modes = traits.supported_preset_modes();
130 if (this->preset_mode < preset_modes.size()) {
131 fan.set_preset_mode_(preset_modes[this->preset_mode]);
132 }
133 }
134
135 fan.publish_state();
136}
137
138FanCall Fan::turn_on() { return this->make_call().set_state(true); }
139FanCall Fan::turn_off() { return this->make_call().set_state(false); }
140FanCall Fan::toggle() { return this->make_call().set_state(!this->state); }
141FanCall Fan::make_call() { return FanCall(*this); }
142
143const char *Fan::find_preset_mode_(const char *preset_mode) { return this->get_traits().find_preset_mode(preset_mode); }
144
146 if (preset_mode == nullptr) {
147 // Treat nullptr as clearing the preset mode
148 if (this->preset_mode_ == nullptr) {
149 return false; // No change
150 }
151 this->clear_preset_mode_();
152 return true;
153 }
154 const char *validated = this->find_preset_mode_(preset_mode);
155 if (validated == nullptr || this->preset_mode_ == validated) {
156 return false; // Preset mode not supported or no change
157 }
158 this->preset_mode_ = validated;
159 return true;
160}
161
162bool Fan::set_preset_mode_(const std::string &preset_mode) { return this->set_preset_mode_(preset_mode.c_str()); }
163
164void Fan::clear_preset_mode_() { this->preset_mode_ = nullptr; }
165
166void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
168 auto traits = this->get_traits();
169
170 ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
171 ESP_LOGD(TAG, " State: %s", ONOFF(this->state));
172 if (traits.supports_speed()) {
173 ESP_LOGD(TAG, " Speed: %d", this->speed);
174 }
175 if (traits.supports_oscillation()) {
176 ESP_LOGD(TAG, " Oscillating: %s", YESNO(this->oscillating));
177 }
178 if (traits.supports_direction()) {
179 ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
180 }
181 const char *preset = this->get_preset_mode();
182 if (preset != nullptr) {
183 ESP_LOGD(TAG, " Preset Mode: %s", preset);
184 }
185 this->state_callback_.call();
186#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
188#endif
189 this->save_state_();
190}
191
192// Random 32-bit value, change this every time the layout of the FanRestoreState struct changes.
193constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
195 FanRestoreState recovered{};
196 this->rtc_ =
198 bool restored = this->rtc_.load(&recovered);
199
200 switch (this->restore_mode_) {
202 return {};
204 recovered.state = false;
205 return recovered;
207 recovered.state = true;
208 return recovered;
210 recovered.state = restored ? recovered.state : false;
211 return recovered;
213 recovered.state = restored ? recovered.state : true;
214 return recovered;
216 recovered.state = restored ? !recovered.state : false;
217 return recovered;
219 recovered.state = restored ? !recovered.state : true;
220 return recovered;
221 }
222
223 return {};
224}
227 return;
228 }
229
230 auto traits = this->get_traits();
231
233 state.state = this->state;
234 state.oscillating = this->oscillating;
235 state.speed = this->speed;
236 state.direction = this->direction;
237
238 const char *preset = this->get_preset_mode();
239 if (preset != nullptr) {
240 const auto &preset_modes = traits.supported_preset_modes();
241 // Find index of current preset mode (pointer comparison is safe since preset is from traits)
242 for (size_t i = 0; i < preset_modes.size(); i++) {
243 if (preset_modes[i] == preset) {
244 state.preset_mode = i;
245 break;
246 }
247 }
248 }
249
250 this->rtc_.save(&state);
251}
252
253void Fan::dump_traits_(const char *tag, const char *prefix) {
254 auto traits = this->get_traits();
255
256 if (traits.supports_speed()) {
257 ESP_LOGCONFIG(tag,
258 "%s Speed: YES\n"
259 "%s Speed count: %d",
260 prefix, prefix, traits.supported_speed_count());
261 }
262 if (traits.supports_oscillation()) {
263 ESP_LOGCONFIG(tag, "%s Oscillation: YES", prefix);
264 }
265 if (traits.supports_direction()) {
266 ESP_LOGCONFIG(tag, "%s Direction: YES", prefix);
267 }
268 if (traits.supports_preset_modes()) {
269 ESP_LOGCONFIG(tag, "%s Supported presets:", prefix);
270 for (const char *s : traits.supported_preset_modes())
271 ESP_LOGCONFIG(tag, "%s - %s", prefix, s);
272 }
273}
274
275} // namespace fan
276} // namespace esphome
static void notify_fan_update(fan::Fan *obj)
bool save(const T *src)
Definition preferences.h:21
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
const StringRef & get_name() const
uint32_t get_preference_hash()
Get a unique hash for storing preferences/settings for this entity.
constexpr const char * c_str() const
Definition string_ref.h:69
FanCall & set_oscillating(bool oscillating)
Definition fan.h:50
FanCall & set_direction(FanDirection direction)
Definition fan.h:64
optional< bool > binary_state_
Definition fan.h:84
optional< FanDirection > direction_
Definition fan.h:87
optional< int > speed_
Definition fan.h:86
const char * preset_mode_
Definition fan.h:88
bool has_preset_mode() const
Definition fan.h:76
FanCall & set_speed(int speed)
Definition fan.h:59
FanCall & set_state(bool binary_state)
Definition fan.h:41
optional< bool > oscillating_
Definition fan.h:85
FanCall & set_preset_mode(const std::string &preset_mode)
Definition fan.cpp:22
friend FanCall
Definition fan.h:137
void publish_state()
Definition fan.cpp:167
FanCall turn_on()
Definition fan.cpp:138
FanCall turn_off()
Definition fan.cpp:139
FanCall make_call()
Definition fan.cpp:141
virtual FanTraits get_traits()=0
bool set_preset_mode_(const char *preset_mode)
Set the preset mode (finds and stores pointer from traits). Returns true if changed.
Definition fan.cpp:145
FanCall toggle()
Definition fan.cpp:140
ESPPreferenceObject rtc_
Definition fan.h:157
void clear_preset_mode_()
Clear the preset mode.
Definition fan.cpp:164
void add_on_state_callback(std::function< void()> &&callback)
Register a callback that will be called each time the state changes.
Definition fan.cpp:166
FanDirection direction
The current direction of the fan.
Definition fan.h:113
void save_state_()
Definition fan.cpp:225
FanRestoreMode restore_mode_
Definition fan.h:158
CallbackManager< void()> state_callback_
Definition fan.h:156
bool oscillating
The current oscillation state of the fan.
Definition fan.h:109
virtual void control(const FanCall &call)=0
bool state
The current on/off state of the fan.
Definition fan.h:107
const char * find_preset_mode_(const char *preset_mode)
Find and return the matching preset mode pointer from traits, or nullptr if not found.
Definition fan.cpp:143
const char * get_preset_mode() const
Get the current preset mode (returns pointer to string stored in traits, or nullptr if not set)
Definition fan.h:131
int speed
The current fan speed level.
Definition fan.h:111
void dump_traits_(const char *tag, const char *prefix)
Definition fan.cpp:253
optional< FanRestoreState > restore_state_()
Definition fan.cpp:194
const char * find_preset_mode(const char *preset_mode) const
Find and return the matching preset mode pointer from supported modes, or nullptr if not found.
Definition fan_traits.h:49
bool has_value() const
Definition optional.h:92
ClimatePreset preset
Definition climate.h:8
FanDirection direction
Definition fan.h:3
uint8_t preset_mode
Definition fan.h:4
const LogString * fan_direction_to_string(FanDirection direction)
Definition fan.cpp:11
constexpr uint32_t RESTORE_STATE_VERSION
Definition fan.cpp:193
FanDirection
Simple enum to represent the direction of a fan.
Definition fan.h:20
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
ESPPreferences * global_preferences
void apply(Fan &fan)
Apply these settings to the fan.
Definition fan.cpp:120
FanDirection direction
Definition fan.h:95
FanCall to_call(Fan &fan)
Convert this struct to a fan call that can be performed.
Definition fan.cpp:103