mirror of
https://github.com/esphome/esphome.git
synced 2024-12-21 16:27:44 +01:00
Support direct relay state feedback for tuya climate component (#1668)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
0ea77de98c
commit
2db8c42e1d
@ -1,3 +1,4 @@
|
||||
from esphome import pins
|
||||
from esphome.components import climate
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
@ -15,6 +16,8 @@ CODEOWNERS = ["@jesserockz"]
|
||||
CONF_ACTIVE_STATE_DATAPOINT = "active_state_datapoint"
|
||||
CONF_ACTIVE_STATE_HEATING_VALUE = "active_state_heating_value"
|
||||
CONF_ACTIVE_STATE_COOLING_VALUE = "active_state_cooling_value"
|
||||
CONF_HEATING_STATE_PIN = "heating_state_pin"
|
||||
CONF_COOLING_STATE_PIN = "cooling_state_pin"
|
||||
CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint"
|
||||
CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint"
|
||||
CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier"
|
||||
@ -69,14 +72,21 @@ def validate_temperature_multipliers(value):
|
||||
|
||||
def validate_active_state_values(value):
|
||||
if CONF_ACTIVE_STATE_DATAPOINT not in value:
|
||||
return value
|
||||
if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value:
|
||||
raise cv.Invalid(
|
||||
(
|
||||
f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using "
|
||||
f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling"
|
||||
if CONF_ACTIVE_STATE_COOLING_VALUE in value:
|
||||
raise cv.Invalid(
|
||||
(
|
||||
f"{CONF_ACTIVE_STATE_DATAPOINT} required if using "
|
||||
f"{CONF_ACTIVE_STATE_COOLING_VALUE}"
|
||||
)
|
||||
)
|
||||
else:
|
||||
if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value:
|
||||
raise cv.Invalid(
|
||||
(
|
||||
f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using "
|
||||
f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling"
|
||||
)
|
||||
)
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
@ -91,6 +101,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ACTIVE_STATE_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_ACTIVE_STATE_HEATING_VALUE, default=1): cv.uint8_t,
|
||||
cv.Optional(CONF_ACTIVE_STATE_COOLING_VALUE): cv.uint8_t,
|
||||
cv.Optional(CONF_HEATING_STATE_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_COOLING_STATE_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float,
|
||||
@ -101,6 +113,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT),
|
||||
validate_temperature_multipliers,
|
||||
validate_active_state_values,
|
||||
cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN),
|
||||
cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN),
|
||||
)
|
||||
|
||||
|
||||
@ -118,14 +132,29 @@ async def to_code(config):
|
||||
cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT]))
|
||||
if CONF_ACTIVE_STATE_DATAPOINT in config:
|
||||
cg.add(var.set_active_state_id(config[CONF_ACTIVE_STATE_DATAPOINT]))
|
||||
if CONF_ACTIVE_STATE_HEATING_VALUE in config:
|
||||
cg.add(
|
||||
var.set_active_state_heating_value(config[CONF_ACTIVE_STATE_HEATING_VALUE])
|
||||
)
|
||||
if CONF_ACTIVE_STATE_COOLING_VALUE in config:
|
||||
cg.add(
|
||||
var.set_active_state_cooling_value(config[CONF_ACTIVE_STATE_COOLING_VALUE])
|
||||
)
|
||||
if CONF_ACTIVE_STATE_HEATING_VALUE in config:
|
||||
cg.add(
|
||||
var.set_active_state_heating_value(
|
||||
config[CONF_ACTIVE_STATE_HEATING_VALUE]
|
||||
)
|
||||
)
|
||||
if CONF_ACTIVE_STATE_COOLING_VALUE in config:
|
||||
cg.add(
|
||||
var.set_active_state_cooling_value(
|
||||
config[CONF_ACTIVE_STATE_COOLING_VALUE]
|
||||
)
|
||||
)
|
||||
else:
|
||||
if CONF_HEATING_STATE_PIN in config:
|
||||
heating_state_pin = await cg.gpio_pin_expression(
|
||||
config[CONF_HEATING_STATE_PIN]
|
||||
)
|
||||
cg.add(var.set_heating_state_pin(heating_state_pin))
|
||||
if CONF_COOLING_STATE_PIN in config:
|
||||
cooling_state_pin = await cg.gpio_pin_expression(
|
||||
config[CONF_COOLING_STATE_PIN]
|
||||
)
|
||||
cg.add(var.set_cooling_state_pin(cooling_state_pin))
|
||||
if CONF_TARGET_TEMPERATURE_DATAPOINT in config:
|
||||
cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT]))
|
||||
if CONF_CURRENT_TEMPERATURE_DATAPOINT in config:
|
||||
|
@ -31,6 +31,15 @@ void TuyaClimate::setup() {
|
||||
this->compute_state_();
|
||||
this->publish_state();
|
||||
});
|
||||
} else {
|
||||
if (this->heating_state_pin_ != nullptr) {
|
||||
this->heating_state_pin_->setup();
|
||||
this->heating_state_ = this->heating_state_pin_->digital_read();
|
||||
}
|
||||
if (this->cooling_state_pin_ != nullptr) {
|
||||
this->cooling_state_pin_->setup();
|
||||
this->cooling_state_ = this->cooling_state_pin_->digital_read();
|
||||
}
|
||||
}
|
||||
if (this->target_temperature_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
@ -50,6 +59,34 @@ void TuyaClimate::setup() {
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::loop() {
|
||||
if (this->active_state_id_.has_value())
|
||||
return;
|
||||
|
||||
bool state_changed = false;
|
||||
if (this->heating_state_pin_ != nullptr) {
|
||||
bool heating_state = this->heating_state_pin_->digital_read();
|
||||
if (heating_state != this->heating_state_) {
|
||||
ESP_LOGV(TAG, "Heating state pin changed to: %s", ONOFF(heating_state));
|
||||
this->heating_state_ = heating_state;
|
||||
state_changed = true;
|
||||
}
|
||||
}
|
||||
if (this->cooling_state_pin_ != nullptr) {
|
||||
bool cooling_state = this->cooling_state_pin_->digital_read();
|
||||
if (cooling_state != this->cooling_state_) {
|
||||
ESP_LOGV(TAG, "Cooling state pin changed to: %s", ONOFF(cooling_state));
|
||||
this->cooling_state_ = cooling_state;
|
||||
state_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (state_changed) {
|
||||
this->compute_state_();
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
void TuyaClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_mode().has_value()) {
|
||||
const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF;
|
||||
@ -86,6 +123,8 @@ void TuyaClimate::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Target Temperature has datapoint ID %u", *this->target_temperature_id_);
|
||||
if (this->current_temperature_id_.has_value())
|
||||
ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_);
|
||||
LOG_PIN(" Heating State Pin: ", this->heating_state_pin_);
|
||||
LOG_PIN(" Cooling State Pin: ", this->cooling_state_pin_);
|
||||
}
|
||||
|
||||
void TuyaClimate::compute_state_() {
|
||||
@ -102,6 +141,7 @@ void TuyaClimate::compute_state_() {
|
||||
|
||||
climate::ClimateAction target_action = climate::CLIMATE_ACTION_IDLE;
|
||||
if (this->active_state_id_.has_value()) {
|
||||
// Use state from MCU datapoint
|
||||
if (this->supports_heat_ && this->active_state_heating_value_.has_value() &&
|
||||
this->active_state_ == this->active_state_heating_value_) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
@ -109,6 +149,13 @@ void TuyaClimate::compute_state_() {
|
||||
this->active_state_ == this->active_state_cooling_value_) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
}
|
||||
} else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) {
|
||||
// Use state from input pins
|
||||
if (this->heating_state_) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
} else if (this->cooling_state_) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
}
|
||||
} else {
|
||||
// Fallback to active state calc based on temp and hysteresis
|
||||
const float temp_diff = this->target_temperature - this->current_temperature;
|
||||
|
@ -10,6 +10,7 @@ namespace tuya {
|
||||
class TuyaClimate : public climate::Climate, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
@ -17,6 +18,8 @@ class TuyaClimate : public climate::Climate, public Component {
|
||||
void set_active_state_id(uint8_t state_id) { this->active_state_id_ = state_id; }
|
||||
void set_active_state_heating_value(uint8_t value) { this->active_state_heating_value_ = value; }
|
||||
void set_active_state_cooling_value(uint8_t value) { this->active_state_cooling_value_ = value; }
|
||||
void set_heating_state_pin(GPIOPin *pin) { this->heating_state_pin_ = pin; }
|
||||
void set_cooling_state_pin(GPIOPin *pin) { this->cooling_state_pin_ = pin; }
|
||||
void set_target_temperature_id(uint8_t target_temperature_id) {
|
||||
this->target_temperature_id_ = target_temperature_id;
|
||||
}
|
||||
@ -51,12 +54,16 @@ class TuyaClimate : public climate::Climate, public Component {
|
||||
optional<uint8_t> active_state_id_{};
|
||||
optional<uint8_t> active_state_heating_value_{};
|
||||
optional<uint8_t> active_state_cooling_value_{};
|
||||
GPIOPin *heating_state_pin_{nullptr};
|
||||
GPIOPin *cooling_state_pin_{nullptr};
|
||||
optional<uint8_t> target_temperature_id_{};
|
||||
optional<uint8_t> current_temperature_id_{};
|
||||
float current_temperature_multiplier_{1.0f};
|
||||
float target_temperature_multiplier_{1.0f};
|
||||
float hysteresis_{1.0f};
|
||||
uint8_t active_state_;
|
||||
bool heating_state_{false};
|
||||
bool cooling_state_{false};
|
||||
};
|
||||
|
||||
} // namespace tuya
|
||||
|
Loading…
Reference in New Issue
Block a user