mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 05:24:53 +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.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ID, CONF_TIME_ID
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
|
@ -11,6 +12,7 @@ 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),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
|
@ -18,3 +20,6 @@ def to_code(config):
|
|||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(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 {
|
||||
|
||||
static const char *TAG = "tuya";
|
||||
static const int COMMAND_DELAY = 50;
|
||||
|
||||
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() {
|
||||
|
@ -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() {
|
||||
ESP_LOGCONFIG(TAG, "Tuya:");
|
||||
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) {
|
||||
this->last_command_timestamp_ = millis();
|
||||
switch ((TuyaCommandType) command) {
|
||||
case TuyaCommandType::HEARTBEAT:
|
||||
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) {
|
||||
this->init_state_ = TuyaInitState::INIT_PRODUCT;
|
||||
this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY);
|
||||
this->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY);
|
||||
}
|
||||
break;
|
||||
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) {
|
||||
this->init_state_ = TuyaInitState::INIT_CONF;
|
||||
this->send_empty_command_(TuyaCommandType::CONF_QUERY);
|
||||
this->schedule_empty_command_(TuyaCommandType::CONF_QUERY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -148,19 +159,27 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
|||
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_ != 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
|
||||
// state updates until connected to both WiFi and API/MQTT.
|
||||
// Instead we just claim to be connected immediately and move on.
|
||||
uint8_t c[] = {0x04};
|
||||
this->init_state_ = TuyaInitState::INIT_WIFI;
|
||||
this->send_command_(TuyaCommandType::WIFI_STATE, c, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
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);
|
||||
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||
}
|
||||
break;
|
||||
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);
|
||||
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:
|
||||
ESP_LOGE(TAG, "invalid command (%02x) received", command);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
#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 {
|
||||
|
||||
|
@ -43,6 +48,7 @@ enum class TuyaCommandType : uint8_t {
|
|||
DATAPOINT_REPORT = 0x07,
|
||||
DATAPOINT_QUERY = 0x08,
|
||||
WIFI_TEST = 0x0E,
|
||||
LOCAL_TIME_QUERY = 0x1C,
|
||||
};
|
||||
|
||||
enum class TuyaInitState : uint8_t {
|
||||
|
@ -62,6 +68,9 @@ class Tuya : public Component, public uart::UARTDevice {
|
|||
void dump_config() override;
|
||||
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
|
||||
void set_datapoint_value(TuyaDatapoint datapoint);
|
||||
#ifdef USE_TIME
|
||||
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
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 send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len);
|
||||
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;
|
||||
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_;
|
||||
|
|
|
@ -49,7 +49,12 @@ web_server:
|
|||
username: admin
|
||||
password: admin
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
tuya:
|
||||
time_id: sntp_time
|
||||
|
||||
sensor:
|
||||
- platform: homeassistant
|
||||
|
|
Loading…
Reference in a new issue