From ae32549759f5d1f1fb810933581381246873aa6f Mon Sep 17 00:00:00 2001 From: Federico Leoni Date: Sat, 28 Nov 2020 11:49:04 -0300 Subject: [PATCH] TuyaUpdate --- tasmota/tasmota.h | 3 + tasmota/xdrv_16_tuyamcu.ino | 173 ++++++++++++++++++++++++++++++------ 2 files changed, 150 insertions(+), 26 deletions(-) diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 697add878..59c130a96 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -381,6 +381,9 @@ enum TuyaSupportedFunctions { TUYA_MCU_FUNC_NONE, TUYA_MCU_FUNC_REL6_INV, TUYA_MCU_FUNC_REL7_INV, TUYA_MCU_FUNC_REL8_INV, TUYA_MCU_FUNC_LOWPOWER_MODE = 51, TUYA_MCU_FUNC_ENUM1 = 61, TUYA_MCU_FUNC_ENUM2, TUYA_MCU_FUNC_ENUM3, TUYA_MCU_FUNC_ENUM4, + TUYA_MCU_FUNC_TEMP = 71, TUYA_MCU_FUNC_TEMPSET, TUYA_MCU_FUNC_HUM, TUYA_MCU_FUNC_HUMSET, + TUYA_MCU_FUNC_LX = 75, TUYA_MCU_FUNC_TVOC, TUYA_MCU_FUNC_CO2, TUYA_MCU_FUNC_ECO2, + TUYA_MCU_FUNC_TIMER1 = 81, TUYA_MCU_FUNC_TIMER2, TUYA_MCU_FUNC_TIMER3, TUYA_MCU_FUNC_TIMER4, TUYA_MCU_FUNC_MOTOR_DIR = 97, TUYA_MCU_FUNC_ERROR = 98, TUYA_MCU_FUNC_DUMMY = 99, diff --git a/tasmota/xdrv_16_tuyamcu.ino b/tasmota/xdrv_16_tuyamcu.ino index a2628c8a5..a7b9f27ec 100644 --- a/tasmota/xdrv_16_tuyamcu.ino +++ b/tasmota/xdrv_16_tuyamcu.ino @@ -62,7 +62,8 @@ struct TUYA { 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 + uint16_t Sensors[14]; // Stores the values of Sensors connected to the Tuya Device + bool SensorsValid[14]; // Bool used for nullify the sensor value until a real value is received from the MCU 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 @@ -90,6 +91,13 @@ struct TUYA { #define D_CMND_TUYARGB "RGB" #define D_CMND_TUYA_ENUM "Enum" #define D_CMND_TUYA_ENUM_LIST "EnumList" +#define D_CMND_TUYA_SET_TEMP "SetTemp" +#define D_CMND_TUYA_SET_HUM "SetHum" +#define D_CMND_TUYA_SET_TIMER "SetTimer" + +const char kTuyaSensors[] PROGMEM = // Lit of available sensors (can be expanded in the future) + "" D_JSON_TEMPERATURE "|TempSet|" D_JSON_HUMIDITY "|HumSet|" D_JSON_ILLUMINANCE + "|" D_JSON_TVOC "|" D_JSON_ECO2 "|" D_JSON_CO2 "|||Timer1|Timer2|Timer3|TImer4"; const char kTuyaCommand[] PROGMEM = D_PRFX_TUYA "|" // Prefix D_CMND_TUYA_MCU "|" D_CMND_TUYA_MCU_SEND_STATE "|" D_CMND_TUYARGB "|" D_CMND_TUYA_ENUM "|" D_CMND_TUYA_ENUM_LIST; @@ -115,6 +123,9 @@ bool TuyaModeSet(void) // ModeSet Status { return Tuya.ModeSet; } +/*********************************************************************************************\ + * Web Interface +\*********************************************************************************************/ /* TuyaSend dpId,data @@ -124,7 +135,7 @@ TuyaSend1 11,1 -> Sends boolean (Type 1) data 0/1 to dpId 11 (Max data length 1 TuyaSend2 11,100 -> Sends integer (Type 2) data 100 to dpId 11 (Max data length 4 bytes) 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/6/7/8/9 to dpId 11 (Max data length 1 bytes) +TuyaSend4 11,1 -> Sends enum (Type 4) data 1 to dpId 11 (Max data length 1 bytes) */ void CmndTuyaSend(void) { @@ -195,12 +206,6 @@ void CmndTuyaMcu(void) { } else { Settings.flag3.pwm_multi_channels = 0; } TuyaAddMcuFunc(parm[0], parm[1]); TasmotaGlobal.restart_flag = 2; - if (TUYA_MCU_FUNC_RGB == parm[0] && parm[1] != 0) { - // if (Settings.tuya_fnid_map[235].dpid > 3) { - // Settings.tuya_fnid_map[235].fnid = 235; - // Settings.tuya_fnid_map[235].dpid = 0; - // } - } } else { AddLog_P(LOG_LEVEL_ERROR, PSTR("TYA: TuyaMcu Invalid function id=%d"), parm[0]); } @@ -228,7 +233,7 @@ void CmndTuyaRgb(void) { // Command to control the RGB format if (payload < 0 || payload > 3 || TuyaGetDpId(TUYA_MCU_FUNC_RGB) == 0) { return; } else { - if (payload != Settings.tuya_fnid_map[230].dpid) { // fnid 230 reserved for RGB + if (payload != Settings.tuya_fnid_map[230].dpid) { // fnid 230 is reserved for RGB Settings.tuya_fnid_map[230].fnid = 230; Settings.tuya_fnid_map[230].dpid = payload; } @@ -271,7 +276,7 @@ void CmndTuyaEnum(void) { // Command to control up to four type 4 Enum } } -void CmndTuyaEnumList(void) { // Command to declare the number of items in list for up to four type 4 enum. Min = 0, Max = 9, Default = 1 +void CmndTuyaEnumList(void) { // Command to declare the number of items in list for up to four type 4 enum. Min = 0, Max = 31, Default = 0 if (XdrvMailbox.data_len > 0) { char *p; @@ -281,7 +286,7 @@ void CmndTuyaEnumList(void) { // Command to declare the number of items in list parm[i] = strtoul(str, nullptr, 0); i++; } - if ((parm[0] >= 1 && parm[0] <= 4) && (parm[1] >= 1 && parm[1] <= 9)) { + if ((parm[0] >= 1 && parm[0] <= 4) && (parm[1] >= 1 && parm[1] <= 31)) { uint16_t idx = parm[0] + 230; // fnid 231, 232, 233 and 234 are reserved for enum Settings.tuya_fnid_map[idx].fnid = idx; Settings.tuya_fnid_map[idx].dpid = parm[1]; @@ -295,7 +300,7 @@ void CmndTuyaEnumList(void) { // Command to declare the number of items in list if (TuyaGetDpId(TUYA_MCU_FUNC_ENUM1 + i) != 0) { if (added) { ResponseAppend_P(PSTR(",")); - if ( Settings.tuya_fnid_map[i + 231].dpid > 9 ) { Settings.tuya_fnid_map[i + 231].dpid = 1; } // default to 1 it the value exceed the range + if ( Settings.tuya_fnid_map[i + 231].dpid > 31 ) { Settings.tuya_fnid_map[i + 231].dpid = 0; } // default to 0 it the value exceed the range } ResponseAppend_P(PSTR("\"Enum%d\":%d"), i + 1, Settings.tuya_fnid_map[i + 231].dpid); // fnid 231, 232, 233 and 234 are reserved for Enum added = true; @@ -305,7 +310,7 @@ void CmndTuyaEnumList(void) { // Command to declare the number of items in list } else { return; } } -int StrCmpNoCase(char const *Str1, char const *Str2) // Compare RGB case sensistive strings +int StrCmpNoCase(char const *Str1, char const *Str2) // Compare case sensistive RGB strings { for (;; Str1++, Str2++) { int StrCmp = tolower((unsigned char)*Str1) - tolower((unsigned char)*Str2); @@ -367,7 +372,10 @@ inline bool TuyaFuncIdValid(uint8_t fnId) { (fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) || (fnId >= TUYA_MCU_FUNC_ENUM1 && fnId <= TUYA_MCU_FUNC_ENUM4) || (fnId >= TUYA_MCU_FUNC_MOTOR_DIR && fnId <= TUYA_MCU_FUNC_DUMMY) || - (fnId == TUYA_MCU_FUNC_LOWPOWER_MODE); + (fnId == TUYA_MCU_FUNC_LOWPOWER_MODE) || + (fnId >= TUYA_MCU_FUNC_TEMP && fnId <= TUYA_MCU_FUNC_HUMSET) || + (fnId >= TUYA_MCU_FUNC_LX && fnId <= TUYA_MCU_FUNC_ECO2) || + (fnId >= TUYA_MCU_FUNC_TIMER1 && fnId <= TUYA_MCU_FUNC_TIMER4); } uint8_t TuyaGetFuncId(uint8_t dpid) { for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) { @@ -527,7 +535,7 @@ bool TuyaSetChannels(void) 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]; + //Tuya.Levels[i] = Tuya.Snapshot[i]; } noupd = false; } @@ -665,6 +673,7 @@ void TuyaProcessStatePacket(void) { uint8_t dpidStart = 6; uint8_t fnId; uint16_t dpDataLen; + bool PowerOff = false; while (dpidStart + 4 < Tuya.byte_counter) { dpDataLen = Tuya.buffer[dpidStart + 2] << 8 | Tuya.buffer[dpidStart + 3]; @@ -676,12 +685,14 @@ void TuyaProcessStatePacket(void) { if (fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX Relay-%d --> MCU State: %s Current State:%s"), fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[dpidStart + 4]?"On":"Off",bitRead(TasmotaGlobal.power, fnId - TUYA_MCU_FUNC_REL1)?"On":"Off"); if ((TasmotaGlobal.power || Settings.light_dimmer > 0) && (Tuya.buffer[dpidStart + 4] != bitRead(TasmotaGlobal.power, fnId - TUYA_MCU_FUNC_REL1))) { + if (!Tuya.buffer[dpidStart + 4]) { PowerOff = true; } ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[dpidStart + 4], SRC_SWITCH); // send SRC_SWITCH? to use as flag to prevent loop from inbound states from faceplate interaction } } else if (fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX Relay-%d-Inverted --> MCU State: %s Current State:%s"), fnId - TUYA_MCU_FUNC_REL1_INV + 1, Tuya.buffer[dpidStart + 4]?"Off":"On",bitRead(TasmotaGlobal.power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1?"Off":"On"); if (Tuya.buffer[dpidStart + 4] != bitRead(TasmotaGlobal.power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1) { ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1_INV + 1, Tuya.buffer[dpidStart + 4] ^ 1, SRC_SWITCH); // send SRC_SWITCH? to use as flag to prevent loop from inbound states from faceplate interaction + if (Tuya.buffer[dpidStart + 4]) { PowerOff = true; } } } else if (fnId >= TUYA_MCU_FUNC_SWT1 && fnId <= TUYA_MCU_FUNC_SWT4) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX Switch-%d --> MCU State: %d Current State:%d"),fnId - TUYA_MCU_FUNC_SWT1 + 1,Tuya.buffer[dpidStart + 4], SwitchGetVirtual(fnId - TUYA_MCU_FUNC_SWT1)); @@ -691,18 +702,46 @@ void TuyaProcessStatePacket(void) { SwitchHandler(1); } } + if (PowerOff) { Tuya.ignore_dimmer_cmd_timeout = millis() + 250; } } else if (Tuya.buffer[dpidStart + 1] == 2) { // Data Type 2 bool tuya_energy_enabled = (XNRG_32 == TasmotaGlobal.energy_driver); uint16_t packetValue = Tuya.buffer[dpidStart + 6] << 8 | Tuya.buffer[dpidStart + 7]; uint8_t dimIndex; + bool SnsUpdate = false; + + if ((fnId >= TUYA_MCU_FUNC_TEMP) && (fnId <= TUYA_MCU_FUNC_TIMER4)) { // Sensors start from fnId 71 + if (packetValue != Tuya.Sensors[fnId-71]) { + Tuya.SensorsValid[fnId-71] = true; + Tuya.Sensors[fnId-71] = packetValue; + SnsUpdate = true; + } + } + + if (SnsUpdate) { + char sname[20]; + char tempval[5]; + uint8_t res; + + if (TasmotaGlobal.uptime < 8) { // delay to avoid multiple topics at the same time at boot time + return; + } else { + if (fnId > 74) { + res = 0; + } else { res = Settings.flag2.temperature_resolution; } + GetTextIndexed(sname, sizeof(sname), (fnId-71), kTuyaSensors); + ResponseClear(); // Clear retained message + Response_P(PSTR("{\"TuyaSNS\":{\"%s\":%s}}"), sname, dtostrfd(packetValue, res, tempval)); // sensor update is just on change + MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_CMND_SENSOR)); + } + } 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 (dimIndex == 1 && !Settings.flag3.pwm_multi_channels) { - Tuya.Levels[dimIndex] = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, Tuya.CTMax, Tuya.CTMin); + Tuya.Levels[1] = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, Tuya.CTMax, Tuya.CTMin); } else { Tuya.Levels[dimIndex] = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, 0, 100); } @@ -714,9 +753,7 @@ void TuyaProcessStatePacket(void) { (fnId == TUYA_MCU_FUNC_CT) || (fnId == TUYA_MCU_FUNC_WHITE)) { if (Tuya.ignore_dimmer_cmd_timeout < millis()) { - if ((TasmotaGlobal.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; TasmotaGlobal.skip_light_fade = true; @@ -743,6 +780,7 @@ void TuyaProcessStatePacket(void) { ExecuteCommand(scmnd, SRC_SWITCH); } } + Tuya.Snapshot[dimIndex] = Tuya.Levels[dimIndex]; } } #ifdef USE_ENERGY_SENSOR @@ -794,7 +832,6 @@ void TuyaProcessStatePacket(void) { snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_COLOR " %s"), RGB); memcpy_P(Tuya.RGBColor, RGB, strlen(RGB)); } - } if (scmnd[0] != '\0') { ExecuteCommand(scmnd, SRC_SWITCH); @@ -809,8 +846,6 @@ void TuyaProcessStatePacket(void) { Tuya.Levels[3] = dpData[0]; } if ((fnId >= TUYA_MCU_FUNC_ENUM1) && (fnId <= TUYA_MCU_FUNC_ENUM4)) { - // if ((fnId == TUYA_MCU_FUNC_ENUM1) || (fnId == TUYA_MCU_FUNC_ENUM2) || - // (fnId == TUYA_MCU_FUNC_ENUM3) || (fnId == TUYA_MCU_FUNC_ENUM4)) { for (uint8_t i = 0; i <= 3; i++) { bool noupdate = false; if ((TUYA_MCU_FUNC_ENUM1 + i) == fnId) { @@ -998,7 +1033,7 @@ void TuyaInit(void) Tuya.SuspendTopic = true; Tuya.ignore_topic_timeout = millis() + 1000; // suppress /STAT topic for 1000ms to avoid data overflow AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Request MCU configuration at %d bps"), baudrate); - TuyaSendCmd(TUYA_CMD_QUERY_PRODUCT); + } } @@ -1217,6 +1252,82 @@ bool Xnrg32(uint8_t function) } #endif // USE_ENERGY_SENSOR +/*********************************************************************************************\ + * Sensors +\*********************************************************************************************/ + +void TuyaSensorsShow(bool json) +{ + bool RootName = false; + bool added = false; + char sname[20]; + char tempval[5]; + uint8_t res; + + for (uint8_t sensor = TUYA_MCU_FUNC_TEMP; sensor <= TUYA_MCU_FUNC_TIMER4; sensor++) { // Sensors start from fnId 71 + if (json) { + if (TuyaGetDpId(sensor) != 0) { + + if (!RootName) { + ResponseAppend_P(PSTR(",\"TuyaSNS\":{")); + RootName = true; + } + if (added) { + ResponseAppend_P(PSTR(",")); + } + if (sensor > 74) { + res = 0; + } else { res = Settings.flag2.temperature_resolution; } + + GetTextIndexed(sname, sizeof(sname), (sensor-71), kTuyaSensors); + ResponseAppend_P(PSTR("\"%s\":%s"), sname, + (Tuya.SensorsValid[sensor-71] ? dtostrfd(Tuya.Sensors[sensor-71], res, tempval) : "null")); + added = true; + } + #ifdef USE_WEBSERVER + } else { + if (TuyaGetDpId(sensor) != 0) { + switch (sensor) { + case 71: + WSContentSend_PD(HTTP_SNS_TEMP, "", dtostrfd(Tuya.Sensors[0], Settings.flag2.temperature_resolution, tempval), TempUnit()); + break; + case 72: + WSContentSend_PD(PSTR("{s}" D_TEMPERATURE " Set{m}%s " D_UNIT_DEGREE "%c{e}"), + dtostrfd(Tuya.Sensors[1], Settings.flag2.temperature_resolution, tempval), TempUnit()); + break; + case 73: + WSContentSend_PD(HTTP_SNS_HUM, "", dtostrfd(Tuya.Sensors[2], Settings.flag2.temperature_resolution, tempval)); + break; + case 74: + WSContentSend_PD(PSTR("{s}" D_HUMIDITY " Set{m}%s " D_UNIT_PERCENT "{e}"), + dtostrfd(Tuya.Sensors[3], Settings.flag2.temperature_resolution, tempval)); + break; + case 75: + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, "", Tuya.Sensors[4]); + break; + case 76: + WSContentSend_PD(PSTR("{s}" D_TVOC "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"), Tuya.Sensors[5]); + break; + case 77: + WSContentSend_PD(HTTP_SNS_CO2, "", Tuya.Sensors[6]); + break; + case 78: + WSContentSend_PD(HTTP_SNS_CO2EAVG, "", Tuya.Sensors[7]); + break; + case 81: + case 82: + case 83: + case 84: + WSContentSend_PD(PSTR("{s}Timer%d{m}%d{e}"), (sensor-80), Tuya.Sensors[sensor-71]); // No UoM for timers since they can be sec or min + break; + } + } + #endif // USE_WEBSERVER + } + } + if (RootName) { ResponseJsonEnd();} +} + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -1243,7 +1354,6 @@ bool Xdrv16(uint8_t function) result = TuyaButtonPressed(); break; case FUNC_EVERY_SECOND: - TuyaSetChannels(); if (TuyaSerial && Tuya.wifi_state != TuyaGetTuyaWifiState()) { TuyaSetWifiLed(); } if (!Tuya.low_power_mode) { Tuya.heartbeat_timer++; @@ -1261,12 +1371,23 @@ bool Xdrv16(uint8_t function) } if (Tuya.ignore_topic_timeout < millis()) { Tuya.SuspendTopic = false; } break; - // case FUNC_SET_CHANNELS: - // result = TuyaSetChannels(); - // break; + case FUNC_SET_CHANNELS: + result = TuyaSetChannels(); + break; + case FUNC_MQTT_INIT: + TuyaSendCmd(TUYA_CMD_QUERY_PRODUCT); + break; case FUNC_COMMAND: result = DecodeCommand(kTuyaCommand, TuyaCommand); break; + case FUNC_JSON_APPEND: + TuyaSensorsShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + TuyaSensorsShow(0); + break; +#endif // USE_WEBSERVER } } return result;