[gree] turbo, light, health, xfan switches (#12160)

Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
H. Árkosi Róbert
2025-12-03 10:20:17 +01:00
committed by GitHub
parent ab60ae092d
commit 6f91c75f86
9 changed files with 218 additions and 57 deletions

View File

@@ -190,6 +190,7 @@ esphome/components/gps/* @coogle @ximex
esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson
esphome/components/gree/* @orestismers
esphome/components/gree/switch/* @nagyrobi
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte

View File

@@ -0,0 +1,3 @@
import esphome.codegen as cg
gree_ns = cg.esphome_ns.namespace("gree")

View File

@@ -3,11 +3,11 @@ from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_MODEL
from . import gree_ns
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")
@@ -23,7 +23,7 @@ MODELS = {
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend(
{
cv.Required(CONF_MODEL): cv.enum(MODELS),
cv.Required(CONF_MODEL): cv.enum(MODELS, lower=True),
}
)

View File

@@ -16,13 +16,28 @@ void GreeClimate::set_model(Model model) {
this->model_ = model;
}
void GreeClimate::set_mode_bit(uint8_t bit_mask, bool enabled) {
if (enabled) {
this->mode_bits_ |= bit_mask;
} else {
this->mode_bits_ &= ~bit_mask;
}
this->transmit_state();
}
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 || this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) {
if (this->model_ == GREE_YAN) {
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[4] = this->vertical_swing_();
}
if (this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) {
remote_state[2] = 0x60;
remote_state[3] = 0x50;
remote_state[4] = this->vertical_swing_();
@@ -41,7 +56,7 @@ void GreeClimate::transmit_state() {
}
if (this->model_ == GREE_YAA || this->model_ == GREE_YAC || this->model_ == GREE_YAC1FB9) {
remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO,LIGHT,HEALTH,X-FAN
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
@@ -52,6 +67,13 @@ void GreeClimate::transmit_state() {
}
}
if (this->model_ == GREE_YAN || this->model_ == GREE_YAA || this->model_ == GREE_YAC ||
this->model_ == GREE_YAC1FB9) {
// Merge the mode bits into remote_state[2]
// Clear the mode bits (bits 4-7) and OR in the current mode_bits_
remote_state[2] = (remote_state[2] & 0x0F) | this->mode_bits_;
}
if (this->model_ == GREE_YX1FF) {
if (this->fan_speed_() == GREE_FAN_TURBO) {
remote_state[2] |= GREE_FAN_TURBO_BIT;

View File

@@ -2,80 +2,79 @@
#include "esphome/components/climate_ir/climate_ir.h"
namespace esphome {
namespace gree {
namespace esphome::gree {
// Values for GREE IR Controllers
// Temperature
const uint8_t GREE_TEMP_MIN = 16; // Celsius
const uint8_t GREE_TEMP_MAX = 30; // Celsius
static constexpr uint8_t GREE_TEMP_MIN = 16; // Celsius
static constexpr 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;
static constexpr uint8_t GREE_MODE_AUTO = 0x00;
static constexpr uint8_t GREE_MODE_COOL = 0x01;
static constexpr uint8_t GREE_MODE_HEAT = 0x04;
static constexpr uint8_t GREE_MODE_DRY = 0x02;
static constexpr uint8_t GREE_MODE_FAN = 0x03;
const uint8_t GREE_MODE_OFF = 0x00;
const uint8_t GREE_MODE_ON = 0x08;
static constexpr uint8_t GREE_MODE_OFF = 0x00;
static constexpr 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;
static constexpr uint8_t GREE_FAN_AUTO = 0x00;
static constexpr uint8_t GREE_FAN_1 = 0x10;
static constexpr uint8_t GREE_FAN_2 = 0x20;
static constexpr uint8_t GREE_FAN_3 = 0x30;
// 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;
static constexpr uint32_t GREE_IR_FREQUENCY = 38000;
static constexpr uint32_t GREE_HEADER_MARK = 9000;
static constexpr uint32_t GREE_HEADER_SPACE = 4000;
static constexpr uint32_t GREE_BIT_MARK = 620;
static constexpr uint32_t GREE_ONE_SPACE = 1600;
static constexpr uint32_t GREE_ZERO_SPACE = 540;
static constexpr 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;
static constexpr uint32_t GREE_YAC_HEADER_MARK = 6000;
static constexpr uint32_t GREE_YAC_HEADER_SPACE = 3000;
static constexpr uint32_t GREE_YAC_BIT_MARK = 650;
// Timing specific to YAC1FB9
const uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500;
const uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980;
static constexpr uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500;
static constexpr uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980;
// State Frame size
const uint8_t GREE_STATE_FRAME_SIZE = 8;
static constexpr 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;
static constexpr uint8_t GREE_VDIR_AUTO = 0x00;
static constexpr uint8_t GREE_VDIR_MANUAL = 0x00;
static constexpr uint8_t GREE_VDIR_SWING = 0x01;
static constexpr uint8_t GREE_VDIR_UP = 0x02;
static constexpr uint8_t GREE_VDIR_MUP = 0x03;
static constexpr uint8_t GREE_VDIR_MIDDLE = 0x04;
static constexpr uint8_t GREE_VDIR_MDOWN = 0x05;
static constexpr uint8_t GREE_VDIR_DOWN = 0x06;
// Only available on YAC/YAG
// 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;
static constexpr uint8_t GREE_HDIR_AUTO = 0x00;
static constexpr uint8_t GREE_HDIR_MANUAL = 0x00;
static constexpr uint8_t GREE_HDIR_SWING = 0x01;
static constexpr uint8_t GREE_HDIR_LEFT = 0x02;
static constexpr uint8_t GREE_HDIR_MLEFT = 0x03;
static constexpr uint8_t GREE_HDIR_MIDDLE = 0x04;
static constexpr uint8_t GREE_HDIR_MRIGHT = 0x05;
static constexpr uint8_t GREE_HDIR_RIGHT = 0x06;
// Only available on YX1FF
// Turbo (high) fan mode + sleep preset mode
const uint8_t GREE_FAN_TURBO = 0x80;
const uint8_t GREE_FAN_TURBO_BIT = 0x10;
const uint8_t GREE_PRESET_NONE = 0x00;
const uint8_t GREE_PRESET_SLEEP = 0x01;
const uint8_t GREE_PRESET_SLEEP_BIT = 0x80;
static constexpr uint8_t GREE_FAN_TURBO = 0x80;
static constexpr uint8_t GREE_FAN_TURBO_BIT = 0x10;
static constexpr uint8_t GREE_PRESET_NONE = 0x00;
static constexpr uint8_t GREE_PRESET_SLEEP = 0x01;
static constexpr uint8_t GREE_PRESET_SLEEP_BIT = 0x80;
// Model codes
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF, GREE_YAG };
@@ -90,6 +89,7 @@ class GreeClimate : public climate_ir::ClimateIR {
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
void set_model(Model model);
void set_mode_bit(uint8_t bit_mask, bool enabled);
protected:
// Transmit via IR the state of this climate controller.
@@ -103,7 +103,7 @@ class GreeClimate : public climate_ir::ClimateIR {
uint8_t preset_();
Model model_{};
uint8_t mode_bits_{0}; // Combined mode bits for remote_state[2]
};
} // namespace gree
} // namespace esphome
} // namespace esphome::gree

View File

@@ -0,0 +1,74 @@
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_LIGHT, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG
import esphome.final_validate as fv
from .. import gree_ns
from ..climate import CONF_MODEL, GreeClimate
CODEOWNERS = ["@nagyrobi"]
GreeModeBitSwitch = gree_ns.class_("GreeModeBitSwitch", switch.Switch, cg.Component)
CONF_TURBO = "turbo"
CONF_HEALTH = "health"
CONF_XFAN = "xfan"
CONF_GREE_ID = "gree_id"
# Switch configurations: (config_key, display_name, bit_mask, icon)
SWITCH_CONFIGS = (
(CONF_TURBO, "Gree Turbo Switch", 0x10, "mdi:car-turbocharger"),
(CONF_LIGHT, "Gree Light Switch", 0x20, "mdi:led-outline"),
(CONF_HEALTH, "Gree Health Switch", 0x40, "mdi:pine-tree"),
(CONF_XFAN, "Gree X-FAN Switch", 0x80, "mdi:wall-sconce-flat"),
)
SUPPORTED_MODELS = {
"yan",
"yaa",
"yac",
"yac1fb9",
}
CONFIG_SCHEMA = cv.Schema(
{
cv.Required(CONF_GREE_ID): cv.use_id(GreeClimate),
**{
cv.Optional(key): switch.switch_schema(
GreeModeBitSwitch,
icon=icon,
default_restore_mode="RESTORE_DEFAULT_OFF",
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
)
for key, _, _, icon in SWITCH_CONFIGS
},
}
)
def _validate_model(config):
full_config = fv.full_config.get()
climate_path = full_config.get_path_for_id(config[CONF_GREE_ID])[:-1]
climate_conf = full_config.get_config_for_path(climate_path)
if climate_conf[CONF_MODEL] not in SUPPORTED_MODELS:
raise cv.Invalid(
"Gree switches are only supported for the "
+ ", ".join(SUPPORTED_MODELS)
+ " models"
)
FINAL_VALIDATE_SCHEMA = _validate_model
async def to_code(config):
parent = await cg.get_variable(config[CONF_GREE_ID])
for conf_key, name, bit_mask, _ in SWITCH_CONFIGS:
if switch_conf := config.get(conf_key):
sw = cg.new_Pvariable(switch_conf[cv.CONF_ID], name, bit_mask)
await switch.register_switch(sw, switch_conf)
await cg.register_component(sw, switch_conf)
await cg.register_parented(sw, parent)

View File

@@ -0,0 +1,24 @@
#include "gree_switch.h"
#include "esphome/core/log.h"
namespace esphome {
namespace gree {
static const char *const TAG = "gree.switch";
void GreeModeBitSwitch::setup() {
auto initial = this->get_initial_state_with_restore_mode();
if (initial.has_value()) {
this->write_state(*initial);
}
}
void GreeModeBitSwitch::dump_config() { log_switch(TAG, " ", this->name_, this); }
void GreeModeBitSwitch::write_state(bool state) {
this->parent_->set_mode_bit(this->bit_mask_, state);
this->publish_state(state);
}
} // namespace gree
} // namespace esphome

View File

@@ -0,0 +1,24 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/gree/gree.h"
namespace esphome {
namespace gree {
class GreeModeBitSwitch : public switch_::Switch, public Component, public Parented<GreeClimate> {
public:
GreeModeBitSwitch(const char *name, uint8_t bit_mask) : name_(name), bit_mask_(bit_mask) {}
void setup() override;
void dump_config() override;
void write_state(bool state) override;
protected:
const char *name_;
uint8_t bit_mask_;
};
} // namespace gree
} // namespace esphome

View File

@@ -1,5 +1,18 @@
climate:
- platform: gree
name: GREE
model: generic
id: my_gree_ac
model: YAN
transmitter_id: xmitr
switch:
- platform: gree
gree_id: my_gree_ac
light:
name: "AC Lights"
turbo:
name: "AC Turbo"
health:
name: "AC Health"
xfan:
name: "AC X-Fan"