Skip to content

Commit

Permalink
Feat: Add GREE climateir component (esphome#4464)
Browse files Browse the repository at this point in the history
Co-authored-by: orestismers <[email protected]>
Co-authored-by: Orestes Mersinias <[email protected]>
Co-authored-by: Jesse Hills <[email protected]>
  • Loading branch information
4 people authored Oct 23, 2023
1 parent b4765fb commit 96822aa
Showing 6 changed files with 291 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -110,6 +110,7 @@ esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/gree/* @orestismers
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte
esphome/components/haier/* @paveldn
Empty file.
33 changes: 33 additions & 0 deletions esphome/components/gree/climate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID, CONF_MODEL

CODEOWNERS = ["@orestismers"]

AUTO_LOAD = ["climate_ir"]

gree_ns = cg.esphome_ns.namespace("gree")
GreeClimate = gree_ns.class_("GreeClimate", climate_ir.ClimateIR)

Model = gree_ns.enum("Model")
MODELS = {
"generic": Model.GREE_GENERIC,
"yan": Model.GREE_YAN,
"yaa": Model.GREE_YAA,
"yac": Model.GREE_YAC,
}

CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(GreeClimate),
cv.Required(CONF_MODEL): cv.enum(MODELS),
}
)


async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_model(config[CONF_MODEL]))

await climate_ir.register_climate_ir(var, config)
157 changes: 157 additions & 0 deletions esphome/components/gree/gree.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#include "gree.h"
#include "esphome/components/remote_base/remote_base.h"

namespace esphome {
namespace gree {

static const char *const TAG = "gree.climate";

void GreeClimate::set_model(Model model) { this->model_ = model; }

void GreeClimate::transmit_state() {
uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00};

remote_state[0] = this->fan_speed_() | this->operation_mode_();
remote_state[1] = this->temperature_();

if (this->model_ == GREE_YAN) {
remote_state[2] = 0x60;
remote_state[3] = 0x50;
remote_state[4] = this->vertical_swing_();
}

if (this->model_ == GREE_YAC) {
remote_state[4] |= (this->horizontal_swing_() << 4);
}

if (this->model_ == GREE_YAA || this->model_ == GREE_YAC) {
remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO,LIGHT,HEALTH,X-FAN
remote_state[3] = 0x50; // bits 4..7 always 0101
remote_state[6] = 0x20; // YAA1FB, FAA1FB1, YB1F2 bits 4..7 always 0010

if (this->vertical_swing_() == GREE_VDIR_SWING) {
remote_state[0] |= (1 << 6); // Enable swing by setting bit 6
} else if (this->vertical_swing_() != GREE_VDIR_AUTO) {
remote_state[5] = this->vertical_swing_();
}
}

// Calculate the checksum
if (this->model_ == GREE_YAN) {
remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0);
} else {
remote_state[7] =
((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +
((remote_state[5] & 0xF0) >> 4) + ((remote_state[6] & 0xF0) >> 4) + ((remote_state[7] & 0xF0) >> 4) + 0x0A) &
0x0F)
<< 4) |
(remote_state[7] & 0x0F);
}

auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
data->set_carrier_frequency(GREE_IR_FREQUENCY);

data->mark(GREE_HEADER_MARK);
data->space(GREE_HEADER_SPACE);

for (int i = 0; i < 4; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(GREE_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? GREE_ONE_SPACE : GREE_ZERO_SPACE);
}
}

data->mark(GREE_BIT_MARK);
data->space(GREE_ZERO_SPACE);
data->mark(GREE_BIT_MARK);
data->space(GREE_ONE_SPACE);
data->mark(GREE_BIT_MARK);
data->space(GREE_ZERO_SPACE);

data->mark(GREE_BIT_MARK);
data->space(GREE_MESSAGE_SPACE);

for (int i = 4; i < 8; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(GREE_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? GREE_ONE_SPACE : GREE_ZERO_SPACE);
}
}

data->mark(GREE_BIT_MARK);
data->space(0);

transmit.perform();
}

uint8_t GreeClimate::operation_mode_() {
uint8_t operating_mode = GREE_MODE_ON;

switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
operating_mode |= GREE_MODE_COOL;
break;
case climate::CLIMATE_MODE_DRY:
operating_mode |= GREE_MODE_DRY;
break;
case climate::CLIMATE_MODE_HEAT:
operating_mode |= GREE_MODE_HEAT;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
operating_mode |= GREE_MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
operating_mode |= GREE_MODE_FAN;
break;
case climate::CLIMATE_MODE_OFF:
default:
operating_mode = GREE_MODE_OFF;
break;
}

return operating_mode;
}

uint8_t GreeClimate::fan_speed_() {
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_LOW:
return GREE_FAN_1;
case climate::CLIMATE_FAN_MEDIUM:
return GREE_FAN_2;
case climate::CLIMATE_FAN_HIGH:
return GREE_FAN_3;
case climate::CLIMATE_FAN_AUTO:
default:
return GREE_FAN_AUTO;
}
}

uint8_t GreeClimate::horizontal_swing_() {
switch (this->swing_mode) {
case climate::CLIMATE_SWING_HORIZONTAL:
case climate::CLIMATE_SWING_BOTH:
return GREE_HDIR_SWING;
default:
return GREE_HDIR_MANUAL;
}
}

uint8_t GreeClimate::vertical_swing_() {
switch (this->swing_mode) {
case climate::CLIMATE_SWING_VERTICAL:
case climate::CLIMATE_SWING_BOTH:
return GREE_VDIR_SWING;
default:
return GREE_VDIR_MANUAL;
}
}

uint8_t GreeClimate::temperature_() {
return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX));
}

} // namespace gree
} // namespace esphome
97 changes: 97 additions & 0 deletions esphome/components/gree/gree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#pragma once

#include "esphome/components/climate_ir/climate_ir.h"

namespace esphome {
namespace gree {

// Values for GREE IR Controllers
// Temperature
const uint8_t GREE_TEMP_MIN = 16; // Celsius
const uint8_t GREE_TEMP_MAX = 30; // Celsius

// Modes
const uint8_t GREE_MODE_AUTO = 0x00;
const uint8_t GREE_MODE_COOL = 0x01;
const uint8_t GREE_MODE_HEAT = 0x04;
const uint8_t GREE_MODE_DRY = 0x02;
const uint8_t GREE_MODE_FAN = 0x03;

const uint8_t GREE_MODE_OFF = 0x00;
const uint8_t GREE_MODE_ON = 0x08;

// Fan Speed
const uint8_t GREE_FAN_AUTO = 0x00;
const uint8_t GREE_FAN_1 = 0x10;
const uint8_t GREE_FAN_2 = 0x20;
const uint8_t GREE_FAN_3 = 0x30;
const uint8_t GREE_FAN_TURBO = 0x80;

// IR Transmission
const uint32_t GREE_IR_FREQUENCY = 38000;
const uint32_t GREE_HEADER_MARK = 9000;
const uint32_t GREE_HEADER_SPACE = 4000;
const uint32_t GREE_BIT_MARK = 620;
const uint32_t GREE_ONE_SPACE = 1600;
const uint32_t GREE_ZERO_SPACE = 540;
const uint32_t GREE_MESSAGE_SPACE = 19000;

// Timing specific for YAC features (I-Feel mode)
const uint32_t GREE_YAC_HEADER_MARK = 6000;
const uint32_t GREE_YAC_HEADER_SPACE = 3000;
const uint32_t GREE_YAC_BIT_MARK = 650;

// State Frame size
const uint8_t GREE_STATE_FRAME_SIZE = 8;

// Only available on YAN
// Vertical air directions. Note that these cannot be set on all heat pumps
const uint8_t GREE_VDIR_AUTO = 0x00;
const uint8_t GREE_VDIR_MANUAL = 0x00;
const uint8_t GREE_VDIR_SWING = 0x01;
const uint8_t GREE_VDIR_UP = 0x02;
const uint8_t GREE_VDIR_MUP = 0x03;
const uint8_t GREE_VDIR_MIDDLE = 0x04;
const uint8_t GREE_VDIR_MDOWN = 0x05;
const uint8_t GREE_VDIR_DOWN = 0x06;

// Only available on YAC
// Horizontal air directions. Note that these cannot be set on all heat pumps
const uint8_t GREE_HDIR_AUTO = 0x00;
const uint8_t GREE_HDIR_MANUAL = 0x00;
const uint8_t GREE_HDIR_SWING = 0x01;
const uint8_t GREE_HDIR_LEFT = 0x02;
const uint8_t GREE_HDIR_MLEFT = 0x03;
const uint8_t GREE_HDIR_MIDDLE = 0x04;
const uint8_t GREE_HDIR_MRIGHT = 0x05;
const uint8_t GREE_HDIR_RIGHT = 0x06;

// Model codes
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC };

class GreeClimate : public climate_ir::ClimateIR {
public:
GreeClimate()
: climate_ir::ClimateIR(GREE_TEMP_MIN, GREE_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}

void set_model(Model model);

protected:
// Transmit via IR the state of this climate controller.
void transmit_state() override;

uint8_t operation_mode_();
uint8_t fan_speed_();
uint8_t horizontal_swing_();
uint8_t vertical_swing_();
uint8_t temperature_();

Model model_{};
};

} // namespace gree
} // namespace esphome
3 changes: 3 additions & 0 deletions tests/test1.yaml
Original file line number Diff line number Diff line change
@@ -2297,6 +2297,9 @@ climate:
heat_mode: extended
- platform: whynter
name: Whynter
- platform: gree
name: GREE
model: generic
- platform: zhlt01
name: ZH/LT-01 Climate

0 comments on commit 96822aa

Please sign in to comment.