Merge branch 'dev' into bump-2022.6.0b1

This commit is contained in:
Jesse Hills
2022-06-08 22:46:20 +12:00
155 changed files with 3503 additions and 1699 deletions
+6
View File
@@ -1,5 +1,6 @@
from esphome.components import time
from esphome import automation
from esphome import pins
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
@@ -11,6 +12,7 @@ CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints"
CONF_ON_DATAPOINT_UPDATE = "on_datapoint_update"
CONF_DATAPOINT_TYPE = "datapoint_type"
CONF_STATUS_PIN = "status_pin"
tuya_ns = cg.esphome_ns.namespace("tuya")
Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice)
@@ -88,6 +90,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list(
cv.uint8_t
),
cv.Optional(CONF_STATUS_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
@@ -114,6 +117,9 @@ async def to_code(config):
if CONF_TIME_ID in config:
time_ = await cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_time_id(time_))
if CONF_STATUS_PIN in config:
status_pin_ = await cg.gpio_pin_expression(config[CONF_STATUS_PIN])
cg.add(var.set_status_pin(status_pin_))
if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config:
for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]:
cg.add(var.add_ignore_mcu_update_on_datapoints(dp))
-1
View File
@@ -1,5 +1,4 @@
#include "esphome/core/log.h"
#include "esphome/components/fan/fan_helpers.h"
#include "tuya_fan.h"
namespace esphome {
@@ -10,6 +10,11 @@ static const char *const TAG = "tuya.light";
void TuyaLight::setup() {
if (this->color_temperature_id_.has_value()) {
this->parent_->register_listener(*this->color_temperature_id_, [this](const TuyaDatapoint &datapoint) {
if (this->state_->current_values != this->state_->remote_values) {
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
return;
}
auto datapoint_value = datapoint.value_uint;
if (this->color_temperature_invert_) {
datapoint_value = this->color_temperature_max_value_ - datapoint_value;
@@ -23,6 +28,11 @@ void TuyaLight::setup() {
}
if (this->dimmer_id_.has_value()) {
this->parent_->register_listener(*this->dimmer_id_, [this](const TuyaDatapoint &datapoint) {
if (this->state_->current_values != this->state_->remote_values) {
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
return;
}
auto call = this->state_->make_call();
call.set_brightness(float(datapoint.value_uint) / this->max_value_);
call.perform();
@@ -30,6 +40,11 @@ void TuyaLight::setup() {
}
if (switch_id_.has_value()) {
this->parent_->register_listener(*this->switch_id_, [this](const TuyaDatapoint &datapoint) {
if (this->state_->current_values != this->state_->remote_values) {
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
return;
}
auto call = this->state_->make_call();
call.set_state(datapoint.value_bool);
call.perform();
@@ -41,6 +56,11 @@ void TuyaLight::setup() {
auto green = parse_hex<uint8_t>(datapoint.value_string.substr(2, 2));
auto blue = parse_hex<uint8_t>(datapoint.value_string.substr(4, 2));
if (red.has_value() && green.has_value() && blue.has_value()) {
if (this->state_->current_values != this->state_->remote_values) {
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
return;
}
auto call = this->state_->make_call();
call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255);
call.perform();
@@ -52,6 +72,11 @@ void TuyaLight::setup() {
auto saturation = parse_hex<uint16_t>(datapoint.value_string.substr(4, 4));
auto value = parse_hex<uint16_t>(datapoint.value_string.substr(8, 4));
if (hue.has_value() && saturation.has_value() && value.has_value()) {
if (this->state_->current_values != this->state_->remote_values) {
ESP_LOGD(TAG, "Light is transitioning, datapoint change ignored");
return;
}
float red, green, blue;
hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);
auto call = this->state_->make_call();
@@ -0,0 +1,47 @@
from esphome.components import select
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_OPTIONS, CONF_OPTIMISTIC, CONF_ENUM_DATAPOINT
from .. import tuya_ns, CONF_TUYA_ID, Tuya
DEPENDENCIES = ["tuya"]
CODEOWNERS = ["@bearpawmaxim"]
TuyaSelect = tuya_ns.class_("TuyaSelect", select.Select, cg.Component)
def ensure_option_map(value):
cv.check_not_templatable(value)
option = cv.All(cv.int_range(0, 2**8 - 1))
mapping = cv.All(cv.string_strict)
options_map_schema = cv.Schema({option: mapping})
value = options_map_schema(value)
all_values = list(value.keys())
unique_values = set(value.keys())
if len(all_values) != len(unique_values):
raise cv.Invalid("Mapping values must be unique.")
return value
CONFIG_SCHEMA = select.SELECT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TuyaSelect),
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
cv.Required(CONF_ENUM_DATAPOINT): cv.uint8_t,
cv.Required(CONF_OPTIONS): ensure_option_map,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
options_map = config[CONF_OPTIONS]
var = await select.new_select(config, options=list(options_map.values()))
await cg.register_component(var, config)
cg.add(var.set_select_mappings(list(options_map.keys())))
parent = await cg.get_variable(config[CONF_TUYA_ID])
cg.add(var.set_tuya_parent(parent))
cg.add(var.set_select_id(config[CONF_ENUM_DATAPOINT]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
@@ -0,0 +1,52 @@
#include "esphome/core/log.h"
#include "tuya_select.h"
namespace esphome {
namespace tuya {
static const char *const TAG = "tuya.select";
void TuyaSelect::setup() {
this->parent_->register_listener(this->select_id_, [this](const TuyaDatapoint &datapoint) {
uint8_t enum_value = datapoint.value_enum;
ESP_LOGV(TAG, "MCU reported select %u value %u", this->select_id_, enum_value);
auto options = this->traits.get_options();
auto mappings = this->mappings_;
auto it = std::find(mappings.cbegin(), mappings.cend(), enum_value);
if (it == mappings.end()) {
ESP_LOGW(TAG, "Invalid value %u", enum_value);
return;
}
size_t mapping_idx = std::distance(mappings.cbegin(), it);
auto value = this->at(mapping_idx);
this->publish_state(value.value());
});
}
void TuyaSelect::control(const std::string &value) {
if (this->optimistic_)
this->publish_state(value);
auto idx = this->index_of(value);
if (idx.has_value()) {
uint8_t mapping = this->mappings_.at(idx.value());
ESP_LOGV(TAG, "Setting %u datapoint value to %u:%s", this->select_id_, mapping, value.c_str());
this->parent_->set_enum_datapoint_value(this->select_id_, mapping);
return;
}
ESP_LOGW(TAG, "Invalid value %s", value.c_str());
}
void TuyaSelect::dump_config() {
LOG_SELECT("", "Tuya Select", this);
ESP_LOGCONFIG(TAG, " Select has datapoint ID %u", this->select_id_);
ESP_LOGCONFIG(TAG, " Options are:");
auto options = this->traits.get_options();
for (auto i = 0; i < this->mappings_.size(); i++) {
ESP_LOGCONFIG(TAG, " %i: %s", this->mappings_.at(i), options.at(i).c_str());
}
}
} // namespace tuya
} // namespace esphome
@@ -0,0 +1,30 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/tuya/tuya.h"
#include "esphome/components/select/select.h"
namespace esphome {
namespace tuya {
class TuyaSelect : public select::Select, public Component {
public:
void setup() override;
void dump_config() override;
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_select_id(uint8_t select_id) { this->select_id_ = select_id; }
void set_select_mappings(std::vector<uint8_t> mappings) { this->mappings_ = std::move(mappings); }
protected:
void control(const std::string &value) override;
Tuya *parent_;
bool optimistic_ = false;
uint8_t select_id_;
std::vector<uint8_t> mappings_;
};
} // namespace tuya
} // namespace esphome
@@ -20,7 +20,7 @@ void TuyaTextSensor::setup() {
break;
}
default:
ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, datapoint.type);
ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, (uint8_t) datapoint.type);
break;
}
});
+31 -10
View File
@@ -3,6 +3,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include "esphome/core/gpio.h"
namespace esphome {
namespace tuya {
@@ -14,6 +15,9 @@ static const int MAX_RETRIES = 5;
void Tuya::setup() {
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
if (this->status_pin_.has_value()) {
this->status_pin_.value()->digital_write(false);
}
}
void Tuya::loop() {
@@ -54,9 +58,12 @@ void Tuya::dump_config() {
ESP_LOGCONFIG(TAG, " Datapoint %u: unknown", info.id);
}
}
if ((this->gpio_status_ != -1) || (this->gpio_reset_ != -1)) {
ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", this->gpio_status_,
this->gpio_reset_);
if ((this->status_pin_reported_ != -1) || (this->reset_pin_reported_ != -1)) {
ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_,
this->reset_pin_reported_);
}
if (this->status_pin_.has_value()) {
LOG_PIN(" Status Pin: ", this->status_pin_.value());
}
ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str());
this->check_uart_settings(9600);
@@ -171,16 +178,27 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
}
case TuyaCommandType::CONF_QUERY: {
if (len >= 2) {
this->gpio_status_ = buffer[0];
this->gpio_reset_ = buffer[1];
this->status_pin_reported_ = buffer[0];
this->reset_pin_reported_ = buffer[1];
}
if (this->init_state_ == TuyaInitState::INIT_CONF) {
// If mcu returned status gpio, then we can omit sending wifi state
if (this->gpio_status_ != -1) {
if (this->status_pin_reported_ != -1) {
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
bool is_pin_equals =
this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_;
// Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send
if (is_pin_equals) {
ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_);
this->set_interval("wifi", 1000, [this] { this->set_status_pin_(); });
} else {
ESP_LOGW(TAG, "Supplied status_pin does not equals the reported pin %i. TuyaMcu will work in limited mode.",
this->status_pin_reported_);
}
} else {
this->init_state_ = TuyaInitState::INIT_WIFI;
ESP_LOGV(TAG, "Configured WIFI_STATE periodic send");
this->set_interval("wifi", 1000, [this] { this->send_wifi_status_(); });
}
}
@@ -415,16 +433,19 @@ void Tuya::send_empty_command_(TuyaCommandType command) {
send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
}
void Tuya::set_status_pin_() {
bool is_network_ready = network::is_connected() && remote_is_connected();
this->status_pin_.value()->digital_write(is_network_ready);
}
void Tuya::send_wifi_status_() {
uint8_t status = 0x02;
if (network::is_connected()) {
status = 0x03;
// Protocol version 3 also supports specifying when connected to "the cloud"
if (this->protocol_version_ >= 0x03) {
if (remote_is_connected()) {
status = 0x04;
}
if (this->protocol_version_ >= 0x03 && remote_is_connected()) {
status = 0x04;
}
}
+5 -2
View File
@@ -79,6 +79,7 @@ class Tuya : public Component, public uart::UARTDevice {
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value);
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value);
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value);
void set_status_pin(InternalGPIOPin *status_pin) { this->status_pin_ = status_pin; }
void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value);
void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value);
void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length);
@@ -115,6 +116,7 @@ class Tuya : public Component, public uart::UARTDevice {
void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced);
void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced);
void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data);
void set_status_pin_();
void send_wifi_status_();
#ifdef USE_TIME
@@ -125,8 +127,9 @@ class Tuya : public Component, public uart::UARTDevice {
bool init_failed_{false};
int init_retries_{0};
uint8_t protocol_version_ = -1;
int gpio_status_ = -1;
int gpio_reset_ = -1;
optional<InternalGPIOPin *> status_pin_{};
int status_pin_reported_ = -1;
int reset_pin_reported_ = -1;
uint32_t last_command_timestamp_ = 0;
uint32_t last_rx_char_timestamp_ = 0;
std::string product_ = "";