diff --git a/esphome/components/climate_ir/__init__.py b/esphome/components/climate_ir/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp new file mode 100644 index 0000000000..3d8b97e131 --- /dev/null +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -0,0 +1,57 @@ +#include "climate_ir.h" + +namespace esphome { +namespace climate { + +climate::ClimateTraits ClimateIR::traits() { + auto traits = climate::ClimateTraits(); + traits.set_supports_current_temperature(this->sensor_ != nullptr); + traits.set_supports_auto_mode(true); + traits.set_supports_cool_mode(this->supports_cool_); + traits.set_supports_heat_mode(this->supports_heat_); + traits.set_supports_two_point_target_temperature(false); + traits.set_supports_away(false); + traits.set_visual_min_temperature(this->minimum_temperature_); + traits.set_visual_max_temperature(this->maximum_temperature_); + traits.set_visual_temperature_step(this->temperature_step_); + return traits; +} + +void ClimateIR::setup() { + if (this->sensor_) { + this->sensor_->add_on_state_callback([this](float state) { + this->current_temperature = state; + // current temperature changed, publish state + this->publish_state(); + }); + this->current_temperature = this->sensor_->state; + } else + this->current_temperature = NAN; + // restore set points + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(this); + } else { + // restore from defaults + this->mode = climate::CLIMATE_MODE_OFF; + // initialize target temperature to some value so that it's not NAN + this->target_temperature = + roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_)); + } + // Never send nan to HA + if (isnan(this->target_temperature)) + this->target_temperature = 24; +} + +void ClimateIR::control(const climate::ClimateCall &call) { + if (call.get_mode().has_value()) + this->mode = *call.get_mode(); + if (call.get_target_temperature().has_value()) + this->target_temperature = *call.get_target_temperature(); + + this->transmit_state(); + this->publish_state(); +} + +} // namespace climate +} // namespace esphome diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h new file mode 100644 index 0000000000..85f56c6b6b --- /dev/null +++ b/esphome/components/climate_ir/climate_ir.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/components/climate/climate.h" +#include "esphome/components/remote_base/remote_base.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace climate { + +/* A base for climate which works by sending (and receiving) IR codes + + To send IR codes implement + void ClimateIR::transmit_state_() + + Likewise to decode a IR into the AC state, implement + bool RemoteReceiverListener::on_receive(remote_base::RemoteReceiveData data) and return true +*/ +class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener { + public: + ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f) { + this->minimum_temperature_ = minimum_temperature; + this->maximum_temperature_ = maximum_temperature; + this->temperature_step_ = temperature_step; + } + + void setup() override; + void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { + this->transmitter_ = transmitter; + } + void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } + void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } + void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } + + protected: + float minimum_temperature_, maximum_temperature_, temperature_step_; + + /// Override control to change settings of the climate device. + void control(const climate::ClimateCall &call) override; + /// Return the traits of this controller. + climate::ClimateTraits traits() override; + + /// Transmit via IR the state of this climate controller. + virtual void transmit_state() {} + + bool supports_cool_{true}; + bool supports_heat_{true}; + + remote_transmitter::RemoteTransmitterComponent *transmitter_; + sensor::Sensor *sensor_{nullptr}; +}; +} // namespace climate +} // namespace esphome diff --git a/esphome/components/coolix/climate.py b/esphome/components/coolix/climate.py index ed88f6ad6a..fe74798689 100644 --- a/esphome/components/coolix/climate.py +++ b/esphome/components/coolix/climate.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.components import climate, remote_transmitter, remote_receiver, sensor from esphome.const import CONF_ID, CONF_SENSOR -AUTO_LOAD = ['sensor'] +AUTO_LOAD = ['sensor', 'climate_ir'] coolix_ns = cg.esphome_ns.namespace('coolix') CoolixClimate = coolix_ns.class_('CoolixClimate', climate.Climate, cg.Component) diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index 4da307a737..c08571c2e9 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -27,8 +27,6 @@ const uint32_t COOLIX_FAN_MED = 0x5000; const uint32_t COOLIX_FAN_MAX = 0x3000; // Temperature -const uint8_t COOLIX_TEMP_MIN = 17; // Celsius -const uint8_t COOLIX_TEMP_MAX = 30; // Celsius const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; const uint8_t COOLIX_FAN_TEMP_CODE = 0b1110; // Part of Fan Mode. const uint32_t COOLIX_TEMP_MASK = 0b11110000; @@ -60,56 +58,7 @@ static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US; const uint16_t COOLIX_BITS = 24; -climate::ClimateTraits CoolixClimate::traits() { - auto traits = climate::ClimateTraits(); - traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); - traits.set_visual_min_temperature(17); - traits.set_visual_max_temperature(30); - traits.set_visual_temperature_step(1); - return traits; -} - -void CoolixClimate::setup() { - if (this->sensor_) { - this->sensor_->add_on_state_callback([this](float state) { - this->current_temperature = state; - // current temperature changed, publish state - this->publish_state(); - }); - this->current_temperature = this->sensor_->state; - } else - this->current_temperature = NAN; - // restore set points - auto restore = this->restore_state_(); - if (restore.has_value()) { - restore->apply(this); - } else { - // restore from defaults - this->mode = climate::CLIMATE_MODE_OFF; - // initialize target temperature to some value so that it's not NAN - this->target_temperature = (uint8_t) roundf(clamp(this->current_temperature, COOLIX_TEMP_MIN, COOLIX_TEMP_MAX)); - } - // never send nan as temperature. HA will disable the user to change the temperature. - if (isnan(this->target_temperature)) - this->target_temperature = 24; -} - -void CoolixClimate::control(const climate::ClimateCall &call) { - if (call.get_mode().has_value()) - this->mode = *call.get_mode(); - if (call.get_target_temperature().has_value()) - this->target_temperature = *call.get_target_temperature(); - - this->transmit_state_(); - this->publish_state(); -} - -void CoolixClimate::transmit_state_() { +void CoolixClimate::transmit_state() { uint32_t remote_state; switch (this->mode) { diff --git a/esphome/components/coolix/coolix.h b/esphome/components/coolix/coolix.h index 392728c654..125d8ffd37 100644 --- a/esphome/components/coolix/coolix.h +++ b/esphome/components/coolix/coolix.h @@ -1,43 +1,23 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "esphome/components/climate/climate.h" -#include "esphome/components/remote_base/remote_base.h" -#include "esphome/components/remote_transmitter/remote_transmitter.h" -#include "esphome/components/sensor/sensor.h" +#include "esphome/components/climate_ir/climate_ir.h" namespace esphome { namespace coolix { -using namespace remote_base; +// Temperature +const uint8_t COOLIX_TEMP_MIN = 17; // Celsius +const uint8_t COOLIX_TEMP_MAX = 30; // Celsius -class CoolixClimate : public climate::Climate, public Component, public RemoteReceiverListener { +class CoolixClimate : public climate::ClimateIR { public: - void setup() override; - void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { - this->transmitter_ = transmitter; - } - void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } - void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } - void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } + CoolixClimate() : climate::ClimateIR(COOLIX_TEMP_MIN, COOLIX_TEMP_MAX) {} protected: - /// Override control to change settings of the climate device. - void control(const climate::ClimateCall &call) override; - /// Return the traits of this controller. - climate::ClimateTraits traits() override; - /// Transmit via IR the state of this climate controller. - void transmit_state_(); - - bool on_receive(RemoteReceiveData data) override; - - bool supports_cool_; - bool supports_heat_; - - remote_transmitter::RemoteTransmitterComponent *transmitter_; - sensor::Sensor *sensor_{nullptr}; + void transmit_state() override; + /// Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; }; } // namespace coolix