Tuya: Make Tuya Mcu implementation more configurable.

More and more Tuya MCU based devices are coming in the market and people requesting to support them. This patch makes Tuya module more configurable and easier to add new functionalities. Its not just a dimmer or a switch anymore.

After this Patch Tuya MCU module has a list of supported functions and the user would need to map the functionId to dpId of their device. Once mapped correctly the Tuya module will take care for handling proper function for dpId.

Currently functions supported are

1. Relays1 to 8 : FunctionID 11 to 18
2. Dimmer : FunctionID 21
3. Power ( Deca Watt )  : Function ID 31
4. Current ( milli Amps ) : Function ID 32
5. Voltage ( deca Volts ) : Function ID 33

The changes are

- Use a TuyaMCU command to map DPs to Functions instead of many different SetOptions. SetOption41, 44, 45, 46, 65 won't be needed after this patch.
- TuyaMCU command takes argument like `11,1` which means Map Function id 11 (Relay1) to DPID 1
- Migrates old settings flags and options to new TuyaMap command
This commit is contained in:
Shantur Rathore 2019-09-03 11:23:44 +01:00
parent af1edb0cdb
commit efd1870d5e
5 changed files with 249 additions and 60 deletions

View File

@ -444,6 +444,10 @@
#define D_CMND_LATITUDE "Latitude"
#define D_CMND_LONGITUDE "Longitude"
// Commands xdrv_16_tuyadimmer.ino
#define D_CMND_TUYA_MCU "TuyaMCU"
// Commands xdrv_23_zigbee.ino
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"

View File

@ -185,6 +185,14 @@ typedef struct {
uint32_t last_return_kWhtotal;
} EnergyUsage;
typedef struct {
uint8_t fnid = 0;
uint8_t dpid = 0;
} TuyaFnidDpidMap;
const uint8_t MAX_TUYA_FUNCTIONS = 16;
/*
struct SYSCFG {
unsigned long cfg_holder; // 000 Pre v6 header
@ -366,8 +374,9 @@ struct SYSCFG {
char rules[MAX_RULE_SETS][MAX_RULE_SIZE]; // 800 uses 512 bytes in v5.12.0m, 3 x 512 bytes in v5.14.0b
uint8_t data8[32]; // E00
uint16_t data16[16]; // E20
TuyaFnidDpidMap tuya_fnid_map[MAX_TUYA_FUNCTIONS]; // E40 32 bytes
uint8_t free_e20[448]; // E40
uint8_t free_e20[416]; // E60
// FFF last location
} Settings;

View File

@ -1096,6 +1096,41 @@ void SettingsDelta(void)
Settings.sbaudrate = Settings.ex_sbaudrate * 4;
}
if (Settings.version < 0x0606000A) {
uint8_t tuyaindex = 0;
if (Settings.param[P_TUYA_DIMMER_ID] > 0) {
Settings.tuya_fnid_map[tuyaindex].fnid = 21; //TUYA_MCU_FUNC_DIMMER; // Move Tuya Dimmer Id to Map
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_TUYA_DIMMER_ID];
tuyaindex++;
} else if (Settings.flag3.tuya_disable_dimmer == 1) {
Settings.tuya_fnid_map[tuyaindex].fnid = 11; //TUYA_MCU_FUNC_REL1; // Create FnID for Switches
Settings.tuya_fnid_map[tuyaindex].dpid = 1;
tuyaindex++;
}
if (Settings.param[P_TUYA_RELAYS] > 0) {
for (uint8_t i = 0 ; i < Settings.param[P_TUYA_RELAYS]; i++) {
Settings.tuya_fnid_map[tuyaindex].fnid = 12 + i; //TUYA_MCU_FUNC_REL2+; // Create FnID for Switches
Settings.tuya_fnid_map[tuyaindex].dpid = i + 2;
tuyaindex++;
}
}
if (Settings.param[P_TUYA_POWER_ID] > 0) {
Settings.tuya_fnid_map[tuyaindex].fnid = 31; //TUYA_MCU_FUNC_POWER; // Move Tuya Power Id to Map
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_TUYA_POWER_ID];
tuyaindex++;
}
if (Settings.param[P_TUYA_VOLTAGE_ID] > 0) {
Settings.tuya_fnid_map[tuyaindex].fnid = 33; //TUYA_MCU_FUNC_VOLTAGE; // Move Tuya Voltage Id to Map
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_TUYA_VOLTAGE_ID];
tuyaindex++;
}
if (Settings.param[P_TUYA_CURRENT_ID] > 0) {
Settings.tuya_fnid_map[tuyaindex].fnid = 32; //TUYA_MCU_FUNC_CURRENT; // Move Tuya Current Id to Map
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_TUYA_CURRENT_ID];
tuyaindex++;
}
}
Settings.version = VERSION;
SettingsSave(1);
}

View File

@ -20,6 +20,6 @@
#ifndef _SONOFF_VERSION_H_
#define _SONOFF_VERSION_H_
const uint32_t VERSION = 0x06060009;
const uint32_t VERSION = 0x0606000A;
#endif // _SONOFF_VERSION_H_

View File

@ -61,10 +61,130 @@ struct TUYA {
int byte_counter = 0; // Index in serial receive buffer
} Tuya;
enum TuyaSupportedFunctions {
TUYA_MCU_FUNC_NONE,
// TUYA_MCU_FUNC_KEY1 = 1, // Buttons
// TUYA_MCU_FUNC_KEY2,
// TUYA_MCU_FUNC_KEY3,
// TUYA_MCU_FUNC_KEY4,
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_LAST = 255
};
const char kTuyaCommand[] PROGMEM = "|" // No prefix
D_CMND_TUYA_MCU;
void (* const TuyaCommand[])(void) PROGMEM = {
&CmndTuyaMcu
};
/*
TuyaMap fnid,dpid
*/
void CmndTuyaMcu(void) {
if (XdrvMailbox.data_len > 0) {
char *p;
uint8_t i = 0;
uint8_t parm[3] = { 0 };
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) {
parm[i] = strtoul(str, nullptr, 0);
i++;
}
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]);
}
}
Response_P(PSTR("["));
bool added = false;
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
if (Settings.tuya_fnid_map[i].fnid != 0) {
if (added) {
ResponseAppend_P(PSTR(","));
}
ResponseAppend_P(PSTR("{\"fnId\":%d, \"dpId\":%d}" ), Settings.tuya_fnid_map[i].fnid, Settings.tuya_fnid_map[i].dpid);
added = true;
}
}
ResponseAppend_P(PSTR("]"));
}
/*********************************************************************************************\
* Internal Functions
\*********************************************************************************************/
void TuyaAddMcuFunc(uint8_t fnId, uint8_t dpId) {
bool added = false;
if (fnId == 0 || dpId == 0) { // Delete entry
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
if ((dpId > 0 && Settings.tuya_fnid_map[i].dpid == dpId) || (fnId > TUYA_MCU_FUNC_NONE && Settings.tuya_fnid_map[i].fnid == fnId)) {
Settings.tuya_fnid_map[i].fnid = TUYA_MCU_FUNC_NONE;
Settings.tuya_fnid_map[i].dpid = 0;
break;
}
}
} else { // Add or update
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
if (Settings.tuya_fnid_map[i].dpid == dpId || Settings.tuya_fnid_map[i].dpid == 0 || Settings.tuya_fnid_map[i].fnid == fnId || Settings.tuya_fnid_map[i].fnid == 0) {
if (!added) { // Update entry if exisiting entry or add
Settings.tuya_fnid_map[i].fnid = fnId;
Settings.tuya_fnid_map[i].dpid = dpId;
added = true;
} else if (Settings.tuya_fnid_map[i].dpid == dpId || Settings.tuya_fnid_map[i].fnid == fnId) { // Remove existing entry if added to empty place
Settings.tuya_fnid_map[i].fnid = TUYA_MCU_FUNC_NONE;
Settings.tuya_fnid_map[i].dpid = 0;
}
}
}
}
}
inline bool TuyaFuncIdValid(uint8_t fnId) {
return (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);
}
uint8_t TuyaGetFuncId(uint8_t dpid) {
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
if (Settings.tuya_fnid_map[i].dpid == dpid) {
return Settings.tuya_fnid_map[i].fnid;
}
}
return TUYA_MCU_FUNC_NONE;
}
uint8_t TuyaGetDpId(uint8_t fnId) {
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
if (Settings.tuya_fnid_map[i].fnid == fnId) {
return Settings.tuya_fnid_map[i].dpid;
}
}
return 0;
}
void TuyaSendCmd(uint8_t cmd, uint8_t payload[] = nullptr, uint16_t payload_len = 0)
{
uint8_t checksum = (0xFF + cmd + (payload_len >> 8) + (payload_len & 0xFF));
@ -146,24 +266,26 @@ bool TuyaSetChannels(void)
void LightSerialDuty(uint8_t duty)
{
if (duty > 0 && !Tuya.ignore_dim && TuyaSerial) {
uint8_t dpid = TuyaGetDpId(TUYA_MCU_FUNC_DIMMER);
if (duty > 0 && !Tuya.ignore_dim && TuyaSerial && dpid > 0) {
if (Settings.flag3.tuya_dimmer_min_limit) { // Enable dimming limit SetOption69: Enabled by default
if (duty < 25) { duty = 25; } // dimming acts odd below 25(10%) - this mirrors the threshold set on the faceplate itself
}
if (Settings.flag3.tuya_disable_dimmer == 0) {
duty = changeUIntScale(duty, 0, 255, 0, Settings.param[P_TUYA_DIMMER_MAX]);
if (Tuya.new_dim != duty) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim value=%d (id=%d)"), duty, Settings.param[P_TUYA_DIMMER_ID]);
TuyaSendValue(Settings.param[P_TUYA_DIMMER_ID], duty);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim value=%d (id=%d)"), duty, dpid);
TuyaSendValue(dpid, duty);
}
}
} else {
} else if (dpid > 0) {
Tuya.ignore_dim = false; // reset flag
if (Settings.flag3.tuya_disable_dimmer == 0) {
duty = changeUIntScale(duty, 0, 255, 0, Settings.param[P_TUYA_DIMMER_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")); //
}
}
@ -189,6 +311,7 @@ void TuyaResetWifi(void)
void TuyaPacketProcess(void)
{
char scmnd[20];
uint8_t fnId = TUYA_MCU_FUNC_NONE;
switch (Tuya.buffer[3]) {
@ -201,24 +324,26 @@ void TuyaPacketProcess(void)
break;
case TUYA_CMD_STATE:
fnId = TuyaGetFuncId(Tuya.buffer[6]);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: FnId=%d is set for dpId=%d"), fnId, Tuya.buffer[6]);
// if (TuyaFuncIdValid(fnId)) {
if (Tuya.buffer[5] == 5) { // on/off packet
/*if ((power || Settings.light_dimmer > 0) && (power != Tuya.buffer[10])) {
ExecuteCommandPower(1, Tuya.buffer[10], SRC_SWITCH); // send SRC_SWITCH? to use as flag to prevent loop from inbound states from faceplate interaction
}*/
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Device-%d --> MCU State: %s Current State:%s"),Tuya.buffer[6],Tuya.buffer[10]?"On":"Off",bitRead(power, Tuya.buffer[6]-1)?"On":"Off");
if ((power || Settings.light_dimmer > 0) && (Tuya.buffer[10] != bitRead(power, Tuya.buffer[6]-1))) {
ExecuteCommandPower(Tuya.buffer[6], Tuya.buffer[10], SRC_SWITCH); // send SRC_SWITCH? to use as flag to prevent loop from inbound states from faceplate interaction
if (fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Device-%d --> MCU State: %s Current State:%s"),Tuya.buffer[6],Tuya.buffer[10]?"On":"Off",bitRead(power, fnId - TUYA_MCU_FUNC_REL1)?"On":"Off");
if ((power || Settings.light_dimmer > 0) && (Tuya.buffer[10] != bitRead(power, fnId - TUYA_MCU_FUNC_REL1))) {
ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[10], SRC_SWITCH); // send SRC_SWITCH? to use as flag to prevent loop from inbound states from faceplate interaction
}
}
}
else if (Tuya.buffer[5] == 8) { // Long value packet
if (Settings.flag3.tuya_disable_dimmer == 0) {
if (!Settings.param[P_TUYA_DIMMER_ID]) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Autoconfiguring Dimmer ID %d"), Tuya.buffer[6]);
Settings.param[P_TUYA_DIMMER_ID] = Tuya.buffer[6];
}
if (Settings.param[P_TUYA_DIMMER_ID] == Tuya.buffer[6]) {
if (fnId == TUYA_MCU_FUNC_DIMMER) {
// if (!Settings.param[P_TUYA_DIMMER_ID]) {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Autoconfiguring Dimmer ID %d"), Tuya.buffer[6]);
// Settings.param[P_TUYA_DIMMER_ID] = Tuya.buffer[6];
// }
// if (Settings.param[P_TUYA_DIMMER_ID] == Tuya.buffer[6]) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Dim State=%d"), Tuya.buffer[13]);
Tuya.new_dim = changeUIntScale((uint8_t) Tuya.buffer[13], 0, Settings.param[P_TUYA_DIMMER_MAX], 0, 100);
if ((power || Settings.flag3.tuya_apply_o20) && (Tuya.new_dim > 0) && (abs(Tuya.new_dim - Settings.light_dimmer) > 1)) {
@ -227,31 +352,32 @@ void TuyaPacketProcess(void)
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER " %d"), Tuya.new_dim );
ExecuteCommand(scmnd, SRC_SWITCH);
}
}
// }
}
#ifdef USE_ENERGY_SENSOR
if (Settings.param[P_TUYA_VOLTAGE_ID] == Tuya.buffer[6]) {
#ifdef USE_ENERGY_SENSOR
else if (fnId == TUYA_MCU_FUNC_VOLTAGE) {
Energy.voltage = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Voltage=%d"), Settings.param[P_TUYA_VOLTAGE_ID], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
} else if (Settings.param[P_TUYA_CURRENT_ID] == Tuya.buffer[6]) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Voltage=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
} else if (fnId == TUYA_MCU_FUNC_CURRENT) {
Energy.current = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 1000;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Current=%d"), Settings.param[P_TUYA_CURRENT_ID], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
} else if (Settings.param[P_TUYA_POWER_ID] == Tuya.buffer[6]) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Current=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
} else if (fnId == TUYA_MCU_FUNC_POWER) {
Energy.active_power = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Active_Power=%d"), Settings.param[P_TUYA_POWER_ID], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Active_Power=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
if (Tuya.lastPowerCheckTime != 0 && Energy.active_power > 0) {
Energy.kWhtoday += (float)Energy.active_power * (Rtc.utc_time - Tuya.lastPowerCheckTime) / 36;
EnergyUpdateToday();
}
Tuya.lastPowerCheckTime = Rtc.utc_time;
} else if (Settings.param[P_TUYA_DIMMER_ID] != Tuya.buffer[6]){
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Unknown ID=%d"), Tuya.buffer[6]);
}
#endif // USE_ENERGY_SENSOR
#endif // USE_ENERGY_SENSOR
}
// } else {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Unknown FnId=%s for dpId=%s"), fnId, Tuya.buffer[6]);
// }
break;
case TUYA_CMD_WIFI_RESET:
@ -266,9 +392,9 @@ void TuyaPacketProcess(void)
break;
case TUYA_CMD_MCU_CONF:
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX MCU configuration"));
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX MCU configuration Mode=%d"), Tuya.buffer[5]);
if (Tuya.buffer[5] == 2) {
if (Tuya.buffer[5] == 2) { // Processing by ESP module mode
uint8_t led1_gpio = Tuya.buffer[6];
uint8_t key1_gpio = Tuya.buffer[7];
bool key1_set = false;
@ -307,7 +433,17 @@ bool TuyaModuleSelected(void)
Settings.my_gp.io[3] = GPIO_TUYA_RX;
restart_flag = 2;
}
if (Settings.flag3.tuya_disable_dimmer == 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) == 0 && TUYA_DIMMER_ID > 0) {
TuyaAddMcuFunc(TUYA_MCU_FUNC_DIMMER, TUYA_DIMMER_ID);
}
if (TuyaGetDpId(TUYA_MCU_FUNC_REL1) == 0) {
TuyaAddMcuFunc(TUYA_MCU_FUNC_REL1, 1);
SettingsSaveAll();
}
if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) != 0) {
light_type = LT_SERIAL1;
} else {
light_type = LT_BASIC;
@ -318,10 +454,12 @@ bool TuyaModuleSelected(void)
void TuyaInit(void)
{
devices_present += Settings.param[P_TUYA_RELAYS]; // SetOption41 - Add virtual relays if present
if (!Settings.param[P_TUYA_DIMMER_ID]) {
Settings.param[P_TUYA_DIMMER_ID] = TUYA_DIMMER_ID;
for(uint8_t i = TUYA_MCU_FUNC_REL2; i <= TUYA_MCU_FUNC_REL8; i++) {
if (TuyaGetDpId(i) != 0) {
devices_present++;
}
}
Tuya.buffer = (char*)(malloc(TUYA_BUFFER_SIZE));
if (Tuya.buffer != nullptr) {
TuyaSerial = new TasmotaSerial(pin[GPIO_TUYA_RX], pin[GPIO_TUYA_TX], 2);
@ -434,11 +572,11 @@ int Xnrg08(uint8_t function)
if (TUYA_DIMMER == my_module_type) {
if (FUNC_PRE_INIT == function) {
if (!energy_flg) {
if (Settings.param[P_TUYA_POWER_ID] != 0) {
if (Settings.param[P_TUYA_CURRENT_ID] == 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_POWER) != 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_CURRENT) == 0) {
Energy.current_available = false;
}
if (Settings.param[P_TUYA_VOLTAGE_ID] == 0) {
if (TuyaGetDpId(TUYA_MCU_FUNC_VOLTAGE) == 0) {
Energy.voltage_available = false;
}
energy_flg = XNRG_08;
@ -486,6 +624,9 @@ bool Xdrv16(uint8_t function)
case FUNC_SET_CHANNELS:
result = TuyaSetChannels();
break;
case FUNC_COMMAND:
result = DecodeCommand(kTuyaCommand, TuyaCommand);
break;
}
}
return result;