mirror of
https://github.com/nuttytree/ESPHome-Devices.git
synced 2024-08-30 18:12:19 +00:00
Updates needed for ESPHome 2021.8.0 (#20)
* Begin making updates needed for ESPHome v2021.8.0 * Wrap up changes needed for ESPHome 2021.8.0 Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
This commit is contained in:
parent
5ab19cc690
commit
18bb3a8e5e
@ -21,9 +21,6 @@ This an enhanced version of the standard [gpio switch](https://esphome.io/compon
|
||||
### TREO LED Pool Light
|
||||
This is a custom light component that works with [TREO LED Pool Lights](https://www.srsmith.com/en-us/products/pool-lighting/treo-led-pool-light/) and exposes the different colors as "effects" so thay can be selected from Home Assistant. More details on how to use this component are available [here](./components/treo_led_pool_light/README.md).
|
||||
|
||||
### Tuya
|
||||
This is a copy of the standard Tuya component with a couple of fixes/tweaks that are still getting hashed out in the main branch of ESPHome that are needed to get my custom Tuya Light Plus component to work reliably. Hopefully I can remove this component soon.
|
||||
|
||||
### Tuya Dimmer as Fan
|
||||
This a modified version of the [Tuya fan](https://esphome.io/components/fan/tuya.html) component I use with [Feit Dimmers](https://www.amazon.com/gp/product/B07SXDFH38/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&psc=1) (but it will likely work with other Tuya dimmers) to control bathroom fans and adds several features. I created this component because I couldn't find a regular on/off switch with the same look and feel as the Feit dimmers so I decided to use the Feit dimmers but use this component to prevent "dimming" the fan. More details on features and how to use this component are available [here](./components/tuya_dimmer_as_fan/README.md).
|
||||
|
||||
|
@ -5,8 +5,7 @@ namespace binary_power {
|
||||
|
||||
light::LightTraits BinaryLightWithPower::get_traits() {
|
||||
auto traits = light::LightTraits();
|
||||
// TODO: Enable this with the 1.21.x version of ESPHome
|
||||
// traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,11 @@ CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend(
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(LightWithPower),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement_=UNIT_WATT,
|
||||
accuracy_decimals_=1,
|
||||
device_class_=DEVICE_CLASS_POWER,
|
||||
state_class_=STATE_CLASS_MEASUREMENT,
|
||||
icon_=ICON_POWER,
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_POWER,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_LIGHT_WATTAGE): cv.positive_float,
|
||||
|
@ -43,11 +43,11 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
|
||||
CONF_INTERLOCK_WAIT_TIME, default="0ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement_=UNIT_WATT,
|
||||
accuracy_decimals_=1,
|
||||
device_class_=DEVICE_CLASS_POWER,
|
||||
state_class_=STATE_CLASS_MEASUREMENT,
|
||||
icon_=ICON_POWER,
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_POWER,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_DEVICE_WATTAGE): cv.positive_float,
|
||||
|
@ -1,65 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, time
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TIME_ID,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["time"]
|
||||
|
||||
CONF_POWER_ID = "power_id"
|
||||
CONF_MIN_SAVE_INTERVAL = "min_save_interval"
|
||||
CONF_TOTAL_DAILY_ENERGY_METHOD = "method"
|
||||
total_daily_energy_ns = cg.esphome_ns.namespace("total_daily_energy")
|
||||
TotalDailyEnergyMethod = total_daily_energy_ns.enum("TotalDailyEnergyMethod")
|
||||
TOTAL_DAILY_ENERGY_METHODS = {
|
||||
"trapezoid": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID,
|
||||
"left": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_LEFT,
|
||||
"right": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_RIGHT,
|
||||
}
|
||||
TotalDailyEnergy = total_daily_energy_ns.class_(
|
||||
"TotalDailyEnergy", sensor.Sensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
accuracy_decimals_=0,
|
||||
device_class_=DEVICE_CLASS_ENERGY,
|
||||
state_class_=STATE_CLASS_MEASUREMENT,
|
||||
last_reset_type_=LAST_RESET_TYPE_AUTO,
|
||||
icon_="",
|
||||
unit_of_measurement_="",
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
|
||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(
|
||||
CONF_MIN_SAVE_INTERVAL, default="0s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_TOTAL_DAILY_ENERGY_METHOD, default="left"): cv.enum(
|
||||
TOTAL_DAILY_ENERGY_METHODS, lower=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
sens = await cg.get_variable(config[CONF_POWER_ID])
|
||||
cg.add(var.set_parent(sens))
|
||||
time_ = await cg.get_variable(config[CONF_TIME_ID])
|
||||
cg.add(var.set_time(time_))
|
||||
cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL]))
|
||||
cg.add(var.set_method(config[CONF_TOTAL_DAILY_ENERGY_METHOD]))
|
@ -1,79 +0,0 @@
|
||||
#include "total_daily_energy.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace total_daily_energy {
|
||||
|
||||
static const char *const TAG = "total_daily_energy";
|
||||
|
||||
void TotalDailyEnergy::setup() {
|
||||
this->pref_ = global_preferences.make_preference<float>(this->get_object_id_hash());
|
||||
|
||||
float recovered;
|
||||
if (this->pref_.load(&recovered)) {
|
||||
this->publish_state_and_save(recovered);
|
||||
} else {
|
||||
this->publish_state_and_save(0);
|
||||
}
|
||||
this->last_update_ = millis();
|
||||
this->last_save_ = this->last_update_;
|
||||
|
||||
this->parent_->add_on_state_callback([this](float state) { this->process_new_state_(state); });
|
||||
}
|
||||
|
||||
void TotalDailyEnergy::dump_config() { LOG_SENSOR("", "Total Daily Energy", this); }
|
||||
|
||||
void TotalDailyEnergy::loop() {
|
||||
auto t = this->time_->now();
|
||||
if (!t.is_valid())
|
||||
return;
|
||||
|
||||
if (this->last_day_of_year_ == 0) {
|
||||
this->last_day_of_year_ = t.day_of_year;
|
||||
return;
|
||||
}
|
||||
|
||||
if (t.day_of_year != this->last_day_of_year_) {
|
||||
this->last_day_of_year_ = t.day_of_year;
|
||||
this->total_energy_ = 0;
|
||||
this->publish_state_and_save(0);
|
||||
}
|
||||
}
|
||||
|
||||
void TotalDailyEnergy::publish_state_and_save(float state) {
|
||||
this->total_energy_ = state;
|
||||
this->publish_state(state);
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_save_ < this->min_save_interval_) {
|
||||
return;
|
||||
}
|
||||
this->last_save_ = now;
|
||||
this->pref_.save(&state);
|
||||
}
|
||||
|
||||
void TotalDailyEnergy::process_new_state_(float state) {
|
||||
if (isnan(state))
|
||||
return;
|
||||
const uint32_t now = millis();
|
||||
const float old_state = this->last_power_state_;
|
||||
const float new_state = state;
|
||||
float delta_hours = (now - this->last_update_) / 1000.0f / 60.0f / 60.0f;
|
||||
float delta_energy = 0.0f;
|
||||
switch (this->method_) {
|
||||
case TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID:
|
||||
delta_energy = delta_hours * (old_state + new_state) / 2.0;
|
||||
break;
|
||||
case TOTAL_DAILY_ENERGY_METHOD_LEFT:
|
||||
delta_energy = delta_hours * old_state;
|
||||
break;
|
||||
case TOTAL_DAILY_ENERGY_METHOD_RIGHT:
|
||||
delta_energy = delta_hours * new_state;
|
||||
break;
|
||||
}
|
||||
this->last_power_state_ = new_state;
|
||||
this->last_update_ = now;
|
||||
this->publish_state_and_save(this->total_energy_ + delta_energy);
|
||||
}
|
||||
|
||||
} // namespace total_daily_energy
|
||||
} // namespace esphome
|
@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace total_daily_energy {
|
||||
|
||||
enum TotalDailyEnergyMethod {
|
||||
TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID = 0,
|
||||
TOTAL_DAILY_ENERGY_METHOD_LEFT,
|
||||
TOTAL_DAILY_ENERGY_METHOD_RIGHT,
|
||||
};
|
||||
|
||||
class TotalDailyEnergy : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; }
|
||||
void set_time(time::RealTimeClock *time) { time_ = time; }
|
||||
void set_parent(Sensor *parent) { parent_ = parent; }
|
||||
void set_method(TotalDailyEnergyMethod method) { method_ = method; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
std::string unit_of_measurement() override { return this->parent_->get_unit_of_measurement() + "h"; }
|
||||
std::string icon() override { return this->parent_->get_icon(); }
|
||||
int8_t accuracy_decimals() override { return this->parent_->get_accuracy_decimals() + 2; }
|
||||
void loop() override;
|
||||
|
||||
void publish_state_and_save(float state);
|
||||
|
||||
protected:
|
||||
void process_new_state_(float state);
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
time::RealTimeClock *time_;
|
||||
Sensor *parent_;
|
||||
TotalDailyEnergyMethod method_;
|
||||
uint16_t last_day_of_year_{};
|
||||
uint32_t last_update_{0};
|
||||
uint32_t last_save_{0};
|
||||
uint32_t min_save_interval_{0};
|
||||
float total_energy_{0.0f};
|
||||
float last_power_state_{0.0f};
|
||||
};
|
||||
|
||||
} // namespace total_daily_energy
|
||||
} // namespace esphome
|
@ -26,11 +26,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TreoLight),
|
||||
cv.Required(CONF_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement_=UNIT_WATT,
|
||||
accuracy_decimals_=1,
|
||||
device_class_=DEVICE_CLASS_POWER,
|
||||
state_class_=STATE_CLASS_MEASUREMENT,
|
||||
icon_=ICON_POWER,
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_POWER,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_LIGHT_WATTAGE): cv.positive_float,
|
||||
|
@ -11,6 +11,7 @@ static const uint32_t RESTORE_COLOR_VERSION = 0x7B715952UL;
|
||||
|
||||
LightTraits TreoLedPoolLight::get_traits() {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
from esphome.components import time
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID, CONF_TIME_ID
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints"
|
||||
|
||||
tuya_ns = cg.esphome_ns.namespace("tuya")
|
||||
Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice)
|
||||
|
||||
CONF_TUYA_ID = "tuya_id"
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Tuya),
|
||||
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list(
|
||||
cv.uint8_t
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, 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_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,548 +0,0 @@
|
||||
#include "tuya.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
static const char *TAG = "tuya";
|
||||
static const int COMMAND_DELAY = 50;
|
||||
static const int RECEIVE_TIMEOUT = 300;
|
||||
|
||||
void Tuya::setup() {
|
||||
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||
}
|
||||
|
||||
void Tuya::loop() {
|
||||
while (this->available()) {
|
||||
uint8_t c;
|
||||
this->read_byte(&c);
|
||||
this->handle_char_(c);
|
||||
}
|
||||
process_command_queue_();
|
||||
}
|
||||
|
||||
void Tuya::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Tuya:");
|
||||
if (this->init_state_ != TuyaInitState::INIT_DONE) {
|
||||
ESP_LOGCONFIG(TAG, " Configuration will be reported when setup is complete. Current init_state: %u",
|
||||
static_cast<uint8_t>(this->init_state_));
|
||||
ESP_LOGCONFIG(TAG, " If no further output is received, confirm that this is a supported Tuya device.");
|
||||
return;
|
||||
}
|
||||
for (auto &info : this->datapoints_) {
|
||||
if (info.type == TuyaDatapointType::RAW)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, hexencode(info.value_raw).c_str());
|
||||
else if (info.type == TuyaDatapointType::BOOLEAN)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool));
|
||||
else if (info.type == TuyaDatapointType::INTEGER)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: int value (value: %d)", info.id, info.value_int);
|
||||
else if (info.type == TuyaDatapointType::STRING)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: string value (value: %s)", info.id, info.value_string.c_str());
|
||||
else if (info.type == TuyaDatapointType::ENUM)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: enum (value: %d)", info.id, info.value_enum);
|
||||
else if (info.type == TuyaDatapointType::BITMASK)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: bitmask (value: %x)", info.id, info.value_bitmask);
|
||||
else
|
||||
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_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str());
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
bool Tuya::validate_message_() {
|
||||
uint32_t at = this->rx_message_.size() - 1;
|
||||
auto *data = &this->rx_message_[0];
|
||||
uint8_t new_byte = data[at];
|
||||
|
||||
// Byte 0: HEADER1 (always 0x55)
|
||||
if (at == 0)
|
||||
return new_byte == 0x55;
|
||||
// Byte 1: HEADER2 (always 0xAA)
|
||||
if (at == 1)
|
||||
return new_byte == 0xAA;
|
||||
|
||||
// Byte 2: VERSION
|
||||
// no validation for the following fields:
|
||||
uint8_t version = data[2];
|
||||
if (at == 2)
|
||||
return true;
|
||||
// Byte 3: COMMAND
|
||||
uint8_t command = data[3];
|
||||
if (at == 3)
|
||||
return true;
|
||||
|
||||
// Byte 4: LENGTH1
|
||||
// Byte 5: LENGTH2
|
||||
if (at <= 5)
|
||||
// no validation for these fields
|
||||
return true;
|
||||
|
||||
uint16_t length = (uint16_t(data[4]) << 8) | (uint16_t(data[5]));
|
||||
|
||||
// wait until all data is read
|
||||
if (at - 6 < length)
|
||||
return true;
|
||||
|
||||
// Byte 6+LEN: CHECKSUM - sum of all bytes (including header) modulo 256
|
||||
uint8_t rx_checksum = new_byte;
|
||||
uint8_t calc_checksum = 0;
|
||||
for (uint32_t i = 0; i < 6 + length; i++)
|
||||
calc_checksum += data[i];
|
||||
|
||||
if (rx_checksum != calc_checksum) {
|
||||
ESP_LOGW(TAG, "Tuya Received invalid message checksum %02X!=%02X", rx_checksum, calc_checksum);
|
||||
return false;
|
||||
}
|
||||
|
||||
// valid message
|
||||
const uint8_t *message_data = data + 6;
|
||||
ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
|
||||
hexencode(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
|
||||
this->handle_command_(command, version, message_data, length);
|
||||
|
||||
// return false to reset rx buffer
|
||||
return false;
|
||||
}
|
||||
|
||||
void Tuya::handle_char_(uint8_t c) {
|
||||
this->rx_message_.push_back(c);
|
||||
if (!this->validate_message_()) {
|
||||
this->rx_message_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
|
||||
TuyaCommandType command_type = (TuyaCommandType) command;
|
||||
|
||||
if(this->expected_response_.has_value() && this->expected_response_ == command_type)
|
||||
{
|
||||
this->expected_response_.reset();
|
||||
}
|
||||
|
||||
switch (command_type) {
|
||||
case TuyaCommandType::HEARTBEAT:
|
||||
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
|
||||
this->protocol_version_ = version;
|
||||
if (buffer[0] == 0) {
|
||||
ESP_LOGI(TAG, "MCU restarted");
|
||||
this->init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
||||
}
|
||||
if (this->init_state_ == TuyaInitState::INIT_HEARTBEAT) {
|
||||
this->init_state_ = TuyaInitState::INIT_PRODUCT;
|
||||
this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY);
|
||||
}
|
||||
break;
|
||||
case TuyaCommandType::PRODUCT_QUERY: {
|
||||
// check it is a valid string made up of printable characters
|
||||
bool valid = true;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!std::isprint(buffer[i])) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
this->product_ = std::string(reinterpret_cast<const char *>(buffer), len);
|
||||
} else {
|
||||
this->product_ = R"({"p":"INVALID"})";
|
||||
}
|
||||
if (this->init_state_ == TuyaInitState::INIT_PRODUCT) {
|
||||
this->init_state_ = TuyaInitState::INIT_CONF;
|
||||
this->send_empty_command_(TuyaCommandType::CONF_QUERY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TuyaCommandType::CONF_QUERY: {
|
||||
if (len >= 2) {
|
||||
this->gpio_status_ = buffer[0];
|
||||
this->gpio_reset_ = buffer[1];
|
||||
}
|
||||
if (this->init_state_ == TuyaInitState::INIT_CONF) {
|
||||
// If mcu returned status gpio, then we can ommit sending wifi state
|
||||
if (this->gpio_status_ != -1) {
|
||||
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
||||
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||
} else {
|
||||
this->init_state_ = TuyaInitState::INIT_WIFI;
|
||||
this->set_interval("wifi", 1000, [this] { this->send_wifi_status_(); });
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TuyaCommandType::WIFI_STATE:
|
||||
if (this->init_state_ == TuyaInitState::INIT_WIFI) {
|
||||
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
||||
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||
}
|
||||
break;
|
||||
case TuyaCommandType::WIFI_RESET:
|
||||
ESP_LOGE(TAG, "WIFI_RESET is not handled");
|
||||
break;
|
||||
case TuyaCommandType::WIFI_SELECT:
|
||||
ESP_LOGE(TAG, "WIFI_SELECT is not handled");
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_DELIVER:
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_REPORT:
|
||||
if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) {
|
||||
this->init_state_ = TuyaInitState::INIT_DONE;
|
||||
this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
|
||||
}
|
||||
this->handle_datapoint_(buffer, len);
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_QUERY:
|
||||
break;
|
||||
case TuyaCommandType::WIFI_TEST:
|
||||
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector<uint8_t>{0x00, 0x00}});
|
||||
break;
|
||||
case TuyaCommandType::LOCAL_TIME_QUERY:
|
||||
#ifdef USE_TIME
|
||||
if (this->time_id_.has_value()) {
|
||||
this->send_local_time_();
|
||||
auto time_id = *this->time_id_;
|
||||
time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
|
||||
} else {
|
||||
ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured");
|
||||
}
|
||||
#else
|
||||
ESP_LOGE(TAG, "LOCAL_TIME_QUERY is not handled");
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid command (0x%02X) received", command);
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
|
||||
if (len < 2)
|
||||
return;
|
||||
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = buffer[0];
|
||||
datapoint.type = (TuyaDatapointType) buffer[1];
|
||||
datapoint.value_uint = 0;
|
||||
|
||||
// Drop update if datapoint is in ignore_mcu_datapoint_update list
|
||||
for (uint8_t i : this->ignore_mcu_update_on_datapoints_) {
|
||||
if (datapoint.id == i) {
|
||||
ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t data_size = (buffer[2] << 8) + buffer[3];
|
||||
const uint8_t *data = buffer + 4;
|
||||
size_t data_len = len - 4;
|
||||
if (data_size != data_len) {
|
||||
ESP_LOGW(TAG, "Datapoint %u is not expected size", datapoint.id);
|
||||
return;
|
||||
}
|
||||
datapoint.len = data_len;
|
||||
|
||||
switch (datapoint.type) {
|
||||
case TuyaDatapointType::RAW:
|
||||
datapoint.value_raw = std::vector<uint8_t>(data, data + data_len);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, hexencode(datapoint.value_raw).c_str());
|
||||
break;
|
||||
case TuyaDatapointType::BOOLEAN:
|
||||
if (data_len != 1) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_len);
|
||||
return;
|
||||
}
|
||||
datapoint.value_bool = data[0];
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
|
||||
break;
|
||||
case TuyaDatapointType::INTEGER:
|
||||
if (data_len != 4) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_len);
|
||||
return;
|
||||
}
|
||||
datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
|
||||
break;
|
||||
case TuyaDatapointType::STRING:
|
||||
datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_len);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
|
||||
break;
|
||||
case TuyaDatapointType::ENUM:
|
||||
if (data_len != 1) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_len);
|
||||
return;
|
||||
}
|
||||
datapoint.value_enum = data[0];
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
|
||||
break;
|
||||
case TuyaDatapointType::BITMASK:
|
||||
switch (data_len) {
|
||||
case 1:
|
||||
datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
|
||||
break;
|
||||
case 2:
|
||||
datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
|
||||
break;
|
||||
case 4:
|
||||
datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_len);
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, datapoint.type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update internal datapoints
|
||||
bool found = false;
|
||||
for (auto &other : this->datapoints_) {
|
||||
if (other.id == datapoint.id) {
|
||||
other = datapoint;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
this->datapoints_.push_back(datapoint);
|
||||
}
|
||||
|
||||
// Run through listeners
|
||||
for (auto &listener : this->listeners_)
|
||||
if (listener.datapoint_id == datapoint.id)
|
||||
listener.on_datapoint(datapoint);
|
||||
}
|
||||
|
||||
void Tuya::send_raw_command_(TuyaCommand command) {
|
||||
uint8_t len_hi = (uint8_t)(command.payload.size() >> 8);
|
||||
uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF);
|
||||
uint8_t version = 0;
|
||||
|
||||
this->last_command_timestamp_ = millis();
|
||||
switch (command.cmd)
|
||||
{
|
||||
case TuyaCommandType::HEARTBEAT:
|
||||
this->expected_response_ = TuyaCommandType::HEARTBEAT;
|
||||
break;
|
||||
case TuyaCommandType::PRODUCT_QUERY:
|
||||
this->expected_response_ = TuyaCommandType::PRODUCT_QUERY;
|
||||
break;
|
||||
case TuyaCommandType::CONF_QUERY:
|
||||
this->expected_response_ = TuyaCommandType::CONF_QUERY;
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_DELIVER:
|
||||
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_QUERY:
|
||||
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
|
||||
version, hexencode(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
|
||||
|
||||
this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
|
||||
if (!command.payload.empty())
|
||||
this->write_array(command.payload.data(), command.payload.size());
|
||||
|
||||
uint8_t checksum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo;
|
||||
for (auto &data : command.payload)
|
||||
checksum += data;
|
||||
this->write_byte(checksum);
|
||||
}
|
||||
|
||||
void Tuya::process_command_queue_() {
|
||||
uint32_t delay = millis() - this->last_command_timestamp_;
|
||||
|
||||
if(this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT)
|
||||
{
|
||||
this->expected_response_.reset();
|
||||
}
|
||||
|
||||
// Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
|
||||
if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && !this->expected_response_.has_value()) {
|
||||
this->send_raw_command_(command_queue_.front());
|
||||
this->command_queue_.erase(command_queue_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::send_command_(TuyaCommand command) {
|
||||
command_queue_.push_back(command);
|
||||
process_command_queue_();
|
||||
}
|
||||
|
||||
void Tuya::send_empty_command_(TuyaCommandType command) {
|
||||
send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
|
||||
}
|
||||
|
||||
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 (status == this->wifi_status_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Sending WiFi Status");
|
||||
this->wifi_status_ = status;
|
||||
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector<uint8_t>{status}});
|
||||
}
|
||||
|
||||
#ifdef USE_TIME
|
||||
void Tuya::send_local_time_() {
|
||||
std::vector<uint8_t> payload;
|
||||
auto time_id = *this->time_id_;
|
||||
time::ESPTime now = time_id->now();
|
||||
if (now.is_valid()) {
|
||||
uint8_t year = now.year - 2000;
|
||||
uint8_t month = now.month;
|
||||
uint8_t day_of_month = now.day_of_month;
|
||||
uint8_t hour = now.hour;
|
||||
uint8_t minute = now.minute;
|
||||
uint8_t second = now.second;
|
||||
// Tuya days starts from Monday, esphome uses Sunday as day 1
|
||||
uint8_t day_of_week = now.day_of_week - 1;
|
||||
if (day_of_week == 0) {
|
||||
day_of_week = 7;
|
||||
}
|
||||
ESP_LOGD(TAG, "Sending local time");
|
||||
payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week};
|
||||
} else {
|
||||
// By spec we need to notify MCU that the time was not obtained if this is a response to a query
|
||||
ESP_LOGW(TAG, "Sending missing local time");
|
||||
payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
}
|
||||
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY, .payload = payload});
|
||||
}
|
||||
#endif
|
||||
|
||||
void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
|
||||
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str());
|
||||
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
|
||||
if (!datapoint.has_value()) {
|
||||
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
|
||||
} else if (datapoint->type != TuyaDatapointType::RAW) {
|
||||
ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
|
||||
return;
|
||||
} else if (datapoint->value_raw == value) {
|
||||
ESP_LOGV(TAG, "Not sending unchanged value");
|
||||
return;
|
||||
}
|
||||
this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value);
|
||||
}
|
||||
|
||||
void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
|
||||
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1);
|
||||
}
|
||||
|
||||
void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
|
||||
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4);
|
||||
}
|
||||
|
||||
void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
|
||||
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str());
|
||||
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
|
||||
if (!datapoint.has_value()) {
|
||||
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
|
||||
} else if (datapoint->type != TuyaDatapointType::STRING) {
|
||||
ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
|
||||
return;
|
||||
} else if (datapoint->value_string == value) {
|
||||
ESP_LOGV(TAG, "Not sending unchanged value");
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> data;
|
||||
for (char const &c : value) {
|
||||
data.push_back(c);
|
||||
}
|
||||
this->send_datapoint_command_(datapoint->id, datapoint->type, data);
|
||||
}
|
||||
|
||||
void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
|
||||
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1);
|
||||
}
|
||||
|
||||
void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
|
||||
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length);
|
||||
}
|
||||
|
||||
optional<TuyaDatapoint> Tuya::get_datapoint_(uint8_t datapoint_id) {
|
||||
for (auto &datapoint : this->datapoints_)
|
||||
if (datapoint.id == datapoint_id)
|
||||
return datapoint;
|
||||
return {};
|
||||
}
|
||||
|
||||
void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value,
|
||||
uint8_t length) {
|
||||
ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value);
|
||||
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
|
||||
if (!datapoint.has_value()) {
|
||||
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
|
||||
} else if (datapoint->type != datapoint_type) {
|
||||
ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
|
||||
return;
|
||||
} else if (datapoint->value_uint == value) {
|
||||
ESP_LOGV(TAG, "Not sending unchanged value");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
switch (length) {
|
||||
case 4:
|
||||
data.push_back(value >> 24);
|
||||
data.push_back(value >> 16);
|
||||
case 2:
|
||||
data.push_back(value >> 8);
|
||||
case 1:
|
||||
data.push_back(value >> 0);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unexpected datapoint length %u", length);
|
||||
return;
|
||||
}
|
||||
this->send_datapoint_command_(datapoint_id, datapoint_type, data);
|
||||
}
|
||||
|
||||
void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data) {
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.push_back(datapoint_id);
|
||||
buffer.push_back(static_cast<uint8_t>(datapoint_type));
|
||||
buffer.push_back(data.size() >> 8);
|
||||
buffer.push_back(data.size() >> 0);
|
||||
buffer.insert(buffer.end(), data.begin(), data.end());
|
||||
|
||||
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer});
|
||||
}
|
||||
|
||||
void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
|
||||
auto listener = TuyaDatapointListener{
|
||||
.datapoint_id = datapoint_id,
|
||||
.on_datapoint = func,
|
||||
};
|
||||
this->listeners_.push_back(listener);
|
||||
|
||||
// Run through existing datapoints
|
||||
for (auto &datapoint : this->datapoints_)
|
||||
if (datapoint.id == datapoint_id)
|
||||
func(datapoint);
|
||||
}
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
@ -1,127 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
enum class TuyaDatapointType : uint8_t {
|
||||
RAW = 0x00, // variable length
|
||||
BOOLEAN = 0x01, // 1 byte (0/1)
|
||||
INTEGER = 0x02, // 4 byte
|
||||
STRING = 0x03, // variable length
|
||||
ENUM = 0x04, // 1 byte
|
||||
BITMASK = 0x05, // 2 bytes
|
||||
};
|
||||
|
||||
struct TuyaDatapoint {
|
||||
uint8_t id;
|
||||
TuyaDatapointType type;
|
||||
size_t len;
|
||||
union {
|
||||
bool value_bool;
|
||||
int value_int;
|
||||
uint32_t value_uint;
|
||||
uint8_t value_enum;
|
||||
uint32_t value_bitmask;
|
||||
};
|
||||
std::string value_string;
|
||||
std::vector<uint8_t> value_raw;
|
||||
};
|
||||
|
||||
struct TuyaDatapointListener {
|
||||
uint8_t datapoint_id;
|
||||
std::function<void(TuyaDatapoint)> on_datapoint;
|
||||
};
|
||||
|
||||
enum class TuyaCommandType : uint8_t {
|
||||
HEARTBEAT = 0x00,
|
||||
PRODUCT_QUERY = 0x01,
|
||||
CONF_QUERY = 0x02,
|
||||
WIFI_STATE = 0x03,
|
||||
WIFI_RESET = 0x04,
|
||||
WIFI_SELECT = 0x05,
|
||||
DATAPOINT_DELIVER = 0x06,
|
||||
DATAPOINT_REPORT = 0x07,
|
||||
DATAPOINT_QUERY = 0x08,
|
||||
WIFI_TEST = 0x0E,
|
||||
LOCAL_TIME_QUERY = 0x1C,
|
||||
};
|
||||
|
||||
enum class TuyaInitState : uint8_t {
|
||||
INIT_HEARTBEAT = 0x00,
|
||||
INIT_PRODUCT,
|
||||
INIT_CONF,
|
||||
INIT_WIFI,
|
||||
INIT_DATAPOINT,
|
||||
INIT_DONE,
|
||||
};
|
||||
|
||||
struct TuyaCommand {
|
||||
TuyaCommandType cmd;
|
||||
std::vector<uint8_t> payload;
|
||||
};
|
||||
|
||||
class Tuya : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::LATE; }
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
|
||||
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_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);
|
||||
#ifdef USE_TIME
|
||||
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
|
||||
#endif
|
||||
void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) {
|
||||
this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints);
|
||||
}
|
||||
|
||||
protected:
|
||||
void handle_char_(uint8_t c);
|
||||
void handle_datapoint_(const uint8_t *buffer, size_t len);
|
||||
optional<TuyaDatapoint> get_datapoint_(uint8_t datapoint_id);
|
||||
bool validate_message_();
|
||||
|
||||
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len);
|
||||
void send_raw_command_(TuyaCommand command);
|
||||
void process_command_queue_();
|
||||
void send_command_(TuyaCommand command);
|
||||
void send_empty_command_(TuyaCommandType command);
|
||||
void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value,
|
||||
uint8_t length);
|
||||
void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data);
|
||||
void send_wifi_status_();
|
||||
|
||||
#ifdef USE_TIME
|
||||
void send_local_time_();
|
||||
optional<time::RealTimeClock *> time_id_{};
|
||||
#endif
|
||||
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
||||
uint8_t protocol_version_ = -1;
|
||||
int gpio_status_ = -1;
|
||||
int gpio_reset_ = -1;
|
||||
uint32_t last_command_timestamp_ = 0;
|
||||
std::string product_ = "";
|
||||
std::vector<TuyaDatapointListener> listeners_;
|
||||
std::vector<TuyaDatapoint> datapoints_;
|
||||
std::vector<uint8_t> rx_message_;
|
||||
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
|
||||
std::vector<TuyaCommand> command_queue_;
|
||||
optional<TuyaCommandType> expected_response_{};
|
||||
uint8_t wifi_status_ = -1;
|
||||
};
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
@ -7,11 +7,11 @@ This a modified version of the [Tuya fan](https://esphome.io/components/fan/tuya
|
||||
|
||||
|
||||
## Setup
|
||||
Using the [External Components](https://esphome.io/components/external_components.html) feature in ESPHome you can add this component to your devices directly from my GitHub repo. Note currently this component requires pulling in my custom version of the Tuya component as well to prevent communication issues between the ESP8266 and the Tuya MCU.
|
||||
Using the [External Components](https://esphome.io/components/external_components.html) feature in ESPHome you can add this component to your devices directly from my GitHub repo.
|
||||
```yaml
|
||||
external_components:
|
||||
- source: github://nuttytree/esphome
|
||||
components: [ tuya, tuya_dimmer_as_fan ]
|
||||
components: [ tuya_dimmer_as_fan ]
|
||||
```
|
||||
|
||||
Like the standard Tuya fan component you need to have the [UART](https://esphome.io/components/uart.html) and [Tuya](https://esphome.io/components/tuya.html) components.
|
||||
|
@ -31,11 +31,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_DIMMER_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_MAX_VALUE): cv.int_,
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement_=UNIT_WATT,
|
||||
accuracy_decimals_=1,
|
||||
device_class_=DEVICE_CLASS_POWER,
|
||||
state_class_=STATE_CLASS_MEASUREMENT,
|
||||
icon_=ICON_POWER,
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_POWER,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_FAN_WATTAGE): cv.positive_float,
|
||||
|
@ -15,11 +15,11 @@ This an enhanced version of the standard [Tuya light](https://esphome.io/compone
|
||||
|
||||
|
||||
## Setup
|
||||
Using the [External Components](https://esphome.io/components/external_components.html) feature in ESPHome you can add this component to your devices directly from my GitHub repo. Note currently this component requires pulling in my custom version of the Tuya component as well to prevent communication issues between the ESP8266 and the Tuya MCU.
|
||||
Using the [External Components](https://esphome.io/components/external_components.html) feature in ESPHome you can add this component to your devices directly from my GitHub repo.
|
||||
```yaml
|
||||
external_components:
|
||||
- source: github://nuttytree/esphome
|
||||
components: [ tuya, tuya_light_plus ]
|
||||
components: [ tuya_light_plus ]
|
||||
```
|
||||
|
||||
Like the standard Tuya Light component you need to have the [UART](https://esphome.io/components/uart.html) and [Tuya](https://esphome.io/components/tuya.html) components.
|
||||
|
@ -97,11 +97,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_DEFAULT_TRANSITION_LENGTH, default="0s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement_=UNIT_WATT,
|
||||
accuracy_decimals_=1,
|
||||
device_class_=DEVICE_CLASS_POWER,
|
||||
state_class_=STATE_CLASS_MEASUREMENT,
|
||||
icon_=ICON_POWER,
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_POWER,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_LIGHT_WATTAGE): cv.positive_float,
|
||||
|
@ -36,7 +36,7 @@ void TuyaLightPlus::dump_config()
|
||||
light::LightTraits TuyaLightPlus::get_traits()
|
||||
{
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,11 @@ external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ binary_light_with_power, total_daily_energy ]
|
||||
components: [ binary_light_with_power ]
|
||||
- source: github://cbpowell/ESPSense
|
||||
components: [ espsense ]
|
||||
- source: github://esphome/esphome@dev
|
||||
components: [ total_daily_energy ]
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
|
@ -12,12 +12,14 @@ packages:
|
||||
device_base: !include ../packages/device_base.yaml
|
||||
|
||||
external_components:
|
||||
- source: github://cbpowell/ESPSense
|
||||
components: [ espsense ]
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ binary_light_with_power, gpio_switch_with_power, total_daily_energy ]
|
||||
components: [ binary_light_with_power, gpio_switch_with_power ]
|
||||
- source: github://cbpowell/ESPSense
|
||||
components: [ espsense ]
|
||||
- source: github://esphome/esphome@dev
|
||||
components: [ total_daily_energy ]
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
|
@ -15,9 +15,11 @@ external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ binary_light_with_power, total_daily_energy ]
|
||||
components: [ binary_light_with_power ]
|
||||
- source: github://cbpowell/ESPSense
|
||||
components: [ espsense ]
|
||||
- source: github://esphome/esphome@dev
|
||||
components: [ total_daily_energy ]
|
||||
|
||||
espsense:
|
||||
plugs:
|
||||
|
@ -15,9 +15,11 @@ external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ treo_led_pool_light, binary_light_with_power, total_daily_energy ]
|
||||
components: [ treo_led_pool_light, binary_light_with_power ]
|
||||
- source: github://cbpowell/ESPSense
|
||||
components: [ espsense ]
|
||||
- source: github://esphome/esphome@dev
|
||||
components: [ total_daily_energy ]
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
|
@ -115,11 +115,13 @@ sensor:
|
||||
- platform: total_daily_energy
|
||||
name: "Pool Pump Total Daily Energy"
|
||||
power_id: pool_pump_power
|
||||
state_class: "measurement"
|
||||
method: left
|
||||
min_save_interval: 10min
|
||||
- platform: total_daily_energy
|
||||
name: "Pool Cleaner Total Daily Energy"
|
||||
power_id: pool_cleaner_power
|
||||
state_class: "measurement"
|
||||
method: left
|
||||
min_save_interval: 10min
|
||||
|
||||
status_led:
|
||||
pin:
|
||||
|
@ -6,9 +6,11 @@ external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ tuya, tuya_light_plus, total_daily_energy ]
|
||||
components: [ tuya_light_plus ]
|
||||
- source: github://cbpowell/ESPSense
|
||||
components: [ espsense ]
|
||||
- source: github://esphome/esphome@dev
|
||||
components: [ total_daily_energy ]
|
||||
|
||||
espsense:
|
||||
plugs:
|
||||
|
@ -6,9 +6,11 @@ external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ tuya, tuya_dimmer_as_fan, total_daily_energy ]
|
||||
components: [ tuya_dimmer_as_fan ]
|
||||
- source: github://cbpowell/ESPSense
|
||||
components: [ espsense ]
|
||||
- source: github://esphome/esphome@dev
|
||||
components: [ total_daily_energy ]
|
||||
|
||||
packages:
|
||||
base: !include device_base.yaml
|
||||
|
@ -6,7 +6,7 @@ external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ tuya, tuya_light_plus ]
|
||||
components: [ tuya_light_plus ]
|
||||
|
||||
packages:
|
||||
base: !include device_base.yaml
|
||||
|
Loading…
Reference in New Issue
Block a user