Tuya: Use queue for sending command messages (#1404)

This commit is contained in:
stubs12 2021-02-25 17:52:40 -06:00 committed by GitHub
parent b91723344e
commit 87154e9b6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 58 deletions

View file

@ -9,7 +9,7 @@ static const char *TAG = "tuya";
static const int COMMAND_DELAY = 50;
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() {
@ -18,15 +18,7 @@ void Tuya::loop() {
this->read_byte(&c);
this->handle_char_(c);
}
}
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); });
}
process_command_queue_();
}
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) {
this->last_command_timestamp_ = millis();
switch ((TuyaCommandType) command) {
case TuyaCommandType::HEARTBEAT:
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) {
this->init_state_ = TuyaInitState::INIT_PRODUCT;
this->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY);
this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY);
}
break;
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) {
this->init_state_ = TuyaInitState::INIT_CONF;
this->schedule_empty_command_(TuyaCommandType::CONF_QUERY);
this->send_empty_command_(TuyaCommandType::CONF_QUERY);
}
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 (this->gpio_status_ != -1) {
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
this->send_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->send_command_(TuyaCommandType::WIFI_STATE, c, 1);
});
// 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.
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector<uint8_t>{0x04}});
}
}
break;
@ -181,7 +169,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
case TuyaCommandType::WIFI_STATE:
if (this->init_state_ == TuyaInitState::INIT_WIFI) {
this->init_state_ = TuyaInitState::INIT_DATAPOINT;
this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY);
}
break;
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:
break;
case TuyaCommandType::WIFI_TEST: {
uint8_t c[] = {0x00, 0x00};
this->send_command_(TuyaCommandType::WIFI_TEST, c, 2);
this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector<uint8_t>{0x00, 0x00}});
break;
}
case TuyaCommandType::LOCAL_TIME_QUERY: {
@ -213,28 +200,26 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
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);
});
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;
}
this->send_command_(TuyaCommand{
.cmd = TuyaCommandType::LOCAL_TIME_QUERY,
.payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week}});
} 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);
});
this->send_command_(
TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY,
.payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}});
}
} else {
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);
}
void Tuya::send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len) {
uint8_t len_hi = len >> 8;
uint8_t len_lo = len >> 0;
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;
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, // NOLINT
hexencode(buffer, len).c_str(), this->init_state_);
this->last_command_timestamp_ = millis();
this->write_array({0x55, 0xAA, version, (uint8_t) command, len_hi, len_lo});
if (len != 0)
this->write_array(buffer, len);
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command.cmd, version, // NOLINT
hexencode(command.payload).c_str(), this->init_state_);
uint8_t checksum = 0x55 + 0xAA + (uint8_t) command + len_hi + len_lo;
for (int i = 0; i < len; i++)
checksum += buffer[i];
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_;
// 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) {
std::vector<uint8_t> buffer;
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() >> 0);
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) {

View file

@ -61,6 +61,11 @@ enum class TuyaInitState : uint8_t {
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; }
@ -82,9 +87,10 @@ class Tuya : public Component, public uart::UARTDevice {
bool validate_message_();
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);
void send_raw_command_(TuyaCommand command);
void process_command_queue_();
void send_command_(TuyaCommand command);
void send_empty_command_(TuyaCommandType command);
#ifdef USE_TIME
optional<time::RealTimeClock *> time_id_{};
@ -98,6 +104,7 @@ class Tuya : public Component, public uart::UARTDevice {
std::vector<TuyaDatapoint> datapoints_;
std::vector<uint8_t> rx_message_;
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
std::vector<TuyaCommand> command_queue_;
};
} // namespace tuya