Skip to content

Instantly share code, notes, and snippets.

@kvvoff
Forked from placidorevilla/tuyadimmer_lightoutput.h
Created September 18, 2019 17:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kvvoff/4ca9ded2210506a762c3abc00ae06dea to your computer and use it in GitHub Desktop.
Save kvvoff/4ca9ded2210506a762c3abc00ae06dea to your computer and use it in GitHub Desktop.
Prototype esphome component for Tuya Dimmer
#pragma once
#include "esphome.h"
using namespace esphome;
enum TuyaCmd : uint8_t {
HEARTBEAT = 0x00,
QUERY_PRODUCT = 0x01,
MCU_CONF = 0x02,
WIFI_STATE = 0x03,
WIFI_RESET = 0x04,
WIFI_SELECT = 0x05,
SET_DP = 0x06,
STATE = 0x07,
QUERY_STATE = 0x08,
};
enum TuyaType : uint8_t {
BOOL = 0x01,
VALUE = 0x02,
};
static const uint8_t DEFAULT_POWER_ID = 1;
static const uint8_t DEFAULT_DIMMER_ID = 2;
static const float DEFAULT_MAX_POWER = 0.64;
static const float DEFAULT_MIN_POWER = 0.21;
static const char *TAG = "tuyadimmerlightoutput";
class TuyaDimmerLightOutput : public Component, public light::LightOutput, public uart::UARTDevice {
public:
TuyaDimmerLightOutput(uart::UARTComponent *parent, uint8_t power_id = DEFAULT_POWER_ID,
uint8_t dimmer_id = DEFAULT_DIMMER_ID, float max_power = DEFAULT_MAX_POWER, float min_power = DEFAULT_MIN_POWER)
: uart::UARTDevice(parent),
power_id_(power_id),
dimmer_id_(dimmer_id),
max_power_(max_power),
min_power_(min_power) {}
LightTraits get_traits() {
auto traits = LightTraits();
traits.set_supports_brightness(true);
return traits;
}
void setup_state(LightState *state) override {
this->state_ = state;
this->send_cmd_(TuyaCmd::MCU_CONF);
}
void write_state(LightState *state) override {
if (state->current_values.is_on()) {
float brightness;
state->current_values_as_brightness(&brightness);
uint8_t value = ((brightness * (this->max_power_ - this->min_power_)) + this->min_power_) * 255;
ESP_LOGD(TAG, "Set output to %f (%d)", brightness, value);
this->set_dp_(this->dimmer_id_, TuyaType::VALUE, value);
} else {
this->set_dp_(this->power_id_, TuyaType::BOOL, 0);
}
}
void loop() {
const uint32_t now = millis();
if (this->data_index_ != 0 && (now - this->last_transmission_ >= 500)) {
// last transmission too long ago. Reset RX index.
ESP_LOGW(TAG, "Partial packet");
this->data_index_ = 0;
}
if (now - this->last_heartbeat_ >= 10000) {
this->last_heartbeat_ = now;
this->send_cmd_(TuyaCmd::HEARTBEAT);
}
if (this->available() == 0)
return;
this->last_transmission_ = now;
while (this->available() != 0) {
this->read_byte(&this->data_[this->data_index_]);
auto check = this->check_byte_();
if (!check.has_value()) {
// finished
this->parse_data_();
this->data_index_ = 0;
} else if (!*check) {
// wrong data
this->data_index_ = 0;
} else {
// next byte
this->data_index_++;
if (this->data_index_ == sizeof(this->data_)) {
ESP_LOGW(TAG, "UART buffer overflow");
this->data_index_ = 0;
}
}
}
}
protected:
void send_cmd_(TuyaCmd cmd, uint8_t payload[] = nullptr, uint16_t payload_len = 0) {
uint8_t checksum = (0xFF + cmd + (payload_len >> 8) + (payload_len & 0xFF));
this->flush();
this->write_byte(0x55); // Tuya header 55AA
this->write_byte(0xAA);
this->write_byte((uint8_t) 0x00); // version 00
this->write_byte(cmd); // Tuya command
this->write_byte(payload_len >> 8); // following data length (Hi)
this->write_byte(payload_len & 0xFF); // following data length (Lo)
for (int i = 0; i < payload_len; ++i) {
this->write_byte(payload[i]);
checksum += payload[i];
}
this->write_byte(checksum);
}
void set_dp_(uint8_t dp_id, TuyaType type, uint32_t value_) {
uint16_t payload_len = 4;
uint8_t payload_buffer[8];
uint8_t *value = (uint8_t *) &value_;
payload_buffer[0] = dp_id;
payload_buffer[1] = type;
switch (type) {
case TuyaType::BOOL:
payload_len += 1;
payload_buffer[2] = 0x00;
payload_buffer[3] = 0x01;
payload_buffer[4] = value[0];
break;
case TuyaType::VALUE:
payload_len += 4;
payload_buffer[2] = 0x00;
payload_buffer[3] = 0x04;
payload_buffer[4] = value[3];
payload_buffer[5] = value[2];
payload_buffer[6] = value[1];
payload_buffer[7] = value[0];
break;
}
this->send_cmd_(TuyaCmd::SET_DP, payload_buffer, payload_len);
}
optional<bool> check_byte_() const {
uint8_t index = this->data_index_;
uint8_t byte = this->data_[index];
uint8_t len = 0;
if (index == 0) {
return byte == 0x55;
}
if (index == 1) {
return byte == 0xAA;
}
if ((index >= 2) && (index <= 4)) {
return true;
}
if (index >= 5) {
if (this->data_[4] != 0) {
return false;
}
len = this->data_[5];
}
if (index < (len + 6)) {
return true;
}
if (index == (len + 6)) {
uint8_t checksum = 0xFF;
for (int i = 3; i < index; ++i) {
checksum += this->data_[i];
}
if (checksum != byte) {
ESP_LOGW(TAG, "Checksum doesn't match: 0x%02X!=0x%02X", byte, checksum);
return false;
}
}
return {};
}
void dump_packet_() {
static char log[768];
log[0] = 0;
for (int i = 0; i < this->data_index_; i++)
snprintf_P(log, sizeof(log), PSTR("%s %02X"), log, this->data_[i]);
ESP_LOGD(TAG, "Packet: %s", log);
}
void parse_data_() {
switch (this->data_[3]) {
case TuyaCmd::HEARTBEAT:
ESP_LOGD(TAG, "MCU Heartbeat (0x%02X)", this->data_[6]);
break;
case TuyaCmd::MCU_CONF:
ESP_LOGD(TAG, "MCU Configuration: %s", this->data_[6] == 2 ? "ESP Exclusive" : "Cooperative");
// TODO: Support ESP Exclusive mode
this->send_cmd_(TuyaCmd::QUERY_STATE);
break;
case TuyaCmd::STATE:
if (this->data_[6] == this->power_id_ && this->data_[5] == 5) {
ESP_LOGD(TAG, "Power State: 0x%02X", this->data_[10]);
if (bool(this->data_[10]) != this->state_->remote_values.is_on()) {
this->state_->make_call().set_state(bool(this->data_[10])).perform();
}
} else if (this->data_[6] == this->dimmer_id_ && this->data_[5] == 8) {
ESP_LOGD(TAG, "Dimmer Value: 0x%02X", this->data_[13]);
} else {
ESP_LOGW(TAG, "Unknown dpID: 0x%02X", this->data_[6]);
}
break;
case TuyaCmd::QUERY_PRODUCT:
case TuyaCmd::WIFI_STATE:
case TuyaCmd::WIFI_RESET:
case TuyaCmd::WIFI_SELECT:
default:
ESP_LOGW(TAG, "Received unknown command: 0x%02X", this->data_[3]);
this->dump_packet_();
}
}
uint8_t data_[254];
uint8_t data_index_{0};
uint32_t last_transmission_{0};
uint32_t last_heartbeat_{0};
LightState *state_{nullptr};
uint8_t power_id_;
uint8_t dimmer_id_;
float max_power_;
float min_power_;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment