-
-
Save Legot/2d72223d321f66941829c80753d3fbb5 to your computer and use it in GitHub Desktop.
ESPHome climate component for Electra AC (RC-3 IR remote)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "esphome.h" | |
| static const char *TAG = "electra.climate"; | |
| typedef enum IRElectraMode { | |
| IRElectraModeCool = 0b001, | |
| IRElectraModeHeat = 0b010, | |
| IRElectraModeAuto = 0b011, | |
| IRElectraModeDry = 0b100, | |
| IRElectraModeFan = 0b101, | |
| IRElectraModeOff = 0b111 | |
| } IRElectraMode; | |
| typedef enum IRElectraFan { | |
| IRElectraFanLow = 0b00, | |
| IRElectraFanMedium = 0b01, | |
| IRElectraFanHigh = 0b10, | |
| IRElectraFanAuto = 0b11 | |
| } IRElectraFan; | |
| // That configuration has a total of 34 bits | |
| // 33: Power bit, if this bit is ON, the A/C will toggle it's power. | |
| // 32-30: Mode - Cool, heat etc. | |
| // 29-28: Fan - Low, medium etc. | |
| // 27-26: Zeros | |
| // 25: Swing On/Off | |
| // 24: iFeel On/Off | |
| // 23: Zero | |
| // 22-19: Temperature, where 15 is 0000, 30 is 1111 | |
| // 18: Sleep mode On/Off | |
| // 17- 2: Zeros | |
| // 1: One | |
| // 0: Zero | |
| typedef union ElectraCode { | |
| uint64_t num; | |
| struct { | |
| uint64_t zeros1 : 1; | |
| uint64_t ones1 : 1; | |
| uint64_t zeros2 : 16; | |
| uint64_t sleep : 1; | |
| uint64_t temperature : 4; | |
| uint64_t zeros3 : 1; | |
| uint64_t ifeel : 1; | |
| uint64_t swing : 1; | |
| uint64_t zeros4 : 2; | |
| uint64_t fan : 2; | |
| uint64_t mode : 3; | |
| uint64_t power : 1; | |
| }; | |
| } ElectraCode; | |
| const uint8_t ELECTRA_TEMP_MIN = 16; // Celsius | |
| const uint8_t ELECTRA_TEMP_MAX = 26; // Celsius | |
| uint8_t target = 21; | |
| #define ELECTRA_TIME_UNIT 1000 | |
| #define ELECTRA_NUM_BITS 34 | |
| class ElectraClimate : public climate::Climate, public Component { | |
| public: | |
| void setup() override | |
| { | |
| 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 to be off (before was CLIMATE_MODE_AUTO | |
| this->mode = climate::CLIMATE_MODE_OFF; | |
| // initialize target temperature to some value so that it's not NAN | |
| this->target_temperature = roundf(this->current_temperature); | |
| } | |
| this->active_mode_ = this->mode; | |
| } | |
| void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { | |
| this->transmitter_ = transmitter; | |
| } | |
| void set_supported_cool(bool supported_cool) { this->supported_cool_ = supported_cool; } | |
| void set_supported_heat(bool supported_heat) { this->supported_heat_ = supported_heat; } | |
| void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } | |
| /// Override control to change settings of the climate device | |
| void control(const climate::ClimateCall &call) override | |
| { | |
| 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(); | |
| //new | |
| if (call.get_fan_mode().has_value()) | |
| this->fan_mode = *call.get_fan_mode(); | |
| // end new | |
| this->transmit_state_(); | |
| this->publish_state(); | |
| this->active_mode_ = this->mode; | |
| } | |
| /// Return the traits of this controller | |
| /* climate::ClimateTraits traits() override | |
| { | |
| 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(ELECTRA_TEMP_MIN); | |
| traits.set_visual_max_temperature(ELECTRA_TEMP_MAX); | |
| traits.set_visual_temperature_step(1); | |
| return traits; | |
| } | |
| */ | |
| climate::ClimateTraits traits() override | |
| { | |
| auto traits = climate::ClimateTraits(); | |
| traits.set_supports_current_temperature(this->sensor_ != nullptr); | |
| traits.set_supported_modes({ climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_OFF }); | |
| // added the below line for having support for the fans modes as from climate_traits.h | |
| traits.set_supported_fan_modes({ climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH }); | |
| traits.set_supports_two_point_target_temperature(false); | |
| //traits.set_supports_away(false); | |
| traits.set_visual_min_temperature(ELECTRA_TEMP_MIN); | |
| traits.set_visual_max_temperature(ELECTRA_TEMP_MAX); | |
| traits.set_visual_temperature_step(1); | |
| return traits; | |
| } | |
| /// Transmit the state of this climate controller via IR | |
| void transmit_state_() | |
| { | |
| ElectraCode code = { 0 }; | |
| code.ones1 = 1; | |
| // original before the switch code.fan = IRElectraFan::IRElectraFanAuto; | |
| /// below is for adding the fan mode | |
| switch (this->fan_mode.value()) { | |
| case climate::CLIMATE_FAN_LOW: | |
| code.fan = IRElectraFan::IRElectraFanLow; | |
| break; | |
| case climate::CLIMATE_FAN_MEDIUM: | |
| code.fan = IRElectraFan::IRElectraFanMedium; | |
| break; | |
| case climate::CLIMATE_FAN_HIGH: | |
| code.fan = IRElectraFan::IRElectraFanHigh; | |
| break; | |
| case climate::CLIMATE_FAN_ON: | |
| default: | |
| code.fan = IRElectraFan::IRElectraFanLow;// original IRElectraFanAuto | |
| break; | |
| } | |
| /// above is for adding the fan mode | |
| switch (this->mode) { | |
| case climate::CLIMATE_MODE_COOL: | |
| code.mode = IRElectraMode::IRElectraModeCool; | |
| code.power = this->active_mode_ == climate::CLIMATE_MODE_OFF ? 1 : 0; | |
| break; | |
| case climate::CLIMATE_MODE_HEAT: | |
| code.mode = IRElectraMode::IRElectraModeHeat; | |
| code.power = this->active_mode_ == climate::CLIMATE_MODE_OFF ? 1 : 0; | |
| break; | |
| case climate::CLIMATE_MODE_AUTO: | |
| code.mode = IRElectraMode::IRElectraModeAuto; | |
| code.power = this->active_mode_ == climate::CLIMATE_MODE_OFF ? 1 : 0; | |
| break; | |
| case climate::CLIMATE_MODE_OFF: | |
| default: | |
| code.mode = IRElectraMode::IRElectraModeOff; | |
| break; | |
| } | |
| target = (uint8_t) this->target_temperature; | |
| auto temp = (uint8_t) roundf(clamp(target, ELECTRA_TEMP_MIN, ELECTRA_TEMP_MAX)); | |
| code.temperature = temp - 15; | |
| ESP_LOGD(TAG, "Sending electra code: %lld", code.num); | |
| auto transmit = this->transmitter_->transmit(); | |
| auto data = transmit.get_data(); | |
| data->set_carrier_frequency(38000); | |
| uint16_t repeat = 3; | |
| for (uint16_t r = 0; r < repeat; r++) { | |
| // Header | |
| data->mark(3 * ELECTRA_TIME_UNIT); | |
| uint16_t next_value = 3 * ELECTRA_TIME_UNIT; | |
| bool is_next_space = true; | |
| // Data | |
| for (int j = ELECTRA_NUM_BITS - 1; j>=0; j--) | |
| { | |
| uint8_t bit = (code.num >> j) & 1; | |
| // if current index is SPACE | |
| if (is_next_space) { | |
| // one is one unit low, then one unit up | |
| // since we're pointing at SPACE, we should increase it by a unit | |
| // then add another MARK unit | |
| if (bit == 1) { | |
| data->space(next_value + ELECTRA_TIME_UNIT); | |
| next_value = ELECTRA_TIME_UNIT; | |
| is_next_space = false; | |
| } else { | |
| // we need a MARK unit, then SPACE unit | |
| data->space(next_value); | |
| data->mark(ELECTRA_TIME_UNIT); | |
| next_value = ELECTRA_TIME_UNIT; | |
| is_next_space = true; | |
| } | |
| } else { | |
| // current index is MARK | |
| // one is one unit low, then one unit up | |
| if (bit == 1) { | |
| data->mark(next_value); | |
| data->space(ELECTRA_TIME_UNIT); | |
| next_value = ELECTRA_TIME_UNIT; | |
| is_next_space = false; | |
| } else { | |
| data->mark(next_value + ELECTRA_TIME_UNIT); | |
| next_value = ELECTRA_TIME_UNIT; | |
| is_next_space = true; | |
| } | |
| } | |
| } | |
| // Last value must be SPACE | |
| data->space(next_value); | |
| } | |
| // Footer | |
| data->mark(4 * ELECTRA_TIME_UNIT); | |
| transmit.perform(); | |
| } | |
| /* these are new rows for getting the IR reciever data - original from coolix.h | |
| /// Handle received IR Buffer | |
| static bool on_electra(climate::Climate *parent, remote_base::RemoteReceiveData data); | |
| bool on_receive(remote_base::RemoteReceiveData data) override { return ElectraClimate::on_electra(this, data); } | |
| ///original | |
| /// bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); } | |
| */ | |
| ClimateMode active_mode_; | |
| bool supported_cool_{true}; | |
| bool supported_heat_{true}; | |
| remote_transmitter::RemoteTransmitterComponent *transmitter_; | |
| sensor::Sensor *sensor_{nullptr}; | |
| }; |
Author
@Legot did this version worked for you? I cannot set desired temperature because it not rendered in the climate circular slider...
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I've forked this so that it doesn't get deleted, the original is 5 years old. I'm planning on importing it as an external component in esphome so it may need some edits (originally it was made as an external component, which will no longer be supported by esphome).