mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-05-19 04:33:27 +02:00
Merge branch 'dev' into bump-2022.6.0b1
This commit is contained in:
@@ -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,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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_ = "";
|
||||
|
||||
Reference in New Issue
Block a user