mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Tuya: Use queue for sending command messages (#1404)
This commit is contained in:
parent
b91723344e
commit
87154e9b6f
2 changed files with 71 additions and 58 deletions
|
@ -9,7 +9,7 @@ static const char *TAG = "tuya";
|
||||||
static const int COMMAND_DELAY = 50;
|
static const int COMMAND_DELAY = 50;
|
||||||
|
|
||||||
void Tuya::setup() {
|
void Tuya::setup() {
|
||||||
this->set_interval("heartbeat", 1000, [this] { this->schedule_empty_command_(TuyaCommandType::HEARTBEAT); });
|
this->set_interval("heartbeat", 1000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::loop() {
|
void Tuya::loop() {
|
||||||
|
@ -18,15 +18,7 @@ void Tuya::loop() {
|
||||||
this->read_byte(&c);
|
this->read_byte(&c);
|
||||||
this->handle_char_(c);
|
this->handle_char_(c);
|
||||||
}
|
}
|
||||||
}
|
process_command_queue_();
|
||||||
|
|
||||||
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() {
|
||||||
|
@ -122,7 +114,6 @@ 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]);
|
||||||
|
@ -132,7 +123,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->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY);
|
this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::PRODUCT_QUERY: {
|
case TuyaCommandType::PRODUCT_QUERY: {
|
||||||
|
@ -151,7 +142,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->schedule_empty_command_(TuyaCommandType::CONF_QUERY);
|
this->send_empty_command_(TuyaCommandType::CONF_QUERY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -164,16 +155,13 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
// If mcu returned status gpio, then we can ommit sending wifi state
|
// If mcu returned status gpio, then we can ommit sending wifi state
|
||||||
if (this->gpio_status_ != -1) {
|
if (this->gpio_status_ != -1) {
|
||||||
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
|
||||||
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||||
} else {
|
} else {
|
||||||
this->init_state_ = TuyaInitState::INIT_WIFI;
|
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};
|
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector<uint8_t>{0x04}});
|
||||||
this->send_command_(TuyaCommandType::WIFI_STATE, c, 1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -181,7 +169,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
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->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::WIFI_RESET:
|
case TuyaCommandType::WIFI_RESET:
|
||||||
|
@ -202,8 +190,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
case TuyaCommandType::DATAPOINT_QUERY:
|
case TuyaCommandType::DATAPOINT_QUERY:
|
||||||
break;
|
break;
|
||||||
case TuyaCommandType::WIFI_TEST: {
|
case TuyaCommandType::WIFI_TEST: {
|
||||||
uint8_t c[] = {0x00, 0x00};
|
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector<uint8_t>{0x00, 0x00}});
|
||||||
this->send_command_(TuyaCommandType::WIFI_TEST, c, 2);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TuyaCommandType::LOCAL_TIME_QUERY: {
|
case TuyaCommandType::LOCAL_TIME_QUERY: {
|
||||||
|
@ -213,7 +200,6 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
auto now = time_id->now();
|
auto now = time_id->now();
|
||||||
|
|
||||||
if (now.is_valid()) {
|
if (now.is_valid()) {
|
||||||
this->set_timeout(COMMAND_DELAY, [this, now] {
|
|
||||||
uint8_t year = now.year - 2000;
|
uint8_t year = now.year - 2000;
|
||||||
uint8_t month = now.month;
|
uint8_t month = now.month;
|
||||||
uint8_t day_of_month = now.day_of_month;
|
uint8_t day_of_month = now.day_of_month;
|
||||||
|
@ -225,16 +211,15 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
if (day_of_week == 0) {
|
if (day_of_week == 0) {
|
||||||
day_of_week = 7;
|
day_of_week = 7;
|
||||||
}
|
}
|
||||||
uint8_t c[] = {0x01, year, month, day_of_month, hour, minute, second, day_of_week};
|
this->send_command_(TuyaCommand{
|
||||||
this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8);
|
.cmd = TuyaCommandType::LOCAL_TIME_QUERY,
|
||||||
});
|
.payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week}});
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not valid");
|
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
|
// By spec we need to notify MCU that the time was not obtained
|
||||||
this->set_timeout(COMMAND_DELAY, [this] {
|
this->send_command_(
|
||||||
uint8_t c[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY,
|
||||||
this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8);
|
.payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not configured");
|
ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not configured");
|
||||||
|
@ -321,24 +306,44 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
|
||||||
listener.on_datapoint(datapoint);
|
listener.on_datapoint(datapoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len) {
|
void Tuya::send_raw_command_(TuyaCommand command) {
|
||||||
uint8_t len_hi = len >> 8;
|
uint8_t len_hi = (uint8_t)(command.payload.size() >> 8);
|
||||||
uint8_t len_lo = len >> 0;
|
uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF);
|
||||||
uint8_t version = 0;
|
uint8_t version = 0;
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, // NOLINT
|
this->last_command_timestamp_ = millis();
|
||||||
hexencode(buffer, len).c_str(), this->init_state_);
|
|
||||||
|
|
||||||
this->write_array({0x55, 0xAA, version, (uint8_t) command, len_hi, len_lo});
|
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command.cmd, version, // NOLINT
|
||||||
if (len != 0)
|
hexencode(command.payload).c_str(), this->init_state_);
|
||||||
this->write_array(buffer, len);
|
|
||||||
|
|
||||||
uint8_t checksum = 0x55 + 0xAA + (uint8_t) command + len_hi + len_lo;
|
this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
|
||||||
for (int i = 0; i < len; i++)
|
if (!command.payload.empty())
|
||||||
checksum += buffer[i];
|
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);
|
this->write_byte(checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tuya::process_command_queue_() {
|
||||||
|
uint32_t delay = millis() - this->last_command_timestamp_;
|
||||||
|
// Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
|
||||||
|
if (delay > COMMAND_DELAY && !command_queue_.empty()) {
|
||||||
|
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>{0x04}});
|
||||||
|
}
|
||||||
|
|
||||||
void Tuya::set_datapoint_value(TuyaDatapoint datapoint) {
|
void Tuya::set_datapoint_value(TuyaDatapoint datapoint) {
|
||||||
std::vector<uint8_t> buffer;
|
std::vector<uint8_t> buffer;
|
||||||
ESP_LOGV(TAG, "Datapoint %u set to %u", datapoint.id, datapoint.value_uint);
|
ESP_LOGV(TAG, "Datapoint %u set to %u", datapoint.id, datapoint.value_uint);
|
||||||
|
@ -389,7 +394,8 @@ void Tuya::set_datapoint_value(TuyaDatapoint datapoint) {
|
||||||
buffer.push_back(data.size() >> 8);
|
buffer.push_back(data.size() >> 8);
|
||||||
buffer.push_back(data.size() >> 0);
|
buffer.push_back(data.size() >> 0);
|
||||||
buffer.insert(buffer.end(), data.begin(), data.end());
|
buffer.insert(buffer.end(), data.begin(), data.end());
|
||||||
this->send_command_(TuyaCommandType::DATAPOINT_DELIVER, buffer.data(), buffer.size());
|
|
||||||
|
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
|
void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
|
||||||
|
|
|
@ -61,6 +61,11 @@ enum class TuyaInitState : uint8_t {
|
||||||
INIT_DONE,
|
INIT_DONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TuyaCommand {
|
||||||
|
TuyaCommandType cmd;
|
||||||
|
std::vector<uint8_t> payload;
|
||||||
|
};
|
||||||
|
|
||||||
class Tuya : public Component, public uart::UARTDevice {
|
class Tuya : public Component, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
float get_setup_priority() const override { return setup_priority::LATE; }
|
float get_setup_priority() const override { return setup_priority::LATE; }
|
||||||
|
@ -82,9 +87,10 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||||
bool validate_message_();
|
bool validate_message_();
|
||||||
|
|
||||||
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_raw_command_(TuyaCommand command);
|
||||||
void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); }
|
void process_command_queue_();
|
||||||
void schedule_empty_command_(TuyaCommandType command);
|
void send_command_(TuyaCommand command);
|
||||||
|
void send_empty_command_(TuyaCommandType command);
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
optional<time::RealTimeClock *> time_id_{};
|
optional<time::RealTimeClock *> time_id_{};
|
||||||
|
@ -98,6 +104,7 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||||
std::vector<TuyaDatapoint> datapoints_;
|
std::vector<TuyaDatapoint> datapoints_;
|
||||||
std::vector<uint8_t> rx_message_;
|
std::vector<uint8_t> rx_message_;
|
||||||
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
|
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
|
||||||
|
std::vector<TuyaCommand> command_queue_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tuya
|
} // namespace tuya
|
||||||
|
|
Loading…
Reference in a new issue