#pragma once #include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/preferences.h" #include "esphome/core/string_ref.h" #include "climate_mode.h" #include "climate_traits.h" namespace esphome::climate { #define LOG_CLIMATE(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ } class Climate; /** This class is used to encode all control actions on a climate device. * * It is supposed to be used by all code that wishes to control a climate device (mqtt, api, lambda etc). * Create an instance of this class by calling `id(climate_device).make_call();`. Then set all attributes * with the `set_x` methods. Finally, to apply the changes call `.perform();`. * * The integration that implements the climate device receives this instance with the `control` method. * It should check all the properties it implements and apply them as needed. It should do so by * getting all properties it controls with the getter methods in this class. If the optional value is * set (check with `.has_value()`) that means the user wants to control this property. Get the value * of the optional with the star operator (`*call.get_mode()`) and apply it. */ class ClimateCall { public: explicit ClimateCall(Climate *parent) : parent_(parent) {} friend struct ClimateDeviceRestoreState; /// Set the mode of the climate device. ClimateCall &set_mode(ClimateMode mode); /// Set the mode of the climate device. ClimateCall &set_mode(optional mode); /// Set the mode of the climate device based on a string. ClimateCall &set_mode(const std::string &mode); /// Set the mode of the climate device based on a C string. ClimateCall &set_mode(const char *mode, size_t len); /// Set the target temperature of the climate device. ClimateCall &set_target_temperature(float target_temperature); /// Set the target temperature of the climate device. ClimateCall &set_target_temperature(optional target_temperature); /** Set the low point target temperature of the climate device * * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_low(float target_temperature_low); /** Set the low point target temperature of the climate device * * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_low(optional target_temperature_low); /** Set the high point target temperature of the climate device * * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(float target_temperature_high); /** Set the high point target temperature of the climate device * * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); /// Set the target humidity of the climate device. ClimateCall &set_target_humidity(float target_humidity); /// Set the target humidity of the climate device. ClimateCall &set_target_humidity(optional target_humidity); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(optional fan_mode); /// Set the fan mode of the climate device based on a string. ClimateCall &set_fan_mode(const std::string &fan_mode); /// Set the fan mode of the climate device based on a string. ClimateCall &set_fan_mode(optional fan_mode); /// Set the custom fan mode of the climate device. ClimateCall &set_fan_mode(const char *custom_fan_mode); /// Set the custom fan mode of the climate device (zero-copy API path). ClimateCall &set_fan_mode(const char *custom_fan_mode, size_t len); /// Set the swing mode of the climate device. ClimateCall &set_swing_mode(ClimateSwingMode swing_mode); /// Set the swing mode of the climate device. ClimateCall &set_swing_mode(optional swing_mode); /// Set the swing mode of the climate device based on a string. ClimateCall &set_swing_mode(const std::string &swing_mode); /// Set the swing mode of the climate device based on a C string. ClimateCall &set_swing_mode(const char *swing_mode, size_t len); /// Set the preset of the climate device. ClimateCall &set_preset(ClimatePreset preset); /// Set the preset of the climate device. ClimateCall &set_preset(optional preset); /// Set the preset of the climate device based on a string. ClimateCall &set_preset(const std::string &preset); /// Set the preset of the climate device based on a string. ClimateCall &set_preset(optional preset); /// Set the custom preset of the climate device. ClimateCall &set_preset(const char *custom_preset); /// Set the custom preset of the climate device (zero-copy API path). ClimateCall &set_preset(const char *custom_preset, size_t len); void perform(); const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; const optional &get_target_humidity() const; const optional &get_mode() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_preset() const; StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); } StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); } bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } bool has_custom_preset() const { return this->custom_preset_ != nullptr; } protected: void validate_(); Climate *const parent_; optional target_temperature_; optional target_temperature_low_; optional target_temperature_high_; optional target_humidity_; optional mode_; optional fan_mode_; optional swing_mode_; optional preset_; private: const char *custom_fan_mode_{nullptr}; const char *custom_preset_{nullptr}; }; /// Struct used to save the state of the climate device in restore memory. /// Make sure to update RESTORE_STATE_VERSION when changing the struct entries. struct ClimateDeviceRestoreState { ClimateMode mode; bool uses_custom_fan_mode{false}; union { ClimateFanMode fan_mode; uint8_t custom_fan_mode; }; bool uses_custom_preset{false}; union { ClimatePreset preset; uint8_t custom_preset; }; ClimateSwingMode swing_mode; union { float target_temperature; struct { float target_temperature_low; float target_temperature_high; }; } __attribute__((packed)); float target_humidity; /// Convert this struct to a climate call that can be performed. ClimateCall to_call(Climate *climate); /// Apply these settings to the climate device. void apply(Climate *climate); } __attribute__((packed)); /** * ClimateDevice - This is the base class for all climate integrations. Each integration * needs to extend this class and implement two functions: * * - get_traits() - return the static traits of the climate device * - control(ClimateDeviceCall call) - Apply the given changes from call. * * To write data to the frontend, the integration must first set the properties using * this->property = value; (for example this->current_temperature = 42.0;); then the integration * must call this->publish_state(); to send the entire state to the frontend. * * The entire state of the climate device is encoded in public properties of the base class (current_temperature, * mode etc). These are read-only for the user and rw for integrations. The reason these are public * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...` */ class Climate : public EntityBase { public: Climate() {} /** Add a callback for the climate device state, each time the state of the climate device is updated * (using publish_state), this callback will be called. * * @param callback The callback to call. */ template void add_on_state_callback(F &&callback) { this->state_callback_.add(std::forward(callback)); } /** * Add a callback for the climate device configuration; each time the configuration parameters of a climate device * is updated (using perform() of a ClimateCall), this callback will be called, before any on_state callback. * * @param callback The callback to call. */ template void add_on_control_callback(F &&callback) { this->control_callback_.add(std::forward(callback)); } /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description * for more info. * @return A new ClimateCall instance targeting this climate device. */ ClimateCall make_call(); /** Publish the state of the climate device, to be called from integrations. * * This will schedule the climate device to publish its state to all listeners and save the current state * to recover memory. */ void publish_state(); /** Get the traits of this climate device with all overrides applied. * * Traits are static data that encode the capabilities and static data for a climate device such as supported * modes, temperature range etc. */ ClimateTraits get_traits(); #ifdef USE_CLIMATE_VISUAL_OVERRIDES void set_visual_min_temperature_override(float visual_min_temperature_override); void set_visual_max_temperature_override(float visual_max_temperature_override); void set_visual_temperature_step_override(float target, float current); void set_visual_min_humidity_override(float visual_min_humidity_override); void set_visual_max_humidity_override(float visual_max_humidity_override); #endif /// Set the supported custom fan modes (stored on Climate, referenced by ClimateTraits). void set_supported_custom_fan_modes(std::initializer_list modes) { this->ensure_custom_fan_modes_().assign(modes.begin(), modes.end()); } void set_supported_custom_fan_modes(const std::vector &modes) { this->ensure_custom_fan_modes_() = modes; } template void set_supported_custom_fan_modes(const char *const (&modes)[N]) { this->ensure_custom_fan_modes_().assign(modes, modes + N); } /// Set the supported custom presets (stored on Climate, referenced by ClimateTraits). void set_supported_custom_presets(std::initializer_list presets) { this->ensure_custom_presets_().assign(presets.begin(), presets.end()); } void set_supported_custom_presets(const std::vector &presets) { this->ensure_custom_presets_() = presets; } template void set_supported_custom_presets(const char *const (&presets)[N]) { this->ensure_custom_presets_().assign(presets, presets + N); } /// Check if a custom fan mode is currently active. bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } /// Check if a custom preset is currently active. bool has_custom_preset() const { return this->custom_preset_ != nullptr; } /// The current temperature of the climate device, as reported from the integration. float current_temperature{NAN}; /// The current humidity of the climate device, as reported from the integration. float current_humidity{NAN}; union { /// The target temperature of the climate device. float target_temperature; struct { /// The minimum target temperature of the climate device, for climate devices with split target temperature. float target_temperature_low{NAN}; /// The maximum target temperature of the climate device, for climate devices with split target temperature. float target_temperature_high{NAN}; }; }; /// The target humidity of the climate device. float target_humidity; /// The active fan mode of the climate device. optional fan_mode; /// The active preset of the climate device. optional preset; /// The active mode of the climate device. ClimateMode mode{CLIMATE_MODE_OFF}; /// The active state of the climate device. ClimateAction action{CLIMATE_ACTION_OFF}; /// The active swing mode of the climate device. ClimateSwingMode swing_mode{CLIMATE_SWING_OFF}; /// Get the active custom fan mode (read-only access). Returns StringRef. StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); } /// Get the active custom preset (read-only access). Returns StringRef. StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); } protected: friend ClimateCall; friend struct ClimateDeviceRestoreState; /// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed. bool set_fan_mode_(ClimateFanMode mode); /// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed. bool set_custom_fan_mode_(const char *mode) { return this->set_custom_fan_mode_(mode, strlen(mode)); } bool set_custom_fan_mode_(const char *mode, size_t len); bool set_custom_fan_mode_(StringRef mode) { return this->set_custom_fan_mode_(mode.c_str(), mode.size()); } /// Clear custom fan mode. void clear_custom_fan_mode_(); /// Set preset. Reset custom preset. Return true if preset has been changed. bool set_preset_(ClimatePreset preset); /// Set custom preset. Reset primary preset. Return true if preset has been changed. bool set_custom_preset_(const char *preset) { return this->set_custom_preset_(preset, strlen(preset)); } bool set_custom_preset_(const char *preset, size_t len); bool set_custom_preset_(StringRef preset) { return this->set_custom_preset_(preset.c_str(), preset.size()); } /// Clear custom preset. void clear_custom_preset_(); /// Find and return the matching custom fan mode pointer from traits, or nullptr if not found. const char *find_custom_fan_mode_(const char *custom_fan_mode); const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len); /// Find and return the matching custom preset pointer from traits, or nullptr if not found. const char *find_custom_preset_(const char *custom_preset); const char *find_custom_preset_(const char *custom_preset, size_t len); /** Get the default traits of this climate device. * * Traits are static data that encode the capabilities and static data for a climate device such as supported * modes, temperature range etc. Each integration must implement this method and the return value must * be constant during all of execution time. */ virtual ClimateTraits traits() = 0; /** Control the climate device, this is a virtual method that each climate integration must implement. * * See more info in ClimateCall. The integration should check all of its values in this method and * set them accordingly. At the end of the call, the integration must call `publish_state()` to * notify the frontend of a changed state. * * @param call The ClimateCall instance encoding all attribute changes. */ virtual void control(const ClimateCall &call) = 0; /// Restore the state of the climate device, call this from your setup() method. optional restore_state_(); /** Internal method to save the state of the climate device to recover memory. This is automatically * called from publish_state() */ void save_state_(const ClimateTraits &traits); void save_state_() { this->save_state_(this->get_traits()); } void dump_traits_(const char *tag); LazyCallbackManager state_callback_{}; LazyCallbackManager control_callback_{}; ESPPreferenceObject rtc_; #ifdef USE_CLIMATE_VISUAL_OVERRIDES float visual_min_temperature_override_{NAN}; float visual_max_temperature_override_{NAN}; float visual_target_temperature_step_override_{NAN}; float visual_current_temperature_step_override_{NAN}; float visual_min_humidity_override_{NAN}; float visual_max_humidity_override_{NAN}; #endif private: /// Lazy-allocate custom mode vectors (never freed — entity lives forever). std::vector &ensure_custom_fan_modes_() { if (!this->supported_custom_fan_modes_) { this->supported_custom_fan_modes_ = new std::vector(); // NOLINT } return *this->supported_custom_fan_modes_; } std::vector &ensure_custom_presets_() { if (!this->supported_custom_presets_) { this->supported_custom_presets_ = new std::vector(); // NOLINT } return *this->supported_custom_presets_; } std::vector *supported_custom_fan_modes_{nullptr}; std::vector *supported_custom_presets_{nullptr}; /** The active custom fan mode (private - enforces use of safe setters). * * Points to an entry in supported_custom_fan_modes_ or nullptr. * Use get_custom_fan_mode() to read, set_custom_fan_mode_() to modify. */ const char *custom_fan_mode_{nullptr}; /** The active custom preset (private - enforces use of safe setters). * * Points to an entry in supported_custom_presets_ or nullptr. * Use get_custom_preset() to read, set_custom_preset_() to modify. */ const char *custom_preset_{nullptr}; }; } // namespace esphome::climate