mirror of
https://github.com/esphome/esphome.git
synced 2024-11-14 11:08:10 +01:00
Add support for Tuya MCU 0x1C (obtain local time) (#1344)
* Fix some Tuya devices not handling commands sent without delay * Also do not report WiFi status if MCU does not support it * Support Tuya MCU 0x1c command (obtain local time) * Use #ifdef USE_TIME to handle optional dependency on RTC * Rename Tuya clock config variable to time to be consistent with the codebase * Add tuya time configuration to test4
This commit is contained in:
parent
eb5c4b7c4f
commit
9fed7cab5f
4 changed files with 92 additions and 11 deletions
|
@ -1,7 +1,8 @@
|
||||||
|
from esphome.components import time
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import uart
|
from esphome.components import uart
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID, CONF_TIME_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['uart']
|
DEPENDENCIES = ['uart']
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ Tuya = tuya_ns.class_('Tuya', cg.Component, uart.UARTDevice)
|
||||||
CONF_TUYA_ID = 'tuya_id'
|
CONF_TUYA_ID = 'tuya_id'
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.declare_id(Tuya),
|
cv.GenerateID(): cv.declare_id(Tuya),
|
||||||
|
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
|
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,3 +20,6 @@ def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
yield uart.register_uart_device(var, config)
|
yield uart.register_uart_device(var, config)
|
||||||
|
if CONF_TIME_ID in config:
|
||||||
|
time_ = yield cg.get_variable(config[CONF_TIME_ID])
|
||||||
|
cg.add(var.set_time_id(time_))
|
||||||
|
|
|
@ -6,9 +6,10 @@ namespace esphome {
|
||||||
namespace tuya {
|
namespace tuya {
|
||||||
|
|
||||||
static const char *TAG = "tuya";
|
static const char *TAG = "tuya";
|
||||||
|
static const int COMMAND_DELAY = 50;
|
||||||
|
|
||||||
void Tuya::setup() {
|
void Tuya::setup() {
|
||||||
this->set_interval("heartbeat", 1000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
this->set_interval("heartbeat", 1000, [this] { this->schedule_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::loop() {
|
void Tuya::loop() {
|
||||||
|
@ -19,6 +20,15 @@ void Tuya::loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tuya::schedule_empty_command_(TuyaCommandType command) {
|
||||||
|
uint32_t delay = millis() - this->last_command_timestamp_;
|
||||||
|
if (delay > COMMAND_DELAY) {
|
||||||
|
send_empty_command_(command);
|
||||||
|
} else {
|
||||||
|
this->set_timeout(COMMAND_DELAY - delay, [this, command] { this->send_empty_command_(command); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Tuya::dump_config() {
|
void Tuya::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "Tuya:");
|
ESP_LOGCONFIG(TAG, "Tuya:");
|
||||||
if (this->init_state_ != TuyaInitState::INIT_DONE) {
|
if (this->init_state_ != TuyaInitState::INIT_DONE) {
|
||||||
|
@ -110,6 +120,7 @@ void Tuya::handle_char_(uint8_t c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
|
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
|
||||||
|
this->last_command_timestamp_ = millis();
|
||||||
switch ((TuyaCommandType) command) {
|
switch ((TuyaCommandType) command) {
|
||||||
case TuyaCommandType::HEARTBEAT:
|
case TuyaCommandType::HEARTBEAT:
|
||||||
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
|
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
|
||||||
|
@ -119,7 +130,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
}
|
}
|
||||||
if (this->init_state_ == TuyaInitState::INIT_HEARTBEAT) {
|
if (this->init_state_ == TuyaInitState::INIT_HEARTBEAT) {
|
||||||
this->init_state_ = TuyaInitState::INIT_PRODUCT;
|
this->init_state_ = TuyaInitState::INIT_PRODUCT;
|
||||||
this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY);
|
this->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::PRODUCT_QUERY: {
|
case TuyaCommandType::PRODUCT_QUERY: {
|
||||||
|
@ -138,7 +149,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
}
|
}
|
||||||
if (this->init_state_ == TuyaInitState::INIT_PRODUCT) {
|
if (this->init_state_ == TuyaInitState::INIT_PRODUCT) {
|
||||||
this->init_state_ = TuyaInitState::INIT_CONF;
|
this->init_state_ = TuyaInitState::INIT_CONF;
|
||||||
this->send_empty_command_(TuyaCommandType::CONF_QUERY);
|
this->schedule_empty_command_(TuyaCommandType::CONF_QUERY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -148,19 +159,27 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
gpio_reset_ = buffer[1];
|
gpio_reset_ = buffer[1];
|
||||||
}
|
}
|
||||||
if (this->init_state_ == TuyaInitState::INIT_CONF) {
|
if (this->init_state_ == TuyaInitState::INIT_CONF) {
|
||||||
|
// If mcu returned status gpio, then we can ommit sending wifi state
|
||||||
|
if (this->gpio_status_ != 0) {
|
||||||
|
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
||||||
|
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||||
|
} else {
|
||||||
|
this->init_state_ = TuyaInitState::INIT_WIFI;
|
||||||
|
this->set_timeout(COMMAND_DELAY, [this] {
|
||||||
// If we were following the spec to the letter we would send
|
// If we were following the spec to the letter we would send
|
||||||
// state updates until connected to both WiFi and API/MQTT.
|
// state updates until connected to both WiFi and API/MQTT.
|
||||||
// Instead we just claim to be connected immediately and move on.
|
// Instead we just claim to be connected immediately and move on.
|
||||||
uint8_t c[] = {0x04};
|
uint8_t c[] = {0x04};
|
||||||
this->init_state_ = TuyaInitState::INIT_WIFI;
|
|
||||||
this->send_command_(TuyaCommandType::WIFI_STATE, c, 1);
|
this->send_command_(TuyaCommandType::WIFI_STATE, c, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TuyaCommandType::WIFI_STATE:
|
case TuyaCommandType::WIFI_STATE:
|
||||||
if (this->init_state_ == TuyaInitState::INIT_WIFI) {
|
if (this->init_state_ == TuyaInitState::INIT_WIFI) {
|
||||||
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
||||||
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::WIFI_RESET:
|
case TuyaCommandType::WIFI_RESET:
|
||||||
|
@ -185,6 +204,44 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
this->send_command_(TuyaCommandType::WIFI_TEST, c, 2);
|
this->send_command_(TuyaCommandType::WIFI_TEST, c, 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TuyaCommandType::LOCAL_TIME_QUERY: {
|
||||||
|
#ifdef USE_TIME
|
||||||
|
if (this->time_id_.has_value()) {
|
||||||
|
auto time_id = *this->time_id_;
|
||||||
|
auto now = time_id->now();
|
||||||
|
|
||||||
|
if (now.is_valid()) {
|
||||||
|
this->set_timeout(COMMAND_DELAY, [this, now] {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
uint8_t c[] = {0x01, year, month, day_of_month, hour, minute, second, day_of_week};
|
||||||
|
this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not valid");
|
||||||
|
// By spec we need to notify MCU that the time was not obtained
|
||||||
|
this->set_timeout(COMMAND_DELAY, [this] {
|
||||||
|
uint8_t c[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not configured");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ESP_LOGE(TAG, "LOCAL_TIME_QUERY is not handled");
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
ESP_LOGE(TAG, "invalid command (%02x) received", command);
|
ESP_LOGE(TAG, "invalid command (%02x) received", command);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace tuya {
|
namespace tuya {
|
||||||
|
|
||||||
|
@ -43,6 +48,7 @@ enum class TuyaCommandType : uint8_t {
|
||||||
DATAPOINT_REPORT = 0x07,
|
DATAPOINT_REPORT = 0x07,
|
||||||
DATAPOINT_QUERY = 0x08,
|
DATAPOINT_QUERY = 0x08,
|
||||||
WIFI_TEST = 0x0E,
|
WIFI_TEST = 0x0E,
|
||||||
|
LOCAL_TIME_QUERY = 0x1C,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TuyaInitState : uint8_t {
|
enum class TuyaInitState : uint8_t {
|
||||||
|
@ -62,6 +68,9 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
|
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
|
||||||
void set_datapoint_value(TuyaDatapoint datapoint);
|
void set_datapoint_value(TuyaDatapoint datapoint);
|
||||||
|
#ifdef USE_TIME
|
||||||
|
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void handle_char_(uint8_t c);
|
void handle_char_(uint8_t c);
|
||||||
|
@ -71,10 +80,15 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||||
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len);
|
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len);
|
||||||
void send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len);
|
void send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len);
|
||||||
void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); }
|
void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); }
|
||||||
|
void schedule_empty_command_(TuyaCommandType command);
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
optional<time::RealTimeClock *> time_id_{};
|
||||||
|
#endif
|
||||||
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
||||||
int gpio_status_ = -1;
|
int gpio_status_ = -1;
|
||||||
int gpio_reset_ = -1;
|
int gpio_reset_ = -1;
|
||||||
|
uint32_t last_command_timestamp_ = 0;
|
||||||
std::string product_ = "";
|
std::string product_ = "";
|
||||||
std::vector<TuyaDatapointListener> listeners_;
|
std::vector<TuyaDatapointListener> listeners_;
|
||||||
std::vector<TuyaDatapoint> datapoints_;
|
std::vector<TuyaDatapoint> datapoints_;
|
||||||
|
|
|
@ -49,7 +49,12 @@ web_server:
|
||||||
username: admin
|
username: admin
|
||||||
password: admin
|
password: admin
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
id: sntp_time
|
||||||
|
|
||||||
tuya:
|
tuya:
|
||||||
|
time_id: sntp_time
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: homeassistant
|
- platform: homeassistant
|
||||||
|
|
Loading…
Reference in a new issue