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:
Stefan Rado 2021-09-15 10:23:35 +02:00 committed by GitHub
parent 0ea77de98c
commit 2db8c42e1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 15 deletions

View file

@ -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:

View file

@ -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;

View file

@ -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