TuyaMCU Update

This commit is contained in:
Federico Leoni 2020-09-16 15:45:56 -03:00
parent e9ee4e8689
commit e45a5f3323
5 changed files with 431 additions and 113 deletions

View File

@ -129,8 +129,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t virtual_ct_cw : 1; // bit 25 (v8.4.0.1) - SetOption107 - Virtual CT Channel - signals whether the hardware white is cold CW (true) or warm WW (false)
uint32_t teleinfo_rawdata : 1; // bit 26 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1)
uint32_t alexa_gen_1 : 1; // bit 27 (v8.4.0.3) - SetOption109 - Alexa gen1 mode - if you only have Echo Dot 2nd gen devices
uint32_t spare28 : 1; // bit 28
uint32_t spare29 : 1; // bit 29
uint32_t zb_disable_autobind : 1; // bit 28 (v8.5.0.1) - SetOption110 - disable Zigbee auto-config when pairing new devices
uint32_t buzzer_freq_mode : 1; // bit 29 (v8.5.0.1) - SetOption111 - Use frequency output for buzzer pin instead of on/off signal
uint32_t spare30 : 1; // bit 30
uint32_t spare31 : 1; // bit 31
};
@ -389,7 +389,7 @@ struct {
uint8_t display_font; // 312
char ex_state_text[4][11]; // 313
uint8_t ex_energy_power_delta; // 33F - Free since 6.6.0.20
uint8_t tuyamcu_topic; // 33F Manage tuyaSend topic. ex_energy_power_delta on 6.6.0.20, replaced on 8.5.0.1
uint16_t domoticz_update_timer; // 340
uint16_t pwm_range; // 342

View File

@ -1356,8 +1356,8 @@ void SettingsDelta(void)
Settings.ex_sbaudrate = 0;
*/
Settings.flag3.fast_power_cycle_disable = 0;
Settings.ex2_energy_power_delta = Settings.ex_energy_power_delta;
Settings.ex_energy_power_delta = 0;
Settings.ex2_energy_power_delta = Settings.tuyamcu_topic;
Settings.tuyamcu_topic = 0; // replaced ex_energy_power_delta on 8.5.0.1
}
if (Settings.version < 0x06060015) {
if ((EX_WIFI_SMARTCONFIG == Settings.ex_sta_config) || (EX_WIFI_WPSCONFIG == Settings.ex_sta_config)) {

View File

@ -353,10 +353,13 @@ const SerConfu8 kTasmotaSerialConfig[] PROGMEM = {
enum TuyaSupportedFunctions { TUYA_MCU_FUNC_NONE, TUYA_MCU_FUNC_SWT1 = 1, TUYA_MCU_FUNC_SWT2, TUYA_MCU_FUNC_SWT3, TUYA_MCU_FUNC_SWT4,
TUYA_MCU_FUNC_REL1 = 11, TUYA_MCU_FUNC_REL2, TUYA_MCU_FUNC_REL3, TUYA_MCU_FUNC_REL4, TUYA_MCU_FUNC_REL5,
TUYA_MCU_FUNC_REL6, TUYA_MCU_FUNC_REL7, TUYA_MCU_FUNC_REL8, TUYA_MCU_FUNC_DIMMER = 21, TUYA_MCU_FUNC_POWER = 31,
TUYA_MCU_FUNC_CURRENT, TUYA_MCU_FUNC_VOLTAGE, TUYA_MCU_FUNC_BATTERY_STATE, TUYA_MCU_FUNC_BATTERY_PERCENTAGE,
TUYA_MCU_FUNC_REL6, TUYA_MCU_FUNC_REL7, TUYA_MCU_FUNC_REL8, TUYA_MCU_FUNC_DIMMER = 21, TUYA_MCU_FUNC_DIMMER2,
TUYA_MCU_FUNC_CT, TUYA_MCU_FUNC_RGB, TUYA_MCU_FUNC_WHITE, TUYA_MCU_FUNC_MODESET, TUYA_MCU_FUNC_REPORT1, TUYA_MCU_FUNC_REPORT2,
TUYA_MCU_FUNC_POWER = 31, TUYA_MCU_FUNC_CURRENT, TUYA_MCU_FUNC_VOLTAGE, TUYA_MCU_FUNC_BATTERY_STATE, TUYA_MCU_FUNC_BATTERY_PERCENTAGE,
TUYA_MCU_FUNC_REL1_INV = 41, TUYA_MCU_FUNC_REL2_INV, TUYA_MCU_FUNC_REL3_INV, TUYA_MCU_FUNC_REL4_INV, TUYA_MCU_FUNC_REL5_INV,
TUYA_MCU_FUNC_REL6_INV, TUYA_MCU_FUNC_REL7_INV, TUYA_MCU_FUNC_REL8_INV, TUYA_MCU_FUNC_LOWPOWER_MODE = 51, TUYA_MCU_FUNC_LAST = 255
TUYA_MCU_FUNC_REL6_INV, TUYA_MCU_FUNC_REL7_INV, TUYA_MCU_FUNC_REL8_INV, TUYA_MCU_FUNC_LOWPOWER_MODE = 51,
TUYA_MCU_FUNC_FAN3 = 61, TUYA_MCU_FUNC_FAN4, TUYA_MCU_FUNC_FAN5, TUYA_MCU_FUNC_FAN6,
TUYA_MCU_FUNC_MOTOR_DIR = 97, TUYA_MCU_FUNC_ERROR = 98 , TUYA_MCU_FUNC_DUMMY = 99, TUYA_MCU_FUNC_LAST = 255
};
#endif // _TASMOTA_H_

View File

@ -1460,9 +1460,37 @@ void HandleRoot(void)
(set_button) ? SettingsText(SET_BUTTON1 + idx -1) : (devices_present < 5) ? D_BUTTON_TOGGLE : "",
(set_button) ? "" : (devices_present > 1) ? stemp : "");
}
#ifdef USE_TUYA_MCU
if (IsModuleTuya()) {
uint8_t modeset = 0;
if (AsModuleTuyaMS()) {
WSContentSend_P(HTTP_TABLE100);
WSContentSend_P(PSTR("<tr><div></div>"));
snprintf_P(stemp, sizeof(stemp), PSTR("" D_JSON_IRHVAC_MODE ""));
WSContentSend_P(HTTP_DEVICE_CONTROL, 26, devices_present + 1,
(strlen(SettingsText(SET_BUTTON1 + devices_present))) ? SettingsText(SET_BUTTON1 + devices_present) : stemp, "");
WSContentSend_P(PSTR("</tr></table>"));
modeset = 1;
}
if (IsTuyaFanCtrl()) {
uint8_t device = devices_present + modeset;
WSContentSend_P(HTTP_TABLE100);
WSContentSend_P(PSTR("<tr><div></div>"));
for (uint32_t i = device + 1; i <= (TuyaFanSpeeds() + device) + 1; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i - (device + 1));
WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i,
(strlen(SettingsText(SET_BUTTON1 + i))) ? SettingsText(SET_BUTTON1 + i) : stemp, "");
}
WSContentSend_P(PSTR("</tr></table>"));
}
} else {
#endif // USE_TUYA_MCU
}
WSContentSend_P(PSTR("</tr></table>"));
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
WSContentSend_P(PSTR("</tr></table>"));
}
#ifdef USE_SONOFF_RF
@ -1535,6 +1563,33 @@ bool HandleRootStatusRefresh(void)
}
} else {
#endif // USE_SONOFF_IFAN
#ifdef USE_TUYA_MCU
if (IsModuleTuya()) {
uint8_t FuncIdx = 0;
if (device <= devices_present) {
ExecuteCommandPower(device, POWER_TOGGLE, SRC_IGNORE);
} else {
if (AsModuleTuyaMS() && device == devices_present + 1) {
uint8_t dpId = TuyaGetDpId(TUYA_MCU_FUNC_MODESET);
snprintf_P(svalue, sizeof(svalue), PSTR("Tuyasend4 %d,%d"), dpId, !TuyaModeSet());
ExecuteCommand(svalue, SRC_WEBGUI);
}
if (IsTuyaFanCtrl()) {
uint8_t dpId = 0;
for (uint32_t i = 0; i <= 3; i++) { // Tuya Function FAN3 to FAN6
if (TuyaGetDpId(TUYA_MCU_FUNC_FAN3 + i) != 0) {
dpId = TuyaGetDpId(TUYA_MCU_FUNC_FAN3 + i);
}
}
if ((AsModuleTuyaMS() && device != devices_present + 1) || !AsModuleTuyaMS()) {
if (AsModuleTuyaMS()) {FuncIdx = 1;}
snprintf_P(svalue, sizeof(svalue), PSTR("Tuyasend2 %d,%d"), dpId, (device - (devices_present + FuncIdx) - 1));
ExecuteCommand(svalue, SRC_WEBGUI);
}
}
}
} else {
#endif // USE_TUYA_MCU
#ifdef USE_SHUTTER
int32_t ShutterWebButton;
if (ShutterWebButton = IsShutterWebButton(device)) {
@ -1549,6 +1604,9 @@ bool HandleRootStatusRefresh(void)
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
#ifdef USE_TUYA_MCU
}
#endif // USE_TUYA_MCU
}
#ifdef USE_LIGHT
WebGetArg("d0", tmp, sizeof(tmp)); // 0 - 100 Dimmer value
@ -1629,8 +1687,22 @@ bool HandleRootStatusRefresh(void)
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
WSContentSend_P(PSTR("</tr></table>"));
}
#ifdef USE_TUYA_MCU
if (IsModuleTuya()) {
uint32_t fanspeed = TuyaFanState();
uint32_t modeset = TuyaModeSet();
if (IsTuyaFanCtrl() && !AsModuleTuyaMS()) {
WSContentSend_P(PSTR("<div style='text-align:center;font-size:25px;'>" D_JSON_IRHVAC_FANSPEED ": %d</div>"), fanspeed);
} else if (!IsTuyaFanCtrl() && AsModuleTuyaMS()) {
WSContentSend_P(PSTR("<div style='text-align:center;font-size:25px;'>" D_JSON_IRHVAC_MODE ": %d</div>"), modeset);
} else if (IsTuyaFanCtrl() && AsModuleTuyaMS()) {
WSContentSend_P(PSTR("<div style='text-align:center;font-size:25px;'>" D_JSON_IRHVAC_MODE ": %d - " D_JSON_IRHVAC_FANSPEED ": %d</div>"), modeset, fanspeed);
}
}
#endif // USE_TUYA_MCU
WSContentEnd();
return true;

View File

@ -1,7 +1,7 @@
/*
xdrv_16_tuyamcu.ino - Tuya MCU support for Tasmota
Copyright (C) 2020 digiblur, Joel Stein and Theo Arends
Copyright (C) 2020 Federico Leoni, digiblur, Joel Stein and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -55,56 +55,32 @@
TasmotaSerial *TuyaSerial = nullptr;
struct TUYA {
uint16_t new_dim = 0; // Tuya dimmer value temp
bool ignore_dim = false; // Flag to skip serial send to prevent looping when processing inbound states from the faceplate interaction
uint8_t cmd_status = 0; // Current status of serial-read
uint8_t cmd_checksum = 0; // Checksum of tuya command
uint8_t data_len = 0; // Data lenght of command
uint16_t Levels[5] = {0,0,0,0,0}; // Array to store the values of TuyaMCU channels
uint16_t Snapshot[5] = {0,0,0,0,0}; // Array to store a snapshot of Tasmota actual values for channels
char HSBColor[13]; // Stores HSB Color string in Hex format
uint16_t CTMin = 153; // Minimum CT level allowed - When SetOption82 is enabled will default to 200
uint16_t CTMax = 500; // Maximum CT level allowed - When SetOption82 is enabled will default to 380
bool ModeSet = false; // Controls 0 - Single Tone light, 1 - RGB Light
uint8_t FanState = 0; // Stores the current fan speed
bool SuspendTopic = false; // Used to reduce the load at init time or when polling the configuraton on demand
uint32_t ignore_topic_timeout = 0; // Suppress the /STAT topic (if enabled) to avoid data overflow until the configuration is over
bool ignore_dim = false; // Flag to skip serial send to prevent looping when processing inbound states from the faceplate interaction
uint8_t cmd_status = 0; // Current status of serial-read
uint8_t cmd_checksum = 0; // Checksum of tuya command
uint8_t data_len = 0; // Data lenght of command
uint8_t wifi_state = -2; // Keep MCU wifi-status in sync with WifiState()
uint8_t heartbeat_timer = 0; // 10 second heartbeat timer for tuya module
uint8_t heartbeat_timer = 0; // 10 second heartbeat timer for tuya module
#ifdef USE_ENERGY_SENSOR
uint32_t lastPowerCheckTime = 0; // Time when last power was checked
uint32_t lastPowerCheckTime = 0; // Time when last power was checked
#endif // USE_ENERGY_SENSOR
char *buffer = nullptr; // Serial receive buffer
int byte_counter = 0; // Index in serial receive buffer
bool low_power_mode = false; // Normal or Low power mode protocol
bool send_success_next_second = false; // Second command success in low power mode
uint32_t ignore_dimmer_cmd_timeout = 0;// Time until which received dimmer commands should be ignored
char *buffer = nullptr; // Serial receive buffer
int byte_counter = 0; // Index in serial receive buffer
bool low_power_mode = false; // Normal or Low power mode protocol
bool send_success_next_second = false; // Second command success in low power mode
uint32_t ignore_dimmer_cmd_timeout = 0; // Time until which received dimmer commands should be ignored
bool ignore_tuyareceived = false; // When a modeset changes ignore stat
} Tuya;
// enum TuyaSupportedFunctions {
// TUYA_MCU_FUNC_NONE,
// TUYA_MCU_FUNC_SWT1 = 1, // Buttons
// TUYA_MCU_FUNC_SWT2,
// TUYA_MCU_FUNC_SWT3,
// TUYA_MCU_FUNC_SWT4,
// TUYA_MCU_FUNC_REL1 = 11, // Relays
// TUYA_MCU_FUNC_REL2,
// TUYA_MCU_FUNC_REL3,
// TUYA_MCU_FUNC_REL4,
// TUYA_MCU_FUNC_REL5,
// TUYA_MCU_FUNC_REL6,
// TUYA_MCU_FUNC_REL7,
// TUYA_MCU_FUNC_REL8,
// TUYA_MCU_FUNC_DIMMER = 21,
// TUYA_MCU_FUNC_POWER = 31,
// TUYA_MCU_FUNC_CURRENT,
// TUYA_MCU_FUNC_VOLTAGE,
// TUYA_MCU_FUNC_BATTERY_STATE,
// TUYA_MCU_FUNC_BATTERY_PERCENTAGE,
// TUYA_MCU_FUNC_REL1_INV = 41, // Inverted Relays
// TUYA_MCU_FUNC_REL2_INV,
// TUYA_MCU_FUNC_REL3_INV,
// TUYA_MCU_FUNC_REL4_INV,
// TUYA_MCU_FUNC_REL5_INV,
// TUYA_MCU_FUNC_REL6_INV,
// TUYA_MCU_FUNC_REL7_INV,
// TUYA_MCU_FUNC_REL8_INV,
// TUYA_MCU_FUNC_LOWPOWER_MODE = 51,
// TUYA_MCU_FUNC_LAST = 255
// };
const char kTuyaCommand[] PROGMEM = "|" // No prefix
D_CMND_TUYA_MCU "|" D_CMND_TUYA_MCU_SEND_STATE;
@ -112,8 +88,48 @@ void (* const TuyaCommand[])(void) PROGMEM = {
&CmndTuyaMcu, &CmndTuyaSend
};
/*
/*********************************************************************************************\
* Web Interface
\*********************************************************************************************/
bool IsModuleTuya(void)
{
return ((TUYA_DIMMER == my_module_type) || (SK03_TUYA == my_module_type));
}
bool AsModuleTuyaMS(void) // ModeSet Layout
{
return ((light_type > LT_RGB) && TuyaGetDpId(TUYA_MCU_FUNC_MODESET) != 0);
}
bool IsTuyaFanCtrl(void) // Fan Speed Controller Layout
{
return ((TuyaGetDpId(TUYA_MCU_FUNC_FAN3) != 0) || (TuyaGetDpId(TUYA_MCU_FUNC_FAN4) != 0) ||
(TuyaGetDpId(TUYA_MCU_FUNC_FAN5) != 0) || (TuyaGetDpId(TUYA_MCU_FUNC_FAN6) != 0));
}
bool TuyaModeSet(void) // ModeSet Status
{
return Tuya.ModeSet;
}
uint8_t TuyaFanSpeeds(void) // Number of Fan Speeds for WebUI
{
uint8_t FanSpeeds = 0;
for (uint32_t i = 0; i <= 3; i++) {
if (TuyaGetDpId(TUYA_MCU_FUNC_FAN3 + i) != 0) {
FanSpeeds = i + 2;
}
}
return FanSpeeds;
}
uint8_t TuyaFanState(void) // Fan Speed Status
{
return Tuya.FanState;
}
// Web Interface
/*
TuyaSend<x> dpId,data
TuyaSend0 -> Sends TUYA_CMD_QUERY_STATE
@ -122,16 +138,20 @@ TuyaSend2 11,100 -> Sends integer (Type 2) data 100 to dpId 11 (Max data length
TuyaSend2 11,0xAABBCCDD -> Sends 4 bytes (Type 2) data to dpId 11 (Max data length 4 bytes)
TuyaSend3 11,ThisIsTheData -> Sends the supplied string (Type 3) to dpId 11 ( Max data length not-known)
TuyaSend4 11,1 -> Sends enum (Type 4) data 0/1/2/3/4/5 to dpId 11 (Max data length 1 bytes)
*/
void CmndTuyaSend(void) {
if (XdrvMailbox.index > 4) {
if (XdrvMailbox.index > 4 && XdrvMailbox.index < 8) {
return;
}
if (XdrvMailbox.index == 0) {
TuyaRequestState();
TuyaRequestState(0);
} else if (XdrvMailbox.index == 8) {
TuyaRequestState(8);
} else if (XdrvMailbox.index == 9) { // TuyaSend Topic Toggle
if (Settings.tuyamcu_topic) { Settings.tuyamcu_topic = 0; } else { Settings.tuyamcu_topic = 1; }
AddLog_P2(LOG_LEVEL_INFO, PSTR("TYA: TuyaMCU Stat Topic %s"), (Settings.tuyamcu_topic ? PSTR("enabled") : PSTR("disabled")));
} else {
if (XdrvMailbox.data_len > 0) {
char *p;
@ -161,11 +181,7 @@ void CmndTuyaSend(void) {
ResponseCmndDone();
}
/*
TuyaMcu fnid,dpid
*/
// TuyaMcu fnid,dpid
void CmndTuyaMcu(void) {
if (XdrvMailbox.data_len > 0) {
@ -178,12 +194,28 @@ void CmndTuyaMcu(void) {
}
if (TuyaFuncIdValid(parm[0])) {
// TuyaAddMcuFunc(parm[0], parm[1]);
// restart_flag = 2;
// } else {
// AddLog_P2(LOG_LEVEL_ERROR, PSTR("TYA: TuyaMcu Invalid function id=%d"), parm[0]);
// }
bool DualDim;
if (TUYA_MCU_FUNC_DIMMER2 == parm[0] && parm[1] != 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) != 0) { DualDim = true; }
} else if (TUYA_MCU_FUNC_DIMMER == parm[0] && parm[1] != 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER2) != 0) { DualDim = true; }
} else if ((TUYA_MCU_FUNC_DIMMER == parm[0] && parm[1] == 0) || (TUYA_MCU_FUNC_DIMMER2 == parm[0] && parm[1] == 0)) { DualDim = false; };
if (DualDim) {
if (TuyaGetDpId(TUYA_MCU_FUNC_CT) != 0) { TuyaAddMcuFunc(TUYA_MCU_FUNC_CT, 0); } // If the second dimmer is enabled CT, RGB or WHITE function must be removed
if (TuyaGetDpId(TUYA_MCU_FUNC_RGB) != 0) { TuyaAddMcuFunc(TUYA_MCU_FUNC_RGB, 0); }
if (TuyaGetDpId(TUYA_MCU_FUNC_WHITE) != 0) { TuyaAddMcuFunc(TUYA_MCU_FUNC_WHITE, 0); }
Settings.flag3.pwm_multi_channels = 1;
} else { Settings.flag3.pwm_multi_channels = 0; }
TuyaAddMcuFunc(parm[0], parm[1]);
restart_flag = 2;
} else {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("TYA: TuyaMcu Invalid function id=%d"), parm[0]);
}
}
Response_P(PSTR("{\"" D_CMND_TUYA_MCU "\":["));
@ -249,11 +281,13 @@ void UpdateDevices() {
inline bool TuyaFuncIdValid(uint8_t fnId) {
return (fnId >= TUYA_MCU_FUNC_SWT1 && fnId <= TUYA_MCU_FUNC_SWT4) ||
(fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) ||
fnId == TUYA_MCU_FUNC_DIMMER ||
(fnId >= TUYA_MCU_FUNC_POWER && fnId <= TUYA_MCU_FUNC_VOLTAGE) ||
(fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) ||
(fnId == TUYA_MCU_FUNC_LOWPOWER_MODE);
(fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) ||
(fnId >= TUYA_MCU_FUNC_DIMMER && fnId <= TUYA_MCU_FUNC_REPORT2) ||
(fnId >= TUYA_MCU_FUNC_POWER && fnId <= TUYA_MCU_FUNC_BATTERY_PERCENTAGE) ||
(fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) ||
(fnId >= TUYA_MCU_FUNC_FAN3 && fnId <= TUYA_MCU_FUNC_FAN6) ||
(fnId >= TUYA_MCU_FUNC_MOTOR_DIR && fnId <= TUYA_MCU_FUNC_DUMMY) ||
(fnId == TUYA_MCU_FUNC_LOWPOWER_MODE);
}
uint8_t TuyaGetFuncId(uint8_t dpid) {
@ -376,38 +410,137 @@ bool TuyaSetPower(void)
bool TuyaSetChannels(void)
{
LightSerialDuty(((uint8_t*)XdrvMailbox.data)[0]);
//delay(20); // Hack when power is off and dimmer is set then both commands go too soon to Serial out.
uint16_t hue, TuyaData;
uint8_t sat, bri;
uint8_t TuyaIdx = 0;
char hex_char[13];
bool noupd = false;
bool LightMode = TuyaGetDpId(TUYA_MCU_FUNC_MODESET) != 0;
uint8_t idx = 0;
snprintf_P(hex_char, sizeof(hex_char), PSTR("000000000000"));
if (LT_SERIAL1 == light_type) {
Tuya.Snapshot[0] = light_state.getDimmer();
}
if (LT_SERIAL2 == light_type || LT_RGBWC == light_type) {
idx = 1;
if (LT_SERIAL2 == light_type && Settings.flag3.pwm_multi_channels && (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER2) != 0)) {
// Special setup for dual dimmer (like the MOES 2 Way Dimmer) emulating 2 PWM channels
Tuya.Snapshot[0] = changeUIntScale(Light.current_color[0], 0, 255, 0, 100);
Tuya.Snapshot[1] = changeUIntScale(Light.current_color[1], 0, 255, 0, 100);
} else { // CT Light or RGBWC
light_state.getCTRange(&Tuya.CTMin, &Tuya.CTMax); // SetOption82 - Reduce the CT range from 153..500 to 200..380 to accomodate with Alexa range
Tuya.Snapshot[0] = light_state.getDimmer();
Tuya.Snapshot[1] = light_state.getCT();
}
}
if (LT_RGBW == light_type) {
idx = 1;
Tuya.Snapshot[0] = light_state.getDimmer(1);
Tuya.Snapshot[1] = light_state.getDimmer(2);
}
if (light_type > LT_BASIC) {
if (LT_RGB != light_type) {
for (uint8_t i = 0; i <= idx; i++) {
if (Tuya.Snapshot[i] != Tuya.Levels[i]) {
if (i == 0 && LightMode && Tuya.ModeSet ) { noupd = true;}
if (!noupd) {
LightSerialDuty(Tuya.Snapshot[i], &hex_char[0], i+1);
Tuya.Levels[i] = Tuya.Snapshot[i];
}
noupd = false;
}
}
}
if (light_type >= LT_RGB) {
light_state.getHSB(&hue, &sat, &bri);
sat = changeUIntScale(sat, 0, 255, 0, 100);
bri = changeUIntScale(bri, 0, 255, 0, 100);
if (hue != Tuya.Snapshot[2] || sat != Tuya.Snapshot[3] || bri != Tuya.Snapshot[4]) {
if ((LightMode && Tuya.ModeSet) || LT_RGB == light_type) {
snprintf_P(hex_char, sizeof(hex_char), PSTR("%04X%04X%04X"), hue, sat * 10, bri * 10); // Create a TuyaMCU readable RGB payload
LightSerialDuty(0, &hex_char[0], 3);
memcpy_P(Tuya.HSBColor, hex_char, strlen(hex_char));
Tuya.Snapshot[2] = hue;
Tuya.Snapshot[3] = sat;
Tuya.Snapshot[4] = bri;
}
}
}
}
return true;
}
void LightSerialDuty(uint16_t duty)
void LightSerialDuty(uint16_t duty, char *hex_char, uint8_t TuyaIdx)
{
uint8_t dpid = TuyaGetDpId(TUYA_MCU_FUNC_DIMMER);
if (duty > 0 && !Tuya.ignore_dim && TuyaSerial && dpid > 0) {
duty = changeUIntScale(duty, 0, 255, 0, Settings.dimmer_hw_max);
if (duty < Settings.dimmer_hw_min) { duty = Settings.dimmer_hw_min; } // dimming acts odd below 25(10%) - this mirrors the threshold set on the faceplate itself
if (Tuya.new_dim != duty) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim value=%d (id=%d)"), duty, dpid);
Tuya.ignore_dimmer_cmd_timeout = millis() + 250; // Ignore serial received dim commands for the next 250ms
TuyaSendValue(dpid, duty);
bool CTLight = false;
if (TuyaIdx > 0 && TuyaIdx <= 2) {
if (TuyaIdx == 2) {
if (!Settings.flag3.pwm_multi_channels) {
CTLight = true;
dpid = TuyaGetDpId(TUYA_MCU_FUNC_CT);
} else { dpid = TuyaGetDpId(TUYA_MCU_FUNC_DIMMER2); }
}
} else if (dpid > 0) {
Tuya.ignore_dim = false; // reset flag
duty = changeUIntScale(duty, 0, 255, 0, Settings.dimmer_hw_max);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim skipped value=%d"), duty); // due to 0 or already set
} else {
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Cannot set dimmer. Dimmer Id unknown")); //
if (duty > 0 && !Tuya.ignore_dim && TuyaSerial && dpid > 0) {
if (TuyaIdx == 2 && CTLight) {
duty = changeUIntScale(duty, Tuya.CTMin, Tuya.CTMax, Settings.dimmer_hw_max, 0);
} else { duty = changeUIntScale(duty, 0, 100, 0, Settings.dimmer_hw_max); }
if (duty < Settings.dimmer_hw_min) { duty = Settings.dimmer_hw_min; } // dimming acts odd below 25(10%) - this mirrors the threshold set on the faceplate itself
Tuya.ignore_dimmer_cmd_timeout = millis() + 250; // Ignore serial received dim commands for the next 250ms
if (Tuya.ModeSet && (TuyaGetDpId(TUYA_MCU_FUNC_MODESET) != 0) && light_type > LT_RGB) {
TuyaSendEnum(TuyaGetDpId(TUYA_MCU_FUNC_MODESET), 0);
}
TuyaSendValue(dpid, duty);
} else if (dpid > 0 && TuyaIdx <= 2) {
Tuya.ignore_dim = false; // reset flag
if (TuyaIdx == 2 && CTLight) {
duty = changeUIntScale(duty, Tuya.CTMin, Tuya.CTMax, Settings.dimmer_hw_max, 0);
} else {
duty = changeUIntScale(duty, 0, 100, 0, Settings.dimmer_hw_max);
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim skipped value %d for dpid %d"), duty, dpid); // due to 0 or already set
} else {
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Cannot set dimmer. Dimmer Id unknown")); //
}
}
if (TuyaIdx == 3) {
dpid = TuyaGetDpId(TUYA_MCU_FUNC_RGB);
if (!Tuya.ModeSet && (TuyaGetDpId(TUYA_MCU_FUNC_MODESET) != 0) && light_type > LT_RGB) {
TuyaSendEnum(TuyaGetDpId(TUYA_MCU_FUNC_MODESET), 1);
}
TuyaSendString(dpid, hex_char);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: TX RGB hex %s to dpId %d"), hex_char, dpid);
}
}
void TuyaRequestState(void)
void TuyaRequestState(uint8_t state_type)
{
if (TuyaSerial) {
// Get current status of MCU
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Read MCU state"));
TuyaSendCmd(TUYA_CMD_QUERY_STATE);
Tuya.SuspendTopic = true;
Tuya.ignore_topic_timeout = millis() + 1000; // suppress /STAT topic for 1000ms to limit data
switch (state_type) {
case 0:
TuyaSendCmd(TUYA_CMD_QUERY_STATE);
break;
case 8:
TuyaSendCmd(TUYA_CMD_QUERY_PRODUCT);
break;
}
}
}
@ -452,25 +585,64 @@ void TuyaProcessStatePacket(void) {
SwitchHandler(1);
}
}
}
else if (Tuya.buffer[dpidStart + 1] == 2) { // Data Type 2
bool tuya_energy_enabled = (XNRG_32 == energy_flg);
uint16_t packetValue = Tuya.buffer[dpidStart + 6] << 8 | Tuya.buffer[dpidStart + 7];
if (fnId == TUYA_MCU_FUNC_DIMMER) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Dim State=%d"), packetValue);
Tuya.new_dim = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, 0, 100);
if (Tuya.ignore_dimmer_cmd_timeout < millis()) {
if ((power || Settings.flag3.tuya_apply_o20) && // SetOption54 - Apply SetOption20 settings to Tuya device
(Tuya.new_dim > 0) && (abs(Tuya.new_dim - Settings.light_dimmer) > 1)) {
Tuya.ignore_dim = true;
uint8_t dimIndex;
if ((fnId == TUYA_MCU_FUNC_FAN3) || (fnId == TUYA_MCU_FUNC_FAN4) ||
(fnId == TUYA_MCU_FUNC_FAN5) || (fnId == TUYA_MCU_FUNC_FAN6)) {
Tuya.FanState = packetValue;
}
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER "3 %d"), Tuya.new_dim );
if ((fnId == TUYA_MCU_FUNC_DIMMER) || (fnId == TUYA_MCU_FUNC_REPORT1)) {
dimIndex = 0;
}
if (fnId == TUYA_MCU_FUNC_DIMMER2 || fnId == TUYA_MCU_FUNC_REPORT2 || fnId == TUYA_MCU_FUNC_CT) {
dimIndex = 1;
if (Settings.flag3.pwm_multi_channels) {
Tuya.Levels[dimIndex] = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, 0, 100);
} else {
Tuya.Levels[dimIndex] = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, Tuya.CTMax, Tuya.CTMin);
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX value %d from dpId %d "), packetValue, Tuya.buffer[dpidStart]);
if (Tuya.ignore_dimmer_cmd_timeout < millis()) {
if ((power || Settings.flag3.tuya_apply_o20) && ((Tuya.Levels[dimIndex] > 0) && (Tuya.Levels[dimIndex] != Tuya.Snapshot[dimIndex]))) { // SetOption54 - Apply SetOption20 settings to Tuya device
Tuya.ignore_dim = true;
skip_light_fade = true;
if ((fnId == TUYA_MCU_FUNC_DIMMER) || (fnId == TUYA_MCU_FUNC_REPORT1)) {
if (Settings.flag3.pwm_multi_channels && (abs(Tuya.Levels[0] - changeUIntScale(Light.current_color[0], 0, 255, 0, 100))) > 1) {
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_CHANNEL "1 %d"), Tuya.Levels[0]);
} else {
if ((abs(Tuya.Levels[0] - light_state.getDimmer())) > 1) { snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER "3 %d"), Tuya.Levels[0]); }
}
ExecuteCommand(scmnd, SRC_SWITCH);
}
if (((fnId == TUYA_MCU_FUNC_DIMMER2) || (fnId == TUYA_MCU_FUNC_REPORT2)) &&
Settings.flag3.pwm_multi_channels && (abs(Tuya.Levels[1] - changeUIntScale(Light.current_color[1], 0, 255, 0, 100))) > 1) {
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_CHANNEL "2 %d"), Tuya.Levels[1]);
ExecuteCommand(scmnd, SRC_SWITCH);
}
if ((fnId == TUYA_MCU_FUNC_CT) && (abs(Tuya.Levels[1] - light_state.getCT())) > 1) {
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_COLORTEMPERATURE " %d"), Tuya.Levels[1]);
ExecuteCommand(scmnd, SRC_SWITCH);
}
if ((fnId == TUYA_MCU_FUNC_WHITE) && (abs(Tuya.Levels[1] - light_state.getDimmer(2))) > 1) {
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WHITE " %d"), Tuya.Levels[1]);
ExecuteCommand(scmnd, SRC_SWITCH);
}
}
}
#ifdef USE_ENERGY_SENSOR
else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_VOLTAGE) {
Energy.voltage[0] = (float)packetValue / 10;
@ -489,14 +661,40 @@ void TuyaProcessStatePacket(void) {
Tuya.lastPowerCheckTime = Rtc.utc_time;
}
#endif // USE_ENERGY_SENSOR
}
// } else {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Unknown FnId=%s for dpId=%s"), fnId, Tuya.buffer[6]);
dpidStart += dpDataLen + 4;
else if (Tuya.buffer[dpidStart + 1] == 3) { // Data Type 3
const unsigned char *dpData = (unsigned char*)&Tuya.buffer[dpidStart + 4];
if ((TuyaGetDpId(TUYA_MCU_FUNC_RGB) != 0) && dpDataLen == 12) { //Decode the RGB hex and transmit HSBCOLOR command
//Tuya.ignore_dim = true;
char RgbData[13];
snprintf_P(RgbData, sizeof(RgbData), PSTR("%.*s"), dpDataLen, dpData);
char HSB1[5], HSB2[5], HSB3[5];
snprintf_P(HSB1, sizeof(HSB1), PSTR("%.4s\n"), &RgbData[0]);
snprintf_P(HSB2, sizeof(HSB2), PSTR("%.4s\n"), &RgbData[4]);
snprintf_P(HSB3, sizeof(HSB3), PSTR("%.4s\n"), &RgbData[8]);
if ((Tuya.Snapshot[2] != ((int)strtol(HSB1, NULL, 16)) ||
Tuya.Snapshot[3] != ((int)strtol(HSB2, NULL, 16)) / 10 || Tuya.Snapshot[4] !=((int)strtol(HSB3, NULL, 16)) / 10)) {
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_HSBCOLOR " %d,%d,%d"), ((int)strtol(HSB1, NULL, 16)),
((int)strtol(HSB2, NULL, 16)) / 10, ((int)strtol(HSB3, NULL, 16)) / 10);
ExecuteCommand(scmnd, SRC_SWITCH);
memcpy_P(Tuya.HSBColor, RgbData, strlen(RgbData));
}
}
}
else if (Tuya.buffer[dpidStart + 1] == 4) { // Data Type 4
const unsigned char *dpData = (unsigned char*)&Tuya.buffer[dpidStart + 4];
if (fnId == TUYA_MCU_FUNC_MODESET) { // toggle light type
Tuya.ModeSet = dpData[0];
Tuya.Levels[3] = dpData[0];
}
}
dpidStart += dpDataLen + 4;
}
}
void TuyaLowPowerModePacketProcess(void) {
switch (Tuya.buffer[3]) {
case TUYA_CMD_QUERY_PRODUCT:
@ -579,7 +777,7 @@ void TuyaNormalPowerModePacketProcess(void)
restart_flag = 2;
}
}
TuyaRequestState();
TuyaRequestState(0);
break;
default:
@ -615,14 +813,33 @@ bool TuyaModuleSelected(void)
}
}
if (!relaySet) {
if (!relaySet && TuyaGetDpId(TUYA_MCU_FUNC_DUMMY) == 0) { //by default the first relay is created automatically the dummy let remove it if not needed
TuyaAddMcuFunc(TUYA_MCU_FUNC_REL1, 1);
devices_present++;
SettingsSaveAll();
}
// Possible combinations for Lights:
// 0: NONE = LT_BASIC
// 1: DIMMER = LT_SERIAL1 - Common one channel dimmer
// 2: DIMMER, DIMMER2 = LT_SERIAL2 - Two channels dimmer (special setup used with SetOption68)
// 3: DIMMER, CT = LT_SERIAL2 - Dimmable light and White Color Temperature
// 4: DIMMER, RGB = LT_RGB - RGB Light
// 5: DIMMER, RGB, CT = LT_RGBWC - RGB LIght and White Color Temperature
// 6: DIMMER, RGB, WHITE = LT_RGBW - RGB LIght and White
if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) != 0) {
light_type = LT_SERIAL1;
if (TuyaGetDpId(TUYA_MCU_FUNC_RGB) != 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_CT) != 0) {
light_type = LT_RGBWC;
} else if (TuyaGetDpId(TUYA_MCU_FUNC_WHITE) != 0) {
light_type = LT_RGBW;
} else { light_type = LT_RGB; }
} else if (TuyaGetDpId(TUYA_MCU_FUNC_CT) != 0 || TuyaGetDpId(TUYA_MCU_FUNC_DIMMER2) != 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_RGB) != 0) {
light_type = LT_RGBWC;
} else { light_type = LT_SERIAL2; }
} else { light_type = LT_SERIAL1; }
} else {
light_type = LT_BASIC;
}
@ -647,9 +864,11 @@ void TuyaInit(void)
if (TuyaSerial->begin(baudrate)) {
if (TuyaSerial->hardwareSerial()) { ClaimSerial(); }
// Get MCU Configuration
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Request MCU configuration at %d baud rate"));
Tuya.SuspendTopic = true;
Tuya.ignore_topic_timeout = millis() + 1000; // suppress /STAT topic for 1000ms to avoid data overflow
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Request MCU configuration at %d bps"), baudrate);
TuyaSendCmd(TUYA_CMD_QUERY_PRODUCT);
}
}
Tuya.heartbeat_timer = 0; // init heartbeat timer when dimmer init is done
@ -687,17 +906,24 @@ void TuyaSerialInput(void)
char hex_char[(Tuya.byte_counter * 2) + 2];
uint16_t len = Tuya.buffer[4] << 8 | Tuya.buffer[5];
Response_P(PSTR("{\"" D_JSON_TUYA_MCU_RECEIVED "\":{\"Data\":\"%s\",\"Cmnd\":%d"), ToHex_P((unsigned char*)Tuya.buffer, Tuya.byte_counter, hex_char, sizeof(hex_char)), Tuya.buffer[3]);
uint16_t DataVal = 0;
uint8_t dpId = 0;
uint8_t dpDataType = 0;
char DataStr[13];
if (len > 0) {
ResponseAppend_P(PSTR(",\"CmndData\":\"%s\""), ToHex_P((unsigned char*)&Tuya.buffer[6], len, hex_char, sizeof(hex_char)));
if (TUYA_CMD_STATE == Tuya.buffer[3]) {
//55 AA 03 07 00 0D 01 04 00 01 02 02 02 00 04 00 00 00 1A 40
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
uint8_t dpidStart = 6;
//snprintf_P(DataStr, sizeof(DataStr), PSTR("000000000000"));
while (dpidStart + 4 < Tuya.byte_counter) {
uint8_t dpId = Tuya.buffer[dpidStart];
uint8_t dpDataType = Tuya.buffer[dpidStart + 1];
dpId = Tuya.buffer[dpidStart];
dpDataType = Tuya.buffer[dpidStart + 1];
uint16_t dpDataLen = Tuya.buffer[dpidStart + 2] << 8 | Tuya.buffer[dpidStart + 3];
const unsigned char *dpData = (unsigned char*)&Tuya.buffer[dpidStart + 4];
const char *dpHexData = ToHex_P(dpData, dpDataLen, hex_char, sizeof(hex_char));
@ -706,15 +932,20 @@ void TuyaSerialInput(void)
ResponseAppend_P(PSTR(",\"DpType%uId%u\":"), dpDataType, dpId);
if (TUYA_TYPE_BOOL == dpDataType && dpDataLen == 1) {
ResponseAppend_P(PSTR("%u"), dpData[0]);
DataVal = dpData[0];
} else if (TUYA_TYPE_VALUE == dpDataType && dpDataLen == 4) {
uint32_t dpValue = (uint32_t)dpData[0] << 24 | (uint32_t)dpData[1] << 16 | (uint32_t)dpData[2] << 8 | (uint32_t)dpData[3] << 0;
ResponseAppend_P(PSTR("%u"), dpValue);
DataVal = dpValue;
} else if (TUYA_TYPE_STRING == dpDataType) {
ResponseAppend_P(PSTR("\"%.*s\""), dpDataLen, dpData);
snprintf_P(DataStr, sizeof(DataStr), PSTR("%.*s"), dpDataLen, dpData);
} else if (TUYA_TYPE_ENUM == dpDataType && dpDataLen == 1) {
ResponseAppend_P(PSTR("%u"), dpData[0]);
DataVal = dpData[0];
} else {
ResponseAppend_P(PSTR("\"0x%s\""), dpHexData);
snprintf_P(DataStr, sizeof(DataStr), PSTR("%s"), dpHexData);
}
}
@ -727,7 +958,6 @@ void TuyaSerialInput(void)
}
}
}
ResponseAppend_P(PSTR("}}"));
if (Settings.flag3.tuya_serial_mqtt_publish) { // SetOption66 - Enable TuyaMcuReceived messages over Mqtt
@ -737,6 +967,17 @@ void TuyaSerialInput(void)
}
XdrvRulesProcess();
if (dpId != 0 && Settings.tuyamcu_topic) { // Publish a /STAT Topic ready to use for any home automation system
if (!Tuya.SuspendTopic) {
char scommand[10];
snprintf_P(scommand, sizeof(scommand), PSTR("TuyaSend%d"), dpDataType);
if (dpDataType != 3 && dpDataType != 5) { Response_P(PSTR("%d,%u"), dpId, DataVal); }
else { Response_P(PSTR("%d,%s"), dpId, DataStr); }
MqttPublishPrefixTopic_P(STAT, (PSTR("%s"), scommand));
}
}
if (!Tuya.low_power_mode) {
TuyaNormalPowerModePacketProcess();
} else {
@ -747,7 +988,7 @@ void TuyaSerialInput(void)
Tuya.cmd_status = 0;
Tuya.cmd_checksum = 0;
Tuya.data_len = 0;
} // read additional packets from TUYA
} // read additional packets from TUYA
else if (Tuya.byte_counter < TUYA_BUFFER_SIZE -1) { // add char to string if it still fits
Tuya.buffer[Tuya.byte_counter++] = serial_in_byte;
Tuya.cmd_checksum += serial_in_byte;
@ -821,6 +1062,7 @@ void TuyaSetTime(void) {
#endif //USE_TUYA_TIME
#ifdef USE_ENERGY_SENSOR
/*********************************************************************************************\
* Energy Interface
\*********************************************************************************************/
@ -887,6 +1129,7 @@ bool Xdrv16(uint8_t function)
} else {
TuyaSendLowPowerSuccessIfNeeded();
}
if (Tuya.ignore_topic_timeout < millis()) { Tuya.SuspendTopic = false; }
break;
case FUNC_SET_CHANNELS:
result = TuyaSetChannels();