1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-27 13:13:50 +00:00

Add preset, custom_preset and custom_fan_mode support to climate (#1471)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Lumpusz
2021-06-04 12:04:54 +02:00
committed by GitHub
parent 7bc51582f0
commit ebadaa9660
30 changed files with 931 additions and 96 deletions

View File

@@ -2,7 +2,12 @@ from esphome.components import climate, sensor
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import (
CONF_CUSTOM_FAN_MODES,
CONF_CUSTOM_PRESETS,
CONF_ID,
CONF_PRESET_BOOST,
CONF_PRESET_ECO,
CONF_PRESET_SLEEP,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -18,7 +23,6 @@ from esphome.components.midea_dongle import CONF_MIDEA_DONGLE_ID, MideaDongle
AUTO_LOAD = ["climate", "sensor", "midea_dongle"]
CODEOWNERS = ["@dudanov"]
CONF_BEEPER = "beeper"
CONF_SWING_HORIZONTAL = "swing_horizontal"
CONF_SWING_BOTH = "swing_both"
@@ -28,14 +32,36 @@ CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
midea_ac_ns = cg.esphome_ns.namespace("midea_ac")
MideaAC = midea_ac_ns.class_("MideaAC", climate.Climate, cg.Component)
CLIMATE_CUSTOM_FAN_MODES = {
"SILENT": "silent",
"TURBO": "turbo",
}
validate_climate_custom_fan_mode = cv.enum(CLIMATE_CUSTOM_FAN_MODES, upper=True)
CLIMATE_CUSTOM_PRESETS = {
"FREEZE_PROTECTION": "freeze protection",
}
validate_climate_custom_preset = cv.enum(CLIMATE_CUSTOM_PRESETS, upper=True)
CONFIG_SCHEMA = cv.All(
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(MideaAC),
cv.GenerateID(CONF_MIDEA_DONGLE_ID): cv.use_id(MideaDongle),
cv.Optional(CONF_BEEPER, default=False): cv.boolean,
cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(
validate_climate_custom_fan_mode
),
cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(
validate_climate_custom_preset
),
cv.Optional(CONF_SWING_HORIZONTAL, default=False): cv.boolean,
cv.Optional(CONF_SWING_BOTH, default=False): cv.boolean,
cv.Optional(CONF_PRESET_ECO, default=False): cv.boolean,
cv.Optional(CONF_PRESET_SLEEP, default=False): cv.boolean,
cv.Optional(CONF_PRESET_BOOST, default=False): cv.boolean,
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_THERMOMETER,
@@ -65,8 +91,15 @@ async def to_code(config):
paren = await cg.get_variable(config[CONF_MIDEA_DONGLE_ID])
cg.add(var.set_midea_dongle_parent(paren))
cg.add(var.set_beeper_feedback(config[CONF_BEEPER]))
if CONF_CUSTOM_FAN_MODES in config:
cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
if CONF_CUSTOM_PRESETS in config:
cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS]))
cg.add(var.set_swing_horizontal(config[CONF_SWING_HORIZONTAL]))
cg.add(var.set_swing_both(config[CONF_SWING_BOTH]))
cg.add(var.set_preset_eco(config[CONF_PRESET_ECO]))
cg.add(var.set_preset_sleep(config[CONF_PRESET_SLEEP]))
cg.add(var.set_preset_boost(config[CONF_PRESET_BOOST]))
if CONF_OUTDOOR_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE])
cg.add(var.set_outdoor_temperature_sensor(sens))

View File

@@ -40,8 +40,24 @@ void MideaAC::on_frame(const midea_dongle::Frame &frame) {
set_property(this->mode, p.get_mode(), need_publish);
set_property(this->target_temperature, p.get_target_temp(), need_publish);
set_property(this->current_temperature, p.get_indoor_temp(), need_publish);
set_property(this->fan_mode, p.get_fan_mode(), need_publish);
if (p.is_custom_fan_mode()) {
this->fan_mode.reset();
optional<std::string> mode = p.get_custom_fan_mode();
set_property(this->custom_fan_mode, mode, need_publish);
} else {
this->custom_fan_mode.reset();
optional<climate::ClimateFanMode> mode = p.get_fan_mode();
set_property(this->fan_mode, mode, need_publish);
}
set_property(this->swing_mode, p.get_swing_mode(), need_publish);
if (p.is_custom_preset()) {
this->preset.reset();
optional<std::string> preset = p.get_custom_preset();
set_property(this->custom_preset, preset, need_publish);
} else {
this->custom_preset.reset();
set_property(this->preset, p.get_preset(), need_publish);
}
if (need_publish)
this->publish_state();
set_sensor(this->outdoor_sensor_, p.get_outdoor_temp());
@@ -61,6 +77,48 @@ void MideaAC::on_update() {
}
}
bool MideaAC::allow_preset(climate::ClimatePreset preset) const {
switch (preset) {
case climate::CLIMATE_PRESET_ECO:
if (this->mode == climate::CLIMATE_MODE_COOL) {
return true;
} else {
ESP_LOGD(TAG, "ECO preset is only available in COOL mode");
}
break;
case climate::CLIMATE_PRESET_SLEEP:
if (this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_DRY) {
ESP_LOGD(TAG, "SLEEP preset is not available in FAN_ONLY or DRY mode");
} else {
return true;
}
break;
case climate::CLIMATE_PRESET_BOOST:
if (this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_COOL) {
return true;
} else {
ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode");
}
break;
case climate::CLIMATE_PRESET_HOME:
return true;
default:
break;
}
return false;
}
bool MideaAC::allow_custom_preset(const std::string &custom_preset) const {
if (custom_preset == MIDEA_FREEZE_PROTECTION_PRESET) {
if (this->mode == climate::CLIMATE_MODE_HEAT) {
return true;
} else {
ESP_LOGD(TAG, "%s is only available in HEAT mode", MIDEA_FREEZE_PROTECTION_PRESET.c_str());
}
}
return false;
}
void MideaAC::control(const climate::ClimateCall &call) {
if (call.get_mode().has_value() && call.get_mode().value() != this->mode) {
this->cmd_frame_.set_mode(call.get_mode().value());
@@ -70,14 +128,34 @@ void MideaAC::control(const climate::ClimateCall &call) {
this->cmd_frame_.set_target_temp(call.get_target_temperature().value());
this->ctrl_request_ = true;
}
if (call.get_fan_mode().has_value() && call.get_fan_mode().value() != this->fan_mode) {
if (call.get_fan_mode().has_value() &&
(!this->fan_mode.has_value() || this->fan_mode.value() != call.get_fan_mode().value())) {
this->custom_fan_mode.reset();
this->cmd_frame_.set_fan_mode(call.get_fan_mode().value());
this->ctrl_request_ = true;
}
if (call.get_custom_fan_mode().has_value() &&
(!this->custom_fan_mode.has_value() || this->custom_fan_mode.value() != call.get_custom_fan_mode().value())) {
this->fan_mode.reset();
this->cmd_frame_.set_custom_fan_mode(call.get_custom_fan_mode().value());
this->ctrl_request_ = true;
}
if (call.get_swing_mode().has_value() && call.get_swing_mode().value() != this->swing_mode) {
this->cmd_frame_.set_swing_mode(call.get_swing_mode().value());
this->ctrl_request_ = true;
}
if (call.get_preset().has_value() && this->allow_preset(call.get_preset().value()) &&
(!this->preset.has_value() || this->preset.value() != call.get_preset().value())) {
this->custom_preset.reset();
this->cmd_frame_.set_preset(call.get_preset().value());
this->ctrl_request_ = true;
}
if (call.get_custom_preset().has_value() && this->allow_custom_preset(call.get_custom_preset().value()) &&
(!this->custom_preset.has_value() || this->custom_preset.value() != call.get_custom_preset().value())) {
this->preset.reset();
this->cmd_frame_.set_custom_preset(call.get_custom_preset().value());
this->ctrl_request_ = true;
}
if (this->ctrl_request_) {
this->cmd_frame_.set_beeper_feedback(this->beeper_feedback_);
this->cmd_frame_.finalize();
@@ -98,10 +176,16 @@ climate::ClimateTraits MideaAC::traits() {
traits.set_supports_fan_mode_low(true);
traits.set_supports_fan_mode_medium(true);
traits.set_supports_fan_mode_high(true);
traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_);
traits.set_supports_swing_mode_off(true);
traits.set_supports_swing_mode_vertical(true);
traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_);
traits.set_supports_swing_mode_both(this->traits_swing_both_);
traits.set_supports_preset_home(true);
traits.set_supports_preset_eco(this->traits_preset_eco_);
traits.set_supports_preset_sleep(this->traits_preset_sleep_);
traits.set_supports_preset_boost(this->traits_preset_boost_);
traits.set_supported_custom_presets(this->traits_custom_presets_);
traits.set_supports_current_temperature(true);
return traits;
}

View File

@@ -22,6 +22,15 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu
void set_beeper_feedback(bool state) { this->beeper_feedback_ = state; }
void set_swing_horizontal(bool state) { this->traits_swing_horizontal_ = state; }
void set_swing_both(bool state) { this->traits_swing_both_ = state; }
void set_preset_eco(bool state) { this->traits_preset_eco_ = state; }
void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; }
void set_preset_boost(bool state) { this->traits_preset_boost_ = state; }
bool allow_preset(climate::ClimatePreset preset) const;
void set_custom_fan_modes(std::vector<std::string> custom_fan_modes) {
this->traits_custom_fan_modes_ = custom_fan_modes;
}
void set_custom_presets(std::vector<std::string> custom_presets) { this->traits_custom_presets_ = custom_presets; }
bool allow_custom_preset(const std::string &custom_preset) const;
protected:
/// Override control to change settings of the climate device.
@@ -41,6 +50,11 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu
bool beeper_feedback_{false};
bool traits_swing_horizontal_{false};
bool traits_swing_both_{false};
bool traits_preset_eco_{false};
bool traits_preset_sleep_{false};
bool traits_preset_boost_{false};
std::vector<std::string> traits_custom_fan_modes_{{}};
std::vector<std::string> traits_custom_presets_{{}};
};
} // namespace midea_ac

View File

@@ -3,6 +3,11 @@
namespace esphome {
namespace midea_ac {
static const char *TAG = "midea_ac";
const std::string MIDEA_SILENT_FAN_MODE = "silent";
const std::string MIDEA_TURBO_FAN_MODE = "turbo";
const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection";
const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00,
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68};
@@ -80,6 +85,54 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) {
this->pbuf_[12] |= m << 5;
}
optional<climate::ClimatePreset> PropertiesFrame::get_preset() const {
if (this->get_eco_mode()) {
return climate::CLIMATE_PRESET_ECO;
} else if (this->get_sleep_mode()) {
return climate::CLIMATE_PRESET_SLEEP;
} else if (this->get_turbo_mode()) {
return climate::CLIMATE_PRESET_BOOST;
} else {
return climate::CLIMATE_PRESET_HOME;
}
}
void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
switch (preset) {
case climate::CLIMATE_PRESET_ECO:
this->set_eco_mode(true);
break;
case climate::CLIMATE_PRESET_SLEEP:
this->set_sleep_mode(true);
break;
case climate::CLIMATE_PRESET_BOOST:
this->set_turbo_mode(true);
break;
default:
break;
}
}
bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); }
const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; };
void PropertiesFrame::set_custom_preset(const std::string &preset) {
if (preset == MIDEA_FREEZE_PROTECTION_PRESET) {
this->set_freeze_protection_mode(true);
}
}
bool PropertiesFrame::is_custom_fan_mode() const {
switch (this->pbuf_[13]) {
case MIDEA_FAN_SILENT:
case MIDEA_FAN_TURBO:
return true;
default:
return false;
}
}
climate::ClimateFanMode PropertiesFrame::get_fan_mode() const {
switch (this->pbuf_[13]) {
case MIDEA_FAN_LOW:
@@ -112,6 +165,25 @@ void PropertiesFrame::set_fan_mode(climate::ClimateFanMode mode) {
this->pbuf_[13] = m;
}
const std::string &PropertiesFrame::get_custom_fan_mode() const {
switch (this->pbuf_[13]) {
case MIDEA_FAN_SILENT:
return MIDEA_SILENT_FAN_MODE;
default:
return MIDEA_TURBO_FAN_MODE;
}
}
void PropertiesFrame::set_custom_fan_mode(const std::string &mode) {
uint8_t m;
if (mode == MIDEA_SILENT_FAN_MODE) {
m = MIDEA_FAN_SILENT;
} else {
m = MIDEA_FAN_TURBO;
}
this->pbuf_[13] = m;
}
climate::ClimateSwingMode PropertiesFrame::get_swing_mode() const {
switch (this->pbuf_[17] & 0x0F) {
case MIDEA_SWING_VERTICAL:

View File

@@ -5,6 +5,10 @@
namespace esphome {
namespace midea_ac {
extern const std::string MIDEA_SILENT_FAN_MODE;
extern const std::string MIDEA_TURBO_FAN_MODE;
extern const std::string MIDEA_FREEZE_PROTECTION_PRESET;
/// Enum for all modes a Midea device can be in.
enum MideaMode : uint8_t {
/// The Midea device is set to automatically change the heating/cooling cycle
@@ -23,12 +27,16 @@ enum MideaMode : uint8_t {
enum MideaFanMode : uint8_t {
/// The fan mode is set to Auto
MIDEA_FAN_AUTO = 102,
/// The fan mode is set to Silent
MIDEA_FAN_SILENT = 20,
/// The fan mode is set to Low
MIDEA_FAN_LOW = 40,
/// The fan mode is set to Medium
MIDEA_FAN_MEDIUM = 60,
/// The fan mode is set to High
MIDEA_FAN_HIGH = 80,
/// The fan mode is set to Turbo
MIDEA_FAN_TURBO = 100,
};
/// Enum for all modes a Midea swing can be in
@@ -65,9 +73,13 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
void set_mode(climate::ClimateMode mode);
/* FAN SPEED */
bool is_custom_fan_mode() const;
climate::ClimateFanMode get_fan_mode() const;
void set_fan_mode(climate::ClimateFanMode mode);
const std::string &get_custom_fan_mode() const;
void set_custom_fan_mode(const std::string &mode);
/* SWING MODE */
climate::ClimateSwingMode get_swing_mode() const;
void set_swing_mode(climate::ClimateSwingMode mode);
@@ -82,16 +94,28 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
float get_humidity_setpoint() const;
/* ECO MODE */
bool get_eco_mode() const { return this->pbuf_[19]; }
void set_eco_mode(bool state) { this->set_bytemask_(19, 0xFF, state); }
bool get_eco_mode() const { return this->pbuf_[19] & 0x10; }
void set_eco_mode(bool state) { this->set_bytemask_(19, 0x80, state); }
/* SLEEP MODE */
bool get_sleep_mode() const { return this->pbuf_[20] & 0x01; }
void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); }
/* TURBO MODE */
bool get_turbo_mode() const { return this->pbuf_[20] & 0x02; }
void set_turbo_mode(bool state) { this->set_bytemask_(20, 0x02, state); }
bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; }
void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); }
/* FREEZE PROTECTION */
bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; }
void set_freeze_protection_mode(bool state) { this->set_bytemask_(31, 0x80, state); }
/* PRESET */
optional<climate::ClimatePreset> get_preset() const;
void set_preset(climate::ClimatePreset preset);
bool is_custom_preset() const;
const std::string &get_custom_preset() const;
void set_custom_preset(const std::string &preset);
/* POWER USAGE */
float get_power_usage() const;