From e45a5f33234bf05b70427a3b6fee586b2bf3f899 Mon Sep 17 00:00:00 2001 From: Federico Leoni Date: Wed, 16 Sep 2020 15:45:56 -0300 Subject: [PATCH 1/2] TuyaMCU Update --- tasmota/settings.h | 6 +- tasmota/settings.ino | 4 +- tasmota/tasmota.h | 9 +- tasmota/xdrv_01_webserver.ino | 72 ++++++ tasmota/xdrv_16_tuyamcu.ino | 453 ++++++++++++++++++++++++++-------- 5 files changed, 431 insertions(+), 113 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index 2ceadd523..0803bcca2 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -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 diff --git a/tasmota/settings.ino b/tasmota/settings.ino index a26c89b3d..2c17cbad2 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -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)) { diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 96cd9e49c..cffb9e57c 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -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_ diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index ed15c9e12..8d37f5183 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -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("
")); + 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("")); + modeset = 1; + } + if (IsTuyaFanCtrl()) { + uint8_t device = devices_present + modeset; + WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(PSTR("
")); + 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("")); + } + } else { +#endif // USE_TUYA_MCU + } + WSContentSend_P(PSTR("")); #ifdef USE_SONOFF_IFAN } #endif // USE_SONOFF_IFAN + WSContentSend_P(PSTR("")); } #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("")); } +#ifdef USE_TUYA_MCU + if (IsModuleTuya()) { + uint32_t fanspeed = TuyaFanState(); + uint32_t modeset = TuyaModeSet(); + if (IsTuyaFanCtrl() && !AsModuleTuyaMS()) { + WSContentSend_P(PSTR("
" D_JSON_IRHVAC_FANSPEED ": %d
"), fanspeed); + } else if (!IsTuyaFanCtrl() && AsModuleTuyaMS()) { + WSContentSend_P(PSTR("
" D_JSON_IRHVAC_MODE ": %d
"), modeset); + } else if (IsTuyaFanCtrl() && AsModuleTuyaMS()) { + WSContentSend_P(PSTR("
" D_JSON_IRHVAC_MODE ": %d - " D_JSON_IRHVAC_FANSPEED ": %d
"), modeset, fanspeed); + } + } +#endif // USE_TUYA_MCU WSContentEnd(); return true; diff --git a/tasmota/xdrv_16_tuyamcu.ino b/tasmota/xdrv_16_tuyamcu.ino index b4b9a5ff4..2c4b9e0d6 100644 --- a/tasmota/xdrv_16_tuyamcu.ino +++ b/tasmota/xdrv_16_tuyamcu.ino @@ -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 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(); From 0bca9a2028bab626b1685178225d1a7181ee8312 Mon Sep 17 00:00:00 2001 From: Federico Leoni Date: Wed, 16 Sep 2020 16:59:10 -0300 Subject: [PATCH 2/2] TuyaMCU update2 oops --- tasmota/xdrv_01_webserver.ino | 53 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 8d37f5183..ef80c4abf 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -1460,39 +1460,36 @@ 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("
")); - 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("")); - modeset = 1; - } - if (IsTuyaFanCtrl()) { - uint8_t device = devices_present + modeset; - WSContentSend_P(HTTP_TABLE100); - WSContentSend_P(PSTR("
")); - 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("")); - } - } else { -#endif // USE_TUYA_MCU - } - WSContentSend_P(PSTR("")); #ifdef USE_SONOFF_IFAN } #endif // USE_SONOFF_IFAN - WSContentSend_P(PSTR("")); } +#ifdef USE_TUYA_MCU + if (IsModuleTuya()) { + uint8_t modeset = 0; + if (AsModuleTuyaMS()) { + WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(PSTR("
")); + 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("")); + modeset = 1; + } + if (IsTuyaFanCtrl()) { + uint8_t device = devices_present + modeset; + WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(PSTR("
")); + 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("")); + } + } +#endif // USE_TUYA_MCU #ifdef USE_SONOFF_RF if (SONOFF_BRIDGE == my_module_type) { WSContentSend_P(HTTP_TABLE100);