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; 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) {

View file

@ -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